Refactor render pipeline and resource management APIs

Split IFenceSynchronizer/IRenderSystem interfaces for clarity. Refactor D3D12GraphicsEngine to use IFenceSynchronizer. Update RenderGraph and context to use explicit resource manager/database/allocator references. Add multi-buffering methods to IRGBuilder (stub). Support history access for multi-frame resources. Remove legacy RenderPipelineBase; introduce IRenderPipelineSettings and sealed GhostRenderPipeline. Clean up resource aliasing and pool logic. Improve modularity and future extensibility.
This commit is contained in:
2026-03-03 20:14:22 +09:00
parent b8af6e8c3a
commit bfe8588d76
17 changed files with 255 additions and 230 deletions

View File

@@ -6,7 +6,6 @@ using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Gdiplus;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias;

View File

@@ -13,9 +13,9 @@ namespace Ghost.Graphics.D3D12;
public static class D3D12GraphicsEngineFactory
{
public static IGraphicsEngine Create(IRenderSystem renderSystem)
public static IGraphicsEngine Create(IFenceSynchronizer fenceSynchronizer)
{
return new D3D12GraphicsEngine(renderSystem);
return new D3D12GraphicsEngine(fenceSynchronizer);
}
}
@@ -23,7 +23,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
{
private GCHandle _thisHandle;
private readonly IRenderSystem _renderSystem;
private readonly IFenceSynchronizer _fenceSynchronizer;
#if ENABLE_DEBUG
private readonly D3D12DebugLayer _debugLayer;
@@ -45,9 +45,9 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public D3D12GraphicsEngine(IRenderSystem renderSystem)
public D3D12GraphicsEngine(IFenceSynchronizer fenceSynchronizer)
{
_renderSystem = renderSystem;
_fenceSynchronizer = fenceSynchronizer;
#if ENABLE_DEBUG
_debugLayer = new D3D12DebugLayer();
@@ -56,7 +56,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_shaderCompiler = new DxcShaderCompiler();
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
_resourceDatabase = new D3D12ResourceDatabase(renderSystem, _descriptorAllocator);
_resourceDatabase = new D3D12ResourceDatabase(_fenceSynchronizer, _descriptorAllocator);
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
_resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
@@ -118,7 +118,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public ISwapChain CreateSwapChain(SwapChainDesc desc)
{
ThrowIfDisposed();
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency);
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _fenceSynchronizer.MaxFrameLatency);
}
public Result RenderFrame(ICommandAllocator commandAllocator)

View File

@@ -28,20 +28,3 @@ public interface IFenceSynchronizer
void SignalCPUReady();
void WaitIdle();
}
public interface IRenderSystem : IFenceSynchronizer, IDisposable
{
IGraphicsEngine GraphicsEngine
{
get;
}
bool IsRunning
{
get;
}
void Start();
void Stop();
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
}

View File

