forked from Misaki/GhostEngine
Continue working on RHI
This commit is contained in:
245
Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs
Normal file
245
Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using Ghost.Graphics.Data;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D.Dxc;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe static class D3D12ShaderCompiler
|
||||
{
|
||||
public struct CompileResult : IDisposable
|
||||
{
|
||||
public UnsafeArray<byte> bytecode;
|
||||
public ComPtr<IDxcBlob> reflection;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
bytecode.Dispose();
|
||||
reflection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static CompileResult CompileDXC(Shader shader, 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(shader.Source);
|
||||
fixed (byte* sourceBytesPtr = sourceBytes)
|
||||
{
|
||||
utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
|
||||
}
|
||||
|
||||
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
|
||||
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 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];
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// Get reflection data using DXC API
|
||||
using ComPtr<IDxcBlob> reflectionBlob = default;
|
||||
result.Get()->GetOutput(DxcOutKind.Reflection, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null);
|
||||
|
||||
if (reflectionBlob.Get() == null)
|
||||
{
|
||||
throw new Exception("DXC compilation succeeded but no reflection data was produced");
|
||||
}
|
||||
|
||||
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
|
||||
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
|
||||
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++)
|
||||
{
|
||||
Marshal.FreeHGlobal(argPointers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PerformDXCReflection(Shader shader, IDxcBlob* reflectionBlob)
|
||||
{
|
||||
// Create DXC utils to parse reflection data
|
||||
using ComPtr<IDxcUtils> utils = default;
|
||||
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
|
||||
|
||||
// Create reflection interface from blob
|
||||
var reflectionData = new DxcBuffer
|
||||
{
|
||||
Ptr = reflectionBlob->GetBufferPointer(),
|
||||
Size = reflectionBlob->GetBufferSize(),
|
||||
Encoding = DXC_CP_ACP
|
||||
};
|
||||
|
||||
using ComPtr<ID3D12ShaderReflection> reflection = default;
|
||||
utils.Get()->CreateReflection(&reflectionData, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf());
|
||||
|
||||
if (reflection.Get() == null)
|
||||
{
|
||||
throw new Exception("Failed to create shader reflection from DXC output");
|
||||
}
|
||||
|
||||
ShaderDescription shaderDesc;
|
||||
reflection.Get()->GetDesc(&shaderDesc);
|
||||
|
||||
var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name);
|
||||
var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name);
|
||||
|
||||
for (uint i = 0; i < shaderDesc.BoundResources; i++)
|
||||
{
|
||||
ShaderInputBindDescription bindDesc;
|
||||
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
|
||||
|
||||
if (bindDesc.Type == ShaderInputType.ConstantBuffer)
|
||||
{
|
||||
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
|
||||
ShaderBufferDescription cbufferDesc;
|
||||
cbuffer->GetDesc(&cbufferDesc);
|
||||
|
||||
var cbufferInfo = new CBufferInfo
|
||||
{
|
||||
Name = cbufferName,
|
||||
Size = cbufferDesc.Size,
|
||||
RegisterSlot = bindDesc.BindPoint
|
||||
};
|
||||
cbufferRegistry.Add(cbufferName, cbufferInfo);
|
||||
|
||||
for (uint j = 0; j < cbufferDesc.Variables; j++)
|
||||
{
|
||||
var variable = cbuffer->GetVariableByIndex(j);
|
||||
ShaderVariableDescription varDesc;
|
||||
variable->GetDesc(&varDesc);
|
||||
|
||||
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
|
||||
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var propInfo = new PropertyInfo
|
||||
{
|
||||
Name = variableName,
|
||||
CBufferIndex = cbufferInfo.RegisterSlot,
|
||||
ByteOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
};
|
||||
|
||||
// Add to the list and create the name-to-ID mapping
|
||||
var newId = shader.Properties.Count;
|
||||
shader.Properties.Add(propInfo);
|
||||
shader.PropertyNameToIdMap.Add(variableName, newId);
|
||||
}
|
||||
}
|
||||
else if (bindDesc.Type == ShaderInputType.Texture)
|
||||
{
|
||||
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
|
||||
if (textureName == null || textureRegistry.ContainsKey(textureName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ALL texture input slots are regular textures!
|
||||
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
|
||||
var textureInfo = new TextureInfo
|
||||
{
|
||||
Name = textureName,
|
||||
RegisterSlot = bindDesc.BindPoint,
|
||||
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
|
||||
};
|
||||
|
||||
textureRegistry.Add(textureName, textureInfo);
|
||||
}
|
||||
}
|
||||
|
||||
shader.ConstantBuffers.Clear();
|
||||
shader.ConstantBuffers.AddRange(cbufferRegistry.Values);
|
||||
|
||||
shader.RegularTextures.Clear();
|
||||
shader.RegularTextures.AddRange(textureRegistry.Values);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user