Refactor folder structure

This commit is contained in:
2026-02-18 00:50:46 +09:00
parent 426786397c
commit db8ca971a8
413 changed files with 2885 additions and 3634 deletions

View File

@@ -0,0 +1,5 @@
namespace Ghost.Graphics.Core;
public class Camera
{
}

View 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;
}

View 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);
}
}

View 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;
}
}

View 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();
}
}

View 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);
}
}

View 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()
{
}
}

View 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);
}
}
}

View 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);
}
}

View 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;
};

View 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();
}
}