feat(render): per-frame render requests & thread safety
Refactor RenderSystem to store render requests per-frame within FrameResource, improving encapsulation and resource management. Update render loop and AddRenderRequest to use the new structure, ensuring proper disposal and clearing of requests to prevent memory leaks. Remove the old global renderRequests array and update Dispose logic accordingly. Add spin lock-based thread safety to D3D12ResourceDatabase for AddResource/AddAllocation, and introduce EnterParallelRead/ExitParallelRead methods for explicit locking. Enhance RenderExtractionSystem and Material to support transparent render lists and a MaterialRenderType property, preparing for advanced rendering features. Includes minor code cleanups and comment improvements.
This commit is contained in:
@@ -65,6 +65,12 @@ public struct Material : IResourceReleasable
|
||||
get; set;
|
||||
}
|
||||
|
||||
// For now, 0 means opaque, 1 means transparent, may be we need 2 means ui, etc. but higher values are reserved for user-defined render types.
|
||||
public uint MaterialRenderType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Error SetShader(Identifier<Shader> shaderId, ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
|
||||
{
|
||||
if (!shaderId.IsValid)
|
||||
|
||||
@@ -235,9 +235,9 @@ public readonly unsafe ref struct RenderingContext
|
||||
worldBoundsMax = meshData.BoundingBox.Max,
|
||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
|
||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
|
||||
meshletBuffer = meshData.MeshLetBuffer.IsInvalid ? 0 : _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()),
|
||||
meshletVerticesBuffer = meshData.MeshletVerticesBuffer.IsInvalid ? 0 : _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
|
||||
meshletTrianglesBuffer = meshData.MeshletTrianglesBuffer.IsInvalid ? 0 : _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
|
||||
meshletBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()),
|
||||
meshletVerticesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
|
||||
meshletTrianglesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
|
||||
};
|
||||
|
||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if flase
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
@@ -47,7 +48,7 @@ public partial class GhostRenderPipeline
|
||||
private readonly uint _padding1;
|
||||
private readonly uint _padding2;
|
||||
}
|
||||
#if flase
|
||||
|
||||
private void RenderTest(RenderGraph graph, Identifier<RGTexture> backbuffer)
|
||||
{
|
||||
Identifier<RGTexture> renderTarget;
|
||||
@@ -107,5 +108,5 @@ public partial class GhostRenderPipeline
|
||||
});
|
||||
}
|
||||
}
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
@@ -8,6 +8,7 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
@@ -37,6 +38,8 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
private struct FrameResource : IDisposable
|
||||
{
|
||||
private UnsafeList<RenderRequest> _renderRequests;
|
||||
|
||||
public required AutoResetEvent CpuReadyEvent
|
||||
{
|
||||
get; init;
|
||||
@@ -57,11 +60,21 @@ public class RenderSystem : IDisposable
|
||||
get; set;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
[UnscopedRef]
|
||||
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CpuReadyEvent.Dispose();
|
||||
GpuReadyEvent.Dispose();
|
||||
CommandAllocator.Dispose();
|
||||
|
||||
for (var i = 0; i < _renderRequests.Count; i++)
|
||||
{
|
||||
_renderRequests[i].Dispose();
|
||||
}
|
||||
|
||||
_renderRequests.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +87,6 @@ public class RenderSystem : IDisposable
|
||||
private readonly Thread _renderThread;
|
||||
private readonly AutoResetEvent _shutdownEvent;
|
||||
|
||||
private UnsafeArray<UnsafeList<RenderRequest>> _renderRequests;
|
||||
private readonly ConcurrentDictionary<ISwapChain, uint2> _resizeRequest;
|
||||
|
||||
private IRenderPipelineSettings _renderPipelineSettings;
|
||||
@@ -109,6 +121,7 @@ public class RenderSystem : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
_renderPipeline?.Dispose();
|
||||
_renderPipelineSettings = value;
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
}
|
||||
@@ -152,7 +165,8 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
CpuReadyEvent = new AutoResetEvent(false),
|
||||
GpuReadyEvent = new AutoResetEvent(true),
|
||||
CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics)
|
||||
CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics),
|
||||
RenderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -165,11 +179,6 @@ public class RenderSystem : IDisposable
|
||||
|
||||
_shutdownEvent = new AutoResetEvent(false);
|
||||
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
|
||||
_renderRequests = new UnsafeArray<UnsafeList<RenderRequest>>((int)desc.FrameBufferCount, Allocator.Persistent);
|
||||
for (var i = 0; i < desc.FrameBufferCount; i++)
|
||||
{
|
||||
_renderRequests[i] = new UnsafeList<RenderRequest>(2, Allocator.Persistent);
|
||||
}
|
||||
|
||||
_renderPipelineSettings = new GhostRenderPipelineSettings();
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
@@ -266,28 +275,41 @@ public class RenderSystem : IDisposable
|
||||
|
||||
// TODO: How can we support async compute and async copy?
|
||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||
cmd.Begin(frameResource.CommandAllocator);
|
||||
|
||||
var renderCtx = new RenderContext
|
||||
try
|
||||
{
|
||||
CommandBuffer = cmd
|
||||
};
|
||||
cmd.Begin(frameResource.CommandAllocator);
|
||||
|
||||
ref var renderRequests = ref _renderRequests[_frameIndex];
|
||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
||||
var renderCtx = new RenderContext
|
||||
{
|
||||
CommandBuffer = cmd
|
||||
};
|
||||
|
||||
// End recording commands and submit
|
||||
r = cmd.End();
|
||||
if (r.IsFailure)
|
||||
ref var renderRequests = ref frameResource.RenderRequests;
|
||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
||||
|
||||
// End recording commands and submit
|
||||
r = cmd.End();
|
||||
if (r.IsFailure)
|
||||
{
|
||||
StopRenderLoop(r);
|
||||
break;
|
||||
}
|
||||
|
||||
_graphicsEngine.Device.GraphicsQueue.Submit(cmd);
|
||||
|
||||
for (var i = 0; i < renderRequests.Count; i++)
|
||||
{
|
||||
renderRequests[i].Dispose();
|
||||
}
|
||||
|
||||
renderRequests.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
|
||||
StopRenderLoop(r);
|
||||
break;
|
||||
}
|
||||
|
||||
_graphicsEngine.Device.GraphicsQueue.Submit(cmd);
|
||||
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
|
||||
|
||||
// End the frame and present
|
||||
_resourceManager.EndFrame(_cpuFenceValue);
|
||||
r = _graphicsEngine.EndFrame(_gpuFenceValue);
|
||||
@@ -302,13 +324,6 @@ public class RenderSystem : IDisposable
|
||||
|
||||
|
||||
// Prepare for the next frame.
|
||||
for (var i = 0; i < renderRequests.Count; i++)
|
||||
{
|
||||
renderRequests[i].Dispose();
|
||||
}
|
||||
|
||||
renderRequests.Clear();
|
||||
|
||||
_gpuFenceValue++;
|
||||
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
@@ -363,7 +378,7 @@ public class RenderSystem : IDisposable
|
||||
Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem.");
|
||||
|
||||
var frameIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
_renderRequests[frameIndex].Add(request);
|
||||
_frameResources[frameIndex].RenderRequests.Add(request);
|
||||
}
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
@@ -395,21 +410,12 @@ public class RenderSystem : IDisposable
|
||||
|
||||
Stop();
|
||||
|
||||
foreach (var frameResource in _frameResources)
|
||||
for (int i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
ref var frameResource = ref _frameResources[i];
|
||||
frameResource.Dispose();
|
||||
}
|
||||
|
||||
foreach (ref var renderRequestList in _renderRequests)
|
||||
{
|
||||
foreach (ref var request in renderRequestList)
|
||||
{
|
||||
request.Dispose();
|
||||
}
|
||||
|
||||
renderRequestList.Dispose();
|
||||
}
|
||||
|
||||
_graphicsEngine.Dispose();
|
||||
_shutdownEvent.Dispose();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user