Refactor and enhance codebase for maintainability

Refactored and reorganized the codebase to improve readability, performance, and maintainability. Introduced new interfaces and structs for better resource management, updated project configuration files, and refactored shader and graphics pipeline management. Improved error handling, code formatting, and removed unused code and namespaces. Updated DLL references and method signatures for consistency and maintainability.
This commit is contained in:
2025-10-22 18:46:39 +09:00
parent 6d1b510ac1
commit d2d9f5feb7
80 changed files with 2836 additions and 2198 deletions

View File

@@ -1,14 +1,15 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.DXGI_Alias;
using static TerraFX.Aliases.D3D_Alias;
namespace Ghost.Graphics.D3D12;
@@ -65,11 +66,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfNotRecording()
{
if (!_isRecording)
@@ -78,6 +81,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IncrementCommandCount()
{
_commandCount++;

View File

@@ -1,3 +1,4 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;

View File

@@ -23,7 +23,7 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
{
_rtvHeap = new D3D12DescriptorHeap("rtv", device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, initialRtvCount, initialRtvCount / 2);
_dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount, initialDsvCount / 2);
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount, initialSrvCount /2);
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount, initialSrvCount / 2);
_samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount, initialSamplerCount);
}

View File

@@ -1,52 +1,64 @@
#undef USE_TRADITIONAL_BINDLESS
using Ghost.Core;
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Utilities;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using Misaki.HighPerformance.LowLevel.Utilities;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.DXGI_Alias;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12;
// TODO: Fixed root signature and use bindless samplers and textures.
// This can dramatically reduce the number of root parameters needed and improve performance.
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
internal struct D3D12GraphicsCompiledResult : IDisposable
{
public ComPtr<ID3D12PipelineState> pipelineState;
public D3D12ShaderCompiler.CompileResult vsResult;
public D3D12ShaderCompiler.CompileResult psResult;
public D3D12ShaderCompiler.CompileResult csResult;
public PipelineType Type
{
get; init;
}
public CompileResult tsResult;
public CompileResult msResult;
public CompileResult psResult;
public void Dispose()
{
pipelineState.Dispose();
vsResult.Dispose();
tsResult.Dispose();
msResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
}
internal struct D3D12PipelineState : IDisposable
{
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
public D3D12GraphicsCompiledResult compileResult;
public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc;
public void Dispose()
{
compileResult.Dispose();
}
}
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
{
private const int _ROOT_PARAM_COUNT =
#if USE_TRADITIONAL_BINDLESS
6
#else
4
#endif
;
private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<ID3D12PipelineLibrary1> _library;
private ComPtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
private readonly Dictionary<GraphicsPipelineKey, D3D12PipelineState> _pipelineCache;
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
@@ -55,20 +67,20 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
_device = device;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
_pipelineCache = new();
InitializePipelineLibrary(cachePath);
InitializeLibrary(cachePath);
CreateDefaultRootSignature();
}
private void InitializePipelineLibrary(string? cachePath)
private void InitializeLibrary(string? filePath)
{
if (!File.Exists(cachePath))
if (!File.Exists(filePath))
{
_device.NativeDevice->CreatePipelineLibrary(null, 0, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
}
var fileBytes = File.ReadAllBytes(cachePath!);
var fileBytes = File.ReadAllBytes(filePath!);
fixed (byte* pFileBytes = fileBytes)
{
_device.NativeDevice->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
@@ -77,18 +89,10 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
private void CreateDefaultRootSignature()
{
const int rootParamCount =
#if USE_TRADITIONAL_BINDLESS
6
#else
4
#endif
;
_defaultRootSignature = default;
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up descriptor tables.
var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[rootParamCount];
var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[_ROOT_PARAM_COUNT];
rootParameters[0] = new D3D12_ROOT_PARAMETER1
{
ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
@@ -151,7 +155,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
{
NumParameters = rootParamCount,
NumParameters = _ROOT_PARAM_COUNT,
pParameters = rootParameters,
NumStaticSamplers = 0,
pStaticSamplers = null,
@@ -175,71 +179,227 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
if (serializeResult.FAILED)
{
var errorMsg = error.Get() != null ? Marshal.PtrToStringAnsi((nint)error.Get()->GetBufferPointer()) : "Unknown error";
var errorMsg = error.Get() != null ? Marshal.PtrToStringUTF8((nint)error.Get()->GetBufferPointer()) : "Unknown error";
throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}");
}
_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(),
__uuidof<ID3D12RootSignature>(), _defaultRootSignature.GetVoidAddressOf()).ThrowIfFailed();
ThrowIfFailed(_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(),
__uuidof<ID3D12RootSignature>(), _defaultRootSignature.GetVoidAddressOf()));
}
public void StorePipeline(string psoIdentifier, ID3D12PipelineState* pso)
private static void ValidateReflectionData(ShaderReflectionData reflectionData)
{
_library.Get()->StorePipeline(psoIdentifier.AsSpan().GetUnsafePtr(), pso);
}
public void* LoadGraphicsPipeline(string psoIdentifier)
{
if (_library.Get()->LoadGraphicsPipeline(psoIdentifier.AsSpan().GetUnsafePtr(), __uuidof<ID3D12PipelineState>(), out var pso).Failure)
if (reflectionData.ConstantBuffers.Count != _ROOT_PARAM_COUNT)
{
return null;
throw new InvalidOperationException($"Shader reflection data has {reflectionData.ConstantBuffers.Count} constant buffers, expected {_ROOT_PARAM_COUNT}");
}
return pso;
if (reflectionData.OtherResources.Count != 0)
{
throw new NotSupportedException("Shader reflection data contains unsupported resource types. Only constant buffers are supported in the current root signature.");
}
// TODO: Validate Cbuffer sizes and bindings.
}
public void CompileShader(Identifier<Shader> id, string shaderPath)
private static Result<D3D12GraphicsCompiledResult> CompileAndValidateFullPass(FullPassDescriptor descriptor)
{
var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
ref var shader = ref _resourceDatabase.GetShaderReference(id);
D3D12ShaderCompiler.PerformDXCReflection(ref shader, vsResult.reflection.Get());
D3D12ShaderCompiler.PerformDXCReflection(ref shader, psResult.reflection.Get());
var shaderPipeline = new D3D12ShaderPipeline
static CompileResult CompileAndValidate(ref CompilerConfig config)
{
Type = PipelineType.Graphics,
vsResult = vsResult,
var reflectionBlob = default(IDxcBlob*);
var result = D3D12ShaderCompiler.Compile(ref config, Allocator.Persistent, &reflectionBlob).GetValueOrThrow();
if (reflectionBlob != null)
{
var reflection = D3D12ShaderCompiler.PerformDXCReflection(reflectionBlob).GetValueOrThrow();
ValidateReflectionData(reflection);
}
return result;
}
var tsResult = default(CompileResult);
var tsEntry = descriptor.taskShader;
if (tsEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
stage = ShaderStage.TaskShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
tsResult = CompileAndValidate(ref config);
}
CompileResult msResult;
var msEntry = descriptor.meshShader;
if (msEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
stage = ShaderStage.MeshShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
msResult = CompileAndValidate(ref config);
}
else
{
return Result<D3D12GraphicsCompiledResult>.Fail("Mesh shader expected.");
}
CompileResult psResult;
var psEntry = descriptor.pixelShader;
if (psEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
stage = ShaderStage.PixelShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
psResult = CompileAndValidate(ref config);
}
else
{
return Result<D3D12GraphicsCompiledResult>.Fail("Pixel shader expected.");
}
return new D3D12GraphicsCompiledResult
{
tsResult = tsResult,
msResult = msResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
}
// Create PSO from SDL (Shader Definition Language) file
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTestOptions z) => z switch
{
var psoDesc = new D3D12_GRAPHICS_PIPELINE_STATE_DESC
ZTestOptions.Disabled => D3D12_COMPARISON_FUNC_ALWAYS,
ZTestOptions.Less => D3D12_COMPARISON_FUNC_LESS,
ZTestOptions.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ZTestOptions.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ZTestOptions.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ZTestOptions.Greater => D3D12_COMPARISON_FUNC_GREATER,
ZTestOptions.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ZTestOptions.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL
};
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ref readonly PipelineDescriptor pipeline)
{
var depthEnabled = pipeline.zTest != ZTestOptions.Disabled;
var writeEnabled = pipeline.zWrite == ZWriteOptions.On;
var cmp = ToD3DCompare(pipeline.zTest);
return D3D12_DEPTH_STENCIL_DESC.Create(depthEnabled, writeEnabled, cmp);
}
private void StorePassState(ShaderPassKey id, ref readonly D3D12GraphicsCompiledResult compiled, ref readonly PipelineDescriptor pipelineDescriptor, ReadOnlySpan<TextureFormat> rtvs, TextureFormat dsv)
{
var rtvCount = (uint)Math.Min(rtvs.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT);
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
{
pRootSignature = _defaultRootSignature.Get(),
VS = new D3D12_SHADER_BYTECODE(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new D3D12_SHADER_BYTECODE(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.psResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = D3D12_RASTERIZER_DESC.CULL_NONE,
BlendState = D3D12_BLEND_DESC.OPAQUE,
DepthStencilState = D3D12_DEPTH_STENCIL_DESC.DEFAULT,
SampleMask = uint.MaxValue,
MS = new D3D12_SHADER_BYTECODE(compiled.msResult.bytecode.GetUnsafePtr(), (nuint)compiled.msResult.bytecode.Count),
PS = new D3D12_SHADER_BYTECODE(compiled.psResult.bytecode.GetUnsafePtr(), (nuint)compiled.psResult.bytecode.Count),
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets = 1,
SampleMask = UINT32_MAX,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
DSVFormat = DXGI_FORMAT_UNKNOWN,
NumRenderTargets = rtvCount,
DSVFormat = dsv.ToD3D12Format(),
DepthStencilState = BuildDepthStencil(in pipelineDescriptor),
NodeMask = 0,
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
BlendState = pipelineDescriptor.blend switch
{
BlendOptions.Opaque => D3D12_BLEND_DESC.OPAQUE,
BlendOptions.Alpha => D3D12_BLEND_DESC.ALPHA_BLEND,
BlendOptions.Additive => D3D12_BLEND_DESC.ADDITIVE,
BlendOptions.Multiply => D3D12_BLEND_DESC.MULTIPLY,
BlendOptions.PremultipliedAlpha => D3D12_BLEND_DESC.PREMULTIPLIED,
_ => D3D12_BLEND_DESC.OPAQUE
},
RasterizerState = pipelineDescriptor.cull switch
{
CullOptions.Off => D3D12_RASTERIZER_DESC.CULL_NONE,
CullOptions.Front => D3D12_RASTERIZER_DESC.CULL_CLOCKWISE,
CullOptions.Back => D3D12_RASTERIZER_DESC.CULL_COUNTER_CLOCKWISE,
_ => D3D12_RASTERIZER_DESC.CULL_NONE
},
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
if (compiled.tsResult.IsCreated)
{
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
}
_device.NativeDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
var hash = new GraphicsPipelineHash
{
id = id,
rtvCount = rtvCount,
dsvFormat = dsv,
};
for (var i = 0; i < rtvCount && i < 6; i++)
{
desc.RTVFormats[i] = rtvs[i].ToD3D12Format();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(pipelineDescriptor.colorMask & 0x0F);
hash.rtvFormats[i] = rtvs[i];
}
var key = hash.GetKey();
ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(_pipelineCache, hash.GetKey(), out var exists);
if (exists)
{
throw new InvalidOperationException($"Pass code cache already contains an entry for key: {key}");
}
existing.compileResult = compiled;
existing.psoDesc = desc;
}
public void CompilePass(IPassDescriptor descriptor)
{
switch (descriptor)
{
case FullPassDescriptor fullPass:
var result = CompileAndValidateFullPass(fullPass).GetValueOrThrow();
StorePassState(new(fullPass.Identifier), in result, in fullPass.localPipeline, [TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown);
break;
// Do we need to support other pass types?
default:
break;
}
}
public void CompileShader(ShaderDescriptor descriptor)
{
foreach (var pass in descriptor.passes)
{
CompilePass(pass);
}
}
// TODO: Pipeline variants (keywords)
@@ -247,33 +407,67 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
// TODO: Async compilation
public void PreCookPipelineState()
{
foreach (var kvp in _shaderPipelines)
foreach (var kvp in _pipelineCache)
{
ref var shader = ref _resourceDatabase.GetShaderReference(kvp.Key);
var key = kvp.Key;
var state = kvp.Value;
CreatePipelineStateObject(kvp.Value);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &state.psoDesc,
SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC)
};
kvp.Value.vsResult.Dispose();
kvp.Value.psResult.Dispose();
kvp.Value.csResult.Dispose();
ComPtr<ID3D12PipelineState> pipelineState = default;
ThrowIfFailed(_device.NativeDevice->CreatePipelineState(&streamDesc, __uuidof<ID3D12PipelineState>(), pipelineState.GetVoidAddressOf()));
var name = key.ToString();
fixed (char* pName = name)
{
ThrowIfFailed(_library.Get()->StorePipeline(pName, pipelineState.Get()));
}
}
}
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
public ID3D12PipelineState* LoadPipelineState(GraphicsPipelineKey key)
{
if (_shaderPipelines.TryGetValue(id, out var pipeline))
var name = key.ToString();
var state = _pipelineCache[key];
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
return pipeline;
}
pPipelineStateSubobjectStream = &state.psoDesc,
SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC)
};
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
fixed (char* pName = name)
{
ID3D12PipelineState* pipelineState;
ThrowIfFailed(_library.Get()->LoadPipeline(pName, &streamDesc, __uuidof<ID3D12PipelineState>(), (void**)&pipelineState));
return pipelineState;
}
}
public void SaveLibraryToDisk(string filePath)
{
var size = _library.Get()->GetSerializedSize();
using var buffer = new UnsafeArray<byte>((int)size, Allocator.Persistent); // We use persistent heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size));
var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
fs.Write(buffer.AsSpan());
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
_defaultRootSignature.Dispose();
foreach (var kvp in _pipelineCache)
{
kvp.Value.Dispose();
}
_library.Dispose();
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;
@@ -34,9 +35,9 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
{
InitializeDevice();
_graphicsQueue = new D3D12CommandQueue(_device, CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device, CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device, CommandQueueType.Copy);
_graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy);
}
~D3D12RenderDevice()
@@ -52,7 +53,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
CreateDXGIFactory2(FALSE, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#endif
using ComPtr<IDXGIAdapter1> adapter = default;
ComPtr<IDXGIAdapter1> adapter = default;
for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof<IDXGIAdapter1>(), adapter.ReleaseAndGetVoidAddressOf()).SUCCEEDED;
@@ -76,6 +77,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
if (_device.Get() == null)
{
adapter.Dispose(); // Dispose the last adapter we tried. If the operation succeeded, we would have moved it.
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
}
}

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections;
@@ -28,17 +29,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
private UnsafeQueue<Handle<GPUResource>> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL
{
get
{
fixed (Guid* pGuid = &Guid.Empty)
{
return pGuid;
}
}
}
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase)
{
var desc = new D3D12MA_ALLOCATOR_DESC
@@ -62,7 +52,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckBufferSize(uint sizeInBytes)
private static void CheckBufferSize(ulong sizeInBytes)
{
if (sizeInBytes > _MAX_BYTES)
{
@@ -409,7 +399,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var initialState = DetermineInitialTextureState(desc.Usage);
ComPtr<D3D12MA_Allocation> allocation = default;
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, allocation.GetAddressOf(), IID_NULL, null));
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null));
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
@@ -482,8 +472,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
ComPtr<D3D12MA_Allocation> allocation = default;
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), IID_NULL, null));
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null));
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
{
@@ -499,7 +489,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = desc.Size / 4;
srvDesc.Buffer.NumElements = (uint)(desc.Size / 4u);
srvDesc.Buffer.StructureByteStride = 0;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
}
@@ -507,7 +497,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = desc.Size / desc.Stride;
srvDesc.Buffer.NumElements = (uint)(desc.Size / desc.Stride);
srvDesc.Buffer.StructureByteStride = desc.Stride;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
}
@@ -519,13 +509,13 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return handle.AsGraphicsBuffer();
}
public Handle<GraphicsBuffer> CreateUploadBuffer(uint size, bool isTemp = true)
public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true)
{
var desc = new BufferDesc
{
Size = size,
Usage = BufferUsage.Upload,
MemoryType = MemoryType.Upload,
MemoryType = ResourceMemoryType.Upload,
};
return CreateBuffer(ref desc, isTemp);
@@ -538,7 +528,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Size = (uint)(vertices.Count * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
MemoryType = ResourceMemoryType.Default,
};
var indexBufferDesc = new BufferDesc
@@ -546,7 +536,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
MemoryType = ResourceMemoryType.Default,
};
var vertexBuffer = CreateBuffer(ref vertexBufferDesc);
@@ -572,11 +562,13 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader);
// TODO: Get per-material constant buffer size from database
var desc = new BufferDesc
{
Size = shaderRef.PerMaterialBufferInfo.Size,
Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default,
MemoryType = ResourceMemoryType.Default,
};
var buffer = CreateBuffer(ref desc);
@@ -641,13 +633,13 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return flags;
}
private static D3D12_HEAP_TYPE ConvertMemoryType(MemoryType memoryType)
private static D3D12_HEAP_TYPE ConvertMemoryType(ResourceMemoryType memoryType)
{
return memoryType switch
{
MemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
MemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
MemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
ResourceMemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
ResourceMemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
ResourceMemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
};
}
@@ -672,14 +664,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return D3D12_RESOURCE_STATE_COMMON;
}
private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, MemoryType memoryType)
private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, ResourceMemoryType memoryType)
{
if (memoryType == MemoryType.Upload)
if (memoryType == ResourceMemoryType.Upload)
{
return D3D12_RESOURCE_STATE_GENERIC_READ;
}
if (memoryType == MemoryType.Readback)
if (memoryType == ResourceMemoryType.Readback)
{
return D3D12_RESOURCE_STATE_COPY_DEST;
}

View File

@@ -1,8 +1,8 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
@@ -12,7 +12,7 @@ namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
internal unsafe struct ResourceInfo
internal unsafe struct ResourceRecord
{
[StructLayout(LayoutKind.Explicit)]
public struct ResourceUnion
@@ -36,14 +36,14 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public ResourceDesc desc;
public ResourceViewGroup descriptor;
public ResourceUnion resourceUnion;
public uint cpuFenceValue;
public ResourceState state;
public uint cpuFenceValue;
public readonly bool isExternal;
public readonly bool Allocated => isExternal ? resourceUnion.resource.Get() != null : resourceUnion.allocation.Get()->IsNotNull;
public readonly bool Allocated => isExternal ? resourceUnion.resource.Get() != null : resourceUnion.allocation.Get() != null;
public readonly ID3D12Resource* ResourcePtr => isExternal ? resourceUnion.resource.Get() : resourceUnion.allocation.Get()->GetResource();
public ResourceInfo(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
public ResourceRecord(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{
this.resourceUnion = new ResourceUnion(allocation);
this.isExternal = false;
@@ -54,7 +54,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
this.desc = desc;
}
public ResourceInfo(ComPtr<ID3D12Resource> resource, ResourceState state)
public ResourceRecord(ComPtr<ID3D12Resource> resource, ResourceState state)
{
this.resourceUnion = new ResourceUnion(resource);
this.isExternal = true;
@@ -65,24 +65,26 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
this.desc = ResourceDesc.FromD3D12(resource.Get()->GetDesc());
}
public uint Release()
public uint Release(D3D12DescriptorAllocator descriptorAllocator)
{
var refCount = 0u;
if (Allocated)
{
if (isExternal)
{
refCount = resourceUnion.resource.Get()->Release();
refCount = resourceUnion.resource.Reset();
}
else
{
refCount = resourceUnion.allocation.Get()->Release();
refCount = resourceUnion.allocation.Reset();
}
resourceUnion = default;
descriptor = default;
}
descriptorAllocator.Release(descriptor);
return refCount;
}
}
@@ -93,31 +95,33 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public bool occupied;
}
private UnsafeSlotMap<ResourceInfo> _resources;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private UnsafeSlotMap<ResourceRecord> _resources;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<ResourceInfo, string> _resourceName;
private readonly Dictionary<ResourceRecord, string> _resourceName;
#endif
private readonly UnsafeSlotMap<Mesh> _meshes;
private readonly UnsafeSlotMap<Material> _materials;
// NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component.
// NOTE: We use a simple list since shaderSlot is not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Shader>> _shaders;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly Dictionary<ShaderPassKey, ShaderPass> _shaderPasses;
private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
{
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_resources = new(64, Allocator.Persistent);
#if DEBUG || GHOST_EDITOR
_resourceName = new(64);
#endif
_meshes = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_materials = new(16, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_meshes = new(64, Allocator.Persistent);
_materials = new(16, Allocator.Persistent);
_shaders = new(16);
_shaderPasses = new(16);
_descriptorAllocator = descriptorAllocator;
}
@@ -127,6 +131,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
Dispose();
}
private void ReleaseResource<T>(ref T resource)
where T : IResourceReleasable
{
resource.ReleaseResource(this);
}
public Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState)
where T : unmanaged
{
@@ -137,7 +147,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
throw new InvalidOperationException($"Expect ComPtr<ID3D12Resource> in D3D12ResourceDatabase, but got {typeof(T)}.");
}
var id = _resources.Add(new ResourceInfo(d3d12Resource, initialState), out var generation);
var id = _resources.Add(new ResourceRecord(d3d12Resource, initialState), out var generation);
return new Handle<GPUResource>(id, generation);
}
@@ -145,11 +155,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
return new Handle<GPUResource>(id, generation);
}
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle)
public ref ResourceRecord GetResourceInfo(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -162,7 +172,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref info;
}
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle, out bool exist)
public ref ResourceRecord GetResourceInfo(Handle<GPUResource> handle, out bool exist)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _resources.GetElementReferenceAt(handle.id, handle.generation, out exist);
@@ -232,7 +242,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return;
}
var refCount = info.Release();
var refCount = info.Release(_descriptorAllocator);
#if DEBUG || GHOST_EDITOR
if (refCount > 0)
{
@@ -268,28 +278,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref mesh;
}
private void ReleaseMeshResources(ref readonly Mesh mesh)
{
mesh.ReleaseCpuResources();
ref var vertexRef = ref GetResourceInfo(mesh.vertexBuffer.AsResource());
ref var indexRef = ref GetResourceInfo(mesh.indexBuffer.AsResource());
_descriptorAllocator.Release(vertexRef.descriptor);
_descriptorAllocator.Release(indexRef.descriptor);
ReleaseResource(mesh.vertexBuffer.AsResource());
ReleaseResource(mesh.indexBuffer.AsResource());
}
public void ReleaseMesh(Handle<Mesh> handle)
{
ref var meshSlot = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
ref var mesh = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
return;
}
ReleaseMeshResources(ref meshSlot);
ReleaseResource(ref mesh);
_meshes.Remove(handle.id, handle.generation);
}
@@ -326,7 +323,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return;
}
material.Dispose();
ReleaseResource(ref material);
_materials.Remove(handle.id, handle.generation);
}
public Identifier<Shader> AddShader(ref readonly Shader shader)
@@ -355,17 +353,39 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref shader;
}
public void ReleaseShader(Identifier<Shader> handle)
public void ReleaseShader(Identifier<Shader> id)
{
if (!HasShader(handle))
if (!HasShader(id))
{
return;
}
ref var shader = ref _shaders[handle.value].value;
shader.Dispose();
ref var shaderSlot = ref _shaders[id.value];
ReleaseResource(ref shaderSlot.value);
shaderSlot.occupied = false;
}
public void AddShaderPass(ShaderPassKey passKey, ShaderPass pass)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_shaderPasses.Add(passKey, pass);
}
public ShaderPass GetShaderPass(ShaderPassKey passKey)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_shaderPasses.TryGetValue(passKey, out var pass))
{
throw new KeyNotFoundException($"Shader pass '{passKey}' not found.");
}
return pass;
}
// Should we need to release the shaderSlot pass?
public void Dispose()
{
if (_disposed)

View File

@@ -1,8 +1,10 @@
#undef SUPPORT_TEXTURE_BINDING
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
@@ -10,63 +12,192 @@ using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12;
internal unsafe static class D3D12ShaderCompiler
internal unsafe struct CompileResult : IDisposable
{
public enum CompilerVersion
public UnsafeArray<byte> bytecode;
public readonly bool IsCreated => bytecode.IsCreated;
public void Dispose()
{
SM_6_6,
SM_7_0
bytecode.Dispose();
}
}
internal readonly struct CBufferVariableInfo
{
public string Name
{
get; init;
}
public enum ShaderStage
public uint StartOffset
{
VertexShader,
PixelShader,
MeshShader,
ComputeShader
get; init;
}
public struct CompileResult : IDisposable
public uint Size
{
public UnsafeArray<byte> bytecode;
public ComPtr<IDxcBlob> reflection;
get; init;
}
}
public void Dispose()
{
bytecode.Dispose();
reflection.Dispose();
}
internal readonly struct CBufferInfo
{
public string Name
{
get; init;
}
private static string GetProfileString(ShaderStage stage, CompilerVersion version)
public uint RegisterSlot
{
get; init;
}
public uint RegisterSpace
{
get; init;
}
public uint SizeInBytes
{
get; init;
}
public IReadOnlyList<CBufferVariableInfo> Variables
{
get; init;
}
}
internal readonly struct ResourceBindingInfo
{
public string Name
{
get; init;
}
public D3D_SHADER_INPUT_TYPE Type
{
get; init;
}
public uint BindPoint
{
get; init;
}
public uint BindCount
{
get; init;
}
public uint Space
{
get; init;
}
}
internal readonly struct ShaderReflectionData
{
public List<CBufferInfo> ConstantBuffers
{
get;
}
public List<ResourceBindingInfo> OtherResources
{
get;
}
// public List<ResourceBindingInfo> Samplers { get; } = new();
// public List<ResourceBindingInfo> ShaderResourceViews { get; } = new();
// public List<ResourceBindingInfo> UnorderedAccessViews { get; } = new();
public ShaderReflectionData()
{
ConstantBuffers = new List<CBufferInfo>();
OtherResources = new List<ResourceBindingInfo>();
}
}
internal static unsafe class D3D12ShaderCompiler
{
private static string GetProfileString(ShaderStage stage, CompilerTier version)
{
return (stage, version) switch
{
(ShaderStage.VertexShader, CompilerVersion.SM_6_6) => "vs_6_6",
(ShaderStage.PixelShader, CompilerVersion.SM_6_6) => "ps_6_6",
(ShaderStage.MeshShader, CompilerVersion.SM_6_6) => "ms_6_6",
(ShaderStage.ComputeShader, CompilerVersion.SM_6_6) => "cs_6_6",
(ShaderStage.VertexShader, CompilerVersion.SM_7_0) => "vs_7_0",
(ShaderStage.PixelShader, CompilerVersion.SM_7_0) => "ps_7_0",
(ShaderStage.MeshShader, CompilerVersion.SM_7_0) => "ms_7_0",
(ShaderStage.ComputeShader, CompilerVersion.SM_7_0) => "cs_7_0",
(ShaderStage.TaskShader, CompilerTier.Tier0) => "as_6_6",
(ShaderStage.PixelShader, CompilerTier.Tier0) => "ps_6_6",
(ShaderStage.MeshShader, CompilerTier.Tier0) => "ms_6_6",
(ShaderStage.ComputeShader, CompilerTier.Tier0) => "cs_6_6",
(ShaderStage.TaskShader, CompilerTier.Tier1) => "as_6_7",
(ShaderStage.PixelShader, CompilerTier.Tier1) => "ps_6_7",
(ShaderStage.MeshShader, CompilerTier.Tier1) => "ms_6_7",
(ShaderStage.ComputeShader, CompilerTier.Tier1) => "cs_6_7",
(ShaderStage.TaskShader, CompilerTier.Tier2) => "as_6_8",
(ShaderStage.PixelShader, CompilerTier.Tier2) => "ps_6_8",
(ShaderStage.MeshShader, CompilerTier.Tier2) => "ms_6_8",
(ShaderStage.ComputeShader, CompilerTier.Tier2) => "cs_6_8",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
};
}
private static string GetEntryPoint(ShaderStage stage)
private static string GetOptimizeLevelString(CompilerOptimizeLevel level)
{
return stage switch
return level switch
{
ShaderStage.VertexShader => "VSMain",
ShaderStage.PixelShader => "PSMain",
ShaderStage.MeshShader => "MSMain",
ShaderStage.ComputeShader => "CSMain",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage")
CompilerOptimizeLevel.O0 => "-O0",
CompilerOptimizeLevel.O1 => "-O1",
CompilerOptimizeLevel.O2 => "-O2",
CompilerOptimizeLevel.O3 => "-O3",
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
};
}
public static CompileResult Compile(string shaderPath, ShaderStage stage, CompilerVersion version)
private static List<string> GetCompilerArguments(ref readonly CompilerConfig config)
{
var argsArray = new List<string>
{
"-T", GetProfileString(config.stage, config.tier), // Target profile (ms_6_6, ps_6_6)
"-E", config.entryPoint, // Entry point
"-HV", "2021", // HLSL version 2021
"-enable-16bit-types", // Enable 16-bit types
GetOptimizeLevelString(config.optimizeLevel), // Optimization level
};
foreach (var include in config.includes)
{
argsArray.Add("-I");
argsArray.Add(include);
}
foreach (var define in config.defines)
{
argsArray.Add("-D");
argsArray.Add(define);
}
if (!config.options.HasFlag(CompilerOption.KeepDebugInfo))
{
argsArray.Add("-Qstrip_debug");
}
if (!config.options.HasFlag(CompilerOption.KeepReflections))
{
argsArray.Add("-Qstrip_reflect");
}
if (config.options.HasFlag(CompilerOption.WarnAsError))
{
argsArray.Add("-WX");
}
return argsArray;
}
public static Result<CompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator, IDxcBlob** ppReflectionBlob)
{
using ComPtr<IDxcCompiler3> compiler = default;
using ComPtr<IDxcUtils> utils = default;
@@ -76,56 +207,38 @@ internal unsafe static class D3D12ShaderCompiler
var pDxcCompiler = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcCompiler);
var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils);
DxcCreateInstance(pDxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
ThrowIfFailed(DxcCreateInstance(pDxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf()));
ThrowIfFailed(DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()));
//includeHandler.Get()->LoadSource();
utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf());
// Create source blob
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
//var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shaderPath);
fixed (char* pShaderPath = shaderPath.AsSpan())
if (utils.Get()->LoadFile(config.shaderPath.AsSpan().GetUnsafePtr(), null, sourceBlob.GetAddressOf()).FAILED)
{
utils.Get()->LoadFile(pShaderPath, null, sourceBlob.GetAddressOf());
//utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
return Result<CompileResult>.Fail($"Failed to load shader file: {config.shaderPath}");
}
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
var argsArray = new string[]
var argsArray = GetCompilerArguments(in config);
var argPtrs = stackalloc char*[argsArray.Count];
for (var i = 0; i < argsArray.Count; i++)
{
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
"-E", GetEntryPoint(stage), // 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 but KEEP reflection
};
// 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];
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
}
try
{
// Compile shader
using ComPtr<IDxcResult> result = default;
fixed (nuint* argsPtr = wideArgs)
var buffer = new DxcBuffer
{
var buffer = new DxcBuffer
{
Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC.DXC_CP_UTF8
};
Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC.DXC_CP_UTF8
};
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf());
}
ThrowIfFailed(compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf()));
// Check compilation result
HRESULT hrStatus;
@@ -139,11 +252,11 @@ internal unsafe static class D3D12ShaderCompiler
if (errorBlob.Get() != null)
{
var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer());
throw new Exception($"DXC shader compilation failed: {errorMessage}");
return Result<CompileResult>.Fail($"DXC shader compilation failed:\n{errorMessage}");
}
else
{
throw new Exception("DXC shader compilation failed with unknown error");
return Result<CompileResult>.Fail("DXC shader compilation failed with unknown error.");
}
}
@@ -152,47 +265,46 @@ internal unsafe static class D3D12ShaderCompiler
ThrowIfFailed(result.Get()->GetResult(bytecodeBlob.GetAddressOf()));
// Get reflection data using DXC API
using ComPtr<IDxcBlob> reflectionBlob = default;
ThrowIfFailed(result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null));
if (ppReflectionBlob != null)
{
ThrowIfFailed(result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), (void**)ppReflectionBlob, null));
}
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
return new CompileResult
{
bytecode = bytecode,
reflection = reflectionBlob.Move()
};
}
finally
{
// Free allocated wide strings
for (var i = 0; i < argPointers.Length; i++)
for (var i = 0; i < argsArray.Count; i++)
{
Marshal.FreeHGlobal(argPointers[i]);
Marshal.FreeHGlobal((nint)argPtrs[i]);
}
}
}
private static void AddProperty(ref Shader shader, string name, PropertyInfo propertyInfo)
{
var id = shader.Properties.Count;
shader.Properties.Add(propertyInfo);
shader.PropertyNameToIdMap[name] = id;
}
// TODO: Since we are using fixed root signature layout, the reflection pass should only validate the layout, not generate it.
public static void PerformDXCReflection(ref Shader shader, IDxcBlob* reflectionBlob)
// TODO: Ideally this should return a structured reflection data instead of populating raw lists/dictionaries.
public static Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* reflectionBlob)
{
if (reflectionBlob == null)
{
return Result<ShaderReflectionData>.Fail("Reflection blob is null.");
}
// Create DXC utils to parse reflection data
var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils);
using ComPtr<IDxcUtils> utils = default;
DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
ThrowIfFailed(DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()));
// Create reflection interface from blob
var reflectionData = new DxcBuffer
var reflectionBuffer = new DxcBuffer
{
Ptr = reflectionBlob->GetBufferPointer(),
Size = reflectionBlob->GetBufferSize(),
@@ -200,94 +312,84 @@ internal unsafe static class D3D12ShaderCompiler
};
using ComPtr<ID3D12ShaderReflection> reflection = default;
ThrowIfFailed(utils.Get()->CreateReflection(&reflectionData, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf()));
ThrowIfFailed(utils.Get()->CreateReflection(&reflectionBuffer, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf()));
D3D12_SHADER_DESC shaderDesc;
reflection.Get()->GetDesc(&shaderDesc);
ThrowIfFailed(reflection.Get()->GetDesc(&shaderDesc));
var cbufferRegistry = new Dictionary<string, CBufferInfo>();
var textureRegistry = new Dictionary<string, TextureInfo>();
var reflectionData = new ShaderReflectionData();
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
D3D12_SHADER_INPUT_BIND_DESC bindDesc;
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
ThrowIfFailed(reflection.Get()->GetResourceBindingDesc(i, &bindDesc));
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
if (resourceName == null)
{
return Result<ShaderReflectionData>.Fail("Failed to get resource name from reflection data.");
}
switch (bindDesc.Type)
{
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
{
continue;
}
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var cbufferInfo = new CBufferInfo
{
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
};
cbufferRegistry.Add(cbufferName, cbufferInfo);
var variables = new List<CBufferVariableInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
continue;
}
var propInfo = new PropertyInfo
variables.Add(new CBufferVariableInfo
{
CBufferIndex = cbufferInfo.RegisterSlot,
ByteOffset = varDesc.StartOffset,
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
};
AddProperty(ref shader, variableName, propInfo);
});
}
reflectionData.ConstantBuffers.Add(new CBufferInfo
{
Name = resourceName,
RegisterSlot = bindDesc.BindPoint,
RegisterSpace = bindDesc.Space,
SizeInBytes = cbufferDesc.Size,
Variables = variables
});
break;
}
case D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE:
// NOTE: Currently we are not support resource bindings yet, everything access through bindless heaps.
default:
{
#if SUPPORT_TEXTURE_BINDING
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName))
reflectionData.OtherResources.Add(new ResourceBindingInfo
{
continue;
}
Name = resourceName,
Type = bindDesc.Type,
BindPoint = bindDesc.BindPoint,
BindCount = bindDesc.BindCount,
Space = bindDesc.Space
});
// ALL texture input slots are regular textures!
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
var textureInfo = new TextureInfo
{
RegisterSlot = bindDesc.BindPoint,
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
};
textureRegistry.Add(textureName, textureInfo);
break;
#endif
throw new NotSupportedException("Texture bindings are not supported in current version. Please use bindless textures.");
}
}
}
shader.PerMaterialBufferInfo.Clear();
foreach (var cbuf in cbufferRegistry.Values)
{
shader.PerMaterialBufferInfo.Add(cbuf);
}
return reflectionData;
}
}

