Skip to content

Commit

Permalink
Fixed transformation problems when using track motion effect. Also fi…
Browse files Browse the repository at this point in the history
…xed problems with task cancellation in export
  • Loading branch information
AngryCarrot789 committed Feb 4, 2024
1 parent 5315d1e commit 2e522cf
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 46 deletions.
3 changes: 3 additions & 0 deletions FramePFX/Editors/Exporting/Controls/ExportDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ await Task.Factory.StartNew(() => {
catch (TaskCanceledException) {
isCancelled = true;
}
catch (OperationCanceledException) {
isCancelled = true;
}
catch (Exception ex) {
string err = ex.GetToString();
AppLogger.Instance.WriteLine("Error exporting: " + err);
Expand Down
6 changes: 5 additions & 1 deletion FramePFX/Editors/Exporting/FFMPEG/FFmpegExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public override unsafe void Export(Project project, IExportProgress progress, Ex
long finalExportFrame = exportFrame;
renderTask = dispatcher.Invoke(() => {
AutomationEngine.UpdateValues(timeline, finalExportFrame);
return renderManager.RenderTimelineAsync(timeline, finalExportFrame, EnumRenderQuality.High);
return renderManager.RenderTimelineAsync(timeline, finalExportFrame, cancellation, EnumRenderQuality.High);
}, DispatcherPriority.Send, cancellation);

surface.Canvas.Clear(SKColors.Black);
Expand All @@ -239,6 +239,10 @@ public override unsafe void Export(Project project, IExportProgress progress, Ex
isRenderCancelled = true;
goto fail_or_end;
}
catch (OperationCanceledException) {
isRenderCancelled = true;
goto fail_or_end;
}
catch (Exception e) {
if (renderTask != null && renderTask.IsCanceled) {
isRenderCancelled = true;
Expand Down
30 changes: 24 additions & 6 deletions FramePFX/Editors/PlaybackManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Windows;
using FramePFX.Editors.Rendering;
using FramePFX.Editors.Timelines;
using FramePFX.Logger;
using FramePFX.Utils;

namespace FramePFX.Editors {
Expand Down Expand Up @@ -113,6 +114,10 @@ private void PlayInternal(long targetFrame) {
this.thread_IsPlaying = true;
}

private void OnAboutToStopPlaying() {
this.thread_IsPlaying = false;
}

public void Pause() {
if (this.PlayState != PlayState.Play) {
return;
Expand All @@ -122,7 +127,7 @@ public void Pause() {
return;
}

this.thread_IsPlaying = false;
this.OnAboutToStopPlaying();
project.MainTimeline.StopHeadPosition = project.MainTimeline.PlayHeadPosition;
this.PlayState = PlayState.Pause;
this.PlaybackStateChanged?.Invoke(this, this.PlayState, project.MainTimeline.StopHeadPosition);
Expand All @@ -137,10 +142,10 @@ public void Stop() {
return;
}

this.OnAboutToStopPlaying();
this.PlayState = PlayState.Stop;
this.PlaybackStateChanged?.Invoke(this, this.PlayState, project.MainTimeline.StopHeadPosition);
project.MainTimeline.PlayHeadPosition = project.MainTimeline.StopHeadPosition;
this.thread_IsPlaying = false;
}

private void OnTimerFrame() {
Expand Down Expand Up @@ -189,19 +194,32 @@ private void OnTimerFrame() {
long newPlayHead = Periodic.Add(timeline.PlayHeadPosition, incr, 0, timeline.MaxDuration - 1);
timeline.PlayHeadPosition = newPlayHead;
if ((project.RenderManager.ScheduledRenderTask?.IsCompleted ?? true)) {
return RenderTimeline(project.RenderManager, timeline, timeline.PlayHeadPosition);
return RenderTimeline(project.RenderManager, timeline, timeline.PlayHeadPosition, CancellationToken.None);
}

return Task.CompletedTask;
});

renderTask.Wait();
try {
renderTask.Wait();
}
catch (TaskCanceledException) {
}
catch (OperationCanceledException) {
}
catch (Exception e) {
this.thread_IsPlaying = false;
AppLogger.Instance.WriteLine("Render exception on playback thread");
AppLogger.Instance.WriteLine(e.GetToString());
}
}
}

private static async Task RenderTimeline(RenderManager renderManager, Timeline timeline, long frame) {
private static async Task RenderTimeline(RenderManager renderManager, Timeline timeline, long frame, CancellationToken cancellationToken) {
// await (renderManager.ScheduledRenderTask ?? Task.CompletedTask);
await renderManager.RenderTimelineAsync(timeline, frame);
if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException();
await renderManager.RenderTimelineAsync(timeline, frame, cancellationToken);
}

private void TimerMain() {
Expand Down
3 changes: 2 additions & 1 deletion FramePFX/Editors/Rendering/RenderContext.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using System.Numerics;
using SkiaSharp;

namespace FramePFX.Editors.Rendering {
/// <summary>
/// The rendering context used to render a clip, and may also be used by effects to access the back buffer
/// </summary>
public readonly struct RenderContext {
public class RenderContext {
/// <summary>
/// The image info associated with our <see cref="Surface"/>
/// </summary>
Expand Down
82 changes: 50 additions & 32 deletions FramePFX/Editors/Rendering/RenderManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class RenderManager {
private volatile Task scheduledRenderTask;
private bool isDisposed;
internal volatile int suspendRenderCount;
private volatile int isRenderCancelled;

private SKBitmap bitmap;
private SKPixmap pixmap;
Expand Down Expand Up @@ -82,7 +83,7 @@ public void UpdateFrameInfo() {
this.surface = SKSurface.Create(this.pixmap);
}

private void CheckRender(Timeline timeline, long frame) {
private void BeginRender(Timeline timeline, long frame) {
if (!Application.Current.Dispatcher.CheckAccess())
throw new InvalidOperationException("Cannot start rendering while not on the main thread");
if (timeline == null)
Expand All @@ -101,47 +102,60 @@ private void CheckRender(Timeline timeline, long frame) {
/// completed when the render is completed
/// </summary>
/// <param name="frame">The frame to render</param>
public async Task RenderTimelineAsync(Timeline timeline, long frame, EnumRenderQuality quality = EnumRenderQuality.UnspecifiedQuality) {
this.CheckRender(timeline, frame);
long beginRender = Time.GetSystemTicks();
SKImageInfo imageInfo = this.ImageInfo;
List<VideoTrack> tracks = new List<VideoTrack>();

// render bottom to top, as most video editors do
for (int i = timeline.Tracks.Count - 1; i >= 0; i--) {
Track track = timeline.Tracks[i];
if (!(track is VideoTrack videoTrack) || !videoTrack.Visible) {
continue;
}

if (videoTrack.PrepareRenderFrame(imageInfo, frame, quality)) {
tracks.Add(videoTrack);
public async Task RenderTimelineAsync(Timeline timeline, long frame, CancellationToken token, EnumRenderQuality quality = EnumRenderQuality.UnspecifiedQuality) {
this.BeginRender(timeline, frame);
long beginRender;
List<VideoTrack> trackList;
SKImageInfo imgInfo;
try {
trackList = new List<VideoTrack>();
imgInfo = this.ImageInfo;
beginRender = Time.GetSystemTicks();

// render bottom to top, as most video editors do
for (int i = timeline.Tracks.Count - 1; i >= 0; i--) {
Track track = timeline.Tracks[i];
if (!(track is VideoTrack videoTrack) || !videoTrack.Visible) {
continue;
}

if (videoTrack.PrepareRenderFrame(imgInfo, frame, quality)) {
trackList.Add(videoTrack);
}
}
}
catch (Exception) {
this.isRendering = 0;
throw;
}

// This seems way too simple... or maybe it really is this simple but other
// open source video editors just design their rendering system completely differently?
// Either way, this works and it works well... for now when there are no composition clips

await Task.Run(async () => {
Task[] tasks = new Task[tracks.Count];
for (int i1 = 0; i1 < tracks.Count; i1++) {
VideoTrack track1 = tracks[i1];
tasks[i1] = Task.Run(() => track1.RenderFrame(imageInfo, quality));
try {
Task[] tasks = new Task[trackList.Count];
for (int i = 0; i < tasks.Length; i++) {
VideoTrack track = trackList[i];
tasks[i] = Task.Run(() => track.RenderFrame(imgInfo, quality), token);
}

this.surface.Canvas.Clear(SKColors.Black);
for (int i = 0; i < trackList.Count; i++) {
if (!tasks[i].IsCompleted)
await tasks[i];
trackList[i].DrawFrameIntoSurface(this.surface);
}

this.averageRenderTimeMillis = (Time.GetSystemTicks() - beginRender) / Time.TICK_PER_MILLIS_D;
}

this.surface.Canvas.Clear(SKColors.Black);
for (int i2 = 0; i2 < tracks.Count; i2++) {
if (!tasks[i2].IsCompleted)
await tasks[i2];
tracks[i2].DrawFrameIntoSurface(this.surface);
finally {
this.isRendering = 0;
}

this.averageRenderTimeMillis = (Time.GetSystemTicks() - beginRender) / Time.TICK_PER_MILLIS_D;
this.isRendering = 0;
});
}, token);

this.averageRenderTimeMillis = (Time.GetSystemTicks() - beginRender) / Time.TICK_PER_MILLIS_D;
this.isRendering = 0;
this.FrameRendered?.Invoke(this);
}

Expand Down Expand Up @@ -183,7 +197,11 @@ public void InvalidateRender() {
private async Task DoScheduledRender() {
if (this.suspendRenderCount < 1) {
try {
await this.RenderTimelineAsync(this.Project.MainTimeline, this.Project.MainTimeline.PlayHeadPosition, EnumRenderQuality.Low);
await this.RenderTimelineAsync(
this.Project.MainTimeline,
this.Project.MainTimeline.PlayHeadPosition,
CancellationToken.None,
EnumRenderQuality.Low);
}
finally {
this.isRenderScheduled = 0;
Expand Down
1 change: 1 addition & 0 deletions FramePFX/Editors/Timelines/Clips/AVMediaVideoClip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public override void RenderFrame(RenderContext rc, ref SKRect renderArea) {
}

renderArea = rc.TranslateRect(new SKRect(0, 0, img.Width, img.Height));
// renderArea = new SKRect(0, 0, img.Width, img.Height);
}

long time = Time.GetSystemTicks() - startA;
Expand Down
2 changes: 2 additions & 0 deletions FramePFX/Editors/Timelines/Clips/TimecodeClip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ public override void RenderFrame(RenderContext rc, ref SKRect renderArea) {
// unacceptable. Even though there will most likely be a bunch of transparent padding pixels, it's still better
renderArea = rc.TranslateRect(realFinalRenderArea);
this.lastRenderRect = renderArea;
// renderArea = realFinalRenderArea;
// this.lastRenderRect = rc.TranslateRect(realFinalRenderArea);
}
}

Expand Down
1 change: 1 addition & 0 deletions FramePFX/Editors/Timelines/Clips/VideoClipShape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public override void RenderFrame(RenderContext rc, ref SKRect renderArea) {
}

renderArea = rc.TranslateRect(new SKRect(0, 0, d.size.X, d.size.Y));
// renderArea = new SKRect(0, 0, d.size.X, d.size.Y);
}

private struct RenderData {
Expand Down
8 changes: 4 additions & 4 deletions FramePFX/Editors/Timelines/Effects/MotionEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class MotionEffect : VideoEffect, ITransformationEffect {
private SKMatrix render_TransformationMatrix;
private bool isMatrixDirty;
private SKMatrix oldMatrix;
private int restoreCount;

public event MatrixChangedEventHandler MatrixChanged;

Expand Down Expand Up @@ -70,14 +71,13 @@ public override void PrepareRender(PreRenderContext ctx, long frame) {

public override void PreProcessFrame(RenderContext rc) {
base.PreProcessFrame(rc);
this.oldMatrix = rc.Canvas.TotalMatrix;
SKMatrix newMatrix = this.oldMatrix.PreConcat(this.render_TransformationMatrix);
rc.Canvas.SetMatrix(newMatrix);
this.restoreCount = rc.Canvas.Save();
rc.Canvas.SetMatrix(rc.Canvas.TotalMatrix.PreConcat(this.render_TransformationMatrix));
}

public override void PostProcessFrame(RenderContext rc, ref SKRect renderArea) {
base.PostProcessFrame(rc, ref renderArea);
// rc.Canvas.SetMatrix(this.oldMatrix);
rc.Canvas.RestoreToCount(this.restoreCount);
}

private static void OnParameterValueChanged(AutomationSequence sequence) {
Expand Down
8 changes: 7 additions & 1 deletion FramePFX/Editors/Timelines/Tracks/VideoTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,11 @@ public void RenderFrame(SKImageInfo imgInfo, EnumRenderQuality quality) {

RenderContext ctx = new RenderContext(imgInfo, rd.surface, rd.bitmap, rd.pixmap, quality);

int trackSaveCount = ctx.Canvas.Save();
foreach (VideoEffect fx in this.theEffectsToApplyToTrack) {
fx.PreProcessFrame(ctx);
}

int clipSaveCount = RenderManager.BeginClipOpacityLayer(ctx.Canvas, this.theClipToRender, ref transparency);

foreach (VideoEffect fx in this.theEffectsToApplyToClip) {
Expand All @@ -188,6 +189,7 @@ public void RenderFrame(SKImageInfo imgInfo, EnumRenderQuality quality) {
}

RenderManager.EndOpacityLayer(ctx.Canvas, clipSaveCount, ref transparency);
ctx.Canvas.RestoreToCount(trackSaveCount);

this.theClipToRender = null;
this.theEffectsToApplyToClip = null;
Expand All @@ -199,6 +201,10 @@ public void RenderFrame(SKImageInfo imgInfo, EnumRenderQuality quality) {

rd.surface.Flush(true, true);
rd.renderArea = renderArea.FloorAndCeil();
// using (SKPaint paint1 = new SKPaint() {Color = SKColors.Green})
// rd.surface.Canvas.DrawRect(renderArea, paint1);
// using (SKPaint paint1 = new SKPaint() {Color = SKColors.Red})
// rd.surface.Canvas.DrawRect(rd.renderArea, paint1);
}

locker.OnRenderFinished();
Expand Down
1 change: 0 additions & 1 deletion FramePFX/Editors/VideoEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ public void SetProject(Project project) {
project.RenderManager.UpdateFrameInfo();
project.RenderManager.InvalidateRender();

// TODO: add event for FrameRate
this.Playback.SetFrameRate(settings.FrameRate);
}

Expand Down
1 change: 1 addition & 0 deletions FramePFX/Editors/Views/EditorWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ private void OnProjectChanged(Project oldProject, Project newProject) {
this.UpdateResourceManager(newProject?.ResourceManager);
this.UpdateTimeline(newProject?.MainTimeline);
this.UpdateWindowTitle(newProject);
this.UpdatePlayBackButtons(newProject?.Editor.Playback);
}

private void OnProjectFilePathChanged(Project project) {
Expand Down

0 comments on commit 2e522cf

Please sign in to comment.