feat(shader): refactor and enhance shader compilation
Refactored shader compilation and resource management systems: - Introduced `DXCShaderCompiler` for HLSL compilation and reflection. - Added `BuildFinalShaderCode` method for robust shader code generation. - Replaced raw strings with `ShaderEntryPoint` struct for shader paths. - Updated `RenderContext` and `RenderGraphContext` for new pipeline methods. - Added thread-safe resource management methods in `ResourceManager`. - Introduced `DXCShaderReflectionData` for shader reflection handling. - Removed redundant code and simplified `ShaderPropertiesRegistry`. BREAKING CHANGE: Updated shader and resource APIs to use new structures and methods.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core.Graphics;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
@@ -7,25 +8,6 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ghost.DSL.Generator;
|
||||
|
||||
public enum PackingRules
|
||||
{
|
||||
Exact,
|
||||
Aligned,
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum)]
|
||||
public class GenerateHLSLAttribute : Attribute
|
||||
{
|
||||
private readonly PackingRules _packingRules;
|
||||
private readonly string? _outputSource;
|
||||
|
||||
public GenerateHLSLAttribute(PackingRules packingRules, string? outputSource)
|
||||
{
|
||||
_packingRules = packingRules;
|
||||
_outputSource = outputSource;
|
||||
}
|
||||
}
|
||||
|
||||
internal static partial class ShaderStructGenerator
|
||||
{
|
||||
private struct ShaderFieldInfo
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.DSL.ShaderParser;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -43,51 +44,121 @@ internal static class DSLShaderCompiler
|
||||
};
|
||||
}
|
||||
|
||||
private static Result<string> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode, string? properties)
|
||||
{
|
||||
string shaderCode;
|
||||
if (shaderPath == "hlsl_block")
|
||||
{
|
||||
if (string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
return Result.Failure("Shader code is empty. Either provide a valid shader path or inject shader code directly.");
|
||||
}
|
||||
|
||||
shaderCode = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(shaderPath))
|
||||
{
|
||||
return Result.Failure("Shader file not found: " + shaderPath);
|
||||
}
|
||||
|
||||
shaderCode = File.ReadAllText(shaderPath);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var includePath in includes)
|
||||
{
|
||||
sb.AppendLine($"#include \"{includePath}\"");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(properties))
|
||||
{
|
||||
sb.AppendLine($"#line 0 \"properties\"");
|
||||
sb.AppendLine(properties);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
sb.AppendLine($"#line 0 \"injected_code\"");
|
||||
sb.AppendLine(injectedCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shaderCode))
|
||||
{
|
||||
sb.AppendLine($"#line 0 \"{shaderPath}\"");
|
||||
sb.AppendLine(shaderCode);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// TODO: Implement shader inheritance resolution, including property and pass merging.
|
||||
// Currently, we just ignore inheritance.
|
||||
public static Result<GraphicsShaderDescriptor> ResolveShader(DSLShaderSemantics semantics)
|
||||
{
|
||||
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
|
||||
{
|
||||
propertyInfo = default;
|
||||
}
|
||||
|
||||
var passes = semantics.passes == null ? Array.Empty<PassDescriptor>() : new PassDescriptor[semantics.passes.Count];
|
||||
for (var i = 0; i < passes.Length; i++)
|
||||
{
|
||||
var pass = semantics.passes![i];
|
||||
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
||||
|
||||
var result = BuildFinalShaderCode(pass.amplificationShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
||||
}
|
||||
|
||||
var amplificationShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.amplificationShader.entry };
|
||||
|
||||
result = BuildFinalShaderCode(pass.meshShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
||||
}
|
||||
|
||||
var meshShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.meshShader.entry };
|
||||
|
||||
result = BuildFinalShaderCode(pass.pixelShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
||||
}
|
||||
|
||||
var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };
|
||||
|
||||
passes[i] = new PassDescriptor
|
||||
{
|
||||
identifier = GetPassUniqueId(semantics, pass),
|
||||
name = pass.name,
|
||||
|
||||
amplificationShaderCode = amplificationShaderCode,
|
||||
meshShaderCode = meshShaderCode,
|
||||
pixelShaderCode = pixelShaderCode,
|
||||
|
||||
localPipeline = localPipeline,
|
||||
defines = pass.defines?.ToArray() ?? Array.Empty<string>(),
|
||||
keywords = pass.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
|
||||
};
|
||||
}
|
||||
|
||||
var descriptor = new GraphicsShaderDescriptor
|
||||
{
|
||||
name = semantics.name,
|
||||
propertyBufferSize = propertyInfo.size,
|
||||
|
||||
shaderModel = semantics.shaderModel,
|
||||
passes = passes
|
||||
};
|
||||
|
||||
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var info))
|
||||
for (int i = 0; i < descriptor.passes.Length; i++)
|
||||
{
|
||||
info = default;
|
||||
}
|
||||
|
||||
descriptor.propertiesCode = info.code ?? string.Empty;
|
||||
descriptor.propertyBufferSize = info.size;
|
||||
|
||||
descriptor.shaderModel = semantics.shaderModel;
|
||||
|
||||
if (semantics.passes != null)
|
||||
{
|
||||
descriptor.passes = new PassDescriptor[semantics.passes.Count];
|
||||
for (var i = 0; i < semantics.passes.Count; i++)
|
||||
{
|
||||
var pass = semantics.passes[i];
|
||||
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
||||
descriptor.passes[i] = new PassDescriptor
|
||||
{
|
||||
shader = descriptor,
|
||||
identifier = GetPassUniqueId(semantics, pass),
|
||||
name = pass.name,
|
||||
taskShader = pass.taskShader,
|
||||
meshShader = pass.meshShader,
|
||||
pixelShader = pass.pixelShader,
|
||||
localPipeline = localPipeline,
|
||||
defines = pass.defines?.ToArray() ?? Array.Empty<string>(),
|
||||
includes = pass.includes?.ToArray() ?? Array.Empty<string>(),
|
||||
keywords = pass.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>(),
|
||||
hlsl = pass.hlsl
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor.passes = Array.Empty<PassDescriptor>();
|
||||
descriptor.passes[i].shader = descriptor;
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
@@ -199,28 +270,32 @@ internal static class DSLShaderCompiler
|
||||
|
||||
public static Result<ComputeShaderDescriptor> ResolveComputeShader(DSLComputeShaderSemantics semantics)
|
||||
{
|
||||
var descriptor = new ComputeShaderDescriptor
|
||||
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
|
||||
{
|
||||
propertyInfo = default;
|
||||
}
|
||||
|
||||
var shaderCodes = new ShaderCode[semantics.entryPoints.Count];
|
||||
for (int i = 0; i < shaderCodes.Length; i++)
|
||||
{
|
||||
var result = BuildFinalShaderCode(semantics.entryPoints[i].shaderPath, semantics.includes.AsSpan(), semantics.hlsl, propertyInfo.code);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure($"Failed to build shader code for entry point '{semantics.entryPoints[i].entry}': {result.Message}");
|
||||
}
|
||||
|
||||
shaderCodes[i] = new ShaderCode { code = result.Value, entryPoint = semantics.entryPoints[i].entry };
|
||||
}
|
||||
|
||||
return new ComputeShaderDescriptor
|
||||
{
|
||||
identifier = XxHash64.HashToUInt64(MemoryMarshal.AsBytes(semantics.name.AsSpan())),
|
||||
name = semantics.name,
|
||||
propertyBufferSize = propertyInfo.size,
|
||||
shaderModel = semantics.shaderModel,
|
||||
shaderCodes = shaderCodes,
|
||||
defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
|
||||
keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
|
||||
};
|
||||
|
||||
if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var info))
|
||||
{
|
||||
info = default;
|
||||
}
|
||||
|
||||
descriptor.propertiesCode = info.code ?? string.Empty;
|
||||
descriptor.propertyBufferSize = info.size;
|
||||
|
||||
descriptor.shaderModel = semantics.shaderModel;
|
||||
|
||||
descriptor.hlsl = semantics.hlsl;
|
||||
descriptor.defines = semantics.defines?.ToArray() ?? Array.Empty<string>();
|
||||
descriptor.includes = semantics.includes?.ToArray() ?? Array.Empty<string>();
|
||||
descriptor.keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>();
|
||||
descriptor.entryPoints = semantics.entryPoints?.ToArray() ?? Array.Empty<ShaderEntryPoint>();
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ public enum PropertyScope
|
||||
Local,
|
||||
}
|
||||
|
||||
public struct ShaderEntryPoint
|
||||
{
|
||||
public string entry;
|
||||
public string shaderPath;
|
||||
|
||||
public readonly bool IsCreated => !string.IsNullOrEmpty(entry) && !string.IsNullOrEmpty(shaderPath);
|
||||
}
|
||||
|
||||
public class PipelineSemantic
|
||||
{
|
||||
public ZTest? zTest;
|
||||
@@ -20,7 +28,7 @@ public class PipelineSemantic
|
||||
public class PassSemantic
|
||||
{
|
||||
public string name = string.Empty;
|
||||
public ShaderEntryPoint taskShader;
|
||||
public ShaderEntryPoint amplificationShader;
|
||||
public ShaderEntryPoint meshShader;
|
||||
public ShaderEntryPoint pixelShader;
|
||||
public string? hlsl;
|
||||
@@ -46,5 +54,5 @@ public class DSLComputeShaderSemantics
|
||||
public List<string>? defines;
|
||||
public List<string>? includes;
|
||||
public List<KeywordsGroup>? keywords;
|
||||
public List<ShaderEntryPoint>? entryPoints;
|
||||
public List<ShaderEntryPoint> entryPoints = null!;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public class AntlrShaderCompiler
|
||||
semantics.entryPoints ??= new List<ShaderEntryPoint>();
|
||||
semantics.entryPoints.Add(new ShaderEntryPoint
|
||||
{
|
||||
shader = entry.ShaderPath,
|
||||
shaderPath = entry.ShaderPath,
|
||||
entry = entry.EntryPoint
|
||||
});
|
||||
}
|
||||
@@ -355,7 +355,7 @@ public class AntlrShaderCompiler
|
||||
var entryType = entry.EntryType.ToLower();
|
||||
var shaderEntry = new ShaderEntryPoint
|
||||
{
|
||||
shader = entry.ShaderPath,
|
||||
shaderPath = entry.ShaderPath,
|
||||
entry = entry.EntryPoint
|
||||
};
|
||||
|
||||
@@ -368,7 +368,7 @@ public class AntlrShaderCompiler
|
||||
semantic.pixelShader = shaderEntry;
|
||||
break;
|
||||
case "as":
|
||||
semantic.taskShader = shaderEntry;
|
||||
semantic.amplificationShader = shaderEntry;
|
||||
break;
|
||||
default:
|
||||
errors.Add(new DSLShaderError
|
||||
@@ -381,7 +381,7 @@ public class AntlrShaderCompiler
|
||||
}
|
||||
}
|
||||
|
||||
if (semantic.meshShader.shader == null || semantic.pixelShader.shader == null)
|
||||
if (semantic.meshShader.shaderPath == null || semantic.pixelShader.shaderPath == null)
|
||||
{
|
||||
errors.Add(new DSLShaderError
|
||||
{
|
||||
|
||||
23
src/Editor/Ghost.DSL/ShaderPropertyRegistry.cs
Normal file
23
src/Editor/Ghost.DSL/ShaderPropertyRegistry.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Ghost.DSL;
|
||||
|
||||
public struct ShaderPropertyInfo
|
||||
{
|
||||
public string shaderName;
|
||||
public string code;
|
||||
public uint size;
|
||||
}
|
||||
|
||||
public static class ShaderPropertiesRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, ShaderPropertyInfo> s_nameToCode = new Dictionary<string, ShaderPropertyInfo>(StringComparer.Ordinal);
|
||||
|
||||
public static void Register(string name, string code, uint size)
|
||||
{
|
||||
s_nameToCode[name] = new ShaderPropertyInfo { shaderName = name, code = code, size = size };
|
||||
}
|
||||
|
||||
public static bool TryGetInfo(string name, out ShaderPropertyInfo info)
|
||||
{
|
||||
return s_nameToCode.TryGetValue(name, out info);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Runtime\Ghost.Core\Ghost.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Runtime\Ghost.Engine\Ghost.Engine.csproj" />
|
||||
<ProjectReference Include="..\..\ThridParty\Ghost.DXC\Ghost.DXC.csproj" />
|
||||
<ProjectReference Include="..\..\ThridParty\Ghost.Nvtt\Ghost.Nvtt.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
453
src/Editor/Ghost.Editor.Core/Services/DXCShaderCompiler.cs
Normal file
453
src/Editor/Ghost.Editor.Core/Services/DXCShaderCompiler.cs
Normal file
@@ -0,0 +1,453 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Ghost.DXC;
|
||||
|
||||
using static Ghost.DXC.UUID;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal sealed partial class DXCShaderCompiler
|
||||
{
|
||||
private static string GetProfileString(ShaderStage stage, ShaderModel version)
|
||||
{
|
||||
return (stage, version) switch
|
||||
{
|
||||
(ShaderStage.TaskShader, ShaderModel.SM_6_6) => "as_6_6",
|
||||
(ShaderStage.PixelShader, ShaderModel.SM_6_6) => "ps_6_6",
|
||||
(ShaderStage.MeshShader, ShaderModel.SM_6_6) => "ms_6_6",
|
||||
(ShaderStage.ComputeShader, ShaderModel.SM_6_6) => "cs_6_6",
|
||||
(ShaderStage.TaskShader, ShaderModel.SM_6_7) => "as_6_7",
|
||||
(ShaderStage.PixelShader, ShaderModel.SM_6_7) => "ps_6_7",
|
||||
(ShaderStage.MeshShader, ShaderModel.SM_6_7) => "ms_6_7",
|
||||
(ShaderStage.ComputeShader, ShaderModel.SM_6_7) => "cs_6_7",
|
||||
(ShaderStage.TaskShader, ShaderModel.SM_6_8) => "as_6_8",
|
||||
(ShaderStage.PixelShader, ShaderModel.SM_6_8) => "ps_6_8",
|
||||
(ShaderStage.MeshShader, ShaderModel.SM_6_8) => "ms_6_8",
|
||||
(ShaderStage.ComputeShader, ShaderModel.SM_6_8) => "cs_6_8",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetOptimizeLevelString(CompilerOptimizeLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
CompilerOptimizeLevel.O0 => "-O0",
|
||||
CompilerOptimizeLevel.O1 => "-O1",
|
||||
CompilerOptimizeLevel.O2 => "-O2",
|
||||
CompilerOptimizeLevel.O3 => "-O3",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<string> GetCompilerArguments(ref readonly ShaderCompilationConfig config)
|
||||
{
|
||||
var argsArray = new List<string>
|
||||
{
|
||||
"-T", GetProfileString(config.stage, config.model), // 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 define in config.defines)
|
||||
{
|
||||
argsArray.Add("-D");
|
||||
argsArray.Add(define);
|
||||
}
|
||||
|
||||
if (config.stage == ShaderStage.TaskShader
|
||||
|| config.stage == ShaderStage.MeshShader
|
||||
|| config.stage == ShaderStage.PixelShader)
|
||||
{
|
||||
argsArray.Add("-D");
|
||||
argsArray.Add("__GRAPHICS__");
|
||||
}
|
||||
else if (config.stage == ShaderStage.ComputeShader)
|
||||
{
|
||||
argsArray.Add("-D");
|
||||
argsArray.Add("__COMPUTE__");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if (config.options.HasFlag(CompilerOption.SpirvCrossCompile))
|
||||
{
|
||||
argsArray.Add("-spirv");
|
||||
}
|
||||
|
||||
argsArray.Add("-rootsig-define");
|
||||
argsArray.Add("GLOBAL_BINDLESS_SIG");
|
||||
|
||||
return argsArray;
|
||||
}
|
||||
|
||||
private static Result<string, Error> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
|
||||
{
|
||||
string shaderCode;
|
||||
if (shaderPath == "hlsl_block")
|
||||
{
|
||||
if (string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
shaderCode = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(shaderPath))
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
shaderCode = File.ReadAllText(shaderPath);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var includePath in includes)
|
||||
{
|
||||
sb.AppendLine($"#include \"{includePath}\"");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
sb.AppendLine($"#line 0 \"injected_code\"");
|
||||
sb.AppendLine(injectedCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shaderCode))
|
||||
{
|
||||
sb.AppendLine($"#line 0 \"{shaderPath}\"");
|
||||
sb.AppendLine(shaderCode);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
{
|
||||
private UniquePtr<IDxcCompiler3> _compiler;
|
||||
private UniquePtr<IDxcUtils> _utils;
|
||||
|
||||
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
|
||||
private readonly Dictionary<Key64<ShaderCompileResult>, ShaderCompileResult> _compiledResults;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public DXCShaderCompiler()
|
||||
{
|
||||
IDxcCompiler3* pCompiler = default;
|
||||
IDxcUtils* pUtils = default;
|
||||
var hr = Api.DxcCreateInstance((Guid*)Unsafe.AsPointer(in Api.CLSID_DxcCompiler), __uuidof(pCompiler), (void**)&pCompiler);
|
||||
if (hr < 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to create DXC compiler instance. HRESULT: 0x{hr:X8}");
|
||||
}
|
||||
|
||||
hr = Api.DxcCreateInstance((Guid*)Unsafe.AsPointer(in Api.CLSID_DxcUtils), __uuidof(pUtils), (void**)&pUtils);
|
||||
if (hr < 0)
|
||||
{
|
||||
pCompiler->Release();
|
||||
throw new InvalidOperationException($"Failed to create DXC utils instance. HRESULT: 0x{hr:X8}");
|
||||
}
|
||||
|
||||
_compiler.Attach(pCompiler);
|
||||
_utils.Attach(pUtils);
|
||||
|
||||
_compiledResults = new Dictionary<Key64<ShaderCompileResult>, ShaderCompileResult>();
|
||||
}
|
||||
|
||||
~DXCShaderCompiler()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public Result<Key64<ShaderCompileResult>> Compile(ref readonly ShaderCompilationConfig config)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
IDxcIncludeHandler* includeHandler = default;
|
||||
IDxcBlobEncoding* sourceBlob = default;
|
||||
try
|
||||
{
|
||||
var hr = _utils.Get()->CreateDefaultIncludeHandler(&includeHandler);
|
||||
if (hr < 0)
|
||||
{
|
||||
return Result.Failure($"Failed to create default include handler. HRESULT: 0x{hr:X8}");
|
||||
}
|
||||
|
||||
fixed (byte* pCode = Encoding.UTF8.GetBytes(config.shaderCode))
|
||||
{
|
||||
var sizeInBytes = Encoding.UTF8.GetByteCount(config.shaderCode);
|
||||
hr = _utils.Get()->CreateBlobFromPinned(pCode, (uint)sizeInBytes, Api.DXC_CP_UTF8, &sourceBlob);
|
||||
if (hr < 0)
|
||||
{
|
||||
return Result.Failure($"Failed to create blob from shader code. HRESULT: 0x{hr:X8}");
|
||||
}
|
||||
}
|
||||
|
||||
var argsArray = GetCompilerArguments(in config);
|
||||
var argPtrs = stackalloc char*[argsArray.Count];
|
||||
for (var i = 0; i < argsArray.Count; i++)
|
||||
{
|
||||
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
|
||||
}
|
||||
|
||||
IDxcResult* result = default;
|
||||
IDxcBlob* bytecodeBlob = default;
|
||||
|
||||
try
|
||||
{
|
||||
// Compile shader
|
||||
var buffer = new DxcBuffer
|
||||
{
|
||||
Ptr = sourceBlob->GetBufferPointer(),
|
||||
Size = sourceBlob->GetBufferSize(),
|
||||
Encoding = Api.DXC_CP_UTF8
|
||||
};
|
||||
|
||||
hr = _compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, __uuidof(result), (void**)&result);
|
||||
if (hr < 0)
|
||||
{
|
||||
return Result.Failure($"Failed to compile shader. HRESULT: 0x{hr:X8}");
|
||||
}
|
||||
|
||||
// Check compilation result
|
||||
int hrStatus;
|
||||
result->GetStatus(&hrStatus);
|
||||
if (hrStatus < 0)
|
||||
{
|
||||
// Get error messages
|
||||
IDxcBlobEncoding* pErrorBlob = default;
|
||||
result->GetErrorBuffer(&pErrorBlob);
|
||||
|
||||
if (pErrorBlob != null)
|
||||
{
|
||||
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)pErrorBlob->GetBufferPointer());
|
||||
pErrorBlob->Release();
|
||||
|
||||
return Result.Failure($"DXC shader compilation failed:\n{errorMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("DXC shader compilation failed with unknown error.");
|
||||
}
|
||||
}
|
||||
|
||||
// Get compiled bytecode
|
||||
hr = result->GetResult(&bytecodeBlob);
|
||||
if (hr < 0)
|
||||
{
|
||||
return Result.Failure($"Failed to get compiled shader bytecode. HRESULT: 0x{hr:X8}");
|
||||
}
|
||||
|
||||
var bytecodeSize = bytecodeBlob->GetBufferSize();
|
||||
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, Allocator.Persistent);
|
||||
|
||||
NativeMemory.Copy(bytecodeBlob->GetBufferPointer(), bytecode.GetUnsafePtr(), (nuint)bytecodeSize);
|
||||
|
||||
var compileResult = new ShaderCompileResult
|
||||
{
|
||||
bytecode = bytecode,
|
||||
hashCode = XxHash64.HashToUInt64(bytecode)
|
||||
};
|
||||
|
||||
_compiledResults[compileResult.hashCode] = compileResult;
|
||||
return new Key64<ShaderCompileResult>(compileResult.hashCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (result != null)
|
||||
{
|
||||
result->Release();
|
||||
}
|
||||
|
||||
if (bytecodeBlob != null)
|
||||
{
|
||||
bytecodeBlob->Release();
|
||||
}
|
||||
|
||||
for (var i = 0; i < argsArray.Count; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal((nint)argPtrs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (includeHandler != null)
|
||||
{
|
||||
includeHandler->Release();
|
||||
}
|
||||
|
||||
if (sourceBlob != null)
|
||||
{
|
||||
sourceBlob->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
string[] fullDefines;
|
||||
var totalDefineCount = descriptor.defines.Length + additionalConfig.defines.Length;
|
||||
if (totalDefineCount == 0)
|
||||
{
|
||||
fullDefines = Array.Empty<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
fullDefines = new string[totalDefineCount];
|
||||
descriptor.defines.CopyTo(fullDefines);
|
||||
additionalConfig.defines.CopyTo(fullDefines.AsSpan(descriptor.defines.Length));
|
||||
}
|
||||
|
||||
Key64<ShaderCompileResult> tsResult = default;
|
||||
var asCode = descriptor.amplificationShaderCode;
|
||||
if (asCode.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines,
|
||||
shaderCode = asCode.code,
|
||||
entryPoint = asCode.entryPoint,
|
||||
stage = ShaderStage.TaskShader,
|
||||
model = additionalConfig.model,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
tsResult = result.Value;
|
||||
}
|
||||
|
||||
Key64<ShaderCompileResult> msResult;
|
||||
var msCode = descriptor.meshShaderCode;
|
||||
if (msCode.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines,
|
||||
shaderCode = msCode.code,
|
||||
entryPoint = msCode.entryPoint,
|
||||
stage = ShaderStage.MeshShader,
|
||||
model = additionalConfig.model,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
msResult = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("Mesh shader expected.");
|
||||
}
|
||||
|
||||
Key64<ShaderCompileResult> psResult;
|
||||
var psCode = descriptor.pixelShaderCode;
|
||||
if (psCode.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines,
|
||||
shaderCode = psCode.code,
|
||||
entryPoint = psCode.entryPoint,
|
||||
stage = ShaderStage.PixelShader,
|
||||
model = additionalConfig.model,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
psResult = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("Pixel shader expected.");
|
||||
}
|
||||
|
||||
var compiled = new GraphicsCompiledResult
|
||||
{
|
||||
tsResultHash = tsResult,
|
||||
msResultHash = msResult,
|
||||
psResultHash = psResult,
|
||||
};
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
public Result<ShaderCompileResult, Error> GetCompiledCache(Key64<ShaderCompileResult> key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_compiledResults.TryGetValue(key, out var compiledResult))
|
||||
{
|
||||
return compiledResult;
|
||||
}
|
||||
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kvp in _compiledResults)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_compiler.Get()->Release();
|
||||
_utils.Get()->Release();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user