Update rendering architecture and resource management

Added a new `Ref<T>` struct for reference semantics.
Added the `RenderGraph` system for managing rendering passes.
Added the `RenderTexture` class for encapsulating GPU resources.
Added `GraphicsBuffer` class for effective GPU resource management.
Changed `CommandList` methods from public to internal for visibility control.
Changed `IRenderPass` interface from internal to public for accessibility.
Changed `GetData<T>()` in `ComponentObject.cs` to return `CompRef<T>`.
Changed `GetComponent<T>()` in `EntityManager.cs` to return `CompRef<T>`.
Changed `GetSingleton<T>()` in `World.cs` to use `CompRef<T>`.
Changed `IQueryTypeParameter` to use `CompRef<T>` for consistency.
Changed `QueryItem<T0>` and related structs to use `CompRef<T>`.
Changed `Material` class to support bindless textures.
Changed `Shader` class to support bindless rendering.
Changed `Mesh` class to support bindless vertex and index buffer access.
Updated documentation to reflect the new bindless rendering architecture.
This commit is contained in:
2025-08-01 21:34:48 +09:00
parent 1284bb17de
commit eafbfb2fa1
43 changed files with 3845 additions and 2183 deletions

View File

@@ -1,32 +1,39 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.Data;
using Ghost.Graphics.Shading;
using Ghost.Graphics.Utilities;
using StbImageSharp;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
using System.Numerics;
namespace Ghost.Graphics.RenderPasses;
/// <summary>
/// Simplified bindless mesh render pass using high-level bindless APIs with fully bindless vertex/index buffer access
/// </summary>
internal unsafe class MeshRenderPass : IRenderPass
{
private const string _HLSL_SOURCE = @"
cbuffer ConstantBuffer : register(b0)
{
float4 _Color;
uint _TextureIndex1;
uint _TextureIndex2;
uint _TextureIndex3;
uint _TextureIndex4;
uint _VertexBufferIndex;
uint _IndexBufferIndex;
};
Texture2D _MainTex : register(t0);
// SM 6.6 approach - direct access to global descriptor heap
SamplerState _MainSampler : register(s0);
struct VertexInput
struct Vertex
{
float4 position : POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
float4 position;
float4 normal;
float4 tangent;
float4 color;
float4 uv;
};
struct PixelInput
@@ -36,237 +43,105 @@ struct PixelInput
float4 uv : TEXCOORD0;
};
PixelInput VSMain(VertexInput input)
// Bindless vertex shader that fetches vertex data from bindless buffers
PixelInput VSMain(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
// Get bindless buffers
ByteAddressBuffer vertexBuffer = ResourceDescriptorHeap[_VertexBufferIndex];
ByteAddressBuffer indexBuffer = ResourceDescriptorHeap[_IndexBufferIndex];
// For fully bindless rendering, we use instanced drawing where:
// - Each instance represents a triangle (instanceId = triangle index)
// - vertexId goes from 0 to 2 (the 3 vertices of the triangle)
// Calculate the index into the index buffer
uint indexOffset = (instanceId * 3 + vertexId) * 4; // 4 bytes per index (uint32)
uint vertexIndex = indexBuffer.Load(indexOffset);
// Calculate the offset into the vertex buffer
uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex (5 * float4)
// Load vertex data from bindless vertex buffer
Vertex vertex;
vertex.position = asfloat(vertexBuffer.Load4(vertexOffset + 0));
vertex.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16));
vertex.tangent = asfloat(vertexBuffer.Load4(vertexOffset + 32));
vertex.color = asfloat(vertexBuffer.Load4(vertexOffset + 48));
vertex.uv = asfloat(vertexBuffer.Load4(vertexOffset + 64));
// Output transformed vertex
PixelInput output;
output.position = input.position;
output.color = input.color;
output.uv = input.uv;
output.position = vertex.position;
output.color = vertex.color;
output.uv = vertex.uv;
return output;
}
float4 PSMain(PixelInput input) : SV_TARGET
{
return _MainTex.Sample(_MainSampler, input.uv.xy);
// SM 6.6 Modern Bindless Approach:
// ResourceDescriptorHeap[index] directly accesses any texture in the heap
Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1];
Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2];
Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3];
Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4];
// Sample the textures
float4 color1 = tex1.Sample(_MainSampler, input.uv.xy);
float4 color2 = tex2.Sample(_MainSampler, input.uv.xy);
float4 color3 = tex3.Sample(_MainSampler, input.uv.xy);
float4 color4 = tex4.Sample(_MainSampler, input.uv.xy);
// Blend all textures together (simple average)
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
return blendedColor * _Color;
}
";
// High-level bindless objects
private Mesh? _mesh;
private Shader? _shader;
private Material? _material;
private Texture2D[]? _textures;
// Direct D3D12 resources for texture
private ComPtr<ID3D12Resource> _textureResource;
private ComPtr<ID3D12DescriptorHeap> _srvHeap;
private CpuDescriptorHandle _srvHandle;
private GpuDescriptorHandle _srvGpuHandle;
private uint _srvDescriptorSize;
// Additional fields for deferred texture upload
private ComPtr<ID3D12Resource> _uploadBuffer;
private uint _textureWidth;
private uint _textureHeight;
private uint _texturePitch;
private bool _textureUploaded = false;
// Texture file paths for this demo
private readonly string[] _textureFiles = [
"C:/Users/Misaki/Downloads/Im/Icon.png",
"C:/Users/Misaki/Downloads/Im/Backdrop.jpg",
"C:/Users/Misaki/Downloads/Im/101167591_p0.png",
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
];
public void Initialize(CommandList cmd)
{
_mesh = MeshBuilder.CreateCube(0.25f);
_mesh = MeshBuilder.CreateCube(0.75f);
_mesh.UploadMeshData();
_shader = new(_HLSL_SOURCE);
_material = new(_shader);
_shader = new Shader(_HLSL_SOURCE);
_material = new Material(_shader);
// Create direct D3D12 texture resources
CreateTextureDirectly();
_material.UploadMaterialData();
// Copy from upload buffer to texture
var srcLocation = new TextureCopyLocation(_uploadBuffer.Get(), new PlacedSubresourceFootprint
_textures = new Texture2D[_textureFiles.Length];
for (var i = 0; i < _textureFiles.Length; i++)
{
Offset = 0,
Footprint = new SubresourceFootprint
{
Format = Format.R8G8B8A8Unorm,
Width = _textureWidth,
Height = _textureHeight,
Depth = 1,
RowPitch = _texturePitch
}
});
var dstLocation = new TextureCopyLocation(_textureResource.Get(), 0);
cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null);
// Transition texture to shader resource
var barrier = new ResourceBarrier
{
Type = ResourceBarrierType.Transition,
Flags = ResourceBarrierFlags.None,
Transition = new ResourceTransitionBarrier
{
pResource = _textureResource.Get(),
StateBefore = ResourceStates.CopyDest,
StateAfter = ResourceStates.PixelShaderResource,
Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES
}
};
cmd.NativeCommandList.Ptr->ResourceBarrier(1, &barrier);
}
private void CreateTextureDirectly()
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
// Load image data
using var stream = new FileStream("C:/Users/Misaki/Downloads/Im/Icon.png", FileMode.Open, FileAccess.Read);
var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
var width = (uint)image.Width;
var height = (uint)image.Height;
uint bytesPerPixel = 4; // RGBA
var pitch = width * bytesPerPixel;
var textureSize = pitch * height;
// Create the texture resource
var textureDesc = new ResourceDescription
{
Dimension = ResourceDimension.Texture2D,
Alignment = 0,
Width = width,
Height = height,
DepthOrArraySize = 1,
MipLevels = 1,
Format = Format.R8G8B8A8Unorm,
SampleDesc = new SampleDescription(1, 0),
Layout = TextureLayout.Unknown,
Flags = ResourceFlags.None
};
var heapProps = new HeapProperties(HeapType.Default);
device->CreateCommittedResource(
&heapProps,
HeapFlags.None,
&textureDesc,
ResourceStates.CopyDest,
null,
__uuidof<ID3D12Resource>(),
_textureResource.GetVoidAddressOf()
);
// Create upload buffer
var uploadBufferSize = GetRequiredIntermediateSize(_textureResource.Get(), 0, 1);
var uploadHeapProps = new HeapProperties(HeapType.Upload);
var uploadBufferDesc = ResourceDescription.Buffer(uploadBufferSize);
ComPtr<ID3D12Resource> uploadBuffer = default;
device->CreateCommittedResource(
&uploadHeapProps,
HeapFlags.None,
&uploadBufferDesc,
ResourceStates.GenericRead,
null,
__uuidof<ID3D12Resource>(),
uploadBuffer.GetVoidAddressOf()
);
// Map and copy texture data
void* mappedData = null;
uploadBuffer.Get()->Map(0, null, &mappedData);
// Copy image data to upload buffer
var srcData = image.Data.AsSpan();
var dstSpan = new Span<byte>(mappedData, (int)uploadBufferSize);
// Copy row by row with proper alignment
var alignedPitch = (pitch + 255) & ~255u; // Align to 256 bytes
for (var y = 0; y < height; y++)
{
var srcRow = srcData.Slice(y * (int)pitch, (int)pitch);
var dstRow = dstSpan.Slice(y * (int)alignedPitch, (int)pitch);
srcRow.CopyTo(dstRow);
_textures[i] = Texture2D.FromFile(_textureFiles[i]);
_textures[i].UploadTextureData();
}
uploadBuffer.Get()->Unmap(0, null);
// We'll copy the texture data during Execute phase when we have access to command list
// Store the upload buffer for later use
_uploadBuffer = uploadBuffer.Move();
_textureWidth = width;
_textureHeight = height;
_texturePitch = alignedPitch;
// Create SRV descriptor heap
var srvHeapDesc = new DescriptorHeapDescription
_material.SetVector("_Color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
for (var i = 0; i < _textures.Length; i++)
{
Type = DescriptorHeapType.CbvSrvUav,
NumDescriptors = 1,
Flags = DescriptorHeapFlags.ShaderVisible
};
var texture = _textures[i];
_material.SetTextureIndex($"_TextureIndex{i + 1}", texture);
}
device->CreateDescriptorHeap(&srvHeapDesc, __uuidof<ID3D12DescriptorHeap>(), _srvHeap.GetVoidAddressOf());
// Get descriptor handles
_srvHandle = _srvHeap.Get()->GetCPUDescriptorHandleForHeapStart();
_srvGpuHandle = _srvHeap.Get()->GetGPUDescriptorHandleForHeapStart();
_srvDescriptorSize = device->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
// Create SRV
var srvDesc = new ShaderResourceViewDescription
{
Format = Format.R8G8B8A8Unorm,
ViewDimension = Win32.Graphics.Direct3D12.SrvDimension.Texture2D,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Texture2D = new Texture2DSrv
{
MostDetailedMip = 0,
MipLevels = 1,
PlaneSlice = 0,
ResourceMinLODClamp = 0.0f
}
};
device->CreateShaderResourceView(_textureResource.Get(), &srvDesc, _srvHandle);
}
private static ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var resourceDesc = destinationResource->GetDesc();
ulong requiredSize = 0;
device->GetCopyableFootprints(&resourceDesc, firstSubresource, numSubresources, 0, null, null, null, &requiredSize);
return requiredSize;
_material.SetMeshBufferIndices(_mesh);
_material.UploadMaterialData();
}
public void Execute(CommandList cmd)
{
var commandList = cmd.NativeCommandList.Ptr;
// Set root signature and pipeline state
commandList->SetGraphicsRootSignature(_material!.Shader.RootSignature);
commandList->SetPipelineState(_material.Shader.PipelineState);
// Set descriptor heap
var heaps = stackalloc ID3D12DescriptorHeap*[1];
heaps[0] = _srvHeap.Get();
commandList->SetDescriptorHeaps(1, heaps);
// Bind texture descriptor table directly
if (_material.Shader.Textures.Count > 0)
{
var textureInfo = _material.Shader.Textures[0];
commandList->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, _srvGpuHandle);
}
// Draw mesh
commandList->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
commandList->IASetVertexBuffers(0, 1, _mesh!.VertexBufferView);
commandList->IASetIndexBuffer(_mesh.IndexBufferView);
commandList->DrawIndexedInstanced(_mesh.IndexCount, 1, 0, 0, 0);
cmd.DrawMesh(_mesh!, _material!);
}
public void Dispose()
@@ -275,8 +150,12 @@ float4 PSMain(PixelInput input) : SV_TARGET
_shader?.Dispose();
_material?.Dispose();
_textureResource.Dispose();
_uploadBuffer.Dispose();
_srvHeap.Dispose();
if (_textures != null)
{
foreach (var texture in _textures)
{
texture?.Dispose();
}
}
}
}
}