feat(core,rendering)!: add cleanup component support, refactor render pipeline

Introduce ICleanupComponent and cleanup archetype logic in ECS. Refactor component versioning to uint. Update IResourceDatabase to use map/unmap pattern. Decouple per-frame render requests from RenderSystem via IRenderPayload. Update render pipeline and extraction system to new API.

BREAKING CHANGE: Entity destruction and render pipeline APIs have changed. IResourceDatabase.MapResource signature is updated; all callers must use map/memcpy/unmap. RenderSystem no longer manages per-frame render requests directly.
This commit is contained in:
2026-04-06 22:05:48 +09:00
parent c6bdbe0710
commit 6c96d4cf50
20 changed files with 399 additions and 200 deletions

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
namespace Ghost.Graphics.Core;
@@ -61,7 +62,9 @@ public readonly unsafe ref struct RenderContext
{
fixed (T* pData = data)
{
ResourceDatabase.MapResource(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
var mappedData = _engine.ResourceDatabase.MapResource(buffer.AsResource(), 0, null);
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
_engine.ResourceDatabase.UnmapResource(buffer.AsResource(), 0, null);
}
}
else
@@ -81,7 +84,9 @@ public readonly unsafe ref struct RenderContext
fixed (T* pData = data)
{
ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
var mappedData = _engine.ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null);
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
_engine.ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
}
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);

View File

