using Ghost.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Mathematics; using System.Collections.Concurrent; namespace Ghost.Graphics; public enum GraphicsAPI { Direct3D12 } public struct RenderingConfig { public GraphicsAPI GraphicsAPI { get; set; } public uint FrameBufferCount { get; set; } } public interface IFenceSynchronizer { uint CPUFenceValue { get; } uint GPUFenceValue { get; } uint FrameIndex { get; } uint MaxFrameLatency { get; } bool WaitForGPUReady(int timeOut = -1); 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); } /// /// Application-level render system that orchestrates multiple renderers /// and handles frame synchronization /// internal class RenderSystem : IRenderSystem { // TODO: Thread local command buffers. private struct FrameResource : IDisposable { public required AutoResetEvent CpuReadyEvent { get; init; } public required AutoResetEvent GpuReadyEvent { get; init; } public required ICommandAllocator CommandAllocator { get; init; } public ulong FenceValue { get; set; } public readonly void Dispose() { CpuReadyEvent.Dispose(); GpuReadyEvent.Dispose(); CommandAllocator.Dispose(); } } private readonly RenderingConfig _config; private readonly IGraphicsEngine _graphicsEngine; private readonly FrameResource[] _frameResources; private readonly Thread _renderThread; private readonly AutoResetEvent _shutdownEvent; private readonly ConcurrentDictionary _resizeRequest; private uint _frameIndex; private uint _cpuFenceValue; private uint _gpuFenceValue; private bool _isRunning; private bool _disposed; public IGraphicsEngine GraphicsEngine => _graphicsEngine; public bool IsRunning => _isRunning; public uint CPUFenceValue => _cpuFenceValue; public uint GPUFenceValue => _gpuFenceValue; public uint FrameIndex => _frameIndex; public uint MaxFrameLatency => _config.FrameBufferCount; public RenderSystem(RenderingConfig config) { _config = config; _graphicsEngine = config.GraphicsAPI switch { GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this), _ => throw new NotSupportedException($"Graphics API {config.GraphicsAPI} is not supported.") }; // Create frame resources for synchronization _frameResources = new FrameResource[config.FrameBufferCount]; for (var i = 0; i < config.FrameBufferCount; i++) { _frameResources[i] = new FrameResource { CpuReadyEvent = new AutoResetEvent(false), GpuReadyEvent = new AutoResetEvent(true), CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics) }; } _renderThread = new Thread(RenderLoop) { IsBackground = true, Name = "Graphics Render Thread", Priority = ThreadPriority.Normal }; _shutdownEvent = new AutoResetEvent(false); _resizeRequest = new ConcurrentDictionary(); _isRunning = false; _disposed = false; } ~RenderSystem() { Dispose(); } public void Start() { ObjectDisposedException.ThrowIf(_disposed, this); if (_isRunning) { return; } _isRunning = true; _renderThread.Start(); } public void Stop() { ObjectDisposedException.ThrowIf(_disposed, this); if (!_isRunning) { return; } _isRunning = false; _shutdownEvent.Set(); _renderThread.Join(); } public void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize) { ObjectDisposedException.ThrowIf(_disposed, this); _resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize); } public bool WaitForGPUReady(int timeOut = -1) { ObjectDisposedException.ThrowIf(_disposed, this); var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount); return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut); } public void SignalCPUReady() { ObjectDisposedException.ThrowIf(_disposed, this); var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount); _frameResources[eventIndex].CpuReadyEvent.Set(); _cpuFenceValue++; } public void WaitIdle() { foreach (var frameResource in _frameResources) { if (frameResource.FenceValue > 0) { _graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue); } } } private void RenderLoop() { var waitHandles = new WaitHandle[] { null!, _shutdownEvent }; while (_isRunning) { _frameIndex = _gpuFenceValue % _config.FrameBufferCount; ref var frameResource = ref _frameResources[_frameIndex]; // Wait for either CPU ready signal or shutdown signal waitHandles[0] = frameResource.CpuReadyEvent; var waitResult = WaitHandle.WaitAny(waitHandles); // If shutdown was signaled or timeout occurred, exit the loop if (!_isRunning || waitResult == 1 || waitResult == WaitHandle.WaitTimeout) { break; } // Only proceed if CPU ready event was signaled if (waitResult == 0) { if (frameResource.FenceValue > 0) { _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(); } foreach (var kvp in _resizeRequest) { var swapChain = kvp.Key; var newSize = kvp.Value; swapChain.Resize(newSize.x, newSize.y); } _resizeRequest.Clear(); } frameResource.CommandAllocator.Reset(); var r = _graphicsEngine.RenderFrame(frameResource.CommandAllocator); if (r.IsFailure) { _isRunning = false; #if DEBUG System.Diagnostics.Debugger.Break(); #endif Logger.LogError($"RenderFrame failed: {r.Message}"); } _gpuFenceValue++; frameResource.GpuReadyEvent.Set(); frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); } } } public void Dispose() { if (_disposed) { return; } Stop(); foreach (var frameResource in _frameResources) { frameResource.Dispose(); } _graphicsEngine.Dispose(); _shutdownEvent.Dispose(); _disposed = true; GC.SuppressFinalize(this); } }