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:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
14
src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs
Normal file
14
src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs
Normal 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);
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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++)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user