Improve tracking of resources

pull/52/head
cosmonaut 2023-12-12 12:43:28 -08:00
parent b24b8a3869
commit aa9baf2dff
15 changed files with 125 additions and 121 deletions

View File

@ -5,7 +5,7 @@ using WellspringCS;
namespace MoonWorks.Graphics.Font
{
public unsafe class Font : IDisposable
public unsafe class Font : GraphicsResource
{
public Texture Texture { get; }
public float PixelsPerEm { get; }
@ -16,8 +16,6 @@ namespace MoonWorks.Graphics.Font
private byte* StringBytes;
private int StringBytesLength;
private bool IsDisposed;
public unsafe static Font Load(
GraphicsDevice graphicsDevice,
CommandBuffer commandBuffer,
@ -49,10 +47,10 @@ namespace MoonWorks.Graphics.Font
NativeMemory.Free(fontFileByteBuffer);
NativeMemory.Free(atlasFileByteBuffer);
return new Font(handle, texture, pixelsPerEm, distanceRange);
return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
}
private Font(IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange)
private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
{
Handle = handle;
Texture = texture;
@ -101,7 +99,7 @@ namespace MoonWorks.Graphics.Font
return true;
}
protected virtual void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
@ -111,21 +109,8 @@ namespace MoonWorks.Graphics.Font
}
Wellspring.Wellspring_DestroyFont(Handle);
IsDisposed = true;
}
}
~Font()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
base.Dispose(disposing);
}
}
}

View File

@ -4,7 +4,7 @@ using WellspringCS;
namespace MoonWorks.Graphics.Font
{
public unsafe class TextBatch : IDisposable
public unsafe class TextBatch : GraphicsResource
{
public const int MAX_CHARS = 4096;
public const int MAX_VERTICES = MAX_CHARS * 4;
@ -22,11 +22,9 @@ namespace MoonWorks.Graphics.Font
private byte* StringBytes;
private int StringBytesLength;
private bool IsDisposed;
public TextBatch(GraphicsDevice graphicsDevice)
public TextBatch(GraphicsDevice device) : base(device)
{
GraphicsDevice = graphicsDevice;
GraphicsDevice = device;
Handle = Wellspring.Wellspring_CreateTextBatch();
StringBytesLength = 128;
@ -102,7 +100,7 @@ namespace MoonWorks.Graphics.Font
PrimitiveCount = vertexCount / 2; // FIXME: is this jank?
}
protected virtual void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
@ -114,22 +112,8 @@ namespace MoonWorks.Graphics.Font
NativeMemory.Free(StringBytes);
Wellspring.Wellspring_DestroyTextBatch(Handle);
IsDisposed = true;
}
}
~TextBatch()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
base.Dispose(disposing);
}
}
}

View File