View File

@@ -1,4 +1,5 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;

View File

@@ -63,6 +63,8 @@ internal static class D3D12_BLEND_DESC_Extensions
public static D3D12_BLEND_DESC OPAQUE => Create(D3D12_BLEND_ONE, D3D12_BLEND_ZERO);
public static D3D12_BLEND_DESC ALPHA_BLEND => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC ADDITIVE => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_ONE);
public static D3D12_BLEND_DESC MULTIPLY => Create(D3D12_BLEND_DEST_COLOR, D3D12_BLEND_ZERO);
public static D3D12_BLEND_DESC PREMULTIPLIED => Create(D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC NON_PREMULTIPLIED => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC Create(D3D12_BLEND srcBlend, D3D12_BLEND destBlend)
@@ -148,16 +150,4 @@ internal static class D3D12_DEPTH_STENCILOP_DESC_Extensions
};
}
}
}
internal unsafe static class D3D12MA_Allocation_Extensions
{
extension(ref readonly D3D12MA_Allocation allocation)
{
public bool IsNull => allocation.GetResource() == null
&& allocation.GetHeap() == null
&& allocation.GetSize() == 0;
public bool IsNotNull => !allocation.IsNull;
}
}

View File

@@ -1,38 +0,0 @@
using System.Runtime.CompilerServices;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe static class Win32Utility
{
public static void ThrowIfFailed(this HRESULT hr)
{
Windows.ThrowIfFailed(hr);
}
public static void** GetVoidAddressOf<T>(this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.GetAddressOf();
}
public static void** ReleaseAndGetVoidAddressOf<T>(this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.ReleaseAndGetAddressOf();
}
public static ComPtr<T> Move<T>(this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
ComPtr<T> copy = default;
Unsafe.AsRef(in comPtr).Swap(ref copy);
return copy;
}
public static bool HasFlag<T>(this uint flags, T flag)
where T : Enum
{
return (flags & Unsafe.As<T, uint>(ref flag)) != 0;
}
}