Files
GhostEngine/Ghost.Graphics/RenderSystem.cs
Misaki a39f377533 Refactor GPU resource management and rendering pipeline
- Introduced `Handle<T>` and `Identifier<T>` for lightweight, strongly-typed resource identifiers.
- Replaced `BitSet` with `UnsafeBitSet` for improved performance and memory safety.
- Refactored `Mesh` and `Material` into `MeshClass` and `MaterialClass` for better GPU resource handling.
- Added `D3D12ResourceDatabase` to centralize GPU resource tracking and lifecycle management.
- Updated `D3D12ShaderCompiler` to load shaders from disk and dynamically populate constant buffers and textures.
- Enhanced `ICommandBuffer` with new upload operations for buffers and textures.
- Refactored `Vertex` struct for simplified memory layout and better performance.
- Updated `MeshBuilder` and rendering logic to align with new resource and shader structures.
- Added `BindlessDescriptor` support to `TextureHandle` and `BufferHandle`.
- Removed unused classes and performed general cleanup.
- Updated unit tests and demos to reflect the new architecture.
2025-09-19 23:20:15 +09:00

203 lines
5.1 KiB
C#

using Ghost.Graphics.RHI;
using System.Collections.Immutable;
namespace Ghost.Graphics;
public enum GraphicsAPI
{
Direct3D12
}
/// <summary>
/// Application-level render system that orchestrates multiple renderers
/// and handles frame synchronization
/// </summary>
internal class RenderSystem
{
private readonly struct FrameResource : IDisposable
{
public readonly AutoResetEvent cpuReadyEvent;
public readonly AutoResetEvent gpuReadyEvent;
public FrameResource()
{
cpuReadyEvent = new(false);
gpuReadyEvent = new(true);
}
public void Dispose()
{
cpuReadyEvent?.Dispose();
gpuReadyEvent?.Dispose();
}
}
private const uint _FRAME_COUNT = 2;
private readonly IGraphicsEngine _graphicsEngine = null!;
private readonly FrameResource[] _frameResources = null!;
private readonly Thread _renderThread = null!;
private readonly AutoResetEvent _shutdownEvent = null!;
private ImmutableArray<IRenderer> _renderers;
private uint _frameIndex;
private uint _cpuFenceValue;
private uint _gpuFenceValue;
private bool _isRunning;
private bool _disposed;
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
public uint CPUFenceValue => _cpuFenceValue;
public uint GPUFenceValue => _gpuFenceValue;
public bool IsRunning => _isRunning;
public RenderSystem(GraphicsAPI api)
{
_graphicsEngine = api switch
{
GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this),
_ => throw new NotSupportedException($"Graphics API {api} is not supported.")
};
_renderers = ImmutableArray<IRenderer>.Empty;
_shutdownEvent = new(false);
// Create frame resources for synchronization
_frameResources = new FrameResource[_FRAME_COUNT];
for (var i = 0; i < _FRAME_COUNT; i++)
{
_frameResources[i] = new();
}
_renderThread = new(RenderLoop)
{
IsBackground = true,
Name = "Graphics Render Thread",
Priority = ThreadPriority.Normal
};
_disposed = true;
}
public IRenderer CreateRenderer()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var renderer = _graphicsEngine.CreateRenderer();
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
return renderer;
}
public void RemoveRenderer(IRenderer renderer)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Remove(renderer));
}
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();
if (_renderThread.IsAlive)
{
_renderThread.Join();
}
}
public bool WaitForGPUReady(int timeOut = -1)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT);
return _frameResources[eventIndex].gpuReadyEvent.WaitOne(timeOut);
}
public void SignalCPUReady()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT);
_frameResources[eventIndex].cpuReadyEvent.Set();
_cpuFenceValue++;
}
private void RenderLoop()
{
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
while (_isRunning)
{
_frameIndex = _gpuFenceValue % _FRAME_COUNT;
var frameResource = _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)
{
_graphicsEngine.BeginFrame();
foreach (var renderer in _renderers)
{
renderer.ExecutePendingResize();
renderer.Render();
}
_graphicsEngine.EndFrame();
_gpuFenceValue++;
frameResource.gpuReadyEvent.Set();
}
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
Stop();
foreach (var frameResource in _frameResources)
{
frameResource.Dispose();
}
_shutdownEvent.Dispose();
_disposed = true;
}
}