restructure video threading implementation

sdl2_gpu
cosmonaut 2024-03-12 11:58:22 -07:00
parent 871b32d742
commit b4021156cf
3 changed files with 159 additions and 140 deletions

View File

@ -11,10 +11,6 @@ namespace MoonWorks.Video
{ {
public string Filename { get; } 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 Width => width;
public int Height => height; public int Height => height;
public double FramesPerSecond { get; set; } public double FramesPerSecond { get; set; }
@ -67,23 +63,6 @@ namespace MoonWorks.Video
FramesPerSecond = framesPerSecond; FramesPerSecond = framesPerSecond;
Filename = filename; 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);
} }
} }
} }

View File

@ -1,8 +1,11 @@
using System; using System;
using System.Collections.Concurrent;
using System.Threading;
using MoonWorks.Graphics; using MoonWorks.Graphics;
namespace MoonWorks.Video namespace MoonWorks.Video
{ {
// Note that all public methods are async.
internal class VideoAV1Stream : GraphicsResource internal class VideoAV1Stream : GraphicsResource
{ {
public IntPtr Handle => handle; public IntPtr Handle => handle;
@ -21,20 +24,65 @@ namespace MoonWorks.Video
public bool FrameDataUpdated { get; set; } public bool FrameDataUpdated { get; set; }
private VideoAV1 Parent; private BlockingCollection<Action> Actions = new BlockingCollection<Action>();
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device) private bool Running = false;
Thread Thread;
public VideoAV1Stream(GraphicsDevice device) : base(device)
{ {
handle = IntPtr.Zero; 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 (!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!"); throw new Exception("Failed to load video file!");
} }
@ -42,29 +90,20 @@ namespace MoonWorks.Video
} }
} }
public void Unload() private void ResetHelper()
{ {
if (Loaded) if (Loaded)
{
Dav1dfile.df_close(handle);
handle = IntPtr.Zero;
}
}
public void Reset()
{
lock (this)
{ {
Dav1dfile.df_reset(handle); Dav1dfile.df_reset(handle);
ReadNextFrame(); ReadNextFrame();
} }
} }
public void ReadNextFrame() private void ReadNextFrameHelper()
{
if (Loaded && !Ended)
{ {
lock (this) lock (this)
{
if (!Ended)
{ {
if (Dav1dfile.df_readvideo( if (Dav1dfile.df_readvideo(
handle, 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) protected override void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
Unload(); Unload();
Running = false;
if (disposing)
{
Thread.Join();
}
} }
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -18,9 +18,9 @@ namespace MoonWorks.Video
private VideoAV1 Video = null; private VideoAV1 Video = null;
private VideoAV1Stream CurrentStream = null; private VideoAV1Stream CurrentStream = null;
private Task ReadNextFrameTask; // "double buffering" so we can loop without a stutter
private Task ResetTask; private VideoAV1Stream StreamA { get; }
private Task ResetSecondaryStreamTask; private VideoAV1Stream StreamB { get; }
private Texture yTexture = null; private Texture yTexture = null;
private Texture uTexture = null; private Texture uTexture = null;
@ -37,6 +37,9 @@ namespace MoonWorks.Video
public VideoPlayer(GraphicsDevice device) : base(device) public VideoPlayer(GraphicsDevice device) : base(device)
{ {
StreamA = new VideoAV1Stream(device);
StreamB = new VideoAV1Stream(device);
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp); LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
timer = new Stopwatch(); timer = new Stopwatch();
@ -50,7 +53,7 @@ namespace MoonWorks.Video
{ {
if (Video != video) if (Video != video)
{ {
Stop(); Unload();
if (RenderTexture == null) if (RenderTexture == null)
{ {
@ -154,7 +157,7 @@ namespace MoonWorks.Video
lastTimestamp = 0; lastTimestamp = 0;
timeElapsed = 0; timeElapsed = 0;
ResetDav1dStream(); ResetDav1dStreams();
State = VideoState.Stopped; State = VideoState.Stopped;
} }
@ -164,9 +167,10 @@ namespace MoonWorks.Video
/// </summary> /// </summary>
public void Unload() public void Unload()
{ {
ReadNextFrameTask?.Wait(); if (Video == null)
ResetTask?.Wait(); {
ResetSecondaryStreamTask?.Wait(); return;
}
timer.Stop(); timer.Stop();
timer.Reset(); timer.Reset();
@ -176,12 +180,9 @@ namespace MoonWorks.Video
State = VideoState.Stopped; State = VideoState.Stopped;
Video.StreamA.Unload(); StreamA.Unload();
Video.StreamB.Unload(); StreamB.Unload();
ReadNextFrameTask = null;
ResetTask = null;
ResetSecondaryStreamTask = null;
Video = null; Video = null;
} }
@ -208,8 +209,7 @@ namespace MoonWorks.Video
} }
currentFrame = thisFrame; currentFrame = thisFrame;
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame); CurrentStream.ReadNextFrame();
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
} }
if (CurrentStream.Ended) if (CurrentStream.Ended)
@ -217,13 +217,12 @@ namespace MoonWorks.Video
timer.Stop(); timer.Stop();
timer.Reset(); timer.Reset();
ResetSecondaryStreamTask = Task.Run(CurrentStream.Reset); CurrentStream.Reset();
ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
if (Loop) if (Loop)
{ {
// Start over on the next stream! // Start over on the next stream!
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA; CurrentStream = (CurrentStream == StreamA) ? StreamB : StreamA;
currentFrame = -1; currentFrame = -1;
timer.Start(); timer.Start();
} }
@ -236,13 +235,11 @@ namespace MoonWorks.Video
private void UpdateRenderTexture() private void UpdateRenderTexture()
{ {
uint uOffset;
uint vOffset;
lock (CurrentStream) lock (CurrentStream)
{ {
ResetTask?.Wait();
ResetTask = null;
var commandBuffer = Device.AcquireCommandBuffer();
var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength); var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength);
var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength); var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength);
var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength); var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength);
@ -256,6 +253,12 @@ namespace MoonWorks.Video
TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe); TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe);
TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe); TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe);
uOffset = (uint) ySpan.Length;
vOffset = (uint) (ySpan.Length + vSpan.Length);
}
var commandBuffer = Device.AcquireCommandBuffer();
commandBuffer.BeginCopyPass(); commandBuffer.BeginCopyPass();
commandBuffer.UploadToTexture( commandBuffer.UploadToTexture(
@ -274,7 +277,7 @@ namespace MoonWorks.Video
TransferBuffer, TransferBuffer,
uTexture, uTexture,
new BufferImageCopy{ new BufferImageCopy{
BufferOffset = (uint) ySpan.Length, BufferOffset = uOffset,
BufferStride = CurrentStream.uvStride, BufferStride = CurrentStream.uvStride,
BufferImageHeight = uTexture.Height BufferImageHeight = uTexture.Height
}, },
@ -286,7 +289,7 @@ namespace MoonWorks.Video
vTexture, vTexture,
new BufferImageCopy new BufferImageCopy
{ {
BufferOffset = (uint) (ySpan.Length + uSpan.Length), BufferOffset = vOffset,
BufferStride = CurrentStream.uvStride, BufferStride = CurrentStream.uvStride,
BufferImageHeight = vTexture.Height BufferImageHeight = vTexture.Height
}, },
@ -312,7 +315,6 @@ namespace MoonWorks.Video
Device.Submit(commandBuffer); Device.Submit(commandBuffer);
} }
}
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height) private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
{ {
@ -338,35 +340,19 @@ namespace MoonWorks.Video
private void InitializeDav1dStream() private void InitializeDav1dStream()
{ {
ReadNextFrameTask?.Wait(); StreamA.Load(Video.Filename);
ReadNextFrameTask = null; StreamB.Load(Video.Filename);
ResetTask?.Wait(); CurrentStream = StreamA;
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;
currentFrame = -1; currentFrame = -1;
} }
private void ResetDav1dStream() private void ResetDav1dStreams()
{ {
ReadNextFrameTask?.Wait(); StreamA.Reset();
ReadNextFrameTask = null; StreamB.Reset();
ResetTask = Task.Run(Video.StreamA.Reset); CurrentStream = StreamA;
ResetTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
ResetSecondaryStreamTask = Task.Run(Video.StreamB.Reset);
ResetSecondaryStreamTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
CurrentStream = Video.StreamA;
currentFrame = -1; currentFrame = -1;
} }