Improve tracking of resources
parent
b24b8a3869
commit
aa9baf2dff
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue