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,22 +1,24 @@
using Ghost.Graphics.D3D12;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.Shading;
using System.Numerics;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.Data;
/// <summary>
/// Material implementation for bindless rendering with SM 6.6 support
/// </summary>
public unsafe class Material : IDisposable
{
private readonly CBufferCache[] _cbufferCaches;
private readonly Texture2D?[] _textures;
private readonly Dictionary<string, int> _textureNameToSlotMap;
private readonly List<Texture2D> _textures = new();
private bool _disposed;
public Shader Shader
{
get;
set;
get; set;
}
public Material(Shader shader)
@@ -37,29 +39,6 @@ public unsafe class Material : IDisposable
{
_cbufferCaches = Array.Empty<CBufferCache>();
}
// Initialize texture storage
if (shader.Textures.Count > 0)
{
var maxTextureSlot = shader.Textures.Max(t => t.RegisterSlot);
_textures = new Texture2D?[maxTextureSlot + 1];
_textureNameToSlotMap = new Dictionary<string, int>();
foreach (var textureInfo in shader.Textures)
{
_textureNameToSlotMap.Add(textureInfo.Name, (int)textureInfo.RegisterSlot);
}
}
else
{
_textures = Array.Empty<Texture2D?>();
_textureNameToSlotMap = new Dictionary<string, int>();
}
}
~Material()
{
Dispose();
}
/// <summary>
@@ -84,6 +63,28 @@ public unsafe class Material : IDisposable
SetFloat(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetUInt(string propertyName, in uint value)
{
SetUInt(Shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
@@ -109,7 +110,7 @@ public unsafe class Material : IDisposable
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyName">The ID of the property to set.</param>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMatrix(int propertyId, in Matrix4x4 value)
@@ -129,73 +130,42 @@ public unsafe class Material : IDisposable
}
/// <summary>
/// Sets a texture property in the material.
/// Adds a bindless texture to the material and returns its index
/// </summary>
/// <param name="textureId">The ID of the texture to set.</param>
/// <param name="texture">The texture to set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetTexture(int textureId, Texture2D? texture)
/// <param name="texture">The bindless texture to add</param>
/// <returns>The index of the texture in the material's texture list</returns>
public int AddTexture(Texture2D texture)
{
if (textureId == -1)
{
throw new ArgumentException("Texture ID is invalid.");
}
if (textureId >= _textures.Length)
{
throw new ArgumentException($"Texture ID {textureId} is out of range.");
}
_textures[textureId] = texture;
_textures.Add(texture);
return _textures.Count - 1;
}
/// <summary>
/// Sets a texture property in the material.
/// Sets a texture index for a shader property (for bindless texture access)
/// </summary>
/// <param name="textureName">The name of the texture to set.</param>
/// <param name="texture">The texture to set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetTexture(string textureName, Texture2D? texture)
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param>
public void SetTextureIndex(string propertyName, Texture2D texture)
{
if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot))
{
throw new ArgumentException($"Texture '{textureName}' not found in shader.");
}
_textures[slot] = texture;
SetUInt(propertyName, texture.DescriptorIndex);
}
/// <summary>
/// Gets a texture property from the material.
/// Sets the mesh buffer indices for bindless vertex and index buffer access
/// </summary>
/// <param name="textureId">The ID of the texture to get.</param>
/// <returns>The texture, or null if not set.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Texture2D? GetTexture(int textureId)
/// <param name="mesh">The mesh whose buffer indices to set</param>
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
public void SetMeshBufferIndices(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
{
if (textureId == -1 || textureId >= _textures.Length)
{
return null;
}
return _textures[textureId];
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
}
/// <summary>
/// Gets a texture property from the material.
/// Gets all textures used by this material
/// </summary>
/// <param name="textureName">The name of the texture to get.</param>
/// <returns>The texture, or null if not set.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Texture2D? GetTexture(string textureName)
{
if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot))
{
return null;
}
return _textures[slot];
}
public IReadOnlyList<Texture2D> Textures => _textures;
private unsafe void WriteToCache<T>(int propertyId, in T value) where T : unmanaged
{
@@ -226,28 +196,35 @@ public unsafe class Material : IDisposable
}
}
/// <summary>
/// Binds the material for bindless rendering
/// </summary>
/// <param name="cmd">Command list to bind to</param>
internal void Bind(CommandList cmd)
{
var commandList = cmd.NativeCommandList.Ptr;
// Set root signature and pipeline state
commandList->SetGraphicsRootSignature(Shader.RootSignature);
commandList->SetPipelineState(Shader.PipelineState);
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
var heaps = stackalloc ID3D12DescriptorHeap*[2];
heaps[0] = GraphicsPipeline.DescriptorAllocator.GetBindlessHeap().Ptr; // Specialized bindless heap
heaps[1] = Shader.SamplerHeap.Ptr; // Sampler heap from shader
commandList->SetDescriptorHeaps(2, heaps);
// Bind constant buffers
var rootParamIndex = 0u;
foreach (var cbufferInfo in Shader.ConstantBuffers)
{
var cache = _cbufferCaches[cbufferInfo.RegisterSlot];
cmd.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress);
commandList->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress);
}
// Bind textures using descriptor table
if (Shader.Textures.Count > 0)
{
// Get the first texture info to determine the root parameter index
var textureInfo = Shader.Textures[0];
var texture = _textures[0]; // Get the first texture
if (texture != null)
{
// Set descriptor table for SRVs
cmd.NativeCommandList.Ptr->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, texture.SRVDescriptor.GpuHandle);
}
}
// Bind sampler descriptor table (last root parameter)
var samplerGpuHandle = Shader.SamplerHeap.Ptr->GetGPUDescriptorHandleForHeapStart();
commandList->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle);
}
public void Dispose()
@@ -262,6 +239,9 @@ public unsafe class Material : IDisposable
cache.Dispose();
}
// NOTE: We don't dispose the textures here as they might be shared
// The user is responsible for disposing BindlessTexture2D instances
GC.SuppressFinalize(this);
_disposed = true;