@@ -148,6 +148,11 @@ public struct RenderList : IDisposable
public void Dispose()
{
if (!IsCreated)
{
return;
}
for (int i = 0; i < _threadLocalRecords.Length; i++)
{
_threadLocalRecords[i].Dispose();

View File

@@ -46,9 +46,8 @@ public struct Frustum
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct RenderView
{
public float4x4 view;
public float4x4 projection;
public float4x4 viewProjection;
public float4x4 viewMatrix;
public float4x4 projectionMatrix;
public float3 position;
public Frustum frustum; // 192 bytes
@@ -69,8 +68,13 @@ public struct RenderView
public unsafe struct RenderRequest
{
public RenderView view;
public Handle<Texture> colorTarget;
public Handle<Texture> depthTarget;
public delegate*<ref readonly RenderingContext, ref readonly RenderRequest, void> renderFunc;
public RenderList opaqueRenderList;
public RenderList transparentRenderList;
public RenderList shadowCasterRenderList;
public delegate*<ref readonly RenderContext, ref readonly RenderRequest, void> renderFunc;
}

View File

@@ -9,6 +9,8 @@ namespace Ghost.Graphics.RenderGraphModule;
public sealed class RenderGraph : IDisposable
{
private readonly IResourceManager _resourceManager;
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
private readonly RenderGraphObjectPool _objectPool;
private readonly RenderGraphResourceRegistry _resources;
@@ -35,9 +37,11 @@ public sealed class RenderGraph : IDisposable
public RenderGraphBlackboard Blackboard => _blackboard;
public RenderGraph(IResourceManager resourceManager, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
public RenderGraph(IResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler)
{
_resourceManager = resourceManager;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
_objectPool = new RenderGraphObjectPool();
_resources = new RenderGraphResourceRegistry(_objectPool);
@@ -47,20 +51,21 @@ public sealed class RenderGraph : IDisposable
_nativePasses = new List<NativeRenderPass>(32);
_builder = new RenderGraphBuilder();
_aliasingManager = new ResourceAliasingManager(resourceManager.ResourceAllocator, _objectPool);
_aliasingManager = new ResourceAliasingManager(_resourceAllocator, _objectPool);
_compilationCache = new RenderGraphCompilationCache();
_context = new RenderGraphContext(
resourceManager,
_resourceManager,
_resourceDatabase,
pipelineLibrary,
shaderCompiler,
_resources
);
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
_compiler = new RenderGraphCompiler(resourceManager, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(resourceManager, _resources, _context);
_compiler = new RenderGraphCompiler(_resourceManager, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context);
_blackboard = new RenderGraphBlackboard();
}
@@ -103,7 +108,7 @@ public sealed class RenderGraph : IDisposable
Color128 clearColor = default, float clearDepth = 1.0f, byte clearStencil = 0,
bool clearAtFirstUse = true, bool discardAtLastUse = true)
{
var r = _resourceManager.ResourceDatabase.GetResourceDescription(texture.AsResource());
var r = _resourceDatabase.GetResourceDescription(texture.AsResource());
if (r.IsFailure)
{
return Identifier<RGTexture>.Invalid;
@@ -120,7 +125,7 @@ public sealed class RenderGraph : IDisposable
/// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns>
public Identifier<RGBuffer> ImportBuffer(Handle<GraphicsBuffer> buffer, string name)
{
var r = _resourceManager.ResourceDatabase.GetResourceDescription(buffer.AsResource());
var r = _resourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure)
{
return Identifier<RGBuffer>.Invalid;
@@ -195,14 +200,14 @@ public sealed class RenderGraph : IDisposable
/// <summary>
/// Executes all compiled passes using native render passes where possible.
/// </summary>
public Error Execute(ICommandBuffer cmd)
public Error Execute(ICommandBuffer commandBuffer)
{
if (!_compiled)
{
return Error.InvalidState;
}
return _executor.Execute(cmd, _compiledPasses, _nativePasses, _compiledBarriers);
return _executor.Execute(commandBuffer, _compiledPasses, _nativePasses, _compiledBarriers);
}
public void Dispose()

View File

@@ -1,12 +1,10 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.RHI;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents a memory block within a heap.
/// </summary>
internal struct MemoryBlock
{
public ulong offset;
@@ -35,17 +33,12 @@ internal struct MemoryBlock
}
}
/// <summary>
/// Represents a GPU memory heap for placed resources.
/// Supports D3D12-style heap tier 2 (buffers and textures can alias).
/// </summary>
internal sealed class ResourceHeap
{
public int index;
public ulong size;
private readonly List<MemoryBlock> _blocks = new(32);
// D3D12 heap alignment requirement (64KB for MSAA textures, 4KB for others)
public const ulong DEFAULT_ALIGNMENT = 65536; // 64KB
public ResourceHeap(int index, ulong initialSize = 16 * 1024 * 1024) // 16MB default
@@ -81,6 +74,7 @@ internal sealed class ResourceHeap
var smallestWaste = ulong.MaxValue;
// Find the best fit block that doesn't overlap with lifetime
// TODO: Is first-fit better? Since we already sort by size beforehand.
var blockSpan = CollectionsMarshal.AsSpan(_blocks);
for (var i = 0; i < blockSpan.Length; i++)
{
@@ -166,10 +160,6 @@ internal sealed class ResourceHeap
return (true, bestFitOffset, bestFit);
}
/// <summary>
/// Checks if a resource can be placed at the given offset without lifetime conflicts.
/// Must check ALL blocks that overlap with this offset range.
/// </summary>
private bool CanPlaceAtOffset(ulong offset, ulong size, int firstUsePass, int lastUsePass)
{
var endOffset = offset + size;
@@ -202,12 +192,9 @@ internal sealed class ResourceHeap
return true;
}
/// <summary>
/// Gets the total memory that would be used if no aliasing occurred.
/// </summary>
public ulong GetTotalAllocatedWithoutAliasing()
{
ulong total = 0;
var total = 0ul;
foreach (var block in _blocks)
{
if (!block.isFree)
@@ -219,12 +206,9 @@ internal sealed class ResourceHeap
return total;
}
/// <summary>
/// Gets the peak memory usage considering aliasing (max offset + size).
/// </summary>
public ulong GetPeakUsage()
{
ulong peak = 0;
var peak = 0ul;
foreach (var block in _blocks)
{
if (!block.isFree)
@@ -242,9 +226,6 @@ internal sealed class ResourceHeap
}
}
/// <summary>
/// Represents a placed resource within a heap.
/// </summary>
internal sealed class PlacedResource
{
public int index;
@@ -279,10 +260,6 @@ internal sealed class PlacedResource
}
}
/// <summary>
/// Manages physical resource allocation and aliasing using heap-based allocation.
/// Supports D3D12 heap tier 2: buffers and textures can alias as long as lifetimes don't overlap.
/// </summary>
internal sealed class ResourceAliasingManager
{
private readonly IResourceAllocator _allocator;
@@ -293,15 +270,11 @@ internal sealed class ResourceAliasingManager
// Mapping from logical resource index to placed resource index
private readonly Dictionary<int, int> _logicalToPlaced;
// D3D12 alignment constants
private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB
private const ulong _DEFAULT_BUFFER_ALIGNMENT = 65536; // 64KB for D3D12
private const ulong _DEFAULT_BUFFER_ALIGNMENT = 65536; // 64KB
public ResourceHeap Heap => _heap;
/// <summary>
/// Helper method to get the size of a resource
/// </summary>
private ulong GetResourceSize(RenderGraphResource resource)
{
if (resource.type == RenderGraphResourceType.Texture)
@@ -311,7 +284,6 @@ internal sealed class ResourceAliasingManager
}
else // Buffer
{
//return resource.bufferDesc.Size;
return _allocator.GetSizeInfo(ResourceDesc.Buffer(resource.bufferDesc)).Size;
}
}
@@ -339,13 +311,6 @@ internal sealed class ResourceAliasingManager
_heap.Reset();
}
/// <summary>
/// Assigns physical resources (placed resources) to logical resources using heap-based allocation.
/// This is the modern D3D12 approach: check if resource fits in a hole, not if it matches size/format.
/// Uses a two-pass algorithm:
/// 1. First pass: Simulate allocation to determine peak memory usage
/// 2. Second pass: Create a single heap of the peak size and do the real allocation
/// </summary>
public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount)
{
// Build list of all logical resources (both textures and buffers) with their lifetimes
@@ -369,7 +334,16 @@ internal sealed class ResourceAliasingManager
return sizeB.CompareTo(sizeA); // Descending
});
// NOTE: We assume we are at least D3D12 heap tier 2 (the engine won't even start if the hardware does not supports)
// so buffers and textures can alias as long as lifetimes don't overlap.
// TODO: Handle non-aliased resources like history buffers that need to persist across frames.
// They will be placed in the same heap but we can mark them as non-aliased and skip them when looking for aliasing candidates.
// We do not place them in a separate heap because our buffers are cached and reused across frames as long as the graph topology doesn't change.
// ===== PASS 1: Simulate allocation to determine peak memory usage =====
var simulationHeap = new ResourceHeap(0, ulong.MaxValue); // Unlimited size for simulation
foreach (var (logicalIndex, logicalResource) in logicalResources)
{
@@ -385,10 +359,7 @@ internal sealed class ResourceAliasingManager
logicalIndex,
alignment);
if (!success)
{
throw new InvalidOperationException("Simulation allocation failed - this should never happen with unlimited heap");
}
Debug.Assert(success, "Simulation allocation failed - heap should be unlimited in size");
}
// Get peak usage from simulation
@@ -398,12 +369,15 @@ internal sealed class ResourceAliasingManager
peakMemoryUsage = AlignUp(peakMemoryUsage, _DEFAULT_TEXTURE_ALIGNMENT);
// ===== PASS 2: Create a single heap of the peak size and do the real allocation =====
_heap.size = peakMemoryUsage;
_heap.Reset();
_heap.size = peakMemoryUsage;
// Allocate each logical resource in the heap
foreach (var (logicalIndex, logicalResource) in logicalResources)
{
// TODO: Currently we are recalculating the aliasing candidates in the real allocation pass.
// We can optimize this by caching the candidates from the simulation pass since the heap layout should be the same.
var size = GetResourceSize(logicalResource);
var alignment = logicalResource.type == RenderGraphResourceType.Texture
? _DEFAULT_TEXTURE_ALIGNMENT
@@ -488,9 +462,6 @@ internal sealed class ResourceAliasingManager
return (value + alignment - 1) & ~(alignment - 1);
}
/// <summary>
/// Restores aliasing state from cache.
/// </summary>
public void RestoreFromCache(Dictionary<int, int> logicalToPlaced, List<PlacedResourceData> placedData)
{
_logicalToPlaced.Clear();
@@ -518,9 +489,6 @@ internal sealed class ResourceAliasingManager
}
}
/// <summary>
/// Stores current aliasing state to cache.
/// </summary>
public void StoreToCache(Dictionary<int, int> outLogicalToPlaced, List<PlacedResourceData> outPlacedData)
{
outLogicalToPlaced.Clear();

View File

@@ -32,6 +32,17 @@ public interface IRenderGraphBuilder : IDisposable
/// <returns>An identifier for the newly created texture resource.</returns>
Identifier<RGTexture> CreateTexture(in RGTextureDesc desc, string name);
/// <summary>
/// Creates multiple texture resources based on the specified desc.
/// </summary>
/// <remarks>
/// Those textures will be used as multi buffering in the render graph automaticlly and not aliasable with other resources.
/// </remarks>
/// <param name="desc">A structure that defines the properties and configuration of the texture to create.</param>
/// <param name="name">The base name of the texture resources. The actual name for each texture will be generated by appending an index to this base name.</param>
/// <param name="textureIDs">A span to receive the identifiers for the newly created texture resources. The length of the span determines how many textures will be created.</param>
void CreateTextures(in RGTextureDesc desc, string name, Span<Identifier<RGTexture>> textureIDs);
/// <summary>
/// Creates a new buffer resource based on the specified desc.
/// </summary>
@@ -40,6 +51,17 @@ public interface IRenderGraphBuilder : IDisposable
/// <returns>An identifier for the newly created buffer resource.</returns>
Identifier<RGBuffer> CreateBuffer(in BufferDesc desc, string name);
/// <summary>
/// Creates multiple buffer resources based on the specified desc.
/// </summary>
/// <remarks>
/// Those buffers will be used as multi buffering in the render graph automaticlly and not aliasable with other resources.
/// </remarks>
/// <param name="desc">A structure that defines the properties and configuration of the buffer to create.</param>
/// <param name="name">The base name of the buffer resources. The actual name for each buffer will be generated by appending an index to this base name.</param>
/// <param name="bufferIDs">A span to receive the identifiers for the newly created buffer resources. The length of the span determines how many buffers will be created.</param>
void CreateBuffers(in BufferDesc desc, string name, Span<Identifier<RGBuffer>> bufferIDs);
/// <summary>
/// Registers the specified texture for use in the current render graph pass with the given access mode.
/// </summary>
@@ -140,7 +162,6 @@ public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder
internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder, IUnsafeRenderGraphBuilder
{
private RenderGraph _graph = null!;
private RenderGraphPassBase _pass = null!;
private RenderGraphResourceRegistry _resources = null!;
@@ -206,6 +227,18 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
return handle;
}
public void CreateTextures(in RGTextureDesc desc, string name, Span<Identifier<RGTexture>> textureIDs)
{
// TODO: Create multiple textures, mark them as no aliasable, and add them to the resource registry and current pass.
throw new NotImplementedException();
}
public void CreateBuffers(in BufferDesc desc, string name, Span<Identifier<RGBuffer>> bufferIDs)
{
// TODO: Create multiple buffers, mark them as no aliasable, and add them to the resource registry and current pass.
throw new NotImplementedException();
}
public Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
{
ThrowIfDisposed();

View File

@@ -12,6 +12,9 @@ public interface IRenderGraphContext
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
Handle<Texture> GetActualTexture(Identifier<RGTexture> texture);
Handle<GraphicsBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
Handle<Texture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> texture, int historyOffset);
Handle<GraphicsBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
}
public interface IRasterRenderContext : IRenderGraphContext
@@ -38,11 +41,13 @@ public interface IUnsafeRenderContext : IRasterRenderContext, IRenderGraphContex
internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderContext, IUnsafeRenderContext
{
private readonly IResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly IPipelineLibrary _pipelineLibrary;
private readonly IShaderCompiler _shaderCompiler;
private readonly RenderGraphResourceRegistry _resources;
private ICommandBuffer _commandBuffer = null!;
private uint _frameIndex;
private ICommandBuffer _commandBuffer;
private readonly TextureFormat[] _rtvFormats;
private TextureFormat _dsvFormat;
@@ -58,19 +63,23 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
public ICommandBuffer CommandBuffer => _commandBuffer;
internal RenderGraphContext(IResourceManager resourceManager, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
internal RenderGraphContext(IResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
{
_resourceManager = resourceManager;
_resourceDatabase = resourceDatabase;
_pipelineLibrary = pipelineLibrary;
_shaderCompiler = shaderCompiler;
_resources = resources;
_commandBuffer = null!;
_rtvFormats = new TextureFormat[RHIUtility.MAX_RENDER_TARGETS];
_dsvFormat = TextureFormat.Unknown;
}
internal void SetCommandBuffer(ICommandBuffer commandBuffer)
internal void BeginNewFrame(uint frameIndex, ICommandBuffer commandBuffer)
{
_frameIndex = frameIndex;
_commandBuffer = commandBuffer;
}
@@ -100,6 +109,38 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
return _resources.GetResource(buffer.AsResource()).backingResource.AsGraphicsBuffer();
}
public Handle<Texture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> textures, int historyOffset)
{
if (historyOffset < 0 || historyOffset >= textures.Length)
{
return Handle<Texture>.Invalid;
}
var index = (int)(_frameIndex % textures.Length) - historyOffset;
if (index < 0)
{
index += textures.Length;
}
return GetActualTexture(textures[index]);
}
public Handle<GraphicsBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffers, int historyOffset)
{
if (historyOffset < 0 || historyOffset >= buffers.Length)
{
return Handle<GraphicsBuffer>.Invalid;
}
var index = (int)(_frameIndex % buffers.Length) - historyOffset;
if (index < 0)
{
index += buffers.Length;
}
return GetActualBuffer(buffers[index]);
}
public void SetActiveMaterial(Handle<Material> material)
{
var r = _resourceManager.GetMaterialReference(material);
@@ -183,8 +224,8 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
// TODO: Global and view constants
var data = new PushConstantsData
{
objectIndex = _resourceManager.ResourceDatabase.GetBindlessIndex(_activePerMeshData.AsResource()),
materialIndex = _resourceManager.ResourceDatabase.GetBindlessIndex(_activePerMaterialData.AsResource()),
objectIndex = _resourceDatabase.GetBindlessIndex(_activePerMeshData.AsResource()),
materialIndex = _resourceDatabase.GetBindlessIndex(_activePerMaterialData.AsResource()),
};
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
@@ -196,4 +237,4 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
{
throw new NotImplementedException();
}
}
}

View File

@@ -3,30 +3,29 @@ using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Handles execution of compiled render graphs, including barrier execution and native render passes.
/// </summary>
internal sealed class RenderGraphExecutor
{
private readonly IResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly RenderGraphResourceRegistry _resources;
private readonly RenderGraphContext _context;
private uint _frameIndex;
public RenderGraphExecutor(
IResourceManager resourceManager,
IResourceDatabase resourceDatabase,
RenderGraphResourceRegistry resources,
RenderGraphContext context)
{
_resourceManager = resourceManager;
_resourceDatabase = resourceDatabase;
_resources = resources;
_context = context;
}
/// <summary>
/// Executes all compiled passes using native render passes where possible.
/// </summary>
public unsafe Error Execute(
ICommandBuffer cmd,
ICommandBuffer commandBuffer,
List<RenderGraphPassBase> compiledPasses,
List<NativeRenderPass> nativePasses,
List<CompiledBarrier> compiledBarriers)
@@ -35,7 +34,7 @@ internal sealed class RenderGraphExecutor
var nativePassIndex = 0;
var logicalPassIndex = 0;
_context.SetCommandBuffer(cmd);
_context.BeginNewFrame(_frameIndex++, commandBuffer);
var pPassRTDescs = stackalloc PassRenderTargetDesc[8];
var pRtFormats = stackalloc TextureFormat[8];
@@ -53,7 +52,7 @@ internal sealed class RenderGraphExecutor
for (var i = 0; i < nativePass.mergedPassIndices.Count; i++)
{
var mergedPassIdx = nativePass.mergedPassIndices[i];
var e = ExecuteBarriersForPass(cmd, mergedPassIdx, ref barrierIndex, compiledBarriers);
var e = ExecuteBarriersForPass(commandBuffer, mergedPassIdx, ref barrierIndex, compiledBarriers);
if (e != Error.None)
{
return e;
@@ -94,7 +93,7 @@ internal sealed class RenderGraphExecutor
: AttachmentStoreOp.DontCare
};
cmd.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc);
commandBuffer.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc);
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
{
@@ -117,13 +116,13 @@ internal sealed class RenderGraphExecutor
logicalPassIndex++;
}
cmd.EndRenderPass();
commandBuffer.EndRenderPass();
nativePassIndex++;
}
else
{
// Compute pass or standalone raster pass (not merged) or Unsafe pass
var e = ExecuteBarriersForPass(cmd, logicalPassIndex, ref barrierIndex, compiledBarriers);
var e = ExecuteBarriersForPass(commandBuffer, logicalPassIndex, ref barrierIndex, compiledBarriers);
if (e != Error.None)
{
return e;
@@ -137,10 +136,6 @@ internal sealed class RenderGraphExecutor
return Error.None;
}
/// <summary>
/// Executes all barriers for a specific pass.
/// Uses pre-compiled barriers and queries before state from ResourceManager.
/// </summary>
private unsafe Error ExecuteBarriersForPass(
ICommandBuffer cmd,
int passIndex,
@@ -168,7 +163,7 @@ internal sealed class RenderGraphExecutor
var resourceHandle = resource.backingResource;
// Always query the before state from ResourceManager (single source of truth)
var currentStateResult = _resourceManager.ResourceDatabase.GetResourceBarrierData(resourceHandle);
var currentStateResult = _resourceDatabase.GetResourceBarrierData(resourceHandle);
if (currentStateResult.IsFailure)
{
return currentStateResult.Error;
@@ -184,7 +179,7 @@ internal sealed class RenderGraphExecutor
if (compiledBarrier.AliasingPredecessor.IsValid)
{
var predHandle = _resources.GetResource(compiledBarrier.AliasingPredecessor).backingResource;
var predStateResult = _resourceManager.ResourceDatabase.GetResourceBarrierData(predHandle);
var predStateResult = _resourceDatabase.GetResourceBarrierData(predHandle);
if (predStateResult.IsFailure)
{
return predStateResult.Error;

View File

@@ -4,10 +4,6 @@ using Misaki.HighPerformance.Buffer;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Object pool for reusing allocated objects across frames.
/// This is key to minimizing GC allocations after the first frame.
/// </summary>
internal sealed class RenderGraphObjectPool
{
private static readonly List<SharedObjectPoolBase> s_allocatedPools = new();
@@ -18,7 +14,8 @@ internal sealed class RenderGraphObjectPool
public virtual void Clear() { }
}
private class SharedObjectPool<T> : SharedObjectPoolBase where T : class, new()
private class SharedObjectPool<T> : SharedObjectPoolBase
where T : class, new()
{
private static readonly ObjectPool<T> s_pool = AllocatePool();
@@ -30,28 +27,20 @@ internal sealed class RenderGraphObjectPool
return newPool;
}
/// <summary>
/// Clear the pool using SharedObjectPool instance.
/// </summary>
/// <returns></returns>
public override void Clear()
{
s_pool.Reset();
}
/// <summary>
/// Rent a new instance from the pool.
/// </summary>
/// <returns></returns>
// FIX: ObjectPool<T>.Rent() has a critical bug that it will put the newly created object into the pool directly and give out the same instance again.
// This will cause multiple renters to get the same instance.
public static T Rent() => s_pool.Rent();
public static T Rent()
{
return s_pool.Rent();
}
/// <summary>
/// Return an object to the pool.
/// </summary>
/// <param name="toRelease">instance to release.</param>
public static void Return(T toRelease) => s_pool.Return(toRelease);
public static void Return(T toRelease)
{
s_pool.Return(toRelease);
}
}
public T Rent<T>()
@@ -75,9 +64,6 @@ internal sealed class RenderGraphObjectPool
}
}
/// <summary>
/// Represents a resource in the render graph (texture or buffer).
/// </summary>
internal sealed class RenderGraphResource
{
public string name = string.Empty;
@@ -121,11 +107,6 @@ internal sealed class RenderGraphResource
}
}
/// <summary>
/// Registry for managing all resources in the render graph.
/// Uses pooling to minimize allocations after the first frame.
/// Uses a single unified list for both textures and buffers with global indexing.
/// </summary>
internal sealed class RenderGraphResourceRegistry
{
private readonly RenderGraphObjectPool _pool;

View File

@@ -1,11 +1,65 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.RenderPipeline;
public partial class GhostRenderPipeline : RenderPipelineBase
public sealed class GhostRenderPipelineSettings : IRenderPipelineSettings
{
public override void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
public static IRenderPipeline CreatePipeline(IRenderSystem renderSystem)
{
return new GhostRenderPipeline(renderSystem);
}
}
public unsafe partial class GhostRenderPipeline : IRenderPipeline
{
private readonly RenderGraph _renderGraph;
private bool _disposed;
~GhostRenderPipeline()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
internal GhostRenderPipeline(IRenderSystem renderSystem)
{
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
renderSystem.GraphicsEngine.ResourceAllocator,
renderSystem.GraphicsEngine.ResourceDatabase,
renderSystem.GraphicsEngine.PipelineLibrary,
renderSystem.GraphicsEngine.ShaderCompiler);
}
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
{
for (int i = 0; i < requests.Length; i++)
{
ref readonly var request = ref requests[i];
if (request.renderFunc != null)
{
request.renderFunc(in ctx, in request);
}
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,14 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPipelineSettings
{
static abstract IRenderPipeline CreatePipeline(IRenderSystem renderSystem);
}
public interface IRenderPipeline : IDisposable
{
void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
}

View File

@@ -1,70 +0,0 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderPipeline;
public interface IRenderPipeline : IDisposable
{
void AddRenderList(RenderList renderList, string key);
void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
}
public abstract class RenderPipelineBase : IRenderPipeline
{
protected readonly Dictionary<string, RenderList> _renderLists = new();
private bool _disposed;
~RenderPipelineBase()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public void AddRenderList(RenderList renderList, string key)
{
ThrowIfDisposed();
ref var existingList = ref CollectionsMarshal.GetValueRefOrAddDefault(_renderLists, key, out var exists);
if (!exists)
{
existingList = renderList;
}
else
{
existingList.Append(renderList);
}
}
public abstract void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests);
public void Dispose()
{
if (_disposed)
{
return;
}
Dispose(true);
foreach (var list in _renderLists.Values)
{
list.Dispose();
}
_disposed = true;
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
}
}

View File

@@ -6,6 +6,28 @@ using System.Collections.Concurrent;
namespace Ghost.Graphics;
public interface IRenderSystem : IFenceSynchronizer, IDisposable
{
IGraphicsEngine GraphicsEngine
{
get;
}
IResourceManager ResourceManager
{
get;
}
bool IsRunning
{
get;
}
void Start();
void Stop();
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
}
public enum GraphicsAPI
{
Direct3D12
@@ -63,6 +85,7 @@ internal class RenderSystem : IRenderSystem
private readonly RenderingConfig _config;
private readonly IGraphicsEngine _graphicsEngine;
private readonly IResourceManager _resourceManager;
private readonly FrameResource[] _frameResources;
private readonly Thread _renderThread;
@@ -77,6 +100,7 @@ internal class RenderSystem : IRenderSystem
private bool _disposed;
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public IResourceManager ResourceManager => _resourceManager;
public bool IsRunning => _isRunning;
public uint CPUFenceValue => _cpuFenceValue;
@@ -107,6 +131,8 @@ internal class RenderSystem : IRenderSystem
throw new NotSupportedException($"The specified graphics API '{config.GraphicsAPI}' is not supported.");
}
_resourceManager = new ResourceManager(_graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
// Create frame resources for synchronization
_frameResources = new FrameResource[config.FrameBufferCount];
for (var i = 0; i < config.FrameBufferCount; i++)

View File

@@ -9,16 +9,6 @@ namespace Ghost.Graphics;
public interface IResourceManager
{
IResourceAllocator ResourceAllocator
{
get;
}
IResourceDatabase ResourceDatabase
{
get;
}
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
@@ -113,16 +103,13 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
private bool _disposed;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
{
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
_materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent);
_materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent);
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
}