From e0f05881b01ee43955d87c074f982a09a87de0df Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 18 Sep 2023 23:18:21 -0700 Subject: [PATCH] new MoonWorks.Graphics.Fence API --- lib/RefreshCS | 2 +- lib/dav1dfile | 2 +- src/Graphics/FencePool.cs | 30 ++++ src/Graphics/GraphicsDevice.cs | 227 ++++++++++++++++++++---------- src/Graphics/Resources/Fence.cs | 36 +++++ src/Graphics/Resources/Texture.cs | 8 +- 6 files changed, 228 insertions(+), 77 deletions(-) create mode 100644 src/Graphics/FencePool.cs create mode 100644 src/Graphics/Resources/Fence.cs diff --git a/lib/RefreshCS b/lib/RefreshCS index 60a7523..b5325e6 160000 --- a/lib/RefreshCS +++ b/lib/RefreshCS @@ -1 +1 @@ -Subproject commit 60a7523fac254d5e2d89185392e8c1afd8581aa9 +Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28 diff --git a/lib/dav1dfile b/lib/dav1dfile index 859f47f..3dcd69f 160000 --- a/lib/dav1dfile +++ b/lib/dav1dfile @@ -1 +1 @@ -Subproject commit 859f47f6fa0dfa0f7f941dcced6664fa83736202 +Subproject commit 3dcd69ff85db80eea51481edd323b42c05993e1a diff --git a/src/Graphics/FencePool.cs b/src/Graphics/FencePool.cs new file mode 100644 index 0000000..d0e6935 --- /dev/null +++ b/src/Graphics/FencePool.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace MoonWorks.Graphics +{ + internal class FencePool + { + private GraphicsDevice GraphicsDevice; + private Queue Fences = new Queue(); + + public FencePool(GraphicsDevice graphicsDevice) + { + GraphicsDevice = graphicsDevice; + } + + public Fence Obtain() + { + if (Fences.Count == 0) + { + return new Fence(GraphicsDevice); + } + + return Fences.Dequeue(); + } + + public void Return(Fence fence) + { + Fences.Enqueue(fence); + } + } +} diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index c1057d6..0662d77 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -19,6 +19,7 @@ namespace MoonWorks.Graphics public bool IsDisposed { get; private set; } private readonly HashSet> resources = new HashSet>(); + private FencePool FencePool; public GraphicsDevice( Backend preferredBackend, @@ -72,6 +73,8 @@ namespace MoonWorks.Graphics } ); } + + FencePool = new FencePool(this); } public bool ClaimWindow(Window window, PresentMode presentMode) @@ -120,94 +123,174 @@ namespace MoonWorks.Graphics return new CommandBuffer(this, Refresh.Refresh_AcquireCommandBuffer(Handle)); } - public unsafe void Submit(CommandBuffer commandBuffer) + /// + /// Submits a command buffer to the GPU for processing. + /// + public void Submit(CommandBuffer commandBuffer) { - var commandBufferPtrs = stackalloc IntPtr[1]; - - commandBufferPtrs[0] = commandBuffer.Handle; - Refresh.Refresh_Submit( Handle, - 1, - (IntPtr) commandBufferPtrs + commandBuffer.Handle ); } - public unsafe void Submit( - CommandBuffer commandBufferOne, - CommandBuffer commandBufferTwo - ) { - var commandBufferPtrs = stackalloc IntPtr[2]; - - commandBufferPtrs[0] = commandBufferOne.Handle; - commandBufferPtrs[1] = commandBufferTwo.Handle; - - Refresh.Refresh_Submit( - Handle, - 2, - (IntPtr) commandBufferPtrs - ); - } - - public unsafe void Submit( - CommandBuffer commandBufferOne, - CommandBuffer commandBufferTwo, - CommandBuffer commandBufferThree - ) { - var commandBufferPtrs = stackalloc IntPtr[3]; - - commandBufferPtrs[0] = commandBufferOne.Handle; - commandBufferPtrs[1] = commandBufferTwo.Handle; - commandBufferPtrs[2] = commandBufferThree.Handle; - - Refresh.Refresh_Submit( - Handle, - 3, - (IntPtr) commandBufferPtrs - ); - } - - public unsafe void Submit( - CommandBuffer commandBufferOne, - CommandBuffer commandBufferTwo, - CommandBuffer commandBufferThree, - CommandBuffer commandBufferFour - ) { - var commandBufferPtrs = stackalloc IntPtr[4]; - - commandBufferPtrs[0] = commandBufferOne.Handle; - commandBufferPtrs[1] = commandBufferTwo.Handle; - commandBufferPtrs[2] = commandBufferThree.Handle; - commandBufferPtrs[3] = commandBufferFour.Handle; - - Refresh.Refresh_Submit( - Handle, - 4, - (IntPtr) commandBufferPtrs - ); - } - - public unsafe void Submit(params CommandBuffer[] commandBuffers) + /// + /// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission. + /// + /// + public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer) { - var commandBufferPtrs = stackalloc IntPtr[commandBuffers.Length]; - - for (var i = 0; i < commandBuffers.Length; i += 1) - { - commandBufferPtrs[i] = commandBuffers[i].Handle; - } - - Refresh.Refresh_Submit( + var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence( Handle, - (uint) commandBuffers.Length, - (IntPtr) commandBufferPtrs + commandBuffer.Handle ); + + var fence = FencePool.Obtain(); + fence.SetHandle(fenceHandle); + + return fence; } + /// + /// Wait for the graphics device to become idle. + /// public void Wait() { Refresh.Refresh_Wait(Handle); } + /// + /// Waits for the given fence to become signaled. + /// + public unsafe void WaitForFences(Fence fence) + { + var handlePtr = stackalloc nint[1]; + handlePtr[0] = fence.Handle; + + Refresh.Refresh_WaitForFences( + Handle, + 1, + 1, + (nint) handlePtr + ); + } + + /// + /// Wait for one or more fences to become signaled. + /// + /// If true, will wait for all given fences to be signaled. + public unsafe void WaitForFences( + Fence fenceOne, + Fence fenceTwo, + bool waitAll + ) { + var handlePtr = stackalloc nint[2]; + handlePtr[0] = fenceOne.Handle; + handlePtr[1] = fenceTwo.Handle; + + Refresh.Refresh_WaitForFences( + Handle, + Conversions.BoolToByte(waitAll), + 2, + (nint) handlePtr + ); + } + + /// + /// Wait for one or more fences to become signaled. + /// + /// If true, will wait for all given fences to be signaled. + public unsafe void WaitForFences( + Fence fenceOne, + Fence fenceTwo, + Fence fenceThree, + bool waitAll + ) { + var handlePtr = stackalloc nint[3]; + handlePtr[0] = fenceOne.Handle; + handlePtr[1] = fenceTwo.Handle; + handlePtr[2] = fenceThree.Handle; + + Refresh.Refresh_WaitForFences( + Handle, + Conversions.BoolToByte(waitAll), + 3, + (nint) handlePtr + ); + } + + /// + /// Wait for one or more fences to become signaled. + /// + /// If true, will wait for all given fences to be signaled. + public unsafe void WaitForFences( + Fence fenceOne, + Fence fenceTwo, + Fence fenceThree, + Fence fenceFour, + bool waitAll + ) { + var handlePtr = stackalloc nint[4]; + handlePtr[0] = fenceOne.Handle; + handlePtr[1] = fenceTwo.Handle; + handlePtr[2] = fenceThree.Handle; + handlePtr[3] = fenceFour.Handle; + + Refresh.Refresh_WaitForFences( + Handle, + Conversions.BoolToByte(waitAll), + 4, + (nint) handlePtr + ); + } + + /// + /// Wait for one or more fences to become signaled. + /// + /// If true, will wait for all given fences to be signaled. + public unsafe void WaitForFences(Fence[] fences, bool waitAll) + { + var handlePtr = stackalloc nint[fences.Length]; + + for (var i = 0; i < fences.Length; i += 1) + { + handlePtr[i] = fences[i].Handle; + } + + Refresh.Refresh_WaitForFences( + Handle, + Conversions.BoolToByte(waitAll), + 4, + (nint) handlePtr + ); + } + + /// + /// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing. + /// + /// Throws if the fence query indicates that the graphics device has been lost. + public bool QueryFence(Fence fence) + { + var result = Refresh.Refresh_QueryFence(Handle, fence.Handle); + + if (result < 0) + { + throw new InvalidOperationException("The graphics device has been lost."); + } + + return result != 0; + } + + /// + /// Release reference to an acquired fence, enabling it to be reused. + /// + public void ReleaseFence(Fence fence) + { + Refresh.Refresh_ReleaseFence(Handle, fence.Handle); + fence.Handle = IntPtr.Zero; + FencePool.Return(fence); + } + private TextureFormat GetSwapchainFormat(Window window) { return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle); diff --git a/src/Graphics/Resources/Fence.cs b/src/Graphics/Resources/Fence.cs new file mode 100644 index 0000000..68a3a70 --- /dev/null +++ b/src/Graphics/Resources/Fence.cs @@ -0,0 +1,36 @@ +using System; +using RefreshCS; + +namespace MoonWorks.Graphics +{ + /// + /// Fences allow you to track the status of a submitted command buffer. + /// You should only acquire a Fence if you will need to track the command buffer. + /// You should make sure to call GraphicsDevice.ReleaseFence when done with a Fence to avoid memory growth. + /// + /// The Fence object itself is basically just a wrapper for the Refresh_Fence. + /// The internal handle is replaced so that we can pool Fence objects to manage garbage. + /// + public class Fence : GraphicsResource + { + protected override Action QueueDestroyFunction => Release; + + internal Fence(GraphicsDevice device) : base(device, true) + { + } + + internal void SetHandle(nint handle) + { + Handle = handle; + } + + private void Release(nint deviceHandle, nint fenceHandle) + { + if (fenceHandle != IntPtr.Zero) + { + // This will only be called if the client forgot to release a handle. Oh no! + Refresh.Refresh_ReleaseFence(deviceHandle, fenceHandle); + } + } + } +} diff --git a/src/Graphics/Resources/Texture.cs b/src/Graphics/Resources/Texture.cs index 8564860..2ff9ca4 100644 --- a/src/Graphics/Resources/Texture.cs +++ b/src/Graphics/Resources/Texture.cs @@ -592,7 +592,7 @@ namespace MoonWorks.Graphics /// /// Asynchronously saves RGBA or BGRA pixel data to a file in PNG format. - /// Warning: this is expensive! + /// Warning: this is expensive and will block to wait for data download from GPU! /// public unsafe void SavePNG(string path) { @@ -608,14 +608,16 @@ namespace MoonWorks.Graphics // immediately request the data copy var commandBuffer = Device.AcquireCommandBuffer(); commandBuffer.CopyTextureToBuffer(this, buffer); - Device.Submit(commandBuffer); + var fence = Device.SubmitAndAcquireFence(commandBuffer); var byteCount = buffer.Size; var pixelsPtr = NativeMemory.Alloc((nuint) byteCount); var pixelsSpan = new Span(pixelsPtr, (int) byteCount); - Device.Wait(); // make sure the data transfer is done... + Device.WaitForFences(fence); // make sure the data transfer is done... + Device.ReleaseFence(fence); // and then release the fence + buffer.GetData(pixelsSpan); if (Format == TextureFormat.B8G8R8A8)