feat(graphics): refactor pipeline keying and allocators
Major refactor of graphics pipeline keying, shader cache, and resource allocation. Replaced most Allocator usage with AllocationHandle, modernized logger usage, and unified pipeline state keys. Updated MeshUtility to use AllocationHandle.FreeList. Added new shader pipeline architecture docs and improved error handling throughout. BREAKING CHANGE: Pipeline keying and resource allocation APIs have changed.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderPipeline;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
@@ -16,6 +15,8 @@ internal unsafe class GPUScene : IDisposable
|
||||
private uint _requiredResize;
|
||||
private bool _disposed;
|
||||
|
||||
public Handle<GPUBuffer> SceneBuffer => _sceneBuffer;
|
||||
|
||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount)
|
||||
{
|
||||
_resourceAllocator = resourceAllocator;
|
||||
@@ -30,7 +31,7 @@ internal unsafe class GPUScene : IDisposable
|
||||
};
|
||||
|
||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
Logger.DebugAssert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
|
||||
_capacity = initialCount;
|
||||
}
|
||||
@@ -48,22 +49,21 @@ internal unsafe class GPUScene : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
var newCapacity = _capacity * 2;
|
||||
newCapacity = Math.Max(newCapacity, _capacity + _requiredResize);
|
||||
var newCapacity = Math.Max(_capacity * 2, _capacity + _requiredResize);
|
||||
|
||||
var newBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (ulong)newCapacity * (ulong)sizeof(InstanceData),
|
||||
Size = newCapacity * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
var newBuffer = _resourceAllocator.CreateBuffer(in newBufferDesc, "SceneBuffer_Resized");
|
||||
Debug.Assert(newBuffer.IsValid);
|
||||
Logger.DebugAssert(newBuffer.IsValid);
|
||||
|
||||
// Copy existing data to the new buffer
|
||||
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, (ulong)_instanceCount * (ulong)sizeof(InstanceData));
|
||||
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, _instanceCount * (ulong)sizeof(InstanceData));
|
||||
|
||||
// Replace old buffer with the new one
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
[GenerateShaderProperty("Internal/UpdateGPUScene")]
|
||||
public partial struct UpdateGPUSceneShaderProperty
|
||||
{
|
||||
public uint gpuSceneBuffer;
|
||||
public uint addBuffer;
|
||||
public uint addCount;
|
||||
public uint removeBuffer;
|
||||
public uint removeCount;
|
||||
}
|
||||
|
||||
internal partial class GhostRenderPipeline
|
||||
{
|
||||
public void UpdateGPUScene(RenderContext ctx, Handle<GPUBuffer> addBuffer, int addCount, Handle<GPUBuffer> removeBuffer, int removeCount)
|
||||
{
|
||||
if (addCount <= 0 && removeCount <= 0)
|
||||
{
|
||||
Logger.DebugAssert(addBuffer.IsInvalid && removeBuffer.IsInvalid, "Buffers should be invalid when there are no updates.");
|
||||
return; // No updates needed
|
||||
}
|
||||
|
||||
// NOTE: We dispatch it here instead of in render graph is because the update does not perform every frame.
|
||||
// The topology change of the graph will trigger the recompilation of the render graph, which is expensive.
|
||||
// Currently the render graph does not support import invalid resources, which means we can not handle the early return in the render func.
|
||||
// Furthermore, updating the GPU scene does not rely on other resources and passes, it's isolated and always run before the actual rendering.
|
||||
// So it's fine to dispatch it here directly.
|
||||
|
||||
var property = new UpdateGPUSceneShaderProperty
|
||||
{
|
||||
gpuSceneBuffer = ctx.ResourceDatabase.GetBindlessIndex(_gpuScene.SceneBuffer.AsResource(), BindlessAccess.UnorderedAccess),
|
||||
addBuffer = ctx.ResourceDatabase.GetBindlessIndex(addBuffer.AsResource()),
|
||||
addCount = (uint)addCount,
|
||||
removeBuffer = ctx.ResourceDatabase.GetBindlessIndex(removeBuffer.AsResource()),
|
||||
removeCount = (uint)removeCount
|
||||
};
|
||||
|
||||
// TODO: Write and load the shader. This is just a placeholder for now.
|
||||
var shader = default(Handle<ComputeShader>);
|
||||
var keywords = new LocalKeywordSet();
|
||||
|
||||
ctx.DispatchCompute(shader, 0, in keywords, in property, new uint3());
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
internal class GhostRenderPipeline : IRenderPipeline
|
||||
internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
private struct AddInstanceData
|
||||
{
|
||||
@@ -39,11 +39,11 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
_renderSystem = renderSystem;
|
||||
|
||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager, renderSystem.GraphicsEngine);
|
||||
_renderGraph = new RenderGraph(renderSystem);
|
||||
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.AddRequest.IsEmpty)
|
||||
{
|
||||
@@ -82,13 +82,16 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return addBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.RemoveRequest.IsEmpty)
|
||||
{
|
||||
@@ -116,9 +119,12 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return removeBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -131,15 +137,20 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
|
||||
foreach (ref readonly var request in ghostPayload.RenderRequests)
|
||||
{
|
||||
if (!RenderPipelineUtility.GetVPMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize))
|
||||
try
|
||||
{
|
||||
continue;
|
||||
using var viewData = new RenderViewData(_renderSystem.SwapChainManager, resourceDatabase, in request);
|
||||
RenderPipelineUtility.GetVPMatrices(in request, viewData.ScreenSize, out var view, out var projection);
|
||||
|
||||
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var addCount);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var removeCount);
|
||||
|
||||
UpdateGPUScene(ctx, addBuffer, addCount, removeBuffer, removeCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
|
||||
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
{
|
||||
_renderPipeline = renderPipeline;
|
||||
|
||||
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
|
||||
_addRequest = new ConcurrentQueue<AddInstanceRequest>();
|
||||
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class RenderPipelineSystemAttribute<T> : RenderPipelineSystemAttribute
|
||||
public override Type SettingsType => typeof(T);
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static class RenderPipelineSystemRegistry
|
||||
{
|
||||
private static readonly Dictionary<nint, List<Func<ISystem>>> s_renderPipelineSystems = new();
|
||||
|
||||
Reference in New Issue
Block a user