Refactor graphics architecture and resource management

Added DescriptorAllocator.cs to manage descriptor allocations for Direct3D 12.
Added Texture2D.cs to handle 2D textures and GPU resource creation.
Added DescriptorAllocatorExample.cs to demonstrate the new descriptor allocator interface.

Changed project files to reference Misaki.HighPerformance.LowLevel instead of Misaki.HighPerformance.Unsafe.
Changed _renderView type from IRenderer? to Renderer? in ScenePage.xaml.cs.
Changed EngineCore.cs to remove explicit graphics API specification during initialization.
Changed Logger.cs to enhance the Assert method with a DoesNotReturnIf attribute.
Changed resource types in Mesh.cs from IResource to GraphicsResource.

Removed multiple interfaces including ICommandBuffer, IDebugLayer, IGraphicsDevice, IPipelineResource, IRenderPass, IRenderer, IResource, and IResourceAllocator to simplify the graphics architecture.
Removed D3D12DebugLayer class from DebugLayer.cs to streamline the debug layer implementation.

Updated CommandList.cs and D3D12CommandBuffer.cs to implement a new command list structure for Direct3D 12.
Updated Material.cs to improve handling of constant buffers and textures.
Updated Shader.cs to include new structures for texture and property information.
Updated GraphicsPipeline.cs to support the new graphics device and resource management system.
Updated UnitTestAppWindow.xaml.cs to reflect changes in the renderer type and ensure proper resource management.
Updated BindlessMeshRenderPass.cs and MeshRenderPass.cs to implement modern rendering techniques, including bindless textures and improved shader management.
Updated CBufferCache.cs to align with the new resource management system and improve memory handling.
This commit is contained in:
2025-07-12 01:20:04 +09:00
parent eed1b9d3d0
commit 1284bb17de
38 changed files with 2831 additions and 517 deletions

View File

