diff --git a/src/.editorconfig b/src/.editorconfig index 2d6ae9a..e6b7b73 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -1,5 +1,5 @@ [*] -max_line_length = 400 +max_line_length = 200 [*.cs] csharp_new_line_before_open_brace = all diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs index f072631..650472a 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs @@ -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; diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs index 65e2c06..fd49c89 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs @@ -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) diff --git a/src/Runtime/Ghost.Graphics.RHI/IRenderSystem.cs b/src/Runtime/Ghost.Graphics.RHI/IFenceSynchronizer.cs similarity index 58% rename from src/Runtime/Ghost.Graphics.RHI/IRenderSystem.cs rename to src/Runtime/Ghost.Graphics.RHI/IFenceSynchronizer.cs index 3c0e176..a12c5e7 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IRenderSystem.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IFenceSynchronizer.cs @@ -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); -} \ No newline at end of file diff --git a/src/Runtime/Ghost.Graphics/Core/RenderList.cs b/src/Runtime/Ghost.Graphics/Core/RenderList.cs index 1a2c0df..582bbfc 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderList.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderList.cs @@ -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(); diff --git a/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs b/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs index 15a86f9..3050e1a 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs @@ -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 colorTarget; public Handle depthTarget; - public delegate* renderFunc; + public RenderList opaqueRenderList; + public RenderList transparentRenderList; + public RenderList shadowCasterRenderList; + + public delegate* renderFunc; } diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs index 5ef1c22..1271843 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs @@ -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(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.Invalid; @@ -120,7 +125,7 @@ public sealed class RenderGraph : IDisposable /// The identifier of the imported render graph buffer. Invalid if import fails. public Identifier ImportBuffer(Handle buffer, string name) { - var r = _resourceManager.ResourceDatabase.GetResourceDescription(buffer.AsResource()); + var r = _resourceDatabase.GetResourceDescription(buffer.AsResource()); if (r.IsFailure) { return Identifier.Invalid; @@ -195,14 +200,14 @@ public sealed class RenderGraph : IDisposable /// /// Executes all compiled passes using native render passes where possible. /// - 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() diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs index aa7e6ca..eed93ac 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphAliasing.cs @@ -1,12 +1,10 @@ using Ghost.Core.Utilities; using Ghost.Graphics.RHI; +using System.Diagnostics; using System.Runtime.InteropServices; namespace Ghost.Graphics.RenderGraphModule; -/// -/// Represents a memory block within a heap. -/// internal struct MemoryBlock { public ulong offset; @@ -35,17 +33,12 @@ internal struct MemoryBlock } } -/// -/// Represents a GPU memory heap for placed resources. -/// Supports D3D12-style heap tier 2 (buffers and textures can alias). -/// internal sealed class ResourceHeap { public int index; public ulong size; private readonly List _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); } - /// - /// Checks if a resource can be placed at the given offset without lifetime conflicts. - /// Must check ALL blocks that overlap with this offset range. - /// 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; } - /// - /// Gets the total memory that would be used if no aliasing occurred. - /// 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; } - /// - /// Gets the peak memory usage considering aliasing (max offset + size). - /// 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 } } -/// -/// Represents a placed resource within a heap. -/// internal sealed class PlacedResource { public int index; @@ -279,10 +260,6 @@ internal sealed class PlacedResource } } -/// -/// 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. -/// 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 _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; - /// - /// Helper method to get the size of a resource - /// 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(); } - /// - /// 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 - /// 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); } - /// - /// Restores aliasing state from cache. - /// public void RestoreFromCache(Dictionary logicalToPlaced, List placedData) { _logicalToPlaced.Clear(); @@ -518,9 +489,6 @@ internal sealed class ResourceAliasingManager } } - /// - /// Stores current aliasing state to cache. - /// public void StoreToCache(Dictionary outLogicalToPlaced, List outPlacedData) { outLogicalToPlaced.Clear(); diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs index a91847e..e8b4121 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs @@ -32,6 +32,17 @@ public interface IRenderGraphBuilder : IDisposable /// An identifier for the newly created texture resource. Identifier CreateTexture(in RGTextureDesc desc, string name); + /// + /// Creates multiple texture resources based on the specified desc. + /// + /// + /// Those textures will be used as multi buffering in the render graph automaticlly and not aliasable with other resources. + /// + /// A structure that defines the properties and configuration of the texture to create. + /// The base name of the texture resources. The actual name for each texture will be generated by appending an index to this base name. + /// A span to receive the identifiers for the newly created texture resources. The length of the span determines how many textures will be created. + void CreateTextures(in RGTextureDesc desc, string name, Span> textureIDs); + /// /// Creates a new buffer resource based on the specified desc. /// @@ -40,6 +51,17 @@ public interface IRenderGraphBuilder : IDisposable /// An identifier for the newly created buffer resource. Identifier CreateBuffer(in BufferDesc desc, string name); + /// + /// Creates multiple buffer resources based on the specified desc. + /// + /// + /// Those buffers will be used as multi buffering in the render graph automaticlly and not aliasable with other resources. + /// + /// A structure that defines the properties and configuration of the buffer to create. + /// The base name of the buffer resources. The actual name for each buffer will be generated by appending an index to this base name. + /// A span to receive the identifiers for the newly created buffer resources. The length of the span determines how many buffers will be created. + void CreateBuffers(in BufferDesc desc, string name, Span> bufferIDs); + /// /// Registers the specified texture for use in the current render graph pass with the given access mode. /// @@ -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> 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> 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 UseTexture(Identifier texture, AccessFlags flags) { ThrowIfDisposed(); diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs index ff186d9..b556d60 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphContext.cs @@ -12,6 +12,9 @@ public interface IRenderGraphContext Handle GetActualResource(Identifier resource); Handle GetActualTexture(Identifier texture); Handle GetActualBuffer(Identifier buffer); + + Handle GetHistoryTexture(ReadOnlySpan> texture, int historyOffset); + Handle GetHistoryBuffer(ReadOnlySpan> 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 GetHistoryTexture(ReadOnlySpan> textures, int historyOffset) + { + if (historyOffset < 0 || historyOffset >= textures.Length) + { + return Handle.Invalid; + } + + var index = (int)(_frameIndex % textures.Length) - historyOffset; + if (index < 0) + { + index += textures.Length; + } + + return GetActualTexture(textures[index]); + } + + public Handle GetHistoryBuffer(ReadOnlySpan> buffers, int historyOffset) + { + if (historyOffset < 0 || historyOffset >= buffers.Length) + { + return Handle.Invalid; + } + + var index = (int)(_frameIndex % buffers.Length) - historyOffset; + if (index < 0) + { + index += buffers.Length; + } + + return GetActualBuffer(buffers[index]); + } + public void SetActiveMaterial(Handle 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(&data, sizeof(PushConstantsData) / sizeof(uint)); @@ -196,4 +237,4 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs index 83c8cc0..5b378f4 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs @@ -3,30 +3,29 @@ using Ghost.Graphics.RHI; namespace Ghost.Graphics.RenderGraphModule; -/// -/// Handles execution of compiled render graphs, including barrier execution and native render passes. -/// 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; } - /// - /// Executes all compiled passes using native render passes where possible. - /// public unsafe Error Execute( - ICommandBuffer cmd, + ICommandBuffer commandBuffer, List compiledPasses, List nativePasses, List 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(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc); + commandBuffer.BeginRenderPass(new Span(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; } - /// - /// Executes all barriers for a specific pass. - /// Uses pre-compiled barriers and queries before state from ResourceManager. - /// 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; diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs index 4ab8b7d..eea254a 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs @@ -4,10 +4,6 @@ using Misaki.HighPerformance.Buffer; namespace Ghost.Graphics.RenderGraphModule; -/// -/// Object pool for reusing allocated objects across frames. -/// This is key to minimizing GC allocations after the first frame. -/// internal sealed class RenderGraphObjectPool { private static readonly List s_allocatedPools = new(); @@ -18,7 +14,8 @@ internal sealed class RenderGraphObjectPool public virtual void Clear() { } } - private class SharedObjectPool : SharedObjectPoolBase where T : class, new() + private class SharedObjectPool : SharedObjectPoolBase + where T : class, new() { private static readonly ObjectPool s_pool = AllocatePool(); @@ -30,28 +27,20 @@ internal sealed class RenderGraphObjectPool return newPool; } - /// - /// Clear the pool using SharedObjectPool instance. - /// - /// public override void Clear() { s_pool.Reset(); } - /// - /// Rent a new instance from the pool. - /// - /// - // FIX: ObjectPool.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(); + } - /// - /// Return an object to the pool. - /// - /// instance to release. - public static void Return(T toRelease) => s_pool.Return(toRelease); + public static void Return(T toRelease) + { + s_pool.Return(toRelease); + } } public T Rent() @@ -75,9 +64,6 @@ internal sealed class RenderGraphObjectPool } } -/// -/// Represents a resource in the render graph (texture or buffer). -/// internal sealed class RenderGraphResource { public string name = string.Empty; @@ -121,11 +107,6 @@ internal sealed class RenderGraphResource } } -/// -/// 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. -/// internal sealed class RenderGraphResourceRegistry { private readonly RenderGraphObjectPool _pool; diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs index 4c5cbd0..3baa141 100644 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs @@ -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 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 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); } } diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs new file mode 100644 index 0000000..d32d5dc --- /dev/null +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs @@ -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 requests); +} diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/RenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/RenderPipeline.cs deleted file mode 100644 index 0fe15e2..0000000 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/RenderPipeline.cs +++ /dev/null @@ -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 requests); -} - -public abstract class RenderPipelineBase : IRenderPipeline -{ - protected readonly Dictionary _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 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) - { - } -} diff --git a/src/Runtime/Ghost.Graphics/RenderSystem.cs b/src/Runtime/Ghost.Graphics/RenderSystem.cs index dbb2dd0..7d5d6bb 100644 --- a/src/Runtime/Ghost.Graphics/RenderSystem.cs +++ b/src/Runtime/Ghost.Graphics/RenderSystem.cs @@ -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++) diff --git a/src/Runtime/Ghost.Graphics/ResourceManager.cs b/src/Runtime/Ghost.Graphics/ResourceManager.cs index 2dd7cb3..fbee3bc 100644 --- a/src/Runtime/Ghost.Graphics/ResourceManager.cs +++ b/src/Runtime/Ghost.Graphics/ResourceManager.cs @@ -9,16 +9,6 @@ namespace Ghost.Graphics; public interface IResourceManager { - IResourceAllocator ResourceAllocator - { - get; - } - - IResourceDatabase ResourceDatabase - { - get; - } - /// /// Creates a new mesh from the specified vertex and index data. /// @@ -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(64, Allocator.Persistent); - _materials = new UnsafeSlotMap(16, Allocator.Persistent); + _materials = new UnsafeSlotMap(64, Allocator.Persistent); _shaders = new UnsafeList(16, Allocator.Persistent); }