feat(d3d12): unify resource mgmt & add pooling system

Refactored D3D12 resource and command management with a new D3D12Object<T> base class for unified lifetime and naming of COM objects. Introduced pooled command buffer and resource management in D3D12GraphicsEngine and ResourceManager, using frame-based return queues for safe reuse. Updated RenderSystem to use pooled command buffers and render requests, and to properly dispose of per-frame resources. Changed frame synchronization and resource release logic to use ulong fence/frame values for improved robustness. Refactored swap chain to DXGISwapChain and improved error handling and code clarity. Removed renderer management from IGraphicsEngine. Changed ResourceDesc, TextureDesc, and BufferDesc to record structs with equality and hashing for pooling.

BREAKING CHANGE: Renderer management APIs removed from IGraphicsEngine. Frame and resource synchronization now use ulong instead of uint. Resource pooling and command buffer pooling are now required for correct usage.
This commit is contained in:
2026-03-23 20:48:08 +09:00
parent 2b3bf21a74
commit d44ec0be31
22 changed files with 623 additions and 440 deletions

View File

@@ -627,7 +627,7 @@ public struct BarrierDesc
}
}
public struct ResourceDesc
public record struct ResourceDesc
{
[StructLayout(LayoutKind.Explicit)]
internal struct resource_union
@@ -638,7 +638,7 @@ public struct ResourceDesc
public BufferDesc bufferDescription;
}
internal resource_union _desc;
private resource_union _desc;
public ResourceType Type
{
@@ -682,6 +682,31 @@ public struct ResourceDesc
TextureDescription = desc
};
}
public bool Equals(ResourceDesc other)
{
if (Type != other.Type)
{
return false;
}
return Type switch
{
ResourceType.Texture => TextureDescription.Equals(other.TextureDescription),
ResourceType.Buffer => BufferDescription.Equals(other.BufferDescription),
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
};
}
public override int GetHashCode()
{
return Type switch
{
ResourceType.Texture => HashCode.Combine(Type, TextureDescription),
ResourceType.Buffer => HashCode.Combine(Type, BufferDescription),
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
};
}
}
/// <summary>
@@ -831,7 +856,7 @@ public struct RenderTargetDesc
/// <summary>
/// Texture description
/// </summary>
public struct TextureDesc
public record struct TextureDesc
{
/// <summary>
/// Width of the texture
@@ -939,7 +964,7 @@ public record struct SamplerDesc
}
}
public struct BufferDesc
public record struct BufferDesc
{
public ulong Size
{

View File

@@ -37,25 +37,6 @@ public interface IGraphicsEngine : IDisposable
get;
}
/// <summary>
/// Creates a new instance of a renderer for drawing graphical content.
/// </summary>
/// <returns>An object that implements the IRenderer interface, which can be used to render graphics.</returns>
IRenderer CreateRenderer();
/// <summary>
/// Removes the specified renderer from the collection of active renderers.
/// </summary>
/// <param name="renderer">The renderer instance to remove.</param>
void RemoveRenderer(IRenderer renderer);
/// <summary>
/// Removes all registered renderers from the collection.
/// </summary>
/// <remarks>Call this method to reset the renderer collection to an empty state. After calling this
/// method, no renderers will be available until new ones are added.</remarks>
void ClearRenderers();
/// <summary>
/// Creates a new command allocator for the specified command buffer space.
/// </summary>
@@ -70,6 +51,19 @@ public interface IGraphicsEngine : IDisposable
/// <returns>A new command buffer instance</returns>
ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics);
/// <summary>
/// Gets a command buffer from the pool for recording rendering commands.
/// </summary>
/// <param name="type">Type of command buffer to get from the pool</param>
/// <returns>A command buffer instance from the pool</returns>
ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics);
/// <summary>
/// Returns a command buffer to the pool after use.
/// </summary>
/// <param name="commandBuffer">The command buffer to return to the pool</param>
void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer);
/// <summary>
/// Creates a swap chain for presentation
/// </summary>
@@ -80,16 +74,14 @@ public interface IGraphicsEngine : IDisposable
/// <summary>
/// Begin the current frame.
/// </summary>
/// <param name="cpuFenceValue">CPU fence value for synchronization</param>
/// <param name="gpuFenceValue">GPU fence value for synchronization</param>
/// <param name="currentFrame">CPU fence value for synchronization</param>
/// <returns>Result of the begin frame operation</returns>
Result BeginFrame(uint cpuFenceValue, uint gpuFenceValue);
Result BeginFrame(ulong currentFrame);
/// <summary>
/// End the current frame.
/// </summary>
/// <param name="cpuFenceValue">CPU fence value for synchronization</param>
/// <param name="gpuFenceValue">GPU fence value for synchronization</param>
/// <param name="completedFrame">GPU fence value for synchronization</param>
/// <returns>Result of the end frame operation</returns>
Result EndFrame(uint cpuFenceValue, uint gpuFenceValue);
Result EndFrame(ulong completedFrame);
}

View File

@@ -4,11 +4,6 @@ namespace Ghost.Graphics.RHI;
public interface IPipelineLibrary : IDisposable
{
/// <summary>
/// Load pipeline library from disk.
/// </summary>
/// <param name="filePath">File path. If null, load default library.</param>
void InitializeLibrary(string? filePath);
void SaveLibraryToDisk(string filePath);
bool HasPipeline(Key128<GraphicsPipeline> key);
Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);

View File

@@ -0,0 +1,6 @@
namespace Ghost.Graphics.RHI;
public interface IRHIObject: IDisposable
{
string Name { get; set; }
}

View File

@@ -5,6 +5,9 @@ namespace Ghost.Graphics.RHI;
public readonly struct RenderContext
{
public ICommandBuffer CommandBuffer { get; init; }
public ICommandQueue GraphicsQueue { get; init; }
public ICommandQueue ComputeQueue { get; init; }
public ICommandQueue CopyQueue { get; init; }
}
// TODO: We may don't need this anymore. We Use RenderExtractionSystem to extract render data from entities and pass them to IRenderPipeline to render.