diff --git a/src/Runtime/Ghost.Core/Result.cs b/src/Runtime/Ghost.Core/Result.cs index de9ed0a..0502edf 100644 --- a/src/Runtime/Ghost.Core/Result.cs +++ b/src/Runtime/Ghost.Core/Result.cs @@ -390,6 +390,16 @@ public static class ResultExtensions return result; } + public static Result Then(this Result result, Func func) + { + if (result.IsFailure) + { + return Result.Failure(result.Message); + } + + return func(); + } + public static Result Then(this Result result, Func> func) { if (result.IsFailure) diff --git a/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs b/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs index 693e287..210fe08 100644 --- a/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs +++ b/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs @@ -1,9 +1,11 @@ using Ghost.Core; using Ghost.Engine.Components; using Ghost.Entities; +using Ghost.Graphics; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics.Geometry; @@ -11,14 +13,14 @@ namespace Ghost.Engine.Systems; public class RenderExtractionSystem : ISystem { - private IGraphicsEngine _graphicsEngine = null!; + private RenderSystem _renderSystem = null!; private Identifier _cameraQueryID; private Identifier _meshQueryID; public void Initialize(ref readonly SystemAPI systemAPI) { - _graphicsEngine = systemAPI.World.GetResource(); + _renderSystem = systemAPI.World.GetService(); var builder = new QueryBuilder(); @@ -77,13 +79,15 @@ public class RenderExtractionSystem : ISystem ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID); ref var meshQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshQueryID); + var renderRequests = new UnsafeList(cameraQuery.CalculateEntityCount(), Allocator.Temp); + // TODO: We should extract the render record for each camera because different cameras may have different culling results. foreach (var (cam, camLtw) in cameraQuery.GetComponentIterator()) { ref readonly var camRef = ref cam.Get(); ref readonly var camLtwRef = ref camLtw.Get(); - var rtResult = _graphicsEngine.ResourceDatabase.GetResourceDescription(camRef.colorTarget.AsResource()); + var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(camRef.colorTarget.AsResource()); if (rtResult.IsFailure) { continue; @@ -206,7 +210,6 @@ public class RenderExtractionSystem : ISystem var viewPos = camLtwRef.matrix.c3.xyz; var frustum = CreateFrustum(camRef, vp, viewDir, viewPos); - // TODO: Send this to render pipeline. var request = new RenderRequest { colorTarget = camRef.colorTarget, @@ -236,7 +239,11 @@ public class RenderExtractionSystem : ISystem renderingLayerMask = camRef.renderingLayerMask, }, }; + + renderRequests.Add(request); } + + _renderPipeline.Render(new RenderContext(), renderRequests.AsSpan()); } public void Cleanup(ref readonly SystemAPI systemAPI) diff --git a/src/Runtime/Ghost.Entities/Query.cs b/src/Runtime/Ghost.Entities/Query.cs index ab99f0f..5d97c0e 100644 --- a/src/Runtime/Ghost.Entities/Query.cs +++ b/src/Runtime/Ghost.Entities/Query.cs @@ -445,8 +445,7 @@ public unsafe partial struct EntityQuery : IDisposable return new ChunkIterator(_matchingArchetypes.AsReadOnly(), world); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly int GetEntityCount() + public readonly int CalculateEntityCount() { var total = 0; var world = World.GetWorld(_worldID); diff --git a/src/Runtime/Ghost.Entities/System.cs b/src/Runtime/Ghost.Entities/System.cs index 2ed5a7a..e3524f8 100644 --- a/src/Runtime/Ghost.Entities/System.cs +++ b/src/Runtime/Ghost.Entities/System.cs @@ -46,7 +46,7 @@ public abstract class SystemBase : ISystem foreach (var queryID in _requiredQueries) { ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier(queryID)); - if (query.GetEntityCount() == 0) + if (query.CalculateEntityCount() == 0) { return false; } diff --git a/src/Runtime/Ghost.Entities/World.cs b/src/Runtime/Ghost.Entities/World.cs index 290be2d..d4073b0 100644 --- a/src/Runtime/Ghost.Entities/World.cs +++ b/src/Runtime/Ghost.Entities/World.cs @@ -1,7 +1,6 @@ using Ghost.Core; using Misaki.HighPerformance.Jobs; using System.Runtime.CompilerServices; -using TerraFX.Interop.Windows; namespace Ghost.Entities; @@ -86,7 +85,7 @@ public partial class World : IDisposable, IEquatable private readonly ComponentManager _componentManager; private readonly SystemManager _systemManager; - private readonly Dictionary _globalResource; + private readonly Dictionary _services; private int _version; private bool _disposed = false; @@ -140,7 +139,7 @@ public partial class World : IDisposable, IEquatable _componentManager = new ComponentManager(this); _systemManager = new SystemManager(this); - _globalResource = new Dictionary(); + _services = new Dictionary(); if (jobScheduler != null) { @@ -195,20 +194,20 @@ public partial class World : IDisposable, IEquatable /// Registers or overwrites a global resource in the world. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetResource(T resource) + public void AddService(T resource) where T : class { - _globalResource[typeof(T)] = resource; + _services[typeof(T)] = resource; } /// /// Retrieves a global resource from the world. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T GetResource() + public T GetService() where T : class { - if (_globalResource.TryGetValue(typeof(T), out var resource)) + if (_services.TryGetValue(typeof(T), out var resource)) { return (T)resource; } @@ -220,10 +219,10 @@ public partial class World : IDisposable, IEquatable /// Checks if a global resource exists. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasResource() + public bool HasService() where T : class { - return _globalResource.ContainsKey(typeof(T)); + return _services.ContainsKey(typeof(T)); } public bool Equals(World? other) diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs index 650472a..777b4e4 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs @@ -79,6 +79,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer Dispose(); } + [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfDisposed() { diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs index 11309eb..13b35d5 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs @@ -1,6 +1,7 @@ using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel; +using System.Diagnostics; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -65,7 +66,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void Submit(ICommandBuffer commandBuffer) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (commandBuffer.IsEmpty) { @@ -86,7 +87,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void Submit(params ReadOnlySpan commandBuffers) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); Span executableIndices = stackalloc int[commandBuffers.Length]; executableIndices.Fill(-1); @@ -129,7 +130,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public ulong Signal(ulong value) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); _fenceValue = value; ThrowIfFailed(_commandQueue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue)); @@ -138,7 +139,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void WaitForValue(ulong value) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (_fence.Get()->GetCompletedValue() < value) { @@ -152,13 +153,13 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public ulong GetCompletedValue() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _fence.Get()->GetCompletedValue(); } public void WaitIdle() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var fenceValue = Signal(Interlocked.Increment(ref _fenceValue)); WaitForValue(fenceValue); diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs index d94fdf2..489063a 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs @@ -1,17 +1,19 @@ #if DEBUG -#define ENABLE_DEBUG +#define ENABLE_DEBUG_LAYER #endif using Ghost.Core; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using System.Collections.Immutable; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace Ghost.Graphics.D3D12; public static class D3D12GraphicsEngineFactory { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IGraphicsEngine Create(GraphicsEngineDesc desc) { return new D3D12GraphicsEngine(desc); @@ -22,7 +24,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine { private readonly GraphicsEngineDesc _desc; -#if ENABLE_DEBUG +#if ENABLE_DEBUG_LAYER private readonly D3D12DebugLayer _debugLayer; #endif private readonly D3D12RenderDevice _device; @@ -46,7 +48,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine { _desc = desc; -#if ENABLE_DEBUG +#if ENABLE_DEBUG_LAYER _debugLayer = new D3D12DebugLayer(); #endif _device = new D3D12RenderDevice(); @@ -67,6 +69,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine Dispose(); } + [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfDisposed() { @@ -118,28 +121,20 @@ internal class D3D12GraphicsEngine : IGraphicsEngine return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount); } - public Result RenderFrame(ICommandAllocator commandAllocator, uint cpuFenceValue, uint gpuFenceValue) + public Result BeginFrame(uint cpuFenceValue, uint gpuFenceValue) { ThrowIfDisposed(); - var r = Result.Success(); - _resourceDatabase.BeginFrame(cpuFenceValue); + return Result.Success(); + } - // TODO: We should not handle renderers in graphics engine since the purpose of graphics engine is to provide low-level graphics resource management and command buffer creation. - // We need to migrate this to IRenderPipeline instead when it's ready. - foreach (var renderer in _renderers) - { - r = renderer.Render(commandAllocator); - if (r.IsFailure) - { - break; - } - } + public Result EndFrame(uint cpuFenceValue, uint gpuFenceValue) + { + ThrowIfDisposed(); _resourceDatabase.EndFrame(gpuFenceValue); - - return r; + return Result.Success(); } public void Dispose() @@ -163,7 +158,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine _descriptorAllocator.Dispose(); _shaderCompiler.Dispose(); _device.Dispose(); -#if ENABLE_DEBUG +#if ENABLE_DEBUG_LAYER _debugLayer.Dispose(); #endif diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs index 5e964be..a212010 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs @@ -2,6 +2,7 @@ using Ghost.Core; using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel; +using System.Diagnostics; using System.Runtime.CompilerServices; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -601,7 +602,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator public Handle CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); CheckTexture2DSize(desc.Width, desc.Height); @@ -699,7 +700,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator public Handle CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var textureDesc = desc.ToTextureDescription(); return CreateTexture(in textureDesc, name, options); @@ -707,7 +708,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator public Handle CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); CheckBufferSize(desc.Size); var resourceDesc = desc.ToD3D12ResourceDesc(); @@ -839,7 +840,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator public Identifier CreateSampler(ref readonly SamplerDesc desc) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (_resourceDatabase.TryGetSampler(in desc, out var id)) { diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs index f0e84f9..421a167 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs @@ -4,6 +4,7 @@ using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; +using System.Diagnostics; using System.Runtime.InteropServices; using TerraFX.Interop.DirectX; @@ -130,7 +131,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase internal unsafe Handle ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, string? name = null) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (pResource == null) { @@ -156,7 +157,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase public unsafe Handle AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (allocation == null) { #if DEBUG @@ -186,13 +187,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase public bool HasResource(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _resources.Contains(handle.ID, handle.Generation); } public RefResult GetResourceRecord(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) @@ -267,7 +268,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase public string? GetResourceName(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); #if DEBUG || GHOST_EDITOR if (_resourceName.TryGetValue(handle, out var name)) @@ -280,7 +281,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase public void ReleaseResource(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (_resources.TryGetElementAt(handle.ID, handle.Generation, out var record)) { @@ -299,7 +300,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase public void ReleaseResourceImmediately(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist || !info.Allocated) @@ -313,7 +314,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase public Identifier AddSampler(ref readonly SamplerDesc desc, int id) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (_samplers.ContainsKey(desc)) { @@ -328,13 +329,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier id) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _samplers.TryGetValue(desc, out id); } public void ReleaseSampler(Identifier id) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); // NOTE: We almost never release samplers individually, because they are cheap and can be reused. // Ideally we would release all samplers at once when disposing the ResourceDatabase. @@ -360,13 +361,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase public void BeginFrame(uint currentFrameFenceValue) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); _currentFrameFenceValue = currentFrameFenceValue; } public void EndFrame(uint completedFenceValue) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); while (_releaseQueue.Count > 0) { @@ -384,7 +385,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase internal void ReleaseAllResourcesImmediately() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); foreach (ref var record in _resources) { diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs index ab21f1a..4e84cae 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs @@ -68,7 +68,9 @@ internal unsafe class D3D12SwapChain : ISwapChain SetScale(desc.ScaleX, desc.ScaleY); if (desc.Target.Type == SwapChainTargetType.Composition) + { _compositionSurface = desc.Target.CompositionSurface; + } } ~D3D12SwapChain() @@ -169,20 +171,20 @@ internal unsafe class D3D12SwapChain : ISwapChain [MethodImpl(MethodImplOptions.AggressiveInlining)] public Handle GetCurrentBackBuffer() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan> GetBackBuffers() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _backBuffers.AsSpan(); } public void Present(bool vsync = true) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var presentFlags = 0u; var syncInterval = vsync ? 1u : 0u; @@ -192,7 +194,7 @@ internal unsafe class D3D12SwapChain : ISwapChain public void Resize(uint width, uint height) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (Width == width && Height == height) { @@ -215,6 +217,8 @@ internal unsafe class D3D12SwapChain : ISwapChain public void SetScale(float scaleX, float scaleY) { + Debug.Assert(!_disposed); + var inverseScaleX = 1.0f / scaleX; var inverseScaleY = 1.0f / scaleY; diff --git a/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs b/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs index a760eaf..eeb3e61 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs @@ -78,11 +78,18 @@ public interface IGraphicsEngine : IDisposable ISwapChain CreateSwapChain(SwapChainDesc desc); /// - /// Renders the current frame. + /// Begin the current frame. /// - /// Command allocator to use for rendering /// CPU fence value for synchronization /// GPU fence value for synchronization - /// Result of the rendering operation - Result RenderFrame(ICommandAllocator commandAllocator, uint cpuFenceValue, uint gpuFenceValue); + /// Result of the begin frame operation + Result BeginFrame(uint cpuFenceValue, uint gpuFenceValue); + + /// + /// End the current frame. + /// + /// CPU fence value for synchronization + /// GPU fence value for synchronization + /// Result of the end frame operation + Result EndFrame(uint cpuFenceValue, uint gpuFenceValue); } diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs index e8b4121..9d88e78 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphBuilder.cs @@ -1,6 +1,7 @@ using Ghost.Core; using Ghost.Graphics.RHI; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Ghost.Graphics.RenderGraphModule; @@ -175,6 +176,8 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra _disposed = false; } + [Conditional("DEBUG")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs index 72955fe..bbb7f4f 100644 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.cs @@ -1,13 +1,14 @@ using Ghost.Graphics.Core; using Ghost.Graphics.RenderGraphModule; using Ghost.Graphics.RHI; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace Ghost.Graphics.RenderPipeline; public sealed class GhostRenderPipelineSettings : IRenderPipelineSettings { - public static IRenderPipeline CreatePipeline(RenderSystem renderSystem) + public IRenderPipeline CreatePipeline(RenderSystem renderSystem) { return new GhostRenderPipeline(renderSystem); } @@ -24,6 +25,7 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline Dispose(); } + [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void ThrowIfDisposed() { diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs index 9836a59..b31cc9d 100644 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/IRenderPipeline.cs @@ -5,7 +5,7 @@ namespace Ghost.Graphics.RenderPipeline; public interface IRenderPipelineSettings { - static abstract IRenderPipeline CreatePipeline(RenderSystem renderSystem); + IRenderPipeline CreatePipeline(RenderSystem renderSystem); } public interface IRenderPipeline : IDisposable diff --git a/src/Runtime/Ghost.Graphics/RenderSystem.cs b/src/Runtime/Ghost.Graphics/RenderSystem.cs index 48b5298..a5fc82a 100644 --- a/src/Runtime/Ghost.Graphics/RenderSystem.cs +++ b/src/Runtime/Ghost.Graphics/RenderSystem.cs @@ -1,8 +1,10 @@ using Ghost.Core; using Ghost.Graphics.D3D12; +using Ghost.Graphics.RenderPipeline; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Mathematics; using System.Collections.Concurrent; +using System.Diagnostics; namespace Ghost.Graphics; @@ -61,6 +63,7 @@ public class RenderSystem : IDisposable } private readonly RenderSystemDesc _config; + private readonly IGraphicsEngine _graphicsEngine; private readonly ResourceManager _resourceManager; @@ -69,6 +72,9 @@ public class RenderSystem : IDisposable private readonly AutoResetEvent _shutdownEvent; private readonly ConcurrentDictionary _resizeRequest; + private IRenderPipelineSettings _renderPipelineSettings; + private IRenderPipeline _renderPipeline; + private uint _frameIndex; private uint _cpuFenceValue; private uint _gpuFenceValue; @@ -85,6 +91,24 @@ public class RenderSystem : IDisposable public uint FrameIndex => _frameIndex; public uint MaxFrameLatency => _config.FrameBufferCount; + public IRenderPipelineSettings RenderPipelineSettings + { + get => _renderPipelineSettings; + set + { + Debug.Assert(value != null, "RenderPipelineSettings cannot be set to null."); + Debug.Assert(!_disposed, "Cannot set RenderPipelineSettings on a disposed RenderSystem."); + + if (value == _renderPipelineSettings) + { + return; + } + + _renderPipelineSettings = value; + _renderPipeline = _renderPipelineSettings.CreatePipeline(this); + } + } + internal RenderSystem(RenderSystemDesc desc) { _config = desc; @@ -137,6 +161,9 @@ public class RenderSystem : IDisposable _shutdownEvent = new AutoResetEvent(false); _resizeRequest = new ConcurrentDictionary(); + _renderPipelineSettings = new GhostRenderPipelineSettings(); + _renderPipeline = _renderPipelineSettings.CreatePipeline(this); + _isRunning = false; _disposed = false; } @@ -148,6 +175,18 @@ public class RenderSystem : IDisposable private void RenderLoop() { + void StopRenderLoop(Result result) + { + Debug.Assert(result.IsFailure, "StopRenderLoop should only be called with a failure result."); + + _isRunning = false; + _shutdownEvent.Set(); +#if DEBUG + Debugger.Break(); +#endif + Logger.LogError($"Render failed: {result.Message}"); + } + var waitHandles = new WaitHandle[] { null!, _shutdownEvent }; while (_isRunning) @@ -166,65 +205,74 @@ public class RenderSystem : IDisposable } // Only proceed if CPU ready event was signaled - if (waitResult == 0) + if (waitResult != 0) { - if (frameResource.FenceValue > 0) - { - _graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue); - } + continue; + } - if (!_resizeRequest.IsEmpty) - { - //WaitIdle(); - _gpuFenceValue++; - var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); - _graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence); - - // Sync the current frame resource to this new fence to keep state consistent - frameResource.FenceValue = flushFence; - - foreach (var resource in _frameResources) - { - resource.CommandAllocator.Reset(); - } - - var keys = _resizeRequest.Keys.ToArray(); - foreach (var swapChain in keys) - { - if (_resizeRequest.TryRemove(swapChain, out var newSize)) - { - swapChain.Resize(newSize.x, newSize.y); - } - } - - frameResource.GpuReadyEvent.Set(); - - continue; // Skip rendering this frame since we just resized and may have invalid render targets - } - - frameResource.CommandAllocator.Reset(); - - var r = _graphicsEngine.RenderFrame(frameResource.CommandAllocator, _cpuFenceValue, _gpuFenceValue); - if (r.IsFailure) - { - _isRunning = false; -#if DEBUG - System.Diagnostics.Debugger.Break(); -#endif - Logger.LogError($"RenderFrame failed: {r.Message}"); - } + _graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue); + if (!_resizeRequest.IsEmpty) + { + //WaitIdle(); _gpuFenceValue++; + var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); + _graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence); + + // Sync the current frame resource to this new fence to keep state consistent + frameResource.FenceValue = flushFence; + + foreach (var resource in _frameResources) + { + resource.CommandAllocator.Reset(); + } + + var keys = _resizeRequest.Keys.ToArray(); + foreach (var swapChain in keys) + { + if (_resizeRequest.TryRemove(swapChain, out var newSize)) + { + swapChain.Resize(newSize.x, newSize.y); + } + } frameResource.GpuReadyEvent.Set(); - frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); + + continue; // Skip rendering this frame since we just resized and may have invalid render targets } + + frameResource.CommandAllocator.Reset(); + + var r = _graphicsEngine.BeginFrame(_cpuFenceValue, _gpuFenceValue); + if (r.IsFailure) + { + StopRenderLoop(r); + } + + var cmd = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics); + var renderCtx = new RenderContext + { + CommandBuffer = cmd, + }; + + _renderPipeline.Render(renderCtx, default); + + r = _graphicsEngine.EndFrame(_cpuFenceValue, _gpuFenceValue); + if (r.IsFailure) + { + StopRenderLoop(r); + } + + _gpuFenceValue++; + + frameResource.GpuReadyEvent.Set(); + frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); } } internal void Start() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed, "Cannot start a disposed RenderSystem."); if (_isRunning) { @@ -237,7 +285,7 @@ public class RenderSystem : IDisposable internal void Stop() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed, "Cannot stop a disposed RenderSystem."); if (!_isRunning) { @@ -251,7 +299,7 @@ public class RenderSystem : IDisposable internal void SignalCPUReady() { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem."); var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount); _frameResources[eventIndex].CpuReadyEvent.Set(); @@ -260,13 +308,13 @@ public class RenderSystem : IDisposable internal void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed, "Cannot request swap chain resize on a disposed RenderSystem."); _resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize); } public bool WaitForGPUReady(int timeOut = -1) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem."); var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount); return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut); @@ -274,6 +322,7 @@ public class RenderSystem : IDisposable public void WaitIdle() { + Debug.Assert(!_disposed, "Cannot wait idle on a disposed RenderSystem."); foreach (var frameResource in _frameResources) { if (frameResource.FenceValue > 0)