@@ -179,7 +179,7 @@ public struct RenderView
public RenderingLayerMask renderingLayerMask;
}
public unsafe struct RenderRequest: IDisposable
public struct RenderRequest: IDisposable
{
public RenderView view;
@@ -191,8 +191,6 @@ public unsafe struct RenderRequest: IDisposable
public RenderList transparentRenderList;
public RenderList shadowCasterRenderList;
public delegate*<ref readonly RenderContext, ref readonly RenderRequest, void> renderFunc;
public void Dispose()
{
opaqueRenderList.Dispose();

View File

@@ -0,0 +1,51 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics;
namespace Ghost.Graphics;
public unsafe class GPUScene : IDisposable
{
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
private Handle<GPUBuffer> _sceneBuffer;
private bool _disposed;
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, ulong initialCount)
{
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
var bufferDesc = new BufferDesc
{
Size = initialCount * (ulong)sizeof(InstanceData),
Stride = (uint)sizeof(InstanceData),
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
}
~GPUScene()
{
Dispose();
}
public void Dispose()
{
if (_disposed)
{
return;
}
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,16 +1,15 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Diagnostics;
namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPayload : IDisposable;
public interface IRenderPipelineSettings
{
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
void CreatePipeline(RenderSystem renderSystem, out IRenderPipeline renderPipeline, out IRenderPayload renderPayload);
}
public interface IRenderPipeline : IDisposable
{
void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
}

View File

@@ -43,8 +43,6 @@ public class RenderSystem : IDisposable
{
private struct FrameResource : IDisposable
{
private UnsafeList<RenderRequest> _renderRequests;
public required AutoResetEvent CpuReadyEvent
{
get; init;
@@ -65,26 +63,11 @@ public class RenderSystem : IDisposable
get; set;
}
public bool CpuWriteOpen
{
get; set;
}
[UnscopedRef]
public ref UnsafeList<RenderRequest> RenderRequests => ref _renderRequests;
public void Dispose()
public readonly void Dispose()
{
CpuReadyEvent.Dispose();
GpuReadyEvent.Dispose();
CommandAllocator.Dispose();
for (var i = 0; i < _renderRequests.Count; i++)
{
_renderRequests[i].Dispose();
}
_renderRequests.Dispose();
}
}
@@ -102,8 +85,8 @@ public class RenderSystem : IDisposable
private IRenderPipelineSettings _renderPipelineSettings;
private IRenderPipeline _renderPipeline;
private IRenderPayload _renderPayload;
private uint _frameIndex;
private ulong _cpuFenceValue;
private ulong _submittedFenceValue;
@@ -118,9 +101,10 @@ public class RenderSystem : IDisposable
public ulong CPUFenceValue => _cpuFenceValue;
public ulong SubmittedFenceValue => _submittedFenceValue;
public uint FrameIndex => _frameIndex;
public uint MaxFrameLatency => _config.FrameBufferCount;
public IRenderPayload RenderPayload => _renderPayload;
public IRenderPipelineSettings RenderPipelineSettings
{
get => _renderPipelineSettings;
@@ -135,8 +119,10 @@ public class RenderSystem : IDisposable
}
_renderPipeline?.Dispose();
_renderPayload?.Dispose();
_renderPipelineSettings = value;
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
_renderPipelineSettings.CreatePipeline(this, out _renderPipeline, out _renderPayload);
}
}
@@ -180,7 +166,6 @@ public class RenderSystem : IDisposable
CpuReadyEvent = new AutoResetEvent(false),
GpuReadyEvent = new AutoResetEvent(true),
CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics),
RenderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent)
};
}
@@ -195,7 +180,7 @@ public class RenderSystem : IDisposable
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
_renderPipelineSettings = _config.InitialRenderPipelineSettings ?? new GhostRenderPipelineSettings();
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
_renderPipelineSettings.CreatePipeline(this, out _renderPipeline, out _renderPayload);
_isRunning = false;
_disposed = false;
@@ -225,8 +210,8 @@ public class RenderSystem : IDisposable
{
try
{
_frameIndex = (uint)(_submittedFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[_frameIndex];
var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[frameIndex];
// Wait for either CPU ready signal or shutdown signal
waitHandles[0] = frameResource.CpuReadyEvent;
@@ -276,15 +261,14 @@ public class RenderSystem : IDisposable
// TODO: How can we support async compute and async copy?
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
ref var renderRequests = ref frameResource.RenderRequests;
try
{
cmd.Begin(frameResource.CommandAllocator);
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
var ctx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
_renderPipeline.Render(ctx, frameIndex, _renderPayload);
_swapChainManager.TransitionToPresent(cmd);
// End recording commands and submit
@@ -301,13 +285,6 @@ public class RenderSystem : IDisposable
finally
{
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
for (var i = 0; i < renderRequests.Count; i++)
{
renderRequests[i].Dispose();
}
renderRequests.Clear();
}
_submittedFenceValue++;
@@ -361,9 +338,6 @@ public class RenderSystem : IDisposable
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[eventIndex];
Debug.Assert(frameResource.CpuWriteOpen, "SignalCPUReady called without a matching successful TryAcquireCPUFrame.");
frameResource.CpuWriteOpen = false;
frameResource.CpuReadyEvent.Set();
_cpuFenceValue++;
}
@@ -388,25 +362,9 @@ public class RenderSystem : IDisposable
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[eventIndex];
Debug.Assert(!frameResource.CpuWriteOpen, "TryAcquireCPUFrame called while the previous CPU frame is still open. Call SignalCPUReady first.");
frameResource.CpuWriteOpen = true;
frameResource.RenderRequests.Clear();
return true;
}
public void AddRenderRequest(in RenderRequest request)
{
Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem.");
var frameIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
ref var frameResource = ref _frameResources[frameIndex];
Debug.Assert(frameResource.CpuWriteOpen, "AddRenderRequest requires a successful TryAcquireCPUFrame and must happen before SignalCPUReady.");
frameResource.RenderRequests.Add(request);
}
public bool WaitForGPUReady(int timeOut = -1)
{
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
namespace Ghost.Graphics.Utilities;
@@ -24,7 +25,9 @@ public static unsafe class RenderingUtility
{
fixed (T* pData = data)
{
resourceDatabase.MapResource(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
var mappedData = resourceDatabase.MapResource(buffer.AsResource(), 0, null);
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
resourceDatabase.UnmapResource(buffer.AsResource(), 0, null);
}
}
else
@@ -44,7 +47,9 @@ public static unsafe class RenderingUtility
fixed (T* pData = data)
{
resourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
var mappedData = resourceDatabase.MapResource(uploadHandle.AsResource(), 0, null);
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
resourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
}
cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);