feat(rhi): refactor resource & barrier management for D3D12

Modernizes resource and barrier management for the D3D12 backend. Key changes:
- Simplifies BarrierDesc by removing nullable "before" states; now inferred from resource database.
- Adds IsAliasing flag to BarrierDesc for aliasing transitions.
- Replaces ResourceMemoryType with HeapType in BufferDesc and related APIs.
- Enhances ResourceViewGroup with usage inference methods.
- Adds D3D12 utility helpers for heap/flag conversions and resource description extraction.
- Optimizes command buffer barrier emission, skipping redundant barriers.
- Refactors Material and RenderContext to use new APIs and state tracking.
- Updates ResourceManager pooling to use HeapType and standard Queue.
- Simplifies RenderGraphExecutor barrier logic and aliasing handling.
- Improves RenderSystem frame synchronization and resource retirement.
- Cleans up obsolete code and improves debug output.

BREAKING CHANGE: Updates to resource and barrier APIs require changes to all code interfacing with resource creation, barriers, and memory types.
This commit is contained in:
2026-04-03 17:03:41 +09:00
parent 6321b36ef5
commit ba9e24c46c
20 changed files with 316 additions and 422 deletions

View File

@@ -0,0 +1,304 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
namespace Ghost.Graphics.Core;
// TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
public readonly unsafe ref struct RenderContext
{
private readonly IGraphicsEngine _engine;
private readonly ResourceManager _resourceManager;
private readonly ICommandBuffer _cmd;
public ICommandBuffer CommandBuffer => _cmd;
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
public ResourceManager ResourceManager => _resourceManager;
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
internal RenderContext(IGraphicsEngine engine, ResourceManager resourceManager, ICommandBuffer cmd)
{
_engine = engine;
_resourceManager = resourceManager;
_cmd = cmd;
}
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
{
BarrierDesc desc;
if (isTexture)
{
desc = BarrierDesc.Texture(resource, newSync, newAccess, newLayout);
}
else
{
desc = BarrierDesc.Buffer(resource, newSync, newAccess);
}
_cmd.Barrier(desc);
}
public void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
where T : unmanaged
{
var r = _engine.ResourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure)
{
return;
}
Debug.Assert(r.Value.Type == ResourceType.Buffer);
var sizeInBytes = (nuint)(data.Length * sizeof(T));
var memoryType = r.Value.BufferDescription.HeapType;
if (memoryType == HeapType.Upload)
{
fixed (T* pData = data)
{
ResourceDatabase.MapResource(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
}
}
else
{
var uploadDesc = new BufferDesc
{
Size = sizeInBytes,
Usage = BufferUsage.Upload,
HeapType = HeapType.Upload,
};
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
if (uploadHandle.IsInvalid)
{
throw new OutOfMemoryException("Failed to create upload buffer for buffer data.");
}
try
{
fixed (T* pData = data)
{
ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
}
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
}
finally
{
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
}
}
}
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
{
var mesh = _resourceManager.CreateMesh(vertices, indices);
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
return mesh;
}
ref var meshData = ref r.Value;
var vertexHandle = meshData.VertexBuffer.AsResource();
var indexHandle = meshData.IndexBuffer.AsResource();
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
if (staticMesh)
{
meshData.CookMeshlets();
UploadMeshlets(mesh);
meshData.ReleaseCpuResources();
}
return mesh;
}
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, bool staticMesh)
{
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
vertexList.CopyFrom(vertices);
indexList.CopyFrom(indices);
return CreateMesh(vertexList, indexList, staticMesh);
}
/// <summary>
/// Uploads the mesh data to the GPU.
/// </summary>
/// <param name="mesh">The handle point to the mesh buffer</param>
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
public void UploadMesh(Handle<Mesh> mesh, bool markMeshStatic)
{
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
return;
}
ref var meshRef = ref r.Value;
var vertexHandle = meshRef.VertexBuffer.AsResource();
var indexHandle = meshRef.IndexBuffer.AsResource();
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
if (markMeshStatic)
{
meshRef.ReleaseCpuResources();
}
}
public void UploadMeshlets(Handle<Mesh> mesh)
{
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
return;
}
ref var meshRef = ref r.Value;
ref readonly var meshletData = ref meshRef.MeshletData;
if (!meshletData.meshlets.IsCreated || meshletData.meshlets.Count == 0) return;
var meshletDesc = new BufferDesc
{
Size = (uint)(meshletData.meshlets.Count * sizeof(Meshlet)),
Stride = (uint)sizeof(Meshlet),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var verticesDesc = new BufferDesc
{
Size = (uint)(meshletData.meshletVertices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
// Ensure size is multiple of 4 for Raw buffer
var trianglesSize = (uint)meshletData.meshletTriangles.Count * sizeof(uint);
var trianglesDesc = new BufferDesc
{
Size = trianglesSize,
Stride = sizeof(uint),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
meshRef.MeshLetBuffer = _engine.ResourceAllocator.CreateBuffer(in meshletDesc, "Meshlets");
meshRef.MeshletVerticesBuffer = _engine.ResourceAllocator.CreateBuffer(in verticesDesc, "MeshletVertices");
meshRef.MeshletTrianglesBuffer = _engine.ResourceAllocator.CreateBuffer(in trianglesDesc, "MeshletTriangles");
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
}
public void UpdateObjectData(Handle<Mesh> mesh)
{
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
return;
}
ref readonly var meshData = ref r.Value;
var data = new MeshData
{
worldBoundsMin = meshData.BoundingBox.Min,
worldBoundsMax = meshData.BoundingBox.Max,
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
meshletBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()),
meshletVerticesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
meshletTrianglesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
};
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshData.ObjectDataBuffer, data);
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
}
public Handle<GPUTexture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> data, string name)
where T : unmanaged
{
var handle = ResourceAllocator.CreateTexture(in desc, name);
UploadTexture(handle, data);
return handle;
}
public void UploadTexture<T>(Handle<GPUTexture> texture, ReadOnlySpan<T> data)
where T : unmanaged
{
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
var requiredSize = ResourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
var uploadDesc = new BufferDesc
{
Size = requiredSize,
Usage = BufferUsage.Upload,
HeapType = HeapType.Upload,
};
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
if (uploadHandle.IsInvalid)
{
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
}
try
{
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
fixed (T* pData = data)
{
var subresourceData = new SubResourceData
{
pData = pData,
rowPitch = rowPitch,
slicePitch = slicePitch
};
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
}
}
finally
{
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
}
}
}