Refactor folder structure
This commit is contained in:
5
src/Runtime/Ghost.Graphics/Core/Camera.cs
Normal file
5
src/Runtime/Ghost.Graphics/Core/Camera.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
}
|
||||
150
src/Runtime/Ghost.Graphics/Core/Common.cs
Normal file
150
src/Runtime/Ghost.Graphics/Core/Common.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color with 4 bytes components.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
||||
public struct Color32 : IEquatable<Color32>
|
||||
{
|
||||
public byte r;
|
||||
public byte g;
|
||||
public byte b;
|
||||
public byte a;
|
||||
|
||||
public Color32(byte r, byte g, byte b, byte a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public Color32(Color color)
|
||||
: this(color.R, color.G, color.B, color.A)
|
||||
{
|
||||
}
|
||||
|
||||
public Color32(Color128 color128)
|
||||
: this((byte)(color128.r * 255.0f), (byte)(color128.g * 255.0f), (byte)(color128.b * 255.0f), (byte)(color128.a * 255.0f))
|
||||
{
|
||||
}
|
||||
|
||||
public Color32(float4 v)
|
||||
: this((byte)(v.x * 255.0f), (byte)(v.y * 255.0f), (byte)(v.z * 255.0f), (byte)(v.w * 255.0f))
|
||||
{
|
||||
}
|
||||
|
||||
public readonly bool Equals(Color32 other)
|
||||
{
|
||||
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is Color32 color && Equals(color);
|
||||
}
|
||||
|
||||
public override readonly int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(r, g, b, a);
|
||||
}
|
||||
|
||||
public static bool operator ==(Color32 left, Color32 right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Color32 left, Color32 right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color with 16 bytes components.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
public struct Color128 : IEquatable<Color128>
|
||||
{
|
||||
public float r;
|
||||
public float g;
|
||||
public float b;
|
||||
public float a;
|
||||
|
||||
public Color128(float r, float g, float b, float a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public Color128(Color color)
|
||||
: this(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public Color128(Color32 color32)
|
||||
: this(color32.r / 255.0f, color32.g / 255.0f, color32.b / 255.0f, color32.a / 255.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public Color128(float4 v)
|
||||
: this(v.x, v.y, v.z, v.w)
|
||||
{
|
||||
}
|
||||
|
||||
public readonly bool Equals(Color128 other)
|
||||
{
|
||||
return r.Equals(other.r) && g.Equals(other.g) && b.Equals(other.b) && a.Equals(other.a);
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is Color128 color && Equals(color);
|
||||
}
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(r, g, b, a);
|
||||
}
|
||||
|
||||
public static bool operator ==(Color128 left, Color128 right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Color128 left, Color128 right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
{
|
||||
public static class Semantic
|
||||
{
|
||||
public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
public const int COUNT = 5;
|
||||
|
||||
public static readonly FixedText32 Position = new("POSITION");
|
||||
public static readonly FixedText32 Normal = new("NORMAL");
|
||||
public static readonly FixedText32 Tangent = new("TANGENT");
|
||||
public static readonly FixedText32 Uv = new("TEXCOORD");
|
||||
public static readonly FixedText32 Color = new("COLOR");
|
||||
}
|
||||
|
||||
public float4 position;
|
||||
public float4 normal;
|
||||
public float4 tangent;
|
||||
public float4 uv;
|
||||
public Color128 color;
|
||||
}
|
||||
520
src/Runtime/Ghost.Graphics/Core/DxcShaderCompiler.cs
Normal file
520
src/Runtime/Ghost.Graphics/Core/DxcShaderCompiler.cs
Normal file
@@ -0,0 +1,520 @@
|
||||
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 ShaderCompilationConfig 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);
|
||||
}
|
||||
|
||||
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 Result<string, Error> GetFinalShaderCode(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 System.Text.StringBuilder();
|
||||
foreach (var includePath in includes)
|
||||
{
|
||||
sb.AppendLine($"#include \"{includePath}\"");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
sb.AppendLine($"#line 1 \"hlsl_block\"");
|
||||
sb.AppendLine(injectedCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shaderCode))
|
||||
{
|
||||
sb.AppendLine($"#line 1 \"{shaderPath}\"");
|
||||
sb.AppendLine(shaderCode);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
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<Key64<ShaderVariant>, 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<Key64<ShaderVariant>, 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 ShaderCompilationConfig config, Allocator allocator)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
using ComPtr<IDxcIncludeHandler> includeHandler = default;
|
||||
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
|
||||
|
||||
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
|
||||
|
||||
var finalShaderCodeResult = GetFinalShaderCode(config.shaderPath, config.includes, config.injectedCode);
|
||||
if (finalShaderCodeResult.IsFailure)
|
||||
{
|
||||
return Result.Failure(finalShaderCodeResult.Error);
|
||||
}
|
||||
|
||||
var finalShaderCode = finalShaderCodeResult.Value;
|
||||
fixed (byte* pCode = System.Text.Encoding.UTF8.GetBytes(finalShaderCode))
|
||||
{
|
||||
var sizeInBytes = System.Text.Encoding.UTF8.GetByteCount(finalShaderCode);
|
||||
ThrowIfFailed(_utils.Get()->CreateBlobFromPinned(pCode, (uint)sizeInBytes, DXC_CP_UTF8, sourceBlob.GetAddressOf()));
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should be shader variant specific compile instead of pass specific.
|
||||
// TODO: Build final shader code in memory before compiling.
|
||||
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var defineCountInDescriptor = descriptor.defines?.Length ?? 0;
|
||||
var fullDefines = new string[defineCountInDescriptor + additionalConfig.defines.Length];
|
||||
descriptor.defines?.CopyTo(fullDefines);
|
||||
additionalConfig.defines.CopyTo(fullDefines.AsSpan(defineCountInDescriptor));
|
||||
|
||||
ShaderCompileResult tsResult = default;
|
||||
var tsEntry = descriptor.taskShader;
|
||||
if (tsEntry.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines.AsSpan(),
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = tsEntry.shader,
|
||||
entryPoint = tsEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
stage = ShaderStage.TaskShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
var result = Compile(ref config, Allocator.Persistent);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return Result.Failure(result.Message);
|
||||
}
|
||||
|
||||
tsResult = result.Value;
|
||||
}
|
||||
|
||||
ShaderCompileResult msResult;
|
||||
var msEntry = descriptor.meshShader;
|
||||
if (msEntry.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines.AsSpan(),
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = msEntry.shader,
|
||||
entryPoint = msEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
stage = ShaderStage.MeshShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
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 = descriptor.pixelShader;
|
||||
if (psEntry.IsCreated)
|
||||
{
|
||||
var config = new ShaderCompilationConfig
|
||||
{
|
||||
defines = fullDefines.AsSpan(),
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = psEntry.shader,
|
||||
entryPoint = psEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
stage = ShaderStage.PixelShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
options = additionalConfig.options,
|
||||
};
|
||||
|
||||
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[key] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
public Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> 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.Dispose();
|
||||
_utils.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
128
src/Runtime/Ghost.Graphics/Core/Keyword.cs
Normal file
128
src/Runtime/Ghost.Graphics/Core/Keyword.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Runtime.Intrinsics;
|
||||
using TerraFX.Interop.Windows;
|
||||
using ElementType = uint;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public unsafe struct LocalKeywordSet
|
||||
{
|
||||
private const int _DATA_ARRAY_LENGTH = 4; // 4 * 32 = 128 bits
|
||||
private const int _BITS_PER_ELEMENT = sizeof(ElementType) * 8;
|
||||
|
||||
private fixed ElementType _data[_DATA_ARRAY_LENGTH];
|
||||
|
||||
public void SetKeyword(int localIndex, bool enabled)
|
||||
{
|
||||
var index = localIndex / _BITS_PER_ELEMENT;
|
||||
var bit = localIndex % _BITS_PER_ELEMENT;
|
||||
if (enabled)
|
||||
{
|
||||
_data[index] |= (uint)(1 << bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[index] &= ~(uint)(1 << bit);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKeywordEnabled(int localIndex)
|
||||
{
|
||||
var index = localIndex / _BITS_PER_ELEMENT;
|
||||
var bit = localIndex % _BITS_PER_ELEMENT;
|
||||
return (_data[index] & (uint)(1 << bit)) != 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
_data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetHash64()
|
||||
{
|
||||
ulong hash = 14695981039346656037ul; // FNV Offset basis
|
||||
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
hash ^= _data[i];
|
||||
hash *= 1099511628211ul; // FNV prime
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = 17;
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
hash = hash * 31 + _data[i].GetHashCode();
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
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 elementOffset = (nuint)i;
|
||||
var vecA = Vector128.LoadUnsafe(ref *pDataA, elementOffset);
|
||||
var vecB = Vector128.LoadUnsafe(ref *pDataB, elementOffset);
|
||||
var vecResult = Vector128.BitwiseOr(vecA, vecB);
|
||||
vecResult.StoreUnsafe(ref result._data[0], elementOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 elementOffset = (nuint)i;
|
||||
var vecA = Vector128.LoadUnsafe(ref *pDataA, elementOffset);
|
||||
var vecB = Vector128.LoadUnsafe(ref *pDataB, elementOffset);
|
||||
var vecResult = Vector128.BitwiseAnd(vecA, vecB);
|
||||
vecResult.StoreUnsafe(ref result._data[0], elementOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
for (var i = 0; i < _DATA_ARRAY_LENGTH; i++)
|
||||
{
|
||||
result._data[i] = a._data[i] & b._data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
286
src/Runtime/Ghost.Graphics/Core/Material.cs
Normal file
286
src/Runtime/Ghost.Graphics/Core/Material.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal struct CBufferCache : IResourceReleasable
|
||||
{
|
||||
private UnsafeArray<byte> _cpuData;
|
||||
private Handle<GraphicsBuffer> _gpuResource;
|
||||
private uint _size;
|
||||
|
||||
public readonly UnsafeArray<byte> CpuData => _cpuData;
|
||||
public readonly Handle<GraphicsBuffer> GpuResource => _gpuResource;
|
||||
public readonly uint Size => _size;
|
||||
|
||||
public readonly bool IsCreated => _size != 0 && _gpuResource.IsValid && _cpuData.IsCreated;
|
||||
|
||||
public CBufferCache(Handle<GraphicsBuffer> buffer, uint bufferSize)
|
||||
{
|
||||
_size = bufferSize;
|
||||
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
|
||||
_gpuResource = buffer;
|
||||
}
|
||||
|
||||
public void ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cpuData.Dispose();
|
||||
|
||||
database.ReleaseResource(GpuResource.AsResource());
|
||||
_gpuResource = Handle<GraphicsBuffer>.Invalid;
|
||||
|
||||
_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Material : IResourceReleasable
|
||||
{
|
||||
private struct PipelineOverride
|
||||
{
|
||||
public Key64<ShaderPass> shaderPass;
|
||||
public PipelineState options;
|
||||
}
|
||||
|
||||
private Identifier<Shader> _shader;
|
||||
private UnsafeArray<PipelineOverride> _passPipelineOverride;
|
||||
private bool _isDirty;
|
||||
|
||||
internal CBufferCache _cBufferCache;
|
||||
internal LocalKeywordSet _keywordMask;
|
||||
|
||||
public readonly Identifier<Shader> Shader => _shader;
|
||||
public readonly bool IsDirty => _isDirty;
|
||||
|
||||
public int ActivePassIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Error SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
|
||||
{
|
||||
if (!shaderId.IsValid)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
_shader = shaderId;
|
||||
|
||||
var r = database.GetShaderReference(shaderId);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
if (_passPipelineOverride.Count < shader.PassCount)
|
||||
{
|
||||
if (!_passPipelineOverride.IsCreated)
|
||||
{
|
||||
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, Allocator.Persistent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_passPipelineOverride.Resize(shader.PassCount);
|
||||
}
|
||||
}
|
||||
|
||||
_keywordMask.Clear();
|
||||
for (var i = 0; i < shader.PassCount; i++)
|
||||
{
|
||||
ref var pass = ref shader.GetPassReference(i);
|
||||
_passPipelineOverride[i] = new PipelineOverride
|
||||
{
|
||||
shaderPass = pass.Key,
|
||||
options = pass.DeafaultState,
|
||||
};
|
||||
}
|
||||
|
||||
if (shader.CBufferSize != 0)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = shader.CBufferSize,
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
MemoryType = ResourceMemoryType.Default,
|
||||
};
|
||||
|
||||
var buffer = allocator.CreateBuffer(ref desc, "MaterialCBuffer");
|
||||
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
|
||||
}
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly unsafe Result<T, Error> GetPropertyCache<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
return *(T*)_cBufferCache.CpuData.GetUnsafePtr();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlySpan<byte> GetRawPropertyCache()
|
||||
{
|
||||
if (_cBufferCache.Size == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return _cBufferCache.CpuData.AsSpan(0, (int)_cBufferCache.Size);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe Error SetPropertyCache<T>(scoped ref readonly T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != _cBufferCache.Size)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
var dataSpan = MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in data));
|
||||
var cacheSpan = _cBufferCache.CpuData.AsSpan();
|
||||
if (cacheSpan.SequenceEqual(dataSpan))
|
||||
{
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
dataSpan.CopyTo(cacheSpan);
|
||||
_isDirty = true;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Error SetRawPropertyCache(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length != _cBufferCache.Size)
|
||||
{
|
||||
return Error.InvalidArgument;
|
||||
}
|
||||
|
||||
var cacheSpan = _cBufferCache.CpuData.AsSpan();
|
||||
if (cacheSpan.SequenceEqual(data))
|
||||
{
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
data.CopyTo(cacheSpan);
|
||||
_isDirty = true;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly PipelineState GetPassPipelineOverride(int passIndex)
|
||||
{
|
||||
return _passPipelineOverride[passIndex].options;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetPassPipelineOverride(int passIndex, scoped ref readonly PipelineState options)
|
||||
{
|
||||
ref var pipelineOverride = ref _passPipelineOverride[passIndex];
|
||||
pipelineOverride.options = options;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Error SetKeyword(IResourceDatabase resourceDatabase, int keywordId, bool enabled)
|
||||
{
|
||||
var r = resourceDatabase.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return r.Error;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
_keywordMask.SetKeyword(localIndex, enabled);
|
||||
_isDirty = true;
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool IsKeywordEnabled(IResourceDatabase resourceDatabase, int keywordId)
|
||||
{
|
||||
var r = resourceDatabase.GetShaderReference(_shader);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ref readonly var shader = ref r.Value;
|
||||
var localIndex = shader.GetLocalKeywordIndex(keywordId);
|
||||
if (localIndex == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _keywordMask.IsKeywordEnabled(localIndex);
|
||||
}
|
||||
|
||||
public readonly void UploadData(ICommandBuffer cmd, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
if (!_isDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cbufferResource = _cBufferCache.GpuResource.AsResource();
|
||||
var r = resourceDatabase.GetResourceBarrierData(cbufferResource);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var barrierData = r.Value;
|
||||
var desc = BarrierDesc.Buffer(
|
||||
cbufferResource,
|
||||
barrierData.sync,
|
||||
BarrierSync.Copy,
|
||||
barrierData.access,
|
||||
BarrierAccess.CopyDest);
|
||||
|
||||
cmd.ResourceBarrier(desc);
|
||||
cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
|
||||
|
||||
desc = BarrierDesc.Buffer(
|
||||
cbufferResource,
|
||||
BarrierSync.Copy,
|
||||
BarrierSync.AllShading,
|
||||
BarrierAccess.CopyDest,
|
||||
BarrierAccess.ShaderResource);
|
||||
|
||||
cmd.ResourceBarrier(desc);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_cBufferCache.ReleaseResource(database);
|
||||
_passPipelineOverride.Dispose();
|
||||
}
|
||||
}
|
||||
171
src/Runtime/Ghost.Graphics/Core/Mesh.cs
Normal file
171
src/Runtime/Ghost.Graphics/Core/Mesh.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public struct Mesh : IResourceReleasable
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
private UnsafeList<uint> _indices;
|
||||
|
||||
internal bool IsMeshDataDirty
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of vertices that define the geometry.
|
||||
/// </summary>
|
||||
public UnsafeList<Vertex> Vertices
|
||||
{
|
||||
readonly get => _vertices;
|
||||
set
|
||||
{
|
||||
_vertices = value;
|
||||
VertexCount = value.Count;
|
||||
IsMeshDataDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of indices that define the order of vertices.
|
||||
/// </summary>
|
||||
public UnsafeList<uint> Indices
|
||||
{
|
||||
readonly get => _indices;
|
||||
set
|
||||
{
|
||||
_indices = value;
|
||||
IndexCount = value.Count;
|
||||
IsMeshDataDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of vertices in the mesh.
|
||||
/// </summary>
|
||||
public int VertexCount
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of indices in the mesh.
|
||||
/// </summary>
|
||||
public int IndexCount
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the axis-aligned bounding box (AABB) of the mesh.
|
||||
/// </summary>
|
||||
public AABB BoundingBox
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the vertex buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> VertexBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the index buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> IndexBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the mesh data buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> ObjectDataBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
|
||||
{
|
||||
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
Indices = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
Vertices.CopyFrom(vertices);
|
||||
Indices.CopyFrom(indices);
|
||||
VertexBuffer = vertexBuffer;
|
||||
IndexBuffer = indexBuffer;
|
||||
|
||||
this.ComputeBounds();
|
||||
}
|
||||
|
||||
public readonly void ReleaseCpuResources()
|
||||
{
|
||||
_vertices.Dispose();
|
||||
_indices.Dispose();
|
||||
}
|
||||
|
||||
readonly void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
ReleaseCpuResources();
|
||||
|
||||
database.ReleaseResource(VertexBuffer.AsResource());
|
||||
database.ReleaseResource(IndexBuffer.AsResource());
|
||||
database.ReleaseResource(ObjectDataBuffer.AsResource());
|
||||
}
|
||||
}
|
||||
|
||||
public static class MeshExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the bounding box of the mesh based on its vertices.
|
||||
/// </summary>
|
||||
public static void ComputeBounds(ref this Mesh mesh)
|
||||
{
|
||||
if (mesh.Vertices.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var min = new float3(float.MaxValue);
|
||||
var max = new float3(float.MinValue);
|
||||
foreach (var vertex in mesh.Vertices)
|
||||
{
|
||||
var pos = vertex.position.xyz;
|
||||
min = math.min(min, pos);
|
||||
max = math.max(max, pos);
|
||||
}
|
||||
|
||||
mesh.BoundingBox = new AABB(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute smooth per-vertex normals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices and indices are valid.
|
||||
/// </remarks>
|
||||
public static void ComputeNormal(ref this Mesh mesh)
|
||||
{
|
||||
MeshBuilder.ComputeNormal(mesh.Vertices, mesh.Indices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute per-vertex tangents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices, normals, and UVs are valid.
|
||||
/// </remarks>
|
||||
public static void ComputeTangents(ref this Mesh mesh)
|
||||
{
|
||||
MeshBuilder.ComputeTangents(mesh.Vertices, mesh.Indices);
|
||||
}
|
||||
}
|
||||
95
src/Runtime/Ghost.Graphics/Core/RenderOutput.cs
Normal file
95
src/Runtime/Ghost.Graphics/Core/RenderOutput.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
internal class SwapChainRenderOutput : IRenderOutput
|
||||
{
|
||||
private readonly ISwapChain _swapChain;
|
||||
|
||||
public ViewportDesc Viewport
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RectDesc Scissor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SwapChainRenderOutput(ISwapChain swapChain)
|
||||
{
|
||||
_swapChain = swapChain;
|
||||
|
||||
Viewport = new ViewportDesc { Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 };
|
||||
Scissor = new RectDesc { Right = swapChain.Width, Bottom = swapChain.Height };
|
||||
}
|
||||
|
||||
public Handle<Texture> GetRenderTarget()
|
||||
{
|
||||
return _swapChain.GetCurrentBackBuffer();
|
||||
}
|
||||
|
||||
public void BeginRender(ICommandBuffer cmd)
|
||||
{
|
||||
var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(),
|
||||
BarrierSync.None, BarrierSync.RenderTarget,
|
||||
BarrierAccess.NoAccess, BarrierAccess.RenderTarget,
|
||||
BarrierLayout.Present, BarrierLayout.RenderTarget);
|
||||
|
||||
cmd.ResourceBarrier(barrierDesc);
|
||||
}
|
||||
|
||||
public void EndRender(ICommandBuffer cmd)
|
||||
{
|
||||
var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(),
|
||||
BarrierSync.RenderTarget, BarrierSync.None,
|
||||
BarrierAccess.RenderTarget, BarrierAccess.NoAccess,
|
||||
BarrierLayout.RenderTarget, BarrierLayout.Present);
|
||||
|
||||
cmd.ResourceBarrier(barrierDesc);
|
||||
}
|
||||
|
||||
public void Present()
|
||||
{
|
||||
_swapChain.Present();
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextureRenderOutput : IRenderOutput
|
||||
{
|
||||
private readonly Handle<Texture> _texture;
|
||||
|
||||
public ViewportDesc Viewport
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RectDesc Scissor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public TextureRenderOutput(Handle<Texture> texture)
|
||||
{
|
||||
_texture = texture;
|
||||
}
|
||||
|
||||
public Handle<Texture> GetRenderTarget()
|
||||
{
|
||||
return _texture;
|
||||
}
|
||||
|
||||
public void BeginRender(ICommandBuffer cmd)
|
||||
{
|
||||
}
|
||||
|
||||
public void EndRender(ICommandBuffer cmd)
|
||||
{
|
||||
}
|
||||
|
||||
public void Present()
|
||||
{
|
||||
}
|
||||
}
|
||||
219
src/Runtime/Ghost.Graphics/Core/RenderingContext.cs
Normal file
219
src/Runtime/Ghost.Graphics/Core/RenderingContext.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public readonly unsafe ref struct RenderingContext
|
||||
{
|
||||
private readonly IGraphicsEngine _engine;
|
||||
private readonly ICommandBuffer _directCmd;
|
||||
|
||||
public ICommandBuffer DirectCommandBuffer => _directCmd;
|
||||
|
||||
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
|
||||
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
|
||||
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
|
||||
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
|
||||
|
||||
internal RenderingContext(IGraphicsEngine engine, ICommandBuffer directCmd)
|
||||
{
|
||||
_engine = engine;
|
||||
_directCmd = directCmd;
|
||||
}
|
||||
|
||||
public ICommandBuffer CrearteCommandBuffer(CommandBufferType type)
|
||||
{
|
||||
return _engine.CreateCommandBuffer(type);
|
||||
}
|
||||
|
||||
// TODO: ExecuteCommandBufferAsync with fencene.Device.GraphicsQueue.Submit(commandBuffer);
|
||||
public void ExecuteCommandBuffer(ICommandBuffer commandBuffer)
|
||||
{
|
||||
var queue = commandBuffer.Type switch
|
||||
{
|
||||
CommandBufferType.Graphics => _engine.Device.GraphicsQueue,
|
||||
CommandBufferType.Compute => _engine.Device.ComputeQueue,
|
||||
CommandBufferType.Copy => _engine.Device.CopyQueue,
|
||||
_ => throw new InvalidOperationException("Unknown command buffer type."),
|
||||
};
|
||||
|
||||
queue.Submit(commandBuffer);
|
||||
queue.WaitIdle();
|
||||
}
|
||||
|
||||
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
|
||||
{
|
||||
var r = ResourceDatabase.GetResourceBarrierData(resource);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = r.Value;
|
||||
if (data.layout == newLayout && data.access == newAccess && data.sync == newSync)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BarrierDesc desc;
|
||||
if (isTexture)
|
||||
{
|
||||
desc = BarrierDesc.Texture(
|
||||
resource,
|
||||
data.sync, newSync,
|
||||
data.access, newAccess,
|
||||
data.layout, newLayout);
|
||||
}
|
||||
else
|
||||
{
|
||||
desc = BarrierDesc.Buffer(
|
||||
resource,
|
||||
data.sync, newSync,
|
||||
data.access, newAccess);
|
||||
}
|
||||
|
||||
_directCmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(in desc));
|
||||
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync));
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
||||
{
|
||||
var mesh = ResourceAllocator.CreateMesh(vertices, indices);
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return mesh;
|
||||
}
|
||||
|
||||
ref readonly var meshData = ref r.Value;
|
||||
var vertexHandle = meshData.VertexBuffer.AsResource();
|
||||
var indexHandle = meshData.IndexBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
||||
|
||||
if (staticMesh)
|
||||
{
|
||||
meshData.ReleaseCpuResources();
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, bool staticMesh)
|
||||
{
|
||||
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
|
||||
vertexList.CopyFrom(vertices);
|
||||
indexList.CopyFrom(indices);
|
||||
|
||||
return CreateMesh(vertexList, indexList, staticMesh);
|
||||
}
|
||||
|
||||
// TODO: Make one memory pool for upload.
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the mesh data to the GPU.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The handle point to the mesh buffer</param>
|
||||
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
|
||||
public void UploadMesh(Handle<Mesh> mesh, bool markMeshStatic)
|
||||
{
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref readonly var meshRef = ref r.Value;
|
||||
var vertexHandle = meshRef.VertexBuffer.AsResource();
|
||||
var indexHandle = meshRef.IndexBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
|
||||
|
||||
if (markMeshStatic)
|
||||
{
|
||||
meshRef.ReleaseCpuResources();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateObjectData(Handle<Mesh> mesh, float4x4 localToWorld)
|
||||
{
|
||||
var r = ResourceDatabase.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ref readonly var meshData = ref r.Value;
|
||||
var data = new PerObjectData
|
||||
{
|
||||
localToWorld = localToWorld,
|
||||
worldBoundsMin = meshData.BoundingBox.Min,
|
||||
worldBoundsMax = meshData.BoundingBox.Max,
|
||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
|
||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
|
||||
};
|
||||
|
||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, data);
|
||||
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
|
||||
}
|
||||
|
||||
public Handle<Texture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> data, string name)
|
||||
where T : unmanaged
|
||||
{
|
||||
var handle = ResourceAllocator.CreateTexture(in desc, name);
|
||||
UploadTexture(handle, data);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void UploadTexture<T>(Handle<Texture> texture, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||
|
||||
//var size = ResourceAllocator.GetSizeInfo(desc).Size;
|
||||
//if ((ulong)(data.Length * sizeof(T)) != ResourceAllocator.GetSizeInfo(desc).Size)
|
||||
//{
|
||||
// throw new ArgumentException("Data size does not match texture size.");
|
||||
//}
|
||||
|
||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
var subresourceData = new SubResourceData
|
||||
{
|
||||
pData = pData,
|
||||
rowPitch = rowPitch,
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
_directCmd.UploadTexture(texture, subresourceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Runtime/Ghost.Graphics/Core/ResourceHandle.cs
Normal file
32
src/Runtime/Ghost.Graphics/Core/ResourceHandle.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public readonly struct GPUResource;
|
||||
public readonly struct Texture;
|
||||
public readonly struct GraphicsBuffer;
|
||||
|
||||
public readonly struct Sampler;
|
||||
|
||||
public static class ResourceHandleExtensions
|
||||
{
|
||||
public static Handle<GPUResource> AsResource(this Handle<Texture> texture)
|
||||
{
|
||||
return new Handle<GPUResource>(texture.ID, texture.Generation);
|
||||
}
|
||||
|
||||
public static Handle<GPUResource> AsResource(this Handle<GraphicsBuffer> buffer)
|
||||
{
|
||||
return new Handle<GPUResource>(buffer.ID, buffer.Generation);
|
||||
}
|
||||
|
||||
internal static Handle<Texture> AsTexture(this Handle<GPUResource> resource)
|
||||
{
|
||||
return new Handle<Texture>(resource.ID, resource.Generation);
|
||||
}
|
||||
|
||||
internal static Handle<GraphicsBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
|
||||
{
|
||||
return new Handle<GraphicsBuffer>(resource.ID, resource.Generation);
|
||||
}
|
||||
}
|
||||
75
src/Runtime/Ghost.Graphics/Core/RootSignatureLayout.cs
Normal file
75
src/Runtime/Ghost.Graphics/Core/RootSignatureLayout.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
/// <summary>
|
||||
/// The layout of the root signature is:
|
||||
/// <list space="bullet">
|
||||
/// <item>
|
||||
/// Global buffer (b0)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Per-view buffer (b1)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Per-object buffer (b2)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Per-material buffer (b3)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Descriptor table for bindless textures (t0)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// Descriptor table for bindless samplers (s0)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class RootSignatureLayout
|
||||
{
|
||||
// public const int GLOBAL_BUFFER_SLOT = 0;
|
||||
// public const int PER_VIEW_BUFFER_SLOT = 1;
|
||||
// public const int PER_OBJECT_BUFFER_SLOT = 2;
|
||||
// public const int PER_MATERIAL_BUFFER_SLOT = 3;
|
||||
|
||||
// public const int TEXTURE_HEAP_SLOT = 0;
|
||||
// public const int SAMPLER_HEAP_SLOT = 0;
|
||||
|
||||
public const int PUSH_CONSTANT_SLOT = 0;
|
||||
|
||||
public const int ROOT_PARAMETER_COUNT = 1;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 16)]
|
||||
public struct PushConstantsData
|
||||
{
|
||||
public uint globalIndex;
|
||||
public uint viewIndex;
|
||||
public uint objectIndex;
|
||||
public uint materialIndex;
|
||||
}
|
||||
|
||||
// The size should be 176 bytes (16-byte aligned)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct PerViewData
|
||||
{
|
||||
public float4x4 viewMatrix;
|
||||
public float4x4 projectionMatrix;
|
||||
public float3 cameraPosition;
|
||||
public float nearClip;
|
||||
public float3 cameraDirection;
|
||||
public float farClip;
|
||||
public float4 screenSize; // xy: size, zw: 1/size
|
||||
};
|
||||
|
||||
// The size should be 96 bytes (16-byte aligned)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct PerObjectData
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public float3 worldBoundsMin;
|
||||
public uint vertexBuffer;
|
||||
public float3 worldBoundsMax;
|
||||
public uint indexBuffer;
|
||||
};
|
||||
210
src/Runtime/Ghost.Graphics/Core/Shader.cs
Normal file
210
src/Runtime/Ghost.Graphics/Core/Shader.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using Ghost.Core;
|
||||
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
|
||||
{
|
||||
public Key64<ShaderPass> Key
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public PipelineState DeafaultState
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public LocalKeywordSet KeywordIDs
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ShaderProperty;
|
||||
|
||||
public partial struct Shader
|
||||
{
|
||||
private static readonly Dictionary<string, int> s_passNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextPassID = 0;
|
||||
|
||||
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 readonly Dictionary<int, string> s_keywordIDToName = new Dictionary<int, string>();
|
||||
private static int s_nextKeywordID = 0;
|
||||
|
||||
public static Identifier<ShaderPass> GetPassID(string passName)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
|
||||
s_keywordIDToName[id] = keywordName;
|
||||
return id;
|
||||
}
|
||||
|
||||
public static string? GetKeywordName(int keywordID)
|
||||
{
|
||||
if (s_keywordIDToName.TryGetValue(keywordID, out var name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Global keywords
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A representation of a GPU shader, including all the passes it contains.
|
||||
/// </summary>
|
||||
public partial struct Shader : IResourceReleasable
|
||||
{
|
||||
private readonly uint _cbufferSize;
|
||||
private UnsafeArray<ShaderPass> _shaderPasses;
|
||||
private UnsafeHashMap<int, int> _passIDToLocal;
|
||||
private UnsafeHashMap<int, int> _keywordIDToLocal;
|
||||
|
||||
// TODO: Tag to pass index for fast lookup.
|
||||
// We can use a int array since the number and index of tags are fixed at compile time.
|
||||
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint CBufferSize => _cbufferSize;
|
||||
|
||||
internal Shader(ShaderDescriptor descriptor)
|
||||
{
|
||||
_cbufferSize = (uint)descriptor.cbufferSize;
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||
{
|
||||
var pass = descriptor.passes[i];
|
||||
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier);
|
||||
var keywords = default(LocalKeywordSet);
|
||||
|
||||
if (pass.keywords.Length > 0)
|
||||
{
|
||||
var localKeywordIndex = 0;
|
||||
|
||||
for (var j = 0; j < pass.keywords.Length; j++)
|
||||
{
|
||||
var group = pass.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
|
||||
{
|
||||
Key = passKey,
|
||||
DeafaultState = pass.localPipeline,
|
||||
KeywordIDs = keywords,
|
||||
};
|
||||
|
||||
_passIDToLocal[GetPassID(pass.name)] = (ushort)i;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly int GetLocalKeywordIndex(int globalKeywordID)
|
||||
{
|
||||
if (_keywordIDToLocal.TryGetValue(globalKeywordID, out var localIndex))
|
||||
{
|
||||
return localIndex;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly int GetPassIndex(Identifier<ShaderPass> passID)
|
||||
{
|
||||
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly int GetPassIndex(string passName)
|
||||
{
|
||||
if (_passIDToLocal.TryGetValue(GetPassID(passName), out var index))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly ref ShaderPass GetPassReference(int index)
|
||||
{
|
||||
return ref _shaderPasses[index];
|
||||
}
|
||||
|
||||
public readonly Result<ShaderPass, Error> TryGetPass(Identifier<ShaderPass> passID, out int passIndex)
|
||||
{
|
||||
if (_passIDToLocal.TryGetValue(passID.Value, out var index))
|
||||
{
|
||||
passIndex = -1;
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
passIndex = index;
|
||||
return _shaderPasses[index];
|
||||
}
|
||||
|
||||
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
_keywordIDToLocal.Dispose();
|
||||
_shaderPasses.Dispose();
|
||||
_passIDToLocal.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user