@ -1,8 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using MoonWorks.Video;
using RefreshCS;
namespace MoonWorks.Graphics
@ -363,6 +363,16 @@ namespace MoonWorks.Graphics
{
lock (resources)
{
// Dispose video players first to avoid race condition on threaded decoding
foreach (var resource in resources)
{
if (resource.Target is VideoPlayer player)
{
player.Dispose();
}
}
// Dispose everything else
foreach (var resource in resources)
{
if (resource.Target is IDisposable disposable)

View File

@ -1,6 +1,5 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace MoonWorks.Graphics
{
@ -8,13 +7,11 @@ namespace MoonWorks.Graphics
public abstract class GraphicsResource : IDisposable
{
public GraphicsDevice Device { get; }
public IntPtr Handle { get => handle; internal set => handle = value; }
private nint handle;
public bool IsDisposed { get; private set; }
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
private GCHandle SelfReference;
public bool IsDisposed { get; private set; }
protected GraphicsResource(GraphicsDevice device)
{
Device = device;
@ -23,7 +20,7 @@ namespace MoonWorks.Graphics
Device.AddResourceReference(SelfReference);
}
protected void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
@ -33,13 +30,6 @@ namespace MoonWorks.Graphics
SelfReference.Free();
}
// Atomically call destroy function in case this is called from the finalizer thread
var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
if (toDispose != IntPtr.Zero)
{
QueueDestroyFunction(Device.Handle, toDispose);
}
IsDisposed = true;
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace MoonWorks.Graphics;
public abstract class RefreshResource : GraphicsResource
{
public IntPtr Handle { get => handle; internal set => handle = value; }
private IntPtr handle;
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
protected RefreshResource(GraphicsDevice device) : base(device)
{
}
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
// Atomically call destroy function in case this is called from the finalizer thread
var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
if (toDispose != IntPtr.Zero)
{
QueueDestroyFunction(Device.Handle, toDispose);
}
}
base.Dispose(disposing);
}
}

View File

@ -7,7 +7,7 @@ namespace MoonWorks.Graphics
/// <summary>
/// Buffers are generic data containers that can be used by the GPU.
/// </summary>
public class Buffer : GraphicsResource
public class Buffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;

View File

@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
/// <summary>
/// Compute pipelines perform arbitrary parallel processing on input data.
/// </summary>
public class ComputePipeline : GraphicsResource
public class ComputePipeline : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;

View File

@ -10,7 +10,7 @@ namespace MoonWorks.Graphics
/// The Fence object itself is basically just a wrapper for the Refresh_Fence. <br/>
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
/// </summary>
public class Fence : GraphicsResource
public class Fence : RefreshResource
{
protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence;

View File

@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
/// These pipelines are bound before draw calls are issued.
/// </summary>
public class GraphicsPipeline : GraphicsResource
public class GraphicsPipeline : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;

View File

@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
/// <summary>
/// A sampler specifies how a texture will be sampled in a shader.
/// </summary>
public class Sampler : GraphicsResource
public class Sampler : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;

View File

@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
/// <summary>
/// Shader modules expect input in Refresh bytecode format.
/// </summary>
public class ShaderModule : GraphicsResource
public class ShaderModule : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;

View File

@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
/// <summary>
/// A container for pixel data.
/// </summary>
public class Texture : GraphicsResource
public class Texture : RefreshResource
{
public uint Width { get; internal set; }
public uint Height { get; internal set; }

View File

@ -1,12 +1,13 @@
using System;
using System.IO;
using MoonWorks.Graphics;
namespace MoonWorks.Video
{
/// <summary>
/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
/// </summary>
public unsafe class VideoAV1
public unsafe class VideoAV1 : GraphicsResource
{
public string Filename { get; }
@ -28,7 +29,7 @@ namespace MoonWorks.Video
/// <summary>
/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
/// </summary>
public VideoAV1(string filename, double framesPerSecond)
public VideoAV1(GraphicsDevice device, string filename, double framesPerSecond) : base(device)
{
if (!File.Exists(filename))
{
@ -67,8 +68,22 @@ namespace MoonWorks.Video
Filename = filename;
StreamA = new VideoAV1Stream(this);
StreamB = new VideoAV1Stream(this);
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,9 @@
using System;
using MoonWorks.Graphics;
namespace MoonWorks.Video
{
internal class VideoAV1Stream
internal class VideoAV1Stream : GraphicsResource
{
public IntPtr Handle => handle;
IntPtr handle;
@ -19,9 +20,7 @@ namespace MoonWorks.Video
public bool FrameDataUpdated { get; set; }
bool IsDisposed;
public VideoAV1Stream(VideoAV1 video)
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
{
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
{
@ -71,32 +70,13 @@ namespace MoonWorks.Video
}
}
protected virtual void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
}
// free unmanaged resources (unmanaged objects)
Dav1dfile.df_close(Handle);
IsDisposed = true;
}
}
~VideoAV1Stream()
{
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
base.Dispose(disposing);
}
}
}

View File

@ -8,7 +8,7 @@ namespace MoonWorks.Video
/// <summary>
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
/// </summary>
public unsafe class VideoPlayer : IDisposable
public unsafe class VideoPlayer : GraphicsResource
{
public Texture RenderTexture { get; private set; } = null;
public VideoState State { get; private set; } = VideoState.Stopped;
@ -18,6 +18,10 @@ namespace MoonWorks.Video
private VideoAV1 Video = null;
private VideoAV1Stream CurrentStream = null;
private Task ReadNextFrameTask;
private Task ResetStreamATask;
private Task ResetStreamBTask;
private GraphicsDevice GraphicsDevice;
private Texture yTexture = null;
private Texture uTexture = null;
@ -30,17 +34,15 @@ namespace MoonWorks.Video
private double lastTimestamp;
private double timeElapsed;
private bool disposed;
public VideoPlayer(GraphicsDevice graphicsDevice)
public VideoPlayer(GraphicsDevice device) : base(device)
{
GraphicsDevice = graphicsDevice;
GraphicsDevice = device;
if (GraphicsDevice.VideoPipeline == null)
{
throw new InvalidOperationException("Missing video shaders!");
}
LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
timer = new Stopwatch();
}
@ -168,6 +170,8 @@ namespace MoonWorks.Video
public void Unload()
{
Stop();
ResetStreamATask?.Wait();
ResetStreamBTask?.Wait();
Video = null;
}
@ -194,7 +198,8 @@ namespace MoonWorks.Video
}
currentFrame = thisFrame;
Task.Run(CurrentStream.ReadNextFrame).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
}
if (CurrentStream.Ended)
@ -202,7 +207,17 @@ namespace MoonWorks.Video
timer.Stop();
timer.Reset();
Task.Run(CurrentStream.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
var task = Task.Run(CurrentStream.Reset);
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
if (CurrentStream == Video.StreamA)
{
ResetStreamATask = task;
}
else
{
ResetStreamBTask = task;
}
if (Loop)
{
@ -280,8 +295,12 @@ namespace MoonWorks.Video
private void InitializeDav1dStream()
{
Task.Run(Video.StreamA.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
Task.Run(Video.StreamB.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
ReadNextFrameTask?.Wait();
ResetStreamATask = Task.Run(Video.StreamA.Reset);
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
CurrentStream = Video.StreamA;
currentFrame = -1;
@ -289,37 +308,27 @@ namespace MoonWorks.Video
private static void HandleTaskException(Task task)
{
throw task.Exception;
if (task.Exception.InnerException is not TaskCanceledException)
{
throw task.Exception;
}
}
protected virtual void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
if (!disposed)
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
Unload();
RenderTexture.Dispose();
yTexture.Dispose();
uTexture.Dispose();
vTexture.Dispose();
}
disposed = true;
}
}
~VideoPlayer()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
base.Dispose(disposing);
}
}
}