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,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