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

@@ -1,5 +1,5 @@
[*] [*]
max_line_length = 400 max_line_length = 200
[*.cs] [*.cs]
csharp_new_line_before_open_brace = all csharp_new_line_before_open_brace = all

View File

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

View File

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

View File

@@ -28,20 +28,3 @@ public interface IFenceSynchronizer
void SignalCPUReady(); void SignalCPUReady();
void WaitIdle(); 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() public void Dispose()
{ {
if (!IsCreated)
{
return;
}
for (int i = 0; i < _threadLocalRecords.Length; i++) for (int i = 0; i < _threadLocalRecords.Length; i++)
{ {
_threadLocalRecords[i].Dispose(); _threadLocalRecords[i].Dispose();

View File

@@ -46,9 +46,8 @@ public struct Frustum
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct RenderView public struct RenderView
{ {
public float4x4 view; public float4x4 viewMatrix;
public float4x4 projection; public float4x4 projectionMatrix;
public float4x4 viewProjection;
public float3 position; public float3 position;
public Frustum frustum; // 192 bytes public Frustum frustum; // 192 bytes
@@ -69,8 +68,13 @@ public struct RenderView
public unsafe struct RenderRequest public unsafe struct RenderRequest
{ {
public RenderView view; public RenderView view;
public Handle<Texture> colorTarget; public Handle<Texture> colorTarget;
public Handle<Texture> depthTarget; 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 public sealed class RenderGraph : IDisposable
{ {
private readonly IResourceManager _resourceManager; private readonly IResourceManager _resourceManager;
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
private readonly RenderGraphObjectPool _objectPool; private readonly RenderGraphObjectPool _objectPool;
private readonly RenderGraphResourceRegistry _resources; private readonly RenderGraphResourceRegistry _resources;
@@ -35,9 +37,11 @@ public sealed class RenderGraph : IDisposable
public RenderGraphBlackboard Blackboard => _blackboard; 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; _resourceManager = resourceManager;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
_objectPool = new RenderGraphObjectPool(); _objectPool = new RenderGraphObjectPool();
_resources = new RenderGraphResourceRegistry(_objectPool); _resources = new RenderGraphResourceRegistry(_objectPool);
@@ -47,20 +51,21 @@ public sealed class RenderGraph : IDisposable
_nativePasses = new List<NativeRenderPass>(32); _nativePasses = new List<NativeRenderPass>(32);
_builder = new RenderGraphBuilder(); _builder = new RenderGraphBuilder();
_aliasingManager = new ResourceAliasingManager(resourceManager.ResourceAllocator, _objectPool); _aliasingManager = new ResourceAliasingManager(_resourceAllocator, _objectPool);
_compilationCache = new RenderGraphCompilationCache(); _compilationCache = new RenderGraphCompilationCache();
_context = new RenderGraphContext( _context = new RenderGraphContext(
resourceManager, _resourceManager,
_resourceDatabase,
pipelineLibrary, pipelineLibrary,
shaderCompiler, shaderCompiler,
_resources _resources
); );
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources); _nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
_compiler = new RenderGraphCompiler(resourceManager, _resources, _aliasingManager, _nativePassBuilder, _compilationCache); _compiler = new RenderGraphCompiler(_resourceManager, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(resourceManager, _resources, _context); _executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context);
_blackboard = new RenderGraphBlackboard(); _blackboard = new RenderGraphBlackboard();
} }
@@ -103,7 +108,7 @@ public sealed class RenderGraph : IDisposable
Color128 clearColor = default, float clearDepth = 1.0f, byte clearStencil = 0, Color128 clearColor = default, float clearDepth = 1.0f, byte clearStencil = 0,
bool clearAtFirstUse = true, bool discardAtLastUse = true) bool clearAtFirstUse = true, bool discardAtLastUse = true)
{ {
var r = _resourceManager.ResourceDatabase.GetResourceDescription(texture.AsResource()); var r = _resourceDatabase.GetResourceDescription(texture.AsResource());
if (r.IsFailure) if (r.IsFailure)
{ {
return Identifier<RGTexture>.Invalid; 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> /// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns>
public Identifier<RGBuffer> ImportBuffer(Handle<GraphicsBuffer> buffer, string name) 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) if (r.IsFailure)
{ {
return Identifier<RGBuffer>.Invalid; return Identifier<RGBuffer>.Invalid;
@@ -195,14 +200,14 @@ public sealed class RenderGraph : IDisposable
/// <summary> /// <summary>
/// Executes all compiled passes using native render passes where possible. /// Executes all compiled passes using native render passes where possible.
/// </summary> /// </summary>
public Error Execute(ICommandBuffer cmd) public Error Execute(ICommandBuffer commandBuffer)
{ {
if (!_compiled) if (!_compiled)
{ {
return Error.InvalidState; return Error.InvalidState;
} }
return _executor.Execute(cmd, _compiledPasses, _nativePasses, _compiledBarriers); return _executor.Execute(commandBuffer, _compiledPasses, _nativePasses, _compiledBarriers);
} }
public void Dispose() public void Dispose()

View File

@@ -1,12 +1,10 @@
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderGraphModule; namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents a memory block within a heap.
/// </summary>
internal struct MemoryBlock internal struct MemoryBlock
{ {
public ulong offset; 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 internal sealed class ResourceHeap
{ {
public int index; public int index;
public ulong size; public ulong size;
private readonly List<MemoryBlock> _blocks = new(32); 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 const ulong DEFAULT_ALIGNMENT = 65536; // 64KB
public ResourceHeap(int index, ulong initialSize = 16 * 1024 * 1024) // 16MB default public ResourceHeap(int index, ulong initialSize = 16 * 1024 * 1024) // 16MB default
@@ -81,6 +74,7 @@ internal sealed class ResourceHeap
var smallestWaste = ulong.MaxValue; var smallestWaste = ulong.MaxValue;
// Find the best fit block that doesn't overlap with lifetime // 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); var blockSpan = CollectionsMarshal.AsSpan(_blocks);
for (var i = 0; i < blockSpan.Length; i++) for (var i = 0; i < blockSpan.Length; i++)
{ {
@@ -166,10 +160,6 @@ internal sealed class ResourceHeap
return (true, bestFitOffset, bestFit); 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) private bool CanPlaceAtOffset(ulong offset, ulong size, int firstUsePass, int lastUsePass)
{ {
var endOffset = offset + size; var endOffset = offset + size;
@@ -202,12 +192,9 @@ internal sealed class ResourceHeap
return true; return true;
} }
/// <summary>
/// Gets the total memory that would be used if no aliasing occurred.
/// </summary>
public ulong GetTotalAllocatedWithoutAliasing() public ulong GetTotalAllocatedWithoutAliasing()
{ {
ulong total = 0; var total = 0ul;
foreach (var block in _blocks) foreach (var block in _blocks)
{ {
if (!block.isFree) if (!block.isFree)
@@ -219,12 +206,9 @@ internal sealed class ResourceHeap
return total; return total;
} }
/// <summary>
/// Gets the peak memory usage considering aliasing (max offset + size).
/// </summary>
public ulong GetPeakUsage() public ulong GetPeakUsage()
{ {
ulong peak = 0; var peak = 0ul;
foreach (var block in _blocks) foreach (var block in _blocks)
{ {
if (!block.isFree) if (!block.isFree)
@@ -242,9 +226,6 @@ internal sealed class ResourceHeap
} }
} }
/// <summary>
/// Represents a placed resource within a heap.
/// </summary>
internal sealed class PlacedResource internal sealed class PlacedResource
{ {
public int index; 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 internal sealed class ResourceAliasingManager
{ {
private readonly IResourceAllocator _allocator; private readonly IResourceAllocator _allocator;
@@ -293,15 +270,11 @@ internal sealed class ResourceAliasingManager
// Mapping from logical resource index to placed resource index // Mapping from logical resource index to placed resource index
private readonly Dictionary<int, int> _logicalToPlaced; private readonly Dictionary<int, int> _logicalToPlaced;
// D3D12 alignment constants
private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB 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; public ResourceHeap Heap => _heap;
/// <summary>
/// Helper method to get the size of a resource
/// </summary>
private ulong GetResourceSize(RenderGraphResource resource) private ulong GetResourceSize(RenderGraphResource resource)
{ {
if (resource.type == RenderGraphResourceType.Texture) if (resource.type == RenderGraphResourceType.Texture)
@@ -311,7 +284,6 @@ internal sealed class ResourceAliasingManager
} }
else // Buffer else // Buffer
{ {
//return resource.bufferDesc.Size;
return _allocator.GetSizeInfo(ResourceDesc.Buffer(resource.bufferDesc)).Size; return _allocator.GetSizeInfo(ResourceDesc.Buffer(resource.bufferDesc)).Size;
} }
} }
@@ -339,13 +311,6 @@ internal sealed class ResourceAliasingManager
_heap.Reset(); _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) public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount)
{ {
// Build list of all logical resources (both textures and buffers) with their lifetimes // 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 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 ===== // ===== PASS 1: Simulate allocation to determine peak memory usage =====
var simulationHeap = new ResourceHeap(0, ulong.MaxValue); // Unlimited size for simulation var simulationHeap = new ResourceHeap(0, ulong.MaxValue); // Unlimited size for simulation
foreach (var (logicalIndex, logicalResource) in logicalResources) foreach (var (logicalIndex, logicalResource) in logicalResources)
{ {
@@ -385,10 +359,7 @@ internal sealed class ResourceAliasingManager
logicalIndex, logicalIndex,
alignment); alignment);
if (!success) Debug.Assert(success, "Simulation allocation failed - heap should be unlimited in size");
{
throw new InvalidOperationException("Simulation allocation failed - this should never happen with unlimited heap");
}
} }
// Get peak usage from simulation // Get peak usage from simulation
@@ -398,12 +369,15 @@ internal sealed class ResourceAliasingManager
peakMemoryUsage = AlignUp(peakMemoryUsage, _DEFAULT_TEXTURE_ALIGNMENT); peakMemoryUsage = AlignUp(peakMemoryUsage, _DEFAULT_TEXTURE_ALIGNMENT);
// ===== PASS 2: Create a single heap of the peak size and do the real allocation ===== // ===== PASS 2: Create a single heap of the peak size and do the real allocation =====
_heap.size = peakMemoryUsage;
_heap.Reset(); _heap.Reset();
_heap.size = peakMemoryUsage;
// Allocate each logical resource in the heap // Allocate each logical resource in the heap
foreach (var (logicalIndex, logicalResource) in logicalResources) 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 size = GetResourceSize(logicalResource);
var alignment = logicalResource.type == RenderGraphResourceType.Texture var alignment = logicalResource.type == RenderGraphResourceType.Texture
? _DEFAULT_TEXTURE_ALIGNMENT ? _DEFAULT_TEXTURE_ALIGNMENT
@@ -488,9 +462,6 @@ internal sealed class ResourceAliasingManager
return (value + alignment - 1) & ~(alignment - 1); return (value + alignment - 1) & ~(alignment - 1);
} }
/// <summary>
/// Restores aliasing state from cache.
/// </summary>
public void RestoreFromCache(Dictionary<int, int> logicalToPlaced, List<PlacedResourceData> placedData) public void RestoreFromCache(Dictionary<int, int> logicalToPlaced, List<PlacedResourceData> placedData)
{ {
_logicalToPlaced.Clear(); _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) public void StoreToCache(Dictionary<int, int> outLogicalToPlaced, List<PlacedResourceData> outPlacedData)
{ {
outLogicalToPlaced.Clear(); outLogicalToPlaced.Clear();

View File

@@ -32,6 +32,17 @@ public interface IRenderGraphBuilder : IDisposable
/// <returns>An identifier for the newly created texture resource.</returns> /// <returns>An identifier for the newly created texture resource.</returns>
Identifier<RGTexture> CreateTexture(in RGTextureDesc desc, string name); 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> /// <summary>
/// Creates a new buffer resource based on the specified desc. /// Creates a new buffer resource based on the specified desc.
/// </summary> /// </summary>
@@ -40,6 +51,17 @@ public interface IRenderGraphBuilder : IDisposable
/// <returns>An identifier for the newly created buffer resource.</returns> /// <returns>An identifier for the newly created buffer resource.</returns>
Identifier<RGBuffer> CreateBuffer(in BufferDesc desc, string name); 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> /// <summary>
/// Registers the specified texture for use in the current render graph pass with the given access mode. /// Registers the specified texture for use in the current render graph pass with the given access mode.
/// </summary> /// </summary>
@@ -140,7 +162,6 @@ public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder
internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder, IUnsafeRenderGraphBuilder internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder, IUnsafeRenderGraphBuilder
{ {
private RenderGraph _graph = null!; private RenderGraph _graph = null!;
private RenderGraphPassBase _pass = null!; private RenderGraphPassBase _pass = null!;
private RenderGraphResourceRegistry _resources = null!; private RenderGraphResourceRegistry _resources = null!;
@@ -206,6 +227,18 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
return handle; 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) public Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
{ {
ThrowIfDisposed(); ThrowIfDisposed();

View File

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

View File

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

View File

@@ -4,10 +4,6 @@ using Misaki.HighPerformance.Buffer;
namespace Ghost.Graphics.RenderGraphModule; 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 internal sealed class RenderGraphObjectPool
{ {
private static readonly List<SharedObjectPoolBase> s_allocatedPools = new(); private static readonly List<SharedObjectPoolBase> s_allocatedPools = new();
@@ -18,7 +14,8 @@ internal sealed class RenderGraphObjectPool
public virtual void Clear() { } 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(); private static readonly ObjectPool<T> s_pool = AllocatePool();
@@ -30,28 +27,20 @@ internal sealed class RenderGraphObjectPool
return newPool; return newPool;
} }
/// <summary>
/// Clear the pool using SharedObjectPool instance.
/// </summary>
/// <returns></returns>
public override void Clear() public override void Clear()
{ {
s_pool.Reset(); s_pool.Reset();
} }
/// <summary> public static T Rent()
/// Rent a new instance from the pool. {
/// </summary> return s_pool.Rent();
/// <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();
/// <summary> public static void Return(T toRelease)
/// Return an object to the pool. {
/// </summary> s_pool.Return(toRelease);
/// <param name="toRelease">instance to release.</param> }
public static void Return(T toRelease) => s_pool.Return(toRelease);
} }
public T Rent<T>() 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 internal sealed class RenderGraphResource
{ {
public string name = string.Empty; 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 internal sealed class RenderGraphResourceRegistry
{ {
private readonly RenderGraphObjectPool _pool; private readonly RenderGraphObjectPool _pool;

View File

@@ -1,11 +1,65 @@
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.RenderPipeline; 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; 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 public enum GraphicsAPI
{ {
Direct3D12 Direct3D12
@@ -63,6 +85,7 @@ internal class RenderSystem : IRenderSystem
private readonly RenderingConfig _config; private readonly RenderingConfig _config;
private readonly IGraphicsEngine _graphicsEngine; private readonly IGraphicsEngine _graphicsEngine;
private readonly IResourceManager _resourceManager;
private readonly FrameResource[] _frameResources; private readonly FrameResource[] _frameResources;
private readonly Thread _renderThread; private readonly Thread _renderThread;
@@ -77,6 +100,7 @@ internal class RenderSystem : IRenderSystem
private bool _disposed; private bool _disposed;
public IGraphicsEngine GraphicsEngine => _graphicsEngine; public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public IResourceManager ResourceManager => _resourceManager;
public bool IsRunning => _isRunning; public bool IsRunning => _isRunning;
public uint CPUFenceValue => _cpuFenceValue; public uint CPUFenceValue => _cpuFenceValue;
@@ -107,6 +131,8 @@ internal class RenderSystem : IRenderSystem
throw new NotSupportedException($"The specified graphics API '{config.GraphicsAPI}' is not supported."); throw new NotSupportedException($"The specified graphics API '{config.GraphicsAPI}' is not supported.");
} }
_resourceManager = new ResourceManager(_graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
// Create frame resources for synchronization // Create frame resources for synchronization
_frameResources = new FrameResource[config.FrameBufferCount]; _frameResources = new FrameResource[config.FrameBufferCount];
for (var i = 0; i < config.FrameBufferCount; i++) for (var i = 0; i < config.FrameBufferCount; i++)

View File

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