forked from Misaki/GhostEngine
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:
@@ -1,10 +1,12 @@
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color with 32-bit components."/>
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
||||
public struct Color32 : IEquatable<Color32>
|
||||
{
|
||||
public byte r;
|
||||
@@ -59,6 +61,7 @@ public struct Color32 : IEquatable<Color32>
|
||||
/// <summary>
|
||||
/// Represents a color with 128-bit components.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
public struct Color128 : IEquatable<Color128>
|
||||
{
|
||||
public float r;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Numerics;
|
||||
@@ -16,10 +16,11 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
|
||||
private Bounds _boundingBox;
|
||||
|
||||
private GraphicsResource? _vertexBuffer;
|
||||
private GraphicsResource? _indexBuffer;
|
||||
private VertexBufferView _vertexBufferView;
|
||||
private IndexBufferView _indexBufferView;
|
||||
private GraphicsBuffer? _vertexBuffer;
|
||||
private GraphicsBuffer? _indexBuffer;
|
||||
|
||||
private BindlessDescriptor? _vertexBufferDescriptor;
|
||||
private BindlessDescriptor? _indexBufferDescriptor;
|
||||
|
||||
public Span<Vertex> Vertices => _vertices.AsSpan();
|
||||
public Span<int> Indices => _indices.AsSpan();
|
||||
@@ -28,8 +29,8 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
public uint VertexCount => (uint)_vertices.Count;
|
||||
public uint IndexCount => (uint)_indices.Count;
|
||||
|
||||
internal ConstPtr<VertexBufferView> VertexBufferView => (VertexBufferView*)Unsafe.AsPointer(ref _vertexBufferView);
|
||||
internal ConstPtr<IndexBufferView> IndexBufferView => (IndexBufferView*)Unsafe.AsPointer(ref _indexBufferView);
|
||||
public uint VertexBufferDescriptorIndex => _vertexBufferDescriptor?.Index ?? throw new InvalidOperationException("Vertex buffer descriptor is not allocated.");
|
||||
public uint IndexBufferDescriptorIndex => _indexBufferDescriptor?.Index ?? throw new InvalidOperationException("Index buffer descriptor is not allocated.");
|
||||
|
||||
~Mesh()
|
||||
{
|
||||
@@ -65,6 +66,24 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
_indices.Add(index2);
|
||||
}
|
||||
|
||||
public void AddTriangles(params ReadOnlySpan<int> indices)
|
||||
{
|
||||
if (indices.Length % 3 != 0)
|
||||
{
|
||||
throw new ArgumentException("The number of indices must be a multiple of 3 to form triangles.");
|
||||
}
|
||||
|
||||
foreach (var index in indices)
|
||||
{
|
||||
if (index < 0 || index >= _vertices.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(indices), "Index out of range for the current vertex count.");
|
||||
}
|
||||
|
||||
_indices.Add(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduces the memory usage of the internal collections by resizing them to match their current element count.
|
||||
/// </summary>
|
||||
@@ -211,35 +230,76 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
return;
|
||||
}
|
||||
|
||||
_vertexBuffer?.Dispose();
|
||||
_indexBuffer?.Dispose();
|
||||
DisposeGpuResources();
|
||||
|
||||
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
|
||||
var indexBufferSize = IndexCount * sizeof(int);
|
||||
_vertexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(vertexBufferSize);
|
||||
_indexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(indexBufferSize);
|
||||
_vertexBuffer = GraphicsBuffer.Create(vertexBufferSize, GraphicsBuffer.Usage.CopyDestination);
|
||||
_indexBuffer = GraphicsBuffer.Create(indexBufferSize, GraphicsBuffer.Usage.CopyDestination);
|
||||
|
||||
using var uploadBatch = new ResourceUploadBatch();
|
||||
uploadBatch.Begin();
|
||||
uploadBatch.Upload(_vertexBuffer, _vertices.AsSpan());
|
||||
uploadBatch.Upload(_indexBuffer, _indices.AsSpan());
|
||||
uploadBatch.Transition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
|
||||
uploadBatch.Transition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
|
||||
uploadBatch.WaitForCompletion(uploadBatch.End());
|
||||
var uploadBatch = GraphicsPipeline.UploadBatch;
|
||||
uploadBatch.Upload(_vertexBuffer.NativeResource, _vertices.AsSpan());
|
||||
uploadBatch.Upload(_indexBuffer.NativeResource, _indices.AsSpan());
|
||||
uploadBatch.Transition(_vertexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
|
||||
uploadBatch.Transition(_indexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
|
||||
|
||||
_vertexBufferView = new VertexBufferView
|
||||
// Create bindless descriptors for vertex and index buffers
|
||||
CreateBindlessDescriptors();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates SRVs for vertex and index buffers in the bindless descriptor heap
|
||||
/// </summary>
|
||||
private void CreateBindlessDescriptors()
|
||||
{
|
||||
if (_vertexBuffer == null || _indexBuffer == null)
|
||||
{
|
||||
BufferLocation = _vertexBuffer.GPUAddress,
|
||||
SizeInBytes = vertexBufferSize,
|
||||
StrideInBytes = (uint)sizeof(Vertex)
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate new descriptors from the descriptor allocator
|
||||
_vertexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
_indexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
|
||||
var vertexSrvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = Format.R32Typeless,
|
||||
ViewDimension = SrvDimension.Buffer,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
||||
Anonymous = new()
|
||||
{
|
||||
Buffer = new()
|
||||
{
|
||||
FirstElement = 0,
|
||||
NumElements = (uint)(_vertexBuffer.GPUAddress != 0 ? (VertexCount * sizeof(Vertex)) / 4 : 0), // Divide by 4 for R32 format
|
||||
StructureByteStride = 0,
|
||||
Flags = BufferSrvFlags.Raw
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_indexBufferView = new IndexBufferView
|
||||
device->CreateShaderResourceView(_vertexBuffer.NativeResource.Ptr, &vertexSrvDesc, _vertexBufferDescriptor.CpuHandle);
|
||||
|
||||
var indexSrvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
BufferLocation = _indexBuffer.GPUAddress,
|
||||
SizeInBytes = indexBufferSize,
|
||||
Format = Format.R32Uint
|
||||
Format = Format.R32Typeless,
|
||||
ViewDimension = SrvDimension.Buffer,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
|
||||
Anonymous = new()
|
||||
{
|
||||
Buffer = new()
|
||||
{
|
||||
FirstElement = 0,
|
||||
NumElements = IndexCount,
|
||||
StructureByteStride = 0,
|
||||
Flags = BufferSrvFlags.Raw
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
device->CreateShaderResourceView(_indexBuffer.NativeResource.Ptr, &indexSrvDesc, _indexBufferDescriptor.CpuHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,6 +319,16 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
|
||||
_indexBuffer?.Dispose();
|
||||
_indexBuffer = null;
|
||||
|
||||
if (_vertexBufferDescriptor != null)
|
||||
{
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseBindless(_vertexBufferDescriptor);
|
||||
}
|
||||
|
||||
if (_indexBufferDescriptor != null)
|
||||
{
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseBindless(_indexBufferDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
305
Ghost.Graphics/Data/RenderTexture.cs
Normal file
305
Ghost.Graphics/Data/RenderTexture.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the type of render texture.
|
||||
/// </summary>
|
||||
public enum RenderTextureType
|
||||
{
|
||||
/// <summary>
|
||||
/// Render target view - used for color output.
|
||||
/// </summary>
|
||||
ColorTarget,
|
||||
|
||||
/// <summary>
|
||||
/// Depth stencil view - used for depth/stencil testing.
|
||||
/// </summary>
|
||||
DepthStencil
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render texture class that encapsulates GPU resources for rendering.
|
||||
/// </summary>
|
||||
public unsafe class RenderTexture : Texture
|
||||
{
|
||||
private readonly RenderTextureType _renderTextureType;
|
||||
private readonly RenderTargetDescriptor? _rtvDescriptor;
|
||||
private readonly DepthStencilDescriptor? _dsvDescriptor;
|
||||
|
||||
private RenderTexture(uint width, uint height, Format format, RenderTextureType renderTextureType, in TextureHandle handle, BindlessDescriptor bindlessDescriptor, RenderTargetDescriptor? rtvDescriptor, DepthStencilDescriptor? dsvDescriptor)
|
||||
: base(width, height, format, in handle, bindlessDescriptor)
|
||||
{
|
||||
_renderTextureType = renderTextureType;
|
||||
_rtvDescriptor = rtvDescriptor;
|
||||
_dsvDescriptor = dsvDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this render texture.
|
||||
/// </summary>
|
||||
public RenderTextureType RenderTextureType => _renderTextureType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the render target view descriptor. Only valid for color render textures.
|
||||
/// </summary>
|
||||
internal RenderTargetDescriptor? RenderTargetView => _rtvDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the depth stencil view descriptor. Only valid for depth render textures.
|
||||
/// </summary>
|
||||
internal DepthStencilDescriptor? DepthStencilView => _dsvDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color render texture.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the render texture</param>
|
||||
/// <param name="height">Height of the render texture</param>
|
||||
/// <param name="format">Color format (e.g., Format.R8G8B8A8Unorm)</param>
|
||||
/// <returns>A new color render texture</returns>
|
||||
public static RenderTexture CreateColorTarget(uint width, uint height, Format format = Format.R8G8B8A8Unorm, bool tempResource = false)
|
||||
{
|
||||
ValidateColorFormat(format);
|
||||
|
||||
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 1, format, resFlags: ResourceFlags.AllowRenderTarget | ResourceFlags.AllowUnorderedAccess, tempResource: tempResource);
|
||||
|
||||
var resource = handle.ResourceHandle.GetAllocation().Resource;
|
||||
var bindlessDescriptor = CreateBindlessDescriptorForRenderTexture(resource, format);
|
||||
var rtvDescriptor = CreateRenderTargetView(resource, format);
|
||||
|
||||
return new RenderTexture(width, height, format, RenderTextureType.ColorTarget, in handle, bindlessDescriptor, rtvDescriptor, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a depth stencil render texture.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the render texture</param>
|
||||
/// <param name="height">Height of the render texture</param>
|
||||
/// <param name="format">Depth format (e.g., Format.D24UnormS8Uint, Format.D32Float)</param>
|
||||
/// <returns>A new depth stencil render texture</returns>
|
||||
public static RenderTexture CreateDepthStencil(uint width, uint height, Format format = Format.D24UnormS8Uint, bool tempResource = false)
|
||||
{
|
||||
ValidateDepthFormat(format);
|
||||
|
||||
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 1, format, resFlags: ResourceFlags.AllowDepthStencil, tempResource: tempResource);
|
||||
|
||||
var resource = handle.ResourceHandle.GetAllocation().Resource;
|
||||
var bindlessDescriptor = CreateBindlessDescriptorForRenderTexture(resource, GetShaderResourceFormat(format));
|
||||
var dsvDescriptor = CreateDepthStencilView(resource, format);
|
||||
|
||||
return new RenderTexture(width, height, format, RenderTextureType.DepthStencil, in handle, bindlessDescriptor, null, dsvDescriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the format is suitable for color render targets.
|
||||
/// </summary>
|
||||
private static void ValidateColorFormat(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8G8B8A8Unorm:
|
||||
case Format.R8G8B8A8UnormSrgb:
|
||||
case Format.B8G8R8A8Unorm:
|
||||
case Format.B8G8R8A8UnormSrgb:
|
||||
case Format.R16G16B16A16Float:
|
||||
case Format.R32G32B32A32Float:
|
||||
case Format.R16G16Float:
|
||||
case Format.R32Float:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Format {format} is not supported for color render targets.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the format is suitable for depth stencil targets.
|
||||
/// </summary>
|
||||
private static void ValidateDepthFormat(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.D32Float:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.D16Unorm:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Format {format} is not supported for depth stencil targets.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader resource format for depth textures (for sampling depth in shaders).
|
||||
/// </summary>
|
||||
private static Format GetShaderResourceFormat(Format depthFormat)
|
||||
{
|
||||
return depthFormat switch
|
||||
{
|
||||
Format.D32Float => Format.R32Float,
|
||||
Format.D24UnormS8Uint => Format.R24UnormX8Typeless,
|
||||
Format.D16Unorm => Format.R16Unorm,
|
||||
_ => throw new ArgumentException($"Cannot determine shader resource format for depth format {depthFormat}")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bindless descriptor for render texture shader resource access.
|
||||
/// </summary>
|
||||
private static BindlessDescriptor CreateBindlessDescriptorForRenderTexture(ID3D12Resource* resource, Format srvFormat)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = srvFormat,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
device->CreateShaderResourceView(resource, &srvDesc, bindlessDescriptor.CpuHandle);
|
||||
return bindlessDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render target view for color render textures.
|
||||
/// </summary>
|
||||
private static RenderTargetDescriptor CreateRenderTargetView(ID3D12Resource* resource, Format format)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
|
||||
|
||||
var rtvDesc = new RenderTargetViewDescription
|
||||
{
|
||||
Format = format,
|
||||
ViewDimension = RtvDimension.Texture2D,
|
||||
Texture2D = new Texture2DRtv { MipSlice = 0 }
|
||||
};
|
||||
|
||||
device->CreateRenderTargetView(resource, &rtvDesc, rtvDescriptor.CpuHandle);
|
||||
return rtvDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a depth stencil view for depth render textures.
|
||||
/// </summary>
|
||||
private static DepthStencilDescriptor CreateDepthStencilView(ID3D12Resource* resource, Format format)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var dsvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateDSV();
|
||||
|
||||
var dsvDesc = new DepthStencilViewDescription
|
||||
{
|
||||
Format = format,
|
||||
ViewDimension = DsvDimension.Texture2D,
|
||||
Texture2D = new Texture2DDsv { MipSlice = 0 }
|
||||
};
|
||||
|
||||
device->CreateDepthStencilView(resource, &dsvDesc, dsvDescriptor.CpuHandle);
|
||||
return dsvDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the render texture with the specified color (for color targets only).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record clear commands</param>
|
||||
/// <param name="clearColor">Color to clear to</param>
|
||||
public void ClearColor(CommandList commandList, Color128 clearColor)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_renderTextureType != RenderTextureType.ColorTarget || _rtvDescriptor == null)
|
||||
{
|
||||
throw new InvalidOperationException("ClearColor can only be called on color render textures.");
|
||||
}
|
||||
|
||||
commandList.NativeCommandList.Ptr->ClearRenderTargetView(_rtvDescriptor.CpuHandle, (float*)&clearColor, 0, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the depth stencil render texture.
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record clear commands</param>
|
||||
/// <param name="clearDepth">Depth value to clear to (0.0 to 1.0)</param>
|
||||
/// <param name="clearStencil">Stencil value to clear to</param>
|
||||
public void ClearDepthStencil(CommandList commandList, ClearFlags flags, float clearDepth = 1.0f, byte clearStencil = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_renderTextureType != RenderTextureType.DepthStencil || _dsvDescriptor == null)
|
||||
{
|
||||
throw new InvalidOperationException("ClearDepthStencil can only be called on depth stencil render textures.");
|
||||
}
|
||||
|
||||
commandList.NativeCommandList.Ptr->ClearDepthStencilView(_dsvDescriptor.CpuHandle, flags, clearDepth, clearStencil, 0, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transitions the render texture to the specified resource state.
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
/// <param name="newState">New resource state</param>
|
||||
internal void TransitionTo(CommandList commandList, ResourceStates newState)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
commandList.BarrierTransition(this, ResourceStates.Common, newState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to transition to render target state (for color targets).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
internal void TransitionToRenderTarget(CommandList commandList)
|
||||
{
|
||||
if (_renderTextureType != RenderTextureType.ColorTarget)
|
||||
{
|
||||
throw new InvalidOperationException("TransitionToRenderTarget can only be called on color render textures.");
|
||||
}
|
||||
|
||||
TransitionTo(commandList, ResourceStates.RenderTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to transition to depth write state (for depth targets).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
internal void TransitionToDepthWrite(CommandList commandList)
|
||||
{
|
||||
if (_renderTextureType != RenderTextureType.DepthStencil)
|
||||
{
|
||||
throw new InvalidOperationException("TransitionToDepthWrite can only be called on depth stencil render textures.");
|
||||
}
|
||||
|
||||
TransitionTo(commandList, ResourceStates.DepthWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to transition to shader resource state (for reading in shaders).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
public void TransitionToShaderResource(CommandList commandList)
|
||||
{
|
||||
TransitionTo(commandList, ResourceStates.PixelShaderResource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the render texture and releases associated descriptors.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
|
||||
if (_rtvDescriptor != null)
|
||||
{
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseRTV(_rtvDescriptor);
|
||||
}
|
||||
|
||||
if (_dsvDescriptor != null)
|
||||
{
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseDSV(_dsvDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Ghost.Graphics/Data/ResourceHandle.cs
Normal file
164
Ghost.Graphics/Data/ResourceHandle.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32.Graphics.D3D12MemoryAllocator;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
internal readonly struct ResourceHandle : IEquatable<ResourceHandle>, IDisposable
|
||||
{
|
||||
private const int _INVALID_ID = -1;
|
||||
|
||||
public readonly int id;
|
||||
public readonly uint generation;
|
||||
|
||||
public static ResourceHandle Invalid => new(-1, 0);
|
||||
|
||||
internal ResourceHandle(int id, uint generation)
|
||||
{
|
||||
this.id = id;
|
||||
this.generation = generation;
|
||||
}
|
||||
|
||||
public bool IsValid => id != _INVALID_ID && generation >= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Allocation GetAllocation()
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot get allocation from an invalid AllocationHandle.");
|
||||
}
|
||||
|
||||
return GraphicsPipeline.ResourceAllocator.GetAllocation(this);
|
||||
}
|
||||
|
||||
public bool Equals(ResourceHandle other)
|
||||
{
|
||||
return id == other.id && generation == other.generation;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (id * 397) ^ (int)generation;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ResourceHandle handle && Equals(handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GraphicsPipeline.ResourceAllocator.ReleaseAllocation(this);
|
||||
}
|
||||
|
||||
public static implicit operator Allocation(ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot convert an invalid AllocationHandle to Allocation.");
|
||||
}
|
||||
|
||||
return handle.GetAllocation();
|
||||
}
|
||||
|
||||
public static bool operator ==(ResourceHandle left, ResourceHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ResourceHandle left, ResourceHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct TextureHandle : IEquatable<TextureHandle>, IDisposable
|
||||
{
|
||||
private readonly ResourceHandle _resourceHandle;
|
||||
|
||||
internal ResourceHandle ResourceHandle => _resourceHandle;
|
||||
|
||||
internal TextureHandle(ResourceHandle resourceHandle)
|
||||
{
|
||||
_resourceHandle = resourceHandle;
|
||||
}
|
||||
|
||||
public bool IsValid => _resourceHandle.IsValid;
|
||||
|
||||
public bool Equals(TextureHandle other)
|
||||
{
|
||||
return _resourceHandle.Equals(other._resourceHandle);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextureHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _resourceHandle.GetHashCode();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_resourceHandle.Dispose();
|
||||
}
|
||||
|
||||
public static bool operator ==(TextureHandle left, TextureHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextureHandle left, TextureHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct BufferHandle : IEquatable<BufferHandle>, IDisposable
|
||||
{
|
||||
private readonly ResourceHandle _resourceHandle;
|
||||
|
||||
internal ResourceHandle ResourceHandle => _resourceHandle;
|
||||
|
||||
internal BufferHandle(ResourceHandle resourceHandle)
|
||||
{
|
||||
_resourceHandle = resourceHandle;
|
||||
}
|
||||
|
||||
public bool IsValid => _resourceHandle.IsValid;
|
||||
|
||||
public bool Equals(BufferHandle other)
|
||||
{
|
||||
return _resourceHandle.Equals(other._resourceHandle);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is BufferHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _resourceHandle.GetHashCode();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_resourceHandle.Dispose();
|
||||
}
|
||||
|
||||
public static bool operator ==(BufferHandle left, BufferHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(BufferHandle left, BufferHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
147
Ghost.Graphics/Data/Texture.cs
Normal file
147
Ghost.Graphics/Data/Texture.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all texture types in the graphics pipeline.
|
||||
/// Provides common functionality for texture dimensions, format, and GPU resource management.
|
||||
/// </summary>
|
||||
public abstract unsafe class Texture : GraphicsResource
|
||||
{
|
||||
private readonly BindlessDescriptor _bindlessDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Width of the texture in pixels.
|
||||
/// </summary>
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height of the texture in pixels.
|
||||
/// </summary>
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes per pixel for the texture format.
|
||||
/// </summary>
|
||||
public uint BytesPerPixel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format of the texture.
|
||||
/// </summary>
|
||||
public Format Format
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of this texture in the global bindless descriptor heap.
|
||||
/// </summary>
|
||||
public uint DescriptorIndex => _bindlessDescriptor.Index;
|
||||
|
||||
internal Texture(uint width, uint height, Format format, in TextureHandle handle, BindlessDescriptor bindlessDescriptor)
|
||||
: base(handle.ResourceHandle)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
BytesPerPixel = GetBytesPerPixel(format);
|
||||
Format = format;
|
||||
_bindlessDescriptor = bindlessDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bindless shader resource view descriptor for the texture.
|
||||
/// </summary>
|
||||
protected static BindlessDescriptor CreateBindlessShaderResourceView(ID3D12Resource* resource, Format format)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = format,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
device->CreateShaderResourceView(resource, &srvDesc, bindlessDescriptor.CpuHandle);
|
||||
return bindlessDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bytes per pixel for the specified format.
|
||||
/// </summary>
|
||||
private static uint GetBytesPerPixel(Format format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.R8G8B8A8Unorm => 4,
|
||||
Format.R8G8B8A8UnormSrgb => 4,
|
||||
Format.B8G8R8A8Unorm => 4,
|
||||
Format.B8G8R8A8UnormSrgb => 4,
|
||||
Format.R8G8B8A8Uint => 4,
|
||||
Format.R8G8B8A8Sint => 4,
|
||||
Format.R8G8B8A8Snorm => 4,
|
||||
Format.R8G8Unorm => 2,
|
||||
Format.R8G8Uint => 2,
|
||||
Format.R8G8Sint => 2,
|
||||
Format.R8G8Snorm => 2,
|
||||
Format.R8Unorm => 1,
|
||||
Format.R8Uint => 1,
|
||||
Format.R8Sint => 1,
|
||||
Format.R8Snorm => 1,
|
||||
Format.A8Unorm => 1,
|
||||
Format.R16G16B16A16Float => 8,
|
||||
Format.R16G16B16A16Unorm => 8,
|
||||
Format.R16G16B16A16Uint => 8,
|
||||
Format.R16G16B16A16Sint => 8,
|
||||
Format.R16G16B16A16Snorm => 8,
|
||||
Format.R32G32B32A32Float => 16,
|
||||
Format.R32G32B32A32Uint => 16,
|
||||
Format.R32G32B32A32Sint => 16,
|
||||
Format.R32G32B32Float => 12,
|
||||
Format.R32G32B32Uint => 12,
|
||||
Format.R32G32B32Sint => 12,
|
||||
Format.R32G32Float => 8,
|
||||
Format.R32G32Uint => 8,
|
||||
Format.R32G32Sint => 8,
|
||||
Format.R32Float => 4,
|
||||
Format.R32Uint => 4,
|
||||
Format.R32Sint => 4,
|
||||
Format.R16G16Float => 4,
|
||||
Format.R16G16Unorm => 4,
|
||||
Format.R16G16Uint => 4,
|
||||
Format.R16G16Sint => 4,
|
||||
Format.R16G16Snorm => 4,
|
||||
Format.R16Float => 2,
|
||||
Format.R16Unorm => 2,
|
||||
Format.R16Uint => 2,
|
||||
Format.R16Sint => 2,
|
||||
Format.R16Snorm => 2,
|
||||
Format.D32Float => 4,
|
||||
Format.D24UnormS8Uint => 4,
|
||||
Format.D16Unorm => 2,
|
||||
_ => throw new NotSupportedException($"Format {format} is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the texture resources.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseBindless(_bindlessDescriptor);
|
||||
}
|
||||
}
|
||||
@@ -1,264 +1,84 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Misaki.HighPerformance.Image;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Runtime.InteropServices;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public unsafe class Texture2D : IDisposable
|
||||
/// <summary>
|
||||
/// Unified texture implementation that supports both bindless and regular descriptor table binding
|
||||
/// for use with SM 6.6 ResourceDescriptorHeap access and traditional frame buffer textures
|
||||
/// </summary>
|
||||
public unsafe class Texture2D : Texture
|
||||
{
|
||||
private UnsafeArray<byte> _cpuData;
|
||||
|
||||
private readonly GraphicsResource _resource;
|
||||
private readonly ShaderResourceDescriptor _srvDescriptor;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public Format Format
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint BytesPerPixel
|
||||
{
|
||||
get;
|
||||
}
|
||||
private UnTypedArray _cpuData;
|
||||
|
||||
public uint Pitch
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint DataSize
|
||||
private Texture2D(uint width, uint height, Format format, in TextureHandle handle, in ReadOnlySpan<byte> cpuData, BindlessDescriptor bindlessDescriptor)
|
||||
: base(width, height, format, in handle, bindlessDescriptor)
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal ShaderResourceDescriptor SRVDescriptor => _srvDescriptor;
|
||||
|
||||
public GraphicsResource? Resource => _resource;
|
||||
public ReadOnlySpan<byte> CpuData => _cpuData.AsSpan();
|
||||
|
||||
public Texture2D(uint width, uint height, Span<byte> data, Format format)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Format = format;
|
||||
BytesPerPixel = GetBytesPerPixel(format);
|
||||
Pitch = width * BytesPerPixel;
|
||||
DataSize = Pitch * height;
|
||||
|
||||
// Initialize CPU-side data
|
||||
_cpuData = new((int)DataSize, Allocator.Persistent);
|
||||
|
||||
if (!data.IsEmpty)
|
||||
uint alignment;
|
||||
if (BytesPerPixel > 4)
|
||||
{
|
||||
if (data.Length != DataSize)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
data.CopyTo(_cpuData.AsSpan());
|
||||
alignment = (uint)MemoryUtilities.AlignOf<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
alignment = (uint)MemoryUtilities.AlignOf<byte>();
|
||||
}
|
||||
|
||||
_resource = CreateGpuResource();
|
||||
_srvDescriptor = CreateShaderResourceView();
|
||||
}
|
||||
private static uint GetBytesPerPixel(Format format)
|
||||
{
|
||||
return format switch
|
||||
_cpuData = new UnTypedArray((uint)Size, alignment, ref AllocationManager.PersistentHandle);
|
||||
if (!cpuData.IsEmpty)
|
||||
{
|
||||
Format.R8G8B8A8Unorm => 4,
|
||||
Format.R8G8B8A8UnormSrgb => 4,
|
||||
Format.B8G8R8A8Unorm => 4,
|
||||
Format.B8G8R8A8UnormSrgb => 4,
|
||||
Format.R8G8B8A8Uint => 4,
|
||||
Format.R8G8B8A8Sint => 4,
|
||||
Format.R8G8B8A8Snorm => 4,
|
||||
Format.R8G8Unorm => 2,
|
||||
Format.R8G8Uint => 2,
|
||||
Format.R8G8Sint => 2,
|
||||
Format.R8G8Snorm => 2,
|
||||
Format.R8Unorm => 1,
|
||||
Format.R8Uint => 1,
|
||||
Format.R8Sint => 1,
|
||||
Format.R8Snorm => 1,
|
||||
Format.A8Unorm => 1,
|
||||
Format.R16G16B16A16Float => 8,
|
||||
Format.R16G16B16A16Unorm => 8,
|
||||
Format.R16G16B16A16Uint => 8,
|
||||
Format.R16G16B16A16Sint => 8,
|
||||
Format.R16G16B16A16Snorm => 8,
|
||||
Format.R32G32B32A32Float => 16,
|
||||
Format.R32G32B32A32Uint => 16,
|
||||
Format.R32G32B32A32Sint => 16,
|
||||
Format.R32G32B32Float => 12,
|
||||
Format.R32G32B32Uint => 12,
|
||||
Format.R32G32B32Sint => 12,
|
||||
Format.R32G32Float => 8,
|
||||
Format.R32G32Uint => 8,
|
||||
Format.R32G32Sint => 8,
|
||||
Format.R32Float => 4,
|
||||
Format.R32Uint => 4,
|
||||
Format.R32Sint => 4,
|
||||
Format.R16G16Float => 4,
|
||||
Format.R16G16Unorm => 4,
|
||||
Format.R16G16Uint => 4,
|
||||
Format.R16G16Sint => 4,
|
||||
Format.R16G16Snorm => 4,
|
||||
Format.R16Float => 2,
|
||||
Format.R16Unorm => 2,
|
||||
Format.R16Uint => 2,
|
||||
Format.R16Sint => 2,
|
||||
Format.R16Snorm => 2,
|
||||
_ => throw new NotSupportedException($"Format {format} is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
private GraphicsResource CreateGpuResource()
|
||||
{
|
||||
var heapProperties = new HeapProperties(HeapType.Default);
|
||||
var resourceDesc = ResourceDescription.Tex2D(Format, Width, Height);
|
||||
|
||||
ComPtr<ID3D12Resource> textureResource = default;
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
|
||||
&heapProperties,
|
||||
HeapFlags.None,
|
||||
&resourceDesc,
|
||||
ResourceStates.Common,
|
||||
null,
|
||||
__uuidof<ID3D12Resource>(),
|
||||
textureResource.GetVoidAddressOf()
|
||||
);
|
||||
|
||||
return new(textureResource.Move());
|
||||
}
|
||||
|
||||
private ShaderResourceDescriptor CreateShaderResourceView()
|
||||
{
|
||||
var srvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateSRV();
|
||||
|
||||
// Create the actual SRV
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = Format,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateShaderResourceView(_resource.NativeResource.Ptr, &srvDesc, srvDescriptor.CpuHandle);
|
||||
|
||||
return srvDescriptor;
|
||||
}
|
||||
|
||||
private void CopyBufferToTexture(CommandList cmd, GraphicsResource srcBuffer, GraphicsResource dstTexture)
|
||||
{
|
||||
// Calculate the proper footprint for the placed subresource
|
||||
var resourceDesc = dstTexture.NativeResource.Ptr->GetDesc();
|
||||
|
||||
PlacedSubresourceFootprint footprint = new()
|
||||
{
|
||||
Footprint = new SubresourceFootprint
|
||||
if ((uint)cpuData.Length > Size)
|
||||
{
|
||||
Format = Format,
|
||||
Width = Width,
|
||||
Height = Height,
|
||||
Depth = 1,
|
||||
RowPitch = (uint)((Pitch + 255) & ~255) // Align to 256 bytes
|
||||
throw new ArgumentException($"Data size mismatch. Expected {Size} bytes, got {cpuData.Length} bytes.");
|
||||
}
|
||||
};
|
||||
|
||||
var srcLocation = new TextureCopyLocation(srcBuffer.NativeResource.Ptr, footprint);
|
||||
var dstLocation = new TextureCopyLocation(dstTexture.NativeResource.Ptr, 0);
|
||||
_cpuData.CopyFrom(cpuData);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null);
|
||||
public static Texture2D Create(uint width, uint height, in ReadOnlySpan<byte> data, Format format)
|
||||
{
|
||||
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 0, format);
|
||||
var bindlessDescriptor = CreateBindlessShaderResourceView(handle.ResourceHandle.GetAllocation().Resource, format);
|
||||
|
||||
return new Texture2D(width, height, format, in handle, in data, bindlessDescriptor);
|
||||
}
|
||||
|
||||
public static Texture2D FromFile(string filePath)
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using var image = ImageResult.FromStream(stream, ColorComponents.RGBA);
|
||||
|
||||
return Create(image.Width, image.Height, image.AsSpan(), Format.R8G8B8A8Unorm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the entire texture data on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="data">The texture data to set</param>
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
public void SetData<T>(ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (data.Length != DataSize)
|
||||
if ((uint)data.Length > Size)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes.");
|
||||
throw new ArgumentException($"Data size mismatch. Expected {Size} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
data.CopyTo(_cpuData.AsSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture data for a specific region on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="data">The texture data to set</param>
|
||||
/// <param name="x">Starting X coordinate</param>
|
||||
/// <param name="y">Starting Y coordinate</param>
|
||||
/// <param name="width">Width of the region</param>
|
||||
/// <param name="height">Height of the region</param>
|
||||
public void SetData(ReadOnlySpan<byte> data, uint x, uint y, uint width, uint height)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (x + width > Width || y + height > Height)
|
||||
{
|
||||
throw new ArgumentException("Region extends beyond texture bounds.");
|
||||
}
|
||||
|
||||
var expectedSize = width * height * BytesPerPixel;
|
||||
if (data.Length != expectedSize)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {expectedSize} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
for (uint row = 0; row < height; row++)
|
||||
{
|
||||
var srcOffset = (int)(row * width * BytesPerPixel);
|
||||
var dstOffset = (int)((y + row) * Pitch + x * BytesPerPixel);
|
||||
var rowSize = (int)(width * BytesPerPixel);
|
||||
|
||||
data.Slice(srcOffset, rowSize).CopyTo(_cpuData.AsSpan().Slice(dstOffset, rowSize));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a single pixel value on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <param name="color">The color data for the pixel</param>
|
||||
public void SetPixel(uint x, uint y, ReadOnlySpan<byte> color)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (x >= Width || y >= Height)
|
||||
{
|
||||
throw new ArgumentException("Pixel coordinates are outside texture bounds.");
|
||||
}
|
||||
|
||||
if (color.Length != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color data size mismatch. Expected {BytesPerPixel} bytes, got {color.Length} bytes.");
|
||||
}
|
||||
|
||||
var offset = (int)(y * Pitch + x * BytesPerPixel);
|
||||
color.CopyTo(_cpuData.AsSpan().Slice(offset, (int)BytesPerPixel));
|
||||
_cpuData.CopyFrom(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -268,7 +88,8 @@ public unsafe class Texture2D : IDisposable
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <param name="color">The color value</param>
|
||||
public void SetPixel<T>(uint x, uint y, T color) where T : unmanaged
|
||||
public void SetPixel<T>(uint x, uint y, T color)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != BytesPerPixel)
|
||||
{
|
||||
@@ -279,6 +100,52 @@ public unsafe class Texture2D : IDisposable
|
||||
SetPixel(x, y, colorSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a single pixel value on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <param name="color">The color data for the pixel</param>
|
||||
public void SetPixel(uint x, uint y, ReadOnlySpan<byte> color)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (x >= Width || y >= Height)
|
||||
{
|
||||
throw new ArgumentException("Pixel coordinates are outside texture bounds.");
|
||||
}
|
||||
|
||||
if (color.Length != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color data size mismatch. Expected {BytesPerPixel} bytes, got {color.Length} bytes.");
|
||||
}
|
||||
|
||||
var offset = y * Pitch + x * BytesPerPixel;
|
||||
_cpuData.CopyFrom(color, 0u, offset, BytesPerPixel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single pixel value as a generic color type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The color type (e.g., uint for RGBA32)</typeparam>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <returns>The pixel color value</returns>
|
||||
public T GetPixel<T>(uint x, uint y)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var pixelData = GetPixel(x, y);
|
||||
fixed (byte* pPixel = pixelData)
|
||||
{
|
||||
return *(T*)pPixel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single pixel value from the CPU side data.
|
||||
/// </summary>
|
||||
@@ -287,7 +154,7 @@ public unsafe class Texture2D : IDisposable
|
||||
/// <returns>The pixel color data</returns>
|
||||
public ReadOnlySpan<byte> GetPixel(uint x, uint y)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (x >= Width || y >= Height)
|
||||
{
|
||||
@@ -298,30 +165,12 @@ public unsafe class Texture2D : IDisposable
|
||||
return _cpuData.AsSpan().Slice(offset, (int)BytesPerPixel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single pixel value as a generic color type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The color type (e.g., uint for RGBA32)</typeparam>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <returns>The pixel color value</returns>
|
||||
public T GetPixel<T>(uint x, uint y) where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var pixelData = GetPixel(x, y);
|
||||
return MemoryMarshal.Read<T>(pixelData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the CPU-side texture data to the GPU resource.
|
||||
/// </summary>
|
||||
public void UploadTextureData()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ThrowIfDisposed();
|
||||
|
||||
Format.GetSurfaceInfo((int)Width, (int)Height, out var rowPitch, out var slicePitch);
|
||||
var initData = new SubresourceData()
|
||||
@@ -331,27 +180,15 @@ public unsafe class Texture2D : IDisposable
|
||||
SlicePitch = slicePitch
|
||||
};
|
||||
|
||||
using var uploadBatch = new ResourceUploadBatch();
|
||||
uploadBatch.Begin();
|
||||
uploadBatch.Transition(_resource, ResourceStates.Common, ResourceStates.CopyDest);
|
||||
uploadBatch.Upload(_resource, 0, &initData, 1);
|
||||
uploadBatch.Transition(_resource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource);
|
||||
uploadBatch.WaitForCompletion(uploadBatch.End());
|
||||
var uploadBatch = GraphicsPipeline.UploadBatch;
|
||||
uploadBatch.Transition(NativeResource, ResourceStates.Common, ResourceStates.CopyDest);
|
||||
uploadBatch.Upload(NativeResource, 0, &initData, 1);
|
||||
uploadBatch.Transition(NativeResource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
_cpuData.Dispose();
|
||||
_resource.Dispose();
|
||||
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseSRV(_srvDescriptor);
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user