diff --git a/src/Video/VideoAV1.cs b/src/Video/VideoAV1.cs index b1eb607..ceca064 100644 --- a/src/Video/VideoAV1.cs +++ b/src/Video/VideoAV1.cs @@ -11,10 +11,6 @@ namespace MoonWorks.Video { public string Filename { get; } - // "double buffering" so we can loop without a stutter - internal VideoAV1Stream StreamA { get; } - internal VideoAV1Stream StreamB { get; } - public int Width => width; public int Height => height; public double FramesPerSecond { get; set; } @@ -67,23 +63,6 @@ namespace MoonWorks.Video FramesPerSecond = framesPerSecond; Filename = filename; - - StreamA = new VideoAV1Stream(device, this); - StreamB = new VideoAV1Stream(device, this); - } - - // NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode - protected override void Dispose(bool disposing) - { - if (!IsDisposed) - { - if (disposing) - { - StreamA.Dispose(); - StreamB.Dispose(); - } - } - base.Dispose(disposing); } } } diff --git a/src/Video/VideoAV1Stream.cs b/src/Video/VideoAV1Stream.cs index a8a583d..c92a7f5 100644 --- a/src/Video/VideoAV1Stream.cs +++ b/src/Video/VideoAV1Stream.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Concurrent; +using System.Threading; using MoonWorks.Graphics; namespace MoonWorks.Video { + // Note that all public methods are async. internal class VideoAV1Stream : GraphicsResource { public IntPtr Handle => handle; @@ -21,20 +24,65 @@ namespace MoonWorks.Video public bool FrameDataUpdated { get; set; } - private VideoAV1 Parent; + private BlockingCollection Actions = new BlockingCollection(); - public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device) + private bool Running = false; + + Thread Thread; + + public VideoAV1Stream(GraphicsDevice device) : base(device) { handle = IntPtr.Zero; - Parent = video; + + Running = true; + + Thread = new Thread(ThreadMain); + Thread.Start(); } - public void Load() + private void ThreadMain() + { + while (Running) + { + // block until we can take an action, then run it + var action = Actions.Take(); + action.Invoke(); + } + + // shutting down... + while (Actions.TryTake(out var action)) + { + action.Invoke(); + } + } + + public void Load(string filename) + { + Actions.Add(() => LoadHelper(filename)); + } + + public void Reset() + { + Actions.Add(ResetHelper); + } + + public void ReadNextFrame() + { + Actions.Add(ReadNextFrameHelper); + } + + public void Unload() + { + Actions.Add(UnloadHelper); + } + + private void LoadHelper(string filename) { if (!Loaded) { - if (Dav1dfile.df_fopen(Parent.Filename, out handle) == 0) + if (Dav1dfile.df_fopen(filename, out handle) == 0) { + Logger.LogError("Failed to load video file: " + filename); throw new Exception("Failed to load video file!"); } @@ -42,29 +90,20 @@ namespace MoonWorks.Video } } - public void Unload() + private void ResetHelper() { if (Loaded) - { - Dav1dfile.df_close(handle); - handle = IntPtr.Zero; - } - } - - public void Reset() - { - lock (this) { Dav1dfile.df_reset(handle); ReadNextFrame(); } } - public void ReadNextFrame() + private void ReadNextFrameHelper() { - lock (this) + if (Loaded && !Ended) { - if (!Ended) + lock (this) { if (Dav1dfile.df_readvideo( handle, @@ -91,11 +130,26 @@ namespace MoonWorks.Video } } + private void UnloadHelper() + { + if (Loaded) + { + Dav1dfile.df_close(handle); + handle = IntPtr.Zero; + } + } + protected override void Dispose(bool disposing) { if (!IsDisposed) { Unload(); + Running = false; + + if (disposing) + { + Thread.Join(); + } } base.Dispose(disposing); } diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs index 39dc44c..141b1a4 100644 --- a/src/Video/VideoPlayer.cs +++ b/src/Video/VideoPlayer.cs @@ -18,9 +18,9 @@ namespace MoonWorks.Video private VideoAV1 Video = null; private VideoAV1Stream CurrentStream = null; - private Task ReadNextFrameTask; - private Task ResetTask; - private Task ResetSecondaryStreamTask; + // "double buffering" so we can loop without a stutter + private VideoAV1Stream StreamA { get; } + private VideoAV1Stream StreamB { get; } private Texture yTexture = null; private Texture uTexture = null; @@ -37,6 +37,9 @@ namespace MoonWorks.Video public VideoPlayer(GraphicsDevice device) : base(device) { + StreamA = new VideoAV1Stream(device); + StreamB = new VideoAV1Stream(device); + LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp); timer = new Stopwatch(); @@ -50,7 +53,7 @@ namespace MoonWorks.Video { if (Video != video) { - Stop(); + Unload(); if (RenderTexture == null) { @@ -154,7 +157,7 @@ namespace MoonWorks.Video lastTimestamp = 0; timeElapsed = 0; - ResetDav1dStream(); + ResetDav1dStreams(); State = VideoState.Stopped; } @@ -164,9 +167,10 @@ namespace MoonWorks.Video /// public void Unload() { - ReadNextFrameTask?.Wait(); - ResetTask?.Wait(); - ResetSecondaryStreamTask?.Wait(); + if (Video == null) + { + return; + } timer.Stop(); timer.Reset(); @@ -176,12 +180,9 @@ namespace MoonWorks.Video State = VideoState.Stopped; - Video.StreamA.Unload(); - Video.StreamB.Unload(); + StreamA.Unload(); + StreamB.Unload(); - ReadNextFrameTask = null; - ResetTask = null; - ResetSecondaryStreamTask = null; Video = null; } @@ -208,8 +209,7 @@ namespace MoonWorks.Video } currentFrame = thisFrame; - ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame); - ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + CurrentStream.ReadNextFrame(); } if (CurrentStream.Ended) @@ -217,13 +217,12 @@ namespace MoonWorks.Video timer.Stop(); timer.Reset(); - ResetSecondaryStreamTask = Task.Run(CurrentStream.Reset); - ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + CurrentStream.Reset(); if (Loop) { // Start over on the next stream! - CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA; + CurrentStream = (CurrentStream == StreamA) ? StreamB : StreamA; currentFrame = -1; timer.Start(); } @@ -236,13 +235,11 @@ namespace MoonWorks.Video private void UpdateRenderTexture() { + uint uOffset; + uint vOffset; + lock (CurrentStream) { - ResetTask?.Wait(); - ResetTask = null; - - var commandBuffer = Device.AcquireCommandBuffer(); - var ySpan = new Span((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength); var uSpan = new Span((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength); var vSpan = new Span((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength); @@ -256,62 +253,67 @@ namespace MoonWorks.Video TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe); TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe); - commandBuffer.BeginCopyPass(); - - commandBuffer.UploadToTexture( - TransferBuffer, - yTexture, - new BufferImageCopy - { - BufferOffset = 0, - BufferStride = CurrentStream.yStride, - BufferImageHeight = yTexture.Height - }, - WriteOptions.Cycle - ); - - commandBuffer.UploadToTexture( - TransferBuffer, - uTexture, - new BufferImageCopy{ - BufferOffset = (uint) ySpan.Length, - BufferStride = CurrentStream.uvStride, - BufferImageHeight = uTexture.Height - }, - WriteOptions.Cycle - ); - - commandBuffer.UploadToTexture( - TransferBuffer, - vTexture, - new BufferImageCopy - { - BufferOffset = (uint) (ySpan.Length + uSpan.Length), - BufferStride = CurrentStream.uvStride, - BufferImageHeight = vTexture.Height - }, - WriteOptions.Cycle - ); - - commandBuffer.EndCopyPass(); - - commandBuffer.BeginRenderPass( - new ColorAttachmentInfo(RenderTexture, WriteOptions.Cycle, Color.Black) - ); - - commandBuffer.BindGraphicsPipeline(Device.VideoPipeline); - commandBuffer.BindFragmentSamplers( - new TextureSamplerBinding(yTexture, LinearSampler), - new TextureSamplerBinding(uTexture, LinearSampler), - new TextureSamplerBinding(vTexture, LinearSampler) - ); - - commandBuffer.DrawPrimitives(0, 1); - - commandBuffer.EndRenderPass(); - - Device.Submit(commandBuffer); + uOffset = (uint) ySpan.Length; + vOffset = (uint) (ySpan.Length + vSpan.Length); } + + var commandBuffer = Device.AcquireCommandBuffer(); + + commandBuffer.BeginCopyPass(); + + commandBuffer.UploadToTexture( + TransferBuffer, + yTexture, + new BufferImageCopy + { + BufferOffset = 0, + BufferStride = CurrentStream.yStride, + BufferImageHeight = yTexture.Height + }, + WriteOptions.Cycle + ); + + commandBuffer.UploadToTexture( + TransferBuffer, + uTexture, + new BufferImageCopy{ + BufferOffset = uOffset, + BufferStride = CurrentStream.uvStride, + BufferImageHeight = uTexture.Height + }, + WriteOptions.Cycle + ); + + commandBuffer.UploadToTexture( + TransferBuffer, + vTexture, + new BufferImageCopy + { + BufferOffset = vOffset, + BufferStride = CurrentStream.uvStride, + BufferImageHeight = vTexture.Height + }, + WriteOptions.Cycle + ); + + commandBuffer.EndCopyPass(); + + commandBuffer.BeginRenderPass( + new ColorAttachmentInfo(RenderTexture, WriteOptions.Cycle, Color.Black) + ); + + commandBuffer.BindGraphicsPipeline(Device.VideoPipeline); + commandBuffer.BindFragmentSamplers( + new TextureSamplerBinding(yTexture, LinearSampler), + new TextureSamplerBinding(uTexture, LinearSampler), + new TextureSamplerBinding(vTexture, LinearSampler) + ); + + commandBuffer.DrawPrimitives(0, 1); + + commandBuffer.EndRenderPass(); + + Device.Submit(commandBuffer); } private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height) @@ -338,35 +340,19 @@ namespace MoonWorks.Video private void InitializeDav1dStream() { - ReadNextFrameTask?.Wait(); - ReadNextFrameTask = null; + StreamA.Load(Video.Filename); + StreamB.Load(Video.Filename); - ResetTask?.Wait(); - ResetTask = null; - - ResetSecondaryStreamTask?.Wait(); - ResetSecondaryStreamTask = null; - - ResetTask = Task.Run(Video.StreamA.Load); - ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); - ResetSecondaryStreamTask = Task.Run(Video.StreamB.Load); - ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); - - CurrentStream = Video.StreamA; + CurrentStream = StreamA; currentFrame = -1; } - private void ResetDav1dStream() + private void ResetDav1dStreams() { - ReadNextFrameTask?.Wait(); - ReadNextFrameTask = null; + StreamA.Reset(); + StreamB.Reset(); - ResetTask = Task.Run(Video.StreamA.Reset); - ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); - ResetSecondaryStreamTask = Task.Run(Video.StreamB.Reset); - ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); - - CurrentStream = Video.StreamA; + CurrentStream = StreamA; currentFrame = -1; }