@@ -0,0 +1,677 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.Utilities;
using StbImageSharp;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D.Dxc;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.RenderPasses;
/// <summary>
/// Modern bindless texture implementation using SM 6.6 with ResourceDescriptorHeap
/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
/// </summary>
internal unsafe class BindlessMeshRenderPass : IRenderPass
{
private const string _HLSL_SOURCE = @"
cbuffer ConstantBuffer : register(b0)
{
float4 _Color;
uint _TextureIndex1;
uint _TextureIndex2;
uint _TextureIndex3;
uint _TextureIndex4;
};
// SM 6.6 approach - no need for explicit texture arrays
// ResourceDescriptorHeap is a global descriptor heap access mechanism
// This allows direct indexing into any texture in the bound descriptor heap
SamplerState _MainSampler : register(s0);
struct VertexInput
{
float4 position : POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
struct PixelInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
PixelInput VSMain(VertexInput input)
{
PixelInput output;
output.position = input.position;
output.color = input.color;
output.uv = input.uv;
return output;
}
float4 PSMain(PixelInput input) : SV_TARGET
{
// SM 6.6 Modern Bindless Approach:
// ResourceDescriptorHeap[index] directly accesses any texture in the heap
// No need for explicit texture arrays or descriptor table binding
// This is enabled by D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1];
Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2];
Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3];
Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4];
// Sample the textures normally
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;
}
";
private Mesh? _mesh;
// Direct D3D12 resources for modern bindless implementation
private ComPtr<ID3D12PipelineState> _pipelineState;
private ComPtr<ID3D12RootSignature> _rootSignature;
private ComPtr<ID3D12Resource> _constantBuffer;
private void* _constantBufferMappedData;
// Bindless texture resources
private ComPtr<ID3D12Resource>[] _textureResources;
private ComPtr<ID3D12DescriptorHeap> _bindlessHeap;
private ComPtr<ID3D12DescriptorHeap> _samplerHeap;
private CpuDescriptorHandle _bindlessHeapStart;
private GpuDescriptorHandle _bindlessHeapGpuStart;
private uint _descriptorSize;
// Texture upload resources
private ComPtr<ID3D12Resource>[] _uploadBuffers;
private uint[] _textureWidths;
private uint[] _textureHeights;
private uint[] _texturePitches;
private bool _texturesUploaded = 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"
];
// Constant buffer data structure
private struct ConstantBufferData
{
public System.Numerics.Vector4 Color;
public uint TextureIndex1;
public uint TextureIndex2;
public uint TextureIndex3;
public uint TextureIndex4;
}
public void Initialize(CommandList cmd)
{
_mesh = MeshBuilder.CreateCube(0.75f);
_mesh.UploadMeshData();
CreateModernBindlessShader();
CreateConstantBuffer();
CreateBindlessTextures();
UploadTextureData(cmd);
UpdateConstantBuffer();
}
private void CreateModernBindlessShader()
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var vsCode = CompileShaderDXC(_HLSL_SOURCE, "VSMain", "vs_6_6");
var psCode = CompileShaderDXC(_HLSL_SOURCE, "PSMain", "ps_6_6");
CreateModernBindlessRootSignature();
CreatePipelineState(vsCode, psCode);
}
private byte[] CompileShaderDXC(string source, string entryPoint, string profile)
{
using ComPtr<IDxcCompiler3> compiler = default;
using ComPtr<IDxcUtils> utils = default;
// Create DXC compiler and utils
DxcCreateInstance(CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
// Create source blob
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
var sourceBytes = System.Text.Encoding.UTF8.GetBytes(source);
fixed (byte* sourceBytesPtr = sourceBytes)
{
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
}
// Prepare compilation arguments
var argsArray = new string[]
{
"-T", profile, // Target profile (vs_6_6, ps_6_6)
"-E", entryPoint, // Entry point
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
"-enable-16bit-types", // Enable 16-bit types
"-O3", // Optimization level
"-Qstrip_debug", // Strip debug info for release
"-Qstrip_reflect" // Strip reflection info for release
};
// Convert to wide strings (DXC expects LPCWSTR)
var wideArgs = new nuint[argsArray.Length];
var argPointers = new IntPtr[argsArray.Length];
for (var i = 0; i < argsArray.Length; i++)
{
argPointers[i] = Marshal.StringToHGlobalUni(argsArray[i]);
wideArgs[i] = (nuint)argPointers[i];
}
try
{
// Compile shader
using ComPtr<IDxcResult> result = default;
fixed (nuint* argsPtr = wideArgs)
{
var buffer = new DxcBuffer
{
Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC_CP_UTF8
};
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof<IDxcResult>(), result.GetVoidAddressOf());
}
// Check compilation result
HResult hrStatus;
result.Get()->GetStatus(&hrStatus);
if (hrStatus.Failure)
{
// Get error messages
using ComPtr<IDxcBlobEncoding> errorBlob = default;
result.Get()->GetErrorBuffer(errorBlob.GetAddressOf());
if (errorBlob.Get() != null)
{
var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer());
throw new Exception($"DXC shader compilation failed: {errorMessage}");
}
else
{
throw new Exception("DXC shader compilation failed with unknown error");
}
}
// Get compiled bytecode
using ComPtr<IDxcBlob> bytecodeBlob = default;
result.Get()->GetResult(bytecodeBlob.GetAddressOf());
if (bytecodeBlob.Get() == null)
{
throw new Exception("DXC compilation succeeded but no bytecode was produced");
}
// Copy bytecode to managed array
var bytecodeSize = (int)bytecodeBlob.Get()->GetBufferSize();
var bytecode = new byte[bytecodeSize];
fixed (byte* bytecodePtr = bytecode)
{
Buffer.MemoryCopy(bytecodeBlob.Get()->GetBufferPointer(), bytecodePtr, bytecodeSize, bytecodeSize);
}
return bytecode;
}
finally
{
// Free allocated wide strings
for (var i = 0; i < argPointers.Length; i++)
{
Marshal.FreeHGlobal(argPointers[i]);
}
}
}
private void CreateModernBindlessRootSignature()
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
// Modern approach: Only CBV + Sampler, no descriptor tables needed
// The CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED flag allows direct heap access
var rootParameters = new RootParameter1[2];
// CBV for constant buffer
rootParameters[0] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(0, 0), // b0
};
// Sampler descriptor table (still needed for samplers)
var samplerRanges = new DescriptorRange1[1];
samplerRanges[0] = new DescriptorRange1
{
RangeType = DescriptorRangeType.Sampler,
NumDescriptors = 1,
BaseShaderRegister = 0, // s0
RegisterSpace = 0,
Flags = DescriptorRangeFlags.None,
OffsetInDescriptorsFromTableStart = 0
};
fixed (DescriptorRange1* samplerRangesPtr = samplerRanges)
{
rootParameters[1] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, samplerRangesPtr)
};
}
// Create root signature with the modern flag
fixed (RootParameter1* rootParamsPtr = rootParameters)
{
var rootSignatureDesc = new RootSignatureDescription1
{
NumParameters = (uint)rootParameters.Length,
pParameters = rootParamsPtr,
NumStaticSamplers = 0,
pStaticSamplers = null,
// Key difference: Use the modern flag for direct heap indexing
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout |
RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
};
var versionedDesc = new VersionedRootSignatureDescription
{
Version = RootSignatureVersion.V1_1,
Desc_1_1 = rootSignatureDesc
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), _rootSignature.GetVoidAddressOf());
}
}
private void CreatePipelineState(byte[] vsCode, byte[] psCode)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
fixed (byte* vsPtr = vsCode)
fixed (byte* psPtr = psCode)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = _rootSignature.Get(),
VS = new ShaderBytecode(vsPtr, (nuint)vsCode.Length),
PS = new ShaderBytecode(psPtr, (nuint)psCode.Length),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
// Fix: Use the correct swap chain back buffer format
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
device->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), _pipelineState.GetVoidAddressOf());
}
}
private void CreateConstantBuffer()
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
// Create constant buffer
var cbSize = (uint)((sizeof(ConstantBufferData) + 255) & ~255); // Align to 256 bytes
var heapProps = new HeapProperties(HeapType.Upload);
var bufferDesc = ResourceDescription.Buffer(cbSize);
device->CreateCommittedResource(
&heapProps,
HeapFlags.None,
&bufferDesc,
ResourceStates.GenericRead,
null,
__uuidof<ID3D12Resource>(),
_constantBuffer.GetVoidAddressOf()
);
// Map constant buffer
fixed (void** mappedDataPtr = &_constantBufferMappedData)
{
_constantBuffer.Get()->Map(0, null, mappedDataPtr);
}
}
private void UpdateConstantBuffer()
{
var cbData = new ConstantBufferData
{
Color = new(1.0f, 1.0f, 1.0f, 1.0f),
TextureIndex1 = 0,
TextureIndex2 = 1,
TextureIndex3 = 2,
TextureIndex4 = 3
};
*(ConstantBufferData*)_constantBufferMappedData = cbData;
}
private void CreateBindlessTextures()
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
var textureCount = _textureFiles.Length;
// Initialize arrays
_textureResources = new ComPtr<ID3D12Resource>[textureCount];
_uploadBuffers = new ComPtr<ID3D12Resource>[textureCount];
_textureWidths = new uint[textureCount];
_textureHeights = new uint[textureCount];
_texturePitches = new uint[textureCount];
// Create bindless descriptor heap
var bindlessHeapDesc = new DescriptorHeapDescription
{
Type = DescriptorHeapType.CbvSrvUav,
NumDescriptors = 1000,
Flags = DescriptorHeapFlags.ShaderVisible
};
device->CreateDescriptorHeap(&bindlessHeapDesc, __uuidof<ID3D12DescriptorHeap>(), _bindlessHeap.GetVoidAddressOf());
// Create sampler heap
var samplerHeapDesc = new DescriptorHeapDescription
{
Type = DescriptorHeapType.Sampler,
NumDescriptors = 1,
Flags = DescriptorHeapFlags.ShaderVisible
};
device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof<ID3D12DescriptorHeap>(), _samplerHeap.GetVoidAddressOf());
// Get descriptor handles
_bindlessHeapStart = _bindlessHeap.Get()->GetCPUDescriptorHandleForHeapStart();
_bindlessHeapGpuStart = _bindlessHeap.Get()->GetGPUDescriptorHandleForHeapStart();
_descriptorSize = device->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
// Create sampler
var samplerDesc = new SamplerDescription
{
Filter = Filter.MinMagMipLinear,
AddressU = TextureAddressMode.Wrap,
AddressV = TextureAddressMode.Wrap,
AddressW = TextureAddressMode.Wrap,
MipLODBias = 0,
MaxAnisotropy = 1,
MinLOD = 0,
MaxLOD = float.MaxValue
};
// Set border color manually
samplerDesc.BorderColor[0] = 0;
samplerDesc.BorderColor[1] = 0;
samplerDesc.BorderColor[2] = 0;
samplerDesc.BorderColor[3] = 0;
var samplerHandle = _samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart();
device->CreateSampler(&samplerDesc, samplerHandle);
// Load and create textures
for (var i = 0; i < textureCount; i++)
{
CreateTextureResource(i);
}
}
private void CreateTextureResource(int index)
{
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
// Load image data
using var stream = new FileStream(_textureFiles[index], FileMode.Open, FileAccess.Read);
var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
var width = (uint)image.Width;
var height = (uint)image.Height;
uint bytesPerPixel = 4;
var pitch = width * bytesPerPixel;
// Store texture dimensions
_textureWidths[index] = width;
_textureHeights[index] = height;
_texturePitches[index] = (pitch + 255) & ~255u;
// Create 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>(),
_textureResources[index].GetVoidAddressOf()
);
// Create upload buffer
var uploadBufferSize = GetRequiredIntermediateSize(_textureResources[index].Get(), 0, 1);
var uploadHeapProps = new HeapProperties(HeapType.Upload);
var uploadBufferDesc = ResourceDescription.Buffer(uploadBufferSize);
device->CreateCommittedResource(
&uploadHeapProps,
HeapFlags.None,
&uploadBufferDesc,
ResourceStates.GenericRead,
null,
__uuidof<ID3D12Resource>(),
_uploadBuffers[index].GetVoidAddressOf()
);
// Map and copy texture data
void* mappedData = null;
_uploadBuffers[index].Get()->Map(0, null, &mappedData);
var srcData = image.Data.AsSpan();
var dstSpan = new Span<byte>(mappedData, (int)uploadBufferSize);
// Copy row by row with proper alignment
for (var y = 0; y < height; y++)
{
var srcRow = srcData.Slice(y * (int)pitch, (int)pitch);
var dstRow = dstSpan.Slice(y * (int)_texturePitches[index], (int)pitch);
srcRow.CopyTo(dstRow);
}
_uploadBuffers[index].Get()->Unmap(0, null);
// Create SRV in the bindless heap
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
}
};
// Calculate SRV handle for this texture
var srvHandle = new CpuDescriptorHandle
{
ptr = _bindlessHeapStart.ptr + (nuint)(index * _descriptorSize)
};
device->CreateShaderResourceView(_textureResources[index].Get(), &srvDesc, srvHandle);
}
private void UploadTextureData(CommandList cmd)
{
var commandList = cmd.NativeCommandList.Ptr;
for (var i = 0; i < _textureResources.Length; i++)
{
// Copy from upload buffer to texture
var srcLocation = new TextureCopyLocation(_uploadBuffers[i].Get(), new PlacedSubresourceFootprint
{
Offset = 0,
Footprint = new SubresourceFootprint
{
Format = Format.R8G8B8A8Unorm,
Width = _textureWidths[i],
Height = _textureHeights[i],
Depth = 1,
RowPitch = _texturePitches[i]
}
});
var dstLocation = new TextureCopyLocation(_textureResources[i].Get(), 0);
commandList->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 = _textureResources[i].Get(),
StateBefore = ResourceStates.CopyDest,
StateAfter = ResourceStates.PixelShaderResource,
Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES
}
};
commandList->ResourceBarrier(1, &barrier);
}
_texturesUploaded = true;
}
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;
}
public void Execute(CommandList cmd)
{
var commandList = cmd.NativeCommandList.Ptr;
// Set root signature and pipeline state
commandList->SetGraphicsRootSignature(_rootSignature.Get());
commandList->SetPipelineState(_pipelineState.Get());
// Set descriptor heaps - this is crucial for modern bindless
var heaps = stackalloc ID3D12DescriptorHeap*[2];
heaps[0] = _bindlessHeap.Get(); // CBV_SRV_UAV heap for direct indexing
heaps[1] = _samplerHeap.Get(); // Sampler heap
commandList->SetDescriptorHeaps(2, heaps);
// Bind constant buffer
commandList->SetGraphicsRootConstantBufferView(0, _constantBuffer.Get()->GetGPUVirtualAddress());
// Bind sampler descriptor table
var samplerGpuHandle = _samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart();
commandList->SetGraphicsRootDescriptorTable(1, samplerGpuHandle);
// No need to explicitly bind texture descriptor table!
// The textures are accessed directly via ResourceDescriptorHeap[index]
// Draw mesh
commandList->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
commandList->IASetVertexBuffers(0, 1, _mesh!.VertexBufferView);
commandList->IASetIndexBuffer(_mesh.IndexBufferView);
commandList->DrawIndexedInstanced(_mesh.IndexCount, 1, 0, 0, 0);
}
public void Dispose()
{
_mesh?.Dispose();
// Unmap constant buffer
if (_constantBuffer.Get() != null)
{
_constantBuffer.Get()->Unmap(0, null);
}
_pipelineState.Dispose();
_rootSignature.Dispose();
_constantBuffer.Dispose();
// Dispose texture resources
if (_textureResources != null)
{
for (var i = 0; i < _textureResources.Length; i++)
{
_textureResources[i].Dispose();
}
}
// Dispose upload buffers
if (_uploadBuffers != null)
{
for (var i = 0; i < _uploadBuffers.Length; i++)
{
_uploadBuffers[i].Dispose();
}
}
_bindlessHeap.Dispose();
_samplerHeap.Dispose();
}
}

