using Ghost.Graphics.RHI; 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(); } /// /// 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 ICommandBuffer CommandBuffer { get; init; } public ulong FenceValue { get; set; } public readonly void Dispose() { CpuReadyEvent.Dispose(); GpuReadyEvent.Dispose(); CommandBuffer.Dispose(); } } private readonly RenderingConfig _config; private readonly IGraphicsEngine _graphicsEngine; private readonly FrameResource[] _frameResources; private readonly Thread _renderThread; private readonly AutoResetEvent _shutdownEvent; 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.") }; _shutdownEvent = new AutoResetEvent(false); // 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), CommandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics), }; } _renderThread = new Thread(RenderLoop) { IsBackground = true, Name = "Graphics Render Thread", Priority = ThreadPriority.Normal }; _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 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); } _graphicsEngine.RenderFrame(frameResource.CommandBuffer); _gpuFenceValue++; frameResource.GpuReadyEvent.Set(); frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_frameIndex); _frameIndex++; } } } public void Dispose() { if (_disposed) { return; } Stop(); foreach (var frameResource in _frameResources) { frameResource.Dispose(); } _graphicsEngine.Dispose(); _shutdownEvent.Dispose(); _disposed = true; GC.SuppressFinalize(this); } }