new MoonWorks.Graphics.Fence API

cosmonaut 2023-09-18 23:18:21 -07:00
parent 7e18764942
commit e0f05881b0
6 changed files with 228 additions and 77 deletions

@ -1 +1 @@
Subproject commit 60a7523fac254d5e2d89185392e8c1afd8581aa9
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28

@ -1 +1 @@
Subproject commit 859f47f6fa0dfa0f7f941dcced6664fa83736202
Subproject commit 3dcd69ff85db80eea51481edd323b42c05993e1a

30
src/Graphics/FencePool.cs Normal file
View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace MoonWorks.Graphics
{
internal class FencePool
{
private GraphicsDevice GraphicsDevice;
private Queue<Fence> Fences = new Queue<Fence>();
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);
}
}
}

View File

@ -19,6 +19,7 @@ namespace MoonWorks.Graphics
public bool IsDisposed { get; private set; }
private readonly HashSet<WeakReference<GraphicsResource>> resources = new HashSet<WeakReference<GraphicsResource>>();
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)
/// <summary>
/// Submits a command buffer to the GPU for processing.
/// </summary>
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)
/// <summary>
/// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission.
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Wait for the graphics device to become idle.
/// </summary>
public void Wait()
{
Refresh.Refresh_Wait(Handle);
}
/// <summary>
/// Waits for the given fence to become signaled.
/// </summary>
public unsafe void WaitForFences(Fence fence)
{
var handlePtr = stackalloc nint[1];
handlePtr[0] = fence.Handle;
Refresh.Refresh_WaitForFences(
Handle,
1,
1,
(nint) handlePtr
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
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
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
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
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
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
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
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
);
}
/// <summary>
/// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing.
/// </summary>
/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
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;
}
/// <summary>
/// Release reference to an acquired fence, enabling it to be reused.
/// </summary>
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);

View File

@ -0,0 +1,36 @@
using System;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// 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.
/// </summary>
public class Fence : GraphicsResource
{
protected override Action<nint, nint> 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);
}
}
}
}

View File

@ -592,7 +592,7 @@ namespace MoonWorks.Graphics
/// <summary>
/// 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!
/// </summary>
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<byte>(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)