View File

@@ -1,9 +1,13 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12;
using Ghost.Graphics.Data;
using Ghost.Graphics.Shading;
using Ghost.Graphics.Utilities;
using System.Drawing;
using System.Numerics;
using StbImageSharp;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.RenderPasses;
@@ -15,29 +19,35 @@ cbuffer ConstantBuffer : register(b0)
float4 _Color;
};
Texture2D _MainTex : register(t0);
SamplerState _MainSampler : register(s0);
struct VertexInput
{
float3 position : POSITION;
float4 position : POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
struct PixelInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
};
PixelInput VSMain(VertexInput input)
{
PixelInput output;
output.position = float4(input.position, 1.0f);
output.position = input.position;
output.color = input.color;
output.uv = input.uv;
return output;
}
float4 PSMain(PixelInput input) : SV_TARGET
{
return float4(_Color.xyz, 1.0);
return _MainTex.Sample(_MainSampler, input.uv.xy);
}
";
@@ -45,21 +55,218 @@ float4 PSMain(PixelInput input) : SV_TARGET
private Shader? _shader;
private Material? _material;
public void Initialize(ICommandBuffer cmb)
// 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;
public void Initialize(CommandList cmd)
{
_mesh = MeshBuilder.CreateCube(0.25f);
_mesh.UploadMeshData(cmb);
_mesh.UploadMeshData();
_shader = new(_HLSL_SOURCE);
_material = new(_shader);
var color = new Vector4(Color.Brown.R / 255f, Color.Brown.G / 255f, Color.Brown.B / 255f, 1.0f);
_material.SetVector("_Color", ref color);
// Create direct D3D12 texture resources
CreateTextureDirectly();
_material.UploadMaterialData();
// Copy from upload buffer to texture
var srcLocation = new TextureCopyLocation(_uploadBuffer.Get(), new PlacedSubresourceFootprint
{
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);
}
public void Execute(ICommandBuffer cmb)
private void CreateTextureDirectly()
{
cmb.DrawMesh(_mesh!, _material!);
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);
}
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
{
Type = DescriptorHeapType.CbvSrvUav,
NumDescriptors = 1,
Flags = DescriptorHeapFlags.ShaderVisible
};
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;
}
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);
}
public void Dispose()
@@ -67,5 +274,9 @@ float4 PSMain(PixelInput input) : SV_TARGET
_mesh?.Dispose();
_shader?.Dispose();
_material?.Dispose();
_textureResource.Dispose();
_uploadBuffer.Dispose();
_srvHeap.Dispose();
}
}