Add high-performance material/shader system (Ghost.Shader.Concept)
Introduces a new Ghost.Shader.Concept project implementing a modern, data-oriented material and shader system with: - Global/local keyword bitsets (fast O(1) ops, 64 bytes) - Multi-pass shader program and per-pass render state overrides - Thread-safe, 16-byte aligned material property blocks - Material pooling to reduce GC pressure - Batch renderer for efficient PSO grouping and async variant warmup - Full demo (Program.cs) and extensive documentation (ARCHITECTURE.md, README.md, PROJECT_SUMMARY.md) - Minor integration: new enums, doc updates, and keyword handling in existing code No breaking changes to the existing engine; all new code is isolated. This serves as a reference implementation for high-performance, extensible material/shader architectures.
This commit is contained in:
480
Ghost.Graphics/Core/DxcShaderCompiler.cs
Normal file
480
Ghost.Graphics/Core/DxcShaderCompiler.cs
Normal file
@@ -0,0 +1,480 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.DirectX.DXC;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal sealed partial class DxcShaderCompiler
|
||||
{
|
||||
private static string GetProfileString(ShaderStage stage, CompilerTier version)
|
||||
{
|
||||
return (stage, version) switch
|
||||
{
|
||||
(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 GetOptimizeLevelString(CompilerOptimizeLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
CompilerOptimizeLevel.O0 => DXC_ARG_OPTIMIZATION_LEVEL0,
|
||||
CompilerOptimizeLevel.O1 => DXC_ARG_OPTIMIZATION_LEVEL1,
|
||||
CompilerOptimizeLevel.O2 => DXC_ARG_OPTIMIZATION_LEVEL2,
|
||||
CompilerOptimizeLevel.O3 => DXC_ARG_OPTIMIZATION_LEVEL3,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
|
||||
};
|
||||
}
|
||||
|
||||
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 define in config.defines)
|
||||
{
|
||||
argsArray.Add("-D");
|
||||
argsArray.Add(define);
|
||||
}
|
||||
|
||||
// HACK: Currently DXC does not support force include, we have to use GENERATED_CODE_PATH define as a workaround.
|
||||
// User must to write '#include GENERATED_CODE_PATH' in their shader code manually.
|
||||
if (File.Exists(config.include))
|
||||
{
|
||||
argsArray.Add("-D");
|
||||
argsArray.Add($"GENERATED_CODE_PATH={'"' + config.include.Replace("\\", "/") + '"'}");
|
||||
}
|
||||
|
||||
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(DXC_ARG_WARNINGS_ARE_ERRORS);
|
||||
}
|
||||
|
||||
if (config.options.HasFlag(CompilerOption.SpirvCrossCompile))
|
||||
{
|
||||
argsArray.Add("-spirv");
|
||||
}
|
||||
|
||||
return argsArray;
|
||||
}
|
||||
|
||||
private static ShaderInputType ToInputType(D3D_SHADER_INPUT_TYPE type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER => ShaderInputType.ConstantBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_TBUFFER => ShaderInputType.Texture,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE => ShaderInputType.Texture,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_SAMPLER => ShaderInputType.Sampler,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWTYPED => ShaderInputType.UAV,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_STRUCTURED => ShaderInputType.StructuredBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_BYTEADDRESS => ShaderInputType.ByteAddressBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWSTRUCTURED => ShaderInputType.RWStructuredBuffer,
|
||||
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWBYTEADDRESS => ShaderInputType.RWByteAddressBuffer,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), "Unsupported shader input type")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// TODO: This should be shader variant specific cache instead of pass specific.
|
||||
private readonly Dictionary<ShaderPassKey, GraphicsCompiledResult> _compiledResults;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public DxcShaderCompiler()
|
||||
{
|
||||
// Initialize DXC _compiler.Get() and _utils.Get()
|
||||
var dxccID = CLSID.CLSID_DxcCompiler;
|
||||
var dxcuID = CLSID.CLSID_DxcUtils;
|
||||
|
||||
IDxcCompiler3* pCompiler = default;
|
||||
IDxcUtils* pUtils = default;
|
||||
ThrowIfFailed(DxcCreateInstance(&dxccID, __uuidof(pCompiler), (void**)&pCompiler));
|
||||
ThrowIfFailed(DxcCreateInstance(&dxcuID, __uuidof(pUtils), (void**)&pUtils));
|
||||
|
||||
_compiler.Attach(pCompiler);
|
||||
_utils.Attach(pUtils);
|
||||
|
||||
_compiledResults = new Dictionary<ShaderPassKey, GraphicsCompiledResult>();
|
||||
}
|
||||
|
||||
~DxcShaderCompiler()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* pReflectionBlob)
|
||||
{
|
||||
ID3D12ShaderReflection* pReflection = default;
|
||||
|
||||
try
|
||||
{
|
||||
// Create DXC _utils.Get() to parse reflection data
|
||||
var dxcuID = CLSID.CLSID_DxcUtils;
|
||||
|
||||
// Create reflection interface from blob
|
||||
var reflectionBuffer = new DxcBuffer
|
||||
{
|
||||
Ptr = pReflectionBlob->GetBufferPointer(),
|
||||
Size = pReflectionBlob->GetBufferSize(),
|
||||
Encoding = DXC_CP_ACP
|
||||
};
|
||||
|
||||
ThrowIfFailed(_utils.Get()->CreateReflection(&reflectionBuffer, __uuidof(pReflection), (void**)&pReflection));
|
||||
|
||||
D3D12_SHADER_DESC shaderDesc;
|
||||
ThrowIfFailed(pReflection->GetDesc(&shaderDesc));
|
||||
|
||||
var reflectionData = new ShaderReflectionData();
|
||||
|
||||
for (uint i = 0; i < shaderDesc.BoundResources; i++)
|
||||
{
|
||||
D3D12_SHADER_INPUT_BIND_DESC bindDesc;
|
||||
ThrowIfFailed(pReflection->GetResourceBindingDesc(i, &bindDesc));
|
||||
|
||||
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
|
||||
if (resourceName == null)
|
||||
{
|
||||
return Result.Failure("Failed to get resource name from reflection data.");
|
||||
}
|
||||
|
||||
var info = new ResourceBindingInfo
|
||||
{
|
||||
Name = resourceName,
|
||||
Type = ToInputType(bindDesc.Type),
|
||||
BindPoint = bindDesc.BindPoint,
|
||||
BindCount = bindDesc.BindCount,
|
||||
Space = bindDesc.Space
|
||||
};
|
||||
|
||||
switch (bindDesc.Type)
|
||||
{
|
||||
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
|
||||
{
|
||||
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
|
||||
D3D12_SHADER_BUFFER_DESC cbufferDesc;
|
||||
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
|
||||
|
||||
var variables = new List<CBufferPropertyInfo>((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.PtrToStringUTF8((IntPtr)varDesc.Name);
|
||||
if (variableName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
variables.Add(new CBufferPropertyInfo
|
||||
{
|
||||
Name = variableName,
|
||||
StartOffset = varDesc.StartOffset,
|
||||
Size = varDesc.Size
|
||||
});
|
||||
}
|
||||
|
||||
info.Size = cbufferDesc.Size;
|
||||
info.Properties = variables;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
|
||||
}
|
||||
|
||||
reflectionData.ResourcesBindings.Add(info);
|
||||
}
|
||||
|
||||
return reflectionData;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pReflection->Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Result<ShaderCompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
using ComPtr<IDxcIncludeHandler> includeHandler = default;
|
||||
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
|
||||
|
||||
// Create DXC _compiler.Get() and _utils.Get()
|
||||
var dxccID = CLSID.CLSID_DxcCompiler;
|
||||
var dxcuID = CLSID.CLSID_DxcUtils;
|
||||
|
||||
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
|
||||
|
||||
// Create source blob
|
||||
fixed (char* pPath = config.shaderPath)
|
||||
{
|
||||
if (_utils.Get()->LoadFile(pPath, null, sourceBlob.GetAddressOf()).FAILED)
|
||||
{
|
||||
return Result.Failure($"Failed to load shader file: {config.shaderPath}");
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
using ComPtr<IDxcResult> result = default;
|
||||
|
||||
try
|
||||
{
|
||||
// Compile shader
|
||||
var buffer = new DxcBuffer
|
||||
{
|
||||
Ptr = sourceBlob.Get()->GetBufferPointer(),
|
||||
Size = sourceBlob.Get()->GetBufferSize(),
|
||||
Encoding = DXC_CP_UTF8
|
||||
};
|
||||
|
||||
var (iid, ppv) = Win32Utility.IID_PPV_ARGS(&result);
|
||||
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, iid, ppv));
|
||||
|
||||
// Check compilation result
|
||||
HRESULT hrStatus;
|
||||
result.Get()->GetStatus(&hrStatus);
|
||||
if (hrStatus.FAILED)
|
||||
{
|
||||
// Get error messages
|
||||
IDxcBlobEncoding* pErrorBlob = default;
|
||||
result.Get()->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
|
||||
using ComPtr<IDxcBlob> bytecodeBlob = default;
|
||||
ThrowIfFailed(result.Get()->GetResult(bytecodeBlob.GetAddressOf()));
|
||||
|
||||
ShaderReflectionData reflectionData = default;
|
||||
if (config.options.HasFlag(CompilerOption.KeepReflections))
|
||||
{
|
||||
using ComPtr<IDxcBlob> reflection = default;
|
||||
(iid, ppv) = Win32Utility.IID_PPV_ARGS(&reflection);
|
||||
|
||||
if (result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, iid, ppv, null).SUCCEEDED)
|
||||
{
|
||||
reflectionData = PerformDXCReflection(reflection).GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
|
||||
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
|
||||
|
||||
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
|
||||
|
||||
return new ShaderCompileResult
|
||||
{
|
||||
bytecode = bytecode,
|
||||
reflectionData = reflectionData,
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (var i = 0; i < argsArray.Count; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal((nint)argPtrs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, string? generatedCodePath)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is not FullPassDescriptor fullDescriptor)
|
||||
{
|
||||
return Result.Failure("FullPassDescriptor expected.");
|
||||
}
|
||||
|
||||
ShaderCompileResult tsResult = default;
|
||||
var tsEntry = fullDescriptor.taskShader;
|
||||
if (tsEntry.IsCreated)
|
||||
{
|
||||
var config = new CompilerConfig
|
||||
{
|
||||
defines = fullDescriptor.defines.AsSpan(),
|
||||
include = generatedCodePath,
|
||||
shaderPath = tsEntry.shader,
|
||||
entryPoint = tsEntry.entry,
|
||||
stage = ShaderStage.TaskShader,
|
||||
tier = CompilerTier.Tier0,
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
tsResult = result.Value;
|
||||
}
|
||||
|
||||
ShaderCompileResult msResult;
|
||||
var msEntry = fullDescriptor.meshShader;
|
||||
if (msEntry.IsCreated)
|
||||
{
|
||||
var config = new CompilerConfig
|
||||
{
|
||||
defines = fullDescriptor.defines.AsSpan(),
|
||||
include = generatedCodePath,
|
||||
shaderPath = msEntry.shader,
|
||||
entryPoint = msEntry.entry,
|
||||
stage = ShaderStage.MeshShader,
|
||||
tier = CompilerTier.Tier0,
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
msResult = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("Mesh shader expected.");
|
||||
}
|
||||
|
||||
ShaderCompileResult psResult;
|
||||
var psEntry = fullDescriptor.pixelShader;
|
||||
if (psEntry.IsCreated)
|
||||
{
|
||||
var config = new CompilerConfig
|
||||
{
|
||||
defines = fullDescriptor.defines.AsSpan(),
|
||||
include = generatedCodePath,
|
||||
shaderPath = psEntry.shader,
|
||||
entryPoint = psEntry.entry,
|
||||
stage = ShaderStage.PixelShader,
|
||||
tier = CompilerTier.Tier0,
|
||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||
options = CompilerOption.KeepReflections,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
psResult = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result.Failure("Pixel shader expected.");
|
||||
}
|
||||
|
||||
var compiled = new GraphicsCompiledResult
|
||||
{
|
||||
tsResult = tsResult,
|
||||
msResult = msResult,
|
||||
psResult = psResult,
|
||||
};
|
||||
|
||||
_compiledResults[new ShaderPassKey(fullDescriptor.Identifier)] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
public Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(ShaderPassKey key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (_compiledResults.TryGetValue(key, out var compiledResult))
|
||||
{
|
||||
return compiledResult;
|
||||
}
|
||||
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kvp in _compiledResults)
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
_compiler.Dispose();
|
||||
_utils.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
133
Ghost.Graphics/Core/Keyword.cs
Normal file
133
Ghost.Graphics/Core/Keyword.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
using ElementType = uint;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public unsafe struct LocalKeywordSet
|
||||
{
|
||||
public struct ReadOnly
|
||||
{
|
||||
private LocalKeywordSet _set;
|
||||
|
||||
internal ReadOnly(LocalKeywordSet set)
|
||||
{
|
||||
_set = set;
|
||||
}
|
||||
|
||||
public bool IsKeywordEnabled(int id)
|
||||
{
|
||||
return _set.IsKeywordEnabled(id);
|
||||
}
|
||||
|
||||
public static ReadOnly operator |(in ReadOnly a, in ReadOnly b)
|
||||
{
|
||||
var resultSet = a._set | b._set;
|
||||
return new ReadOnly(resultSet);
|
||||
}
|
||||
|
||||
public static ReadOnly operator &(in ReadOnly a, in ReadOnly b)
|
||||
{
|
||||
var resultSet = a._set & b._set;
|
||||
return new ReadOnly(resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
private const int _DATA_ARRAY_LENGTH = 4; // 4 * 32 = 128 bits
|
||||
private const int _SIZE_OF_ELEMENT = sizeof(ElementType);
|
||||
|
||||
private fixed ElementType _data[_DATA_ARRAY_LENGTH];
|
||||
|
||||
public void SetKeyword(int localIndex, bool enabled)
|
||||
{
|
||||
var index = localIndex / _SIZE_OF_ELEMENT;
|
||||
var bit = localIndex % _SIZE_OF_ELEMENT;
|
||||
if (enabled)
|
||||
{
|
||||
_data[index] |= (uint)(1 << bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[index] &= ~(uint)(1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKeywordEnabled(int localIndex)
|
||||
{
|
||||
var index = localIndex / _SIZE_OF_ELEMENT;
|
||||
var bit = localIndex % _SIZE_OF_ELEMENT;
|
||||
return (_data[index] & (uint)(1 << bit)) != 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
_data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ReadOnly AsReadOnly()
|
||||
{
|
||||
return new ReadOnly(this);
|
||||
}
|
||||
|
||||
public static LocalKeywordSet operator |(in LocalKeywordSet a, in LocalKeywordSet b)
|
||||
{
|
||||
var result = default(LocalKeywordSet);
|
||||
|
||||
if (Vector128<ElementType>.IsSupported)
|
||||
{
|
||||
fixed (ElementType* pDataA = a._data)
|
||||
fixed (ElementType* pDataB = b._data)
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i += Vector128<ElementType>.Count)
|
||||
{
|
||||
var vecA = Vector128.LoadUnsafe(ref *pDataA, (uint)(i * _SIZE_OF_ELEMENT));
|
||||
var vecB = Vector128.LoadUnsafe(ref *pDataB, (uint)(i * _SIZE_OF_ELEMENT));
|
||||
var vecResult = Vector128.BitwiseOr(vecA, vecB);
|
||||
vecResult.StoreUnsafe(ref result._data[0], (uint)(i * _SIZE_OF_ELEMENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
result._data[i] = a._data[i] | b._data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LocalKeywordSet operator &(in LocalKeywordSet a, in LocalKeywordSet b)
|
||||
{
|
||||
var result = default(LocalKeywordSet);
|
||||
|
||||
if (Vector128<ElementType>.IsSupported)
|
||||
{
|
||||
fixed (ElementType* pDataA = a._data)
|
||||
fixed (ElementType* pDataB = b._data)
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i += Vector128<ElementType>.Count)
|
||||
{
|
||||
var vecA = Vector128.LoadUnsafe(ref *pDataA, (uint)(i * _SIZE_OF_ELEMENT));
|
||||
var vecB = Vector128.LoadUnsafe(ref *pDataB, (uint)(i * _SIZE_OF_ELEMENT));
|
||||
var vecResult = Vector128.BitwiseAnd(vecA, vecB);
|
||||
vecResult.StoreUnsafe(ref result._data[0], (uint)(i * _SIZE_OF_ELEMENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
result._data[i] = a._data[i] & b._data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -66,16 +66,32 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
private Identifier<Shader> _shader;
|
||||
private CBufferCache _cBufferCache;
|
||||
private UnsafeArray<PipelineOverride> _passPipelineOverride;
|
||||
private LocalKeywordSet _keywordMask;
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
internal readonly CBufferCache CBufferCache => _cBufferCache;
|
||||
|
||||
public readonly Identifier<Shader> Shader => _shader;
|
||||
public readonly bool IsDirty => _isDirty;
|
||||
|
||||
public Result SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetDirty()
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly MaterialPipelineKey GetPassPipelineKey(int passIndex)
|
||||
{
|
||||
return _passPipelineOverride[passIndex].pipelineKey;
|
||||
}
|
||||
|
||||
public ErrorStatus SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
|
||||
{
|
||||
if (!shaderId.IsValid)
|
||||
{
|
||||
return Result.Failure("Shader ID is invalid.");
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
@@ -95,9 +111,10 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
}
|
||||
|
||||
_keywordMask.Clear();
|
||||
for (var i = 0; i < shader.PassCount; i++)
|
||||
{
|
||||
var pass = shader.GetPass(i);
|
||||
ref var pass = ref shader.GetPassReference(i);
|
||||
_passPipelineOverride[i] = new PipelineOverride
|
||||
{
|
||||
shaderPass = pass.Identifier,
|
||||
@@ -119,7 +136,7 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
|
||||
}
|
||||
|
||||
return Result.Success();
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -135,7 +152,7 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Span<byte> GetRawPropertyCache()
|
||||
public readonly ReadOnlySpan<byte> GetRawPropertyCache()
|
||||
{
|
||||
if (_cBufferCache.Size == 0)
|
||||
{
|
||||
@@ -146,7 +163,7 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe ErrorStatus SetPropertyCache<T>(ref readonly T data)
|
||||
public unsafe ErrorStatus SetPropertyCache<T>(ref readonly T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
@@ -155,11 +172,13 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
|
||||
Unsafe.WriteUnaligned(_cBufferCache.CpuData.GetUnsafePtr(), data);
|
||||
SetDirty();
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe ErrorStatus SetRawPropertyCache(ReadOnlySpan<byte> data)
|
||||
public unsafe ErrorStatus SetRawPropertyCache(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length != _cBufferCache.Size)
|
||||
{
|
||||
@@ -167,9 +186,48 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
}
|
||||
|
||||
Unsafe.WriteUnaligned(_cBufferCache.CpuData.GetUnsafePtr(), data);
|
||||
SetDirty();
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly PipelineState GetPassPipelineOverride(int passIndex)
|
||||
{
|
||||
return _passPipelineOverride[passIndex].options;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetPassPipelineOverride(int passIndex, ref readonly PipelineState options)
|
||||
{
|
||||
ref var pipelineOverride = ref _passPipelineOverride[passIndex];
|
||||
pipelineOverride.options = options;
|
||||
pipelineOverride.pipelineKey = new MaterialPipelineKey(pipelineOverride.shaderPass, options);
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ErrorStatus SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled)
|
||||
{
|
||||
ref var shader = ref resourceDatabase.GetShaderReference(_shader);
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
return ErrorStatus.NotFound;
|
||||
}
|
||||
|
||||
_keywordMask.SetKeyword(localIndex, enabled);
|
||||
SetDirty();
|
||||
|
||||
return ErrorStatus.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool IsKeywordEnabled(int keywordId)
|
||||
{
|
||||
return _keywordMask.IsKeywordEnabled(keywordId);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void UploadData(ICommandBuffer cmb)
|
||||
{
|
||||
@@ -177,29 +235,10 @@ public struct Material : IResourceReleasable, IHandleType
|
||||
cmb.ResourceBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.VertexAndConstantBuffer);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly PipelineState GetPassPipelineOverride(int passIndex)
|
||||
{
|
||||
return _passPipelineOverride[passIndex].options;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void SetPassPipelineOverride(int passIndex, in PipelineState options)
|
||||
{
|
||||
ref var pipelineOverride = ref _passPipelineOverride[passIndex];
|
||||
pipelineOverride.options = options;
|
||||
pipelineOverride.pipelineKey = new MaterialPipelineKey(pipelineOverride.shaderPass, options);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly MaterialPipelineKey GetPassPipelineKey(int passIndex)
|
||||
{
|
||||
return _passPipelineOverride[passIndex].pipelineKey;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
_passPipelineOverride.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +182,24 @@ public readonly unsafe ref struct RenderingContext
|
||||
|
||||
if (!_engine.PipelineLibrary.HasPipeline(pipelineKey))
|
||||
{
|
||||
// TODO: Compile pso if not exist.
|
||||
// _engine.PipelineLibrary.CompilePSO(pipelineKey, ref shader, passIndex, materialRef.GetPassPipelineOverride());
|
||||
throw new InvalidOperationException("Pipeline state object not found in the pipeline library.");
|
||||
var pass = shader.GetPassReference(passIndex);
|
||||
var r = _engine.ShaderCompiler.LoadCompiledCache(pass.Identifier);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||
}
|
||||
|
||||
var psoDes = new GraphicsPSODescriptor
|
||||
{
|
||||
PassId = pass.Identifier,
|
||||
PipelineOption = materialRef.GetPassPipelineOverride(passIndex),
|
||||
|
||||
RtvFormats = [TextureFormat.B8G8R8A8_UNorm],
|
||||
DsvFormat = TextureFormat.Unknown,
|
||||
};
|
||||
|
||||
var compiled = r.Value;
|
||||
_engine.PipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_directCmd.SetPipelineState(pipelineKey);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Ghost.Graphics.Core;
|
||||
|
||||
/// <summary>
|
||||
/// The layout of the root signature is:
|
||||
/// <list type="bullet">
|
||||
/// <list space="bullet">
|
||||
/// <item>
|
||||
/// Global buffer (b0)
|
||||
/// </item>
|
||||
|
||||
@@ -3,10 +3,11 @@ using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public readonly struct ShaderPass : IResourceReleasable
|
||||
public readonly struct ShaderPass
|
||||
{
|
||||
public ShaderPassKey Identifier
|
||||
{
|
||||
@@ -18,8 +19,9 @@ public readonly struct ShaderPass : IResourceReleasable
|
||||
get; init;
|
||||
}
|
||||
|
||||
readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
public LocalKeywordSet.ReadOnly KeywordIDs
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,15 +35,43 @@ public partial struct Shader
|
||||
private static readonly Dictionary<string, int> s_propertyNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextPropertyID = 0;
|
||||
|
||||
private static readonly Dictionary<string, int> s_keywordNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextkeywordID = 0;
|
||||
|
||||
public static Identifier<ShaderPass> GetPassID(string passName)
|
||||
{
|
||||
return new Identifier<ShaderPass>(s_passNameToID.GetValueOrDefault(passName, s_nextPassID++));
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_passNameToID, passName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextPassID++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Identifier<ShaderProperty> GetPropertyID(string propertyName)
|
||||
{
|
||||
return new Identifier<ShaderProperty>(s_propertyNameToID.GetValueOrDefault(propertyName, s_nextPropertyID++));
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_propertyNameToID, propertyName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextPropertyID++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static int GetKeywordID(string keywordName)
|
||||
{
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_keywordNameToID, keywordName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextkeywordID++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// TODO: Global keywords
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,7 +81,8 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
{
|
||||
private readonly uint _cbufferSize;
|
||||
private UnsafeArray<ShaderPass> _shaderPasses;
|
||||
private UnsafeHashMap<int, int> _passLookup; // pass id to index
|
||||
private UnsafeHashMap<int, int> _passIDToLocal;
|
||||
private UnsafeHashMap<int, int> _keywordIDToLocal;
|
||||
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint CBufferSize => _cbufferSize;
|
||||
@@ -60,7 +91,8 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
{
|
||||
_cbufferSize = descriptor.cbufferSize;
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Count, Allocator.Persistent);
|
||||
_passLookup = new UnsafeHashMap<int, int>(descriptor.passes.Count, Allocator.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Count, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Count; i++)
|
||||
{
|
||||
@@ -73,20 +105,60 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
}
|
||||
|
||||
var passKey = new ShaderPassKey(pass.Identifier);
|
||||
var keywords = default(LocalKeywordSet);
|
||||
|
||||
if (fullPass.keywords != null && fullPass.keywords.Count > 0)
|
||||
{
|
||||
var localKeywordIndex = 0;
|
||||
|
||||
for (var j = 0; j < fullPass.keywords.Count; j++)
|
||||
{
|
||||
var group = fullPass.keywords[j];
|
||||
if (group.keywords == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (group.space == KeywordSpace.Local)
|
||||
{
|
||||
foreach (var kw in group.keywords)
|
||||
{
|
||||
var kwID = GetKeywordID(kw);
|
||||
var idx = localKeywordIndex++;
|
||||
|
||||
keywords.SetKeyword(idx, true);
|
||||
_keywordIDToLocal.TryAdd(kwID, idx);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Global keywords
|
||||
}
|
||||
}
|
||||
|
||||
_shaderPasses[i] = new ShaderPass
|
||||
{
|
||||
Identifier = passKey,
|
||||
DeafaultState = fullPass.localPipeline
|
||||
DeafaultState = fullPass.localPipeline,
|
||||
KeywordIDs = keywords.AsReadOnly(),
|
||||
};
|
||||
|
||||
_passLookup[GetPassID(pass.Name)] = i;
|
||||
_passIDToLocal[GetPassID(pass.Name)] = (ushort)i;
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetLocalKeywordIndex(int globalKeywordID)
|
||||
{
|
||||
if (_keywordIDToLocal.TryGetValue(globalKeywordID, out var localIndex))
|
||||
{
|
||||
return localIndex;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly int GetPassIndex(Identifier<ShaderPass> passID)
|
||||
{
|
||||
if (_passLookup.TryGetValue(passID.Value, out var index))
|
||||
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
@@ -96,7 +168,7 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
|
||||
public readonly int GetPassIndex(string passName)
|
||||
{
|
||||
if (_passLookup.TryGetValue(GetPassID(passName), out var index))
|
||||
if (_passIDToLocal.TryGetValue(GetPassID(passName), out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
@@ -104,14 +176,14 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly ShaderPass GetPass(int index)
|
||||
public readonly ref ShaderPass GetPassReference(int index)
|
||||
{
|
||||
return _shaderPasses[index];
|
||||
return ref _shaderPasses[index];
|
||||
}
|
||||
|
||||
public readonly Result<ShaderPass, ErrorStatus> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
|
||||
{
|
||||
if (_passLookup.TryGetValue(passID.Value, out var index))
|
||||
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
passIndex = -1;
|
||||
return ErrorStatus.NotFound;
|
||||
@@ -124,6 +196,6 @@ public partial struct Shader : IResourceReleasable, IIdentifierType
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_shaderPasses.Dispose();
|
||||
_passLookup.Dispose();
|
||||
_passIDToLocal.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user