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);
}
}