Files
GhostEngine/Ghost.Graphics/RHI/Common.cs
Misaki f988c34b3d Add high-performance material/shader system (Ghost.Shader.Concept)
Introduces a new Ghost.Shader.Concept project implementing a modern, data-oriented material and shader system with:
- Global/local keyword bitsets (fast O(1) ops, 64 bytes)
- Multi-pass shader program and per-pass render state overrides
- Thread-safe, 16-byte aligned material property blocks
- Material pooling to reduce GC pressure
- Batch renderer for efficient PSO grouping and async variant warmup
- Full demo (Program.cs) and extensive documentation (ARCHITECTURE.md, README.md, PROJECT_SUMMARY.md)
- Minor integration: new enums, doc updates, and keyword handling in existing code

No breaking changes to the existing engine; all new code is isolated. This serves as a reference implementation for high-performance, extensible material/shader architectures.
2025-12-26 19:19:30 +09:00

1024 lines
19 KiB
C#

using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Core;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RHI;
public readonly struct ShaderPassKey : IEquatable<ShaderPassKey>
{
public readonly ulong value;
public ShaderPassKey(ulong value)
{
this.value = value;
}
public ShaderPassKey(string passId)
{
var passIdSpan = passId.AsSpan();
value = XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan));
}
public override string ToString()
{
return value.ToString("X16");
}
public bool Equals(ShaderPassKey other)
{
return value == other.value;
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override bool Equals(object? obj)
{
return obj is ShaderPassKey key && Equals(key);
}
public static bool operator ==(ShaderPassKey left, ShaderPassKey right)
{
return left.Equals(right);
}
public static bool operator !=(ShaderPassKey left, ShaderPassKey right)
{
return !(left == right);
}
}
public readonly struct GraphicsPipelineKey
{
public const int KEY_STRING_LENGTH = 33; // 32 chars + null terminator
public readonly UInt128 value;
public GraphicsPipelineKey(UInt128 value)
{
this.value = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsPipelineKey Combine(MaterialPipelineKey materialKey, PassPipelineKey passKey)
{
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
static ulong Mix64(ulong x)
{
x ^= x >> 30;
x *= 0xBF58476D1CE4E5B9ul;
x ^= x >> 27;
x *= 0x94D049BB133111EBul;
x ^= x >> 31;
return x;
}
unsafe static ulong GetLow(UInt128 value) => ((ulong*)&value)[0];
unsafe static ulong GetHigh(UInt128 value) => ((ulong*)&value)[1];
var mLo = GetLow(materialKey.value);
var mHi = GetHigh(materialKey.value);
var pLo = GetLow(passKey.value);
var pHi = GetHigh(passKey.value);
// Distinct constants + cross-feeding to reduce structural collisions.
var lo = Mix64(mLo ^ (pLo + 0x9E3779B97F4A7C15ul) ^ (mHi * 0xD6E8FEB86659FD93ul));
var hi = Mix64(mHi ^ (pHi + 0xC2B2AE3D27D4EB4Ful) ^ (pLo * 0x165667B19E3779F9ul));
return new GraphicsPipelineKey(new UInt128(lo, hi));
}
public Result GetString(Span<char> destination)
{
if (!value.TryFormat(destination, out var num, "X16"))
{
return Result.Failure("Failed to format GraphicsPipelineKey to string.");
}
// Just in case.
destination[num] = '\0';
return Result.Success();
}
public override string ToString()
{
return value.ToString("X16");
}
public override int GetHashCode()
{
return value.GetHashCode();
}
}
public readonly struct MaterialPipelineKey : IEquatable<MaterialPipelineKey>
{
public readonly UInt128 value;
// TODO: Variants
public MaterialPipelineKey(ShaderPassKey passKey, PipelineState psoOptions)
{
// 32-bit packed key for states controlled by material / overrides.
// layout:
// 0..3 Blend (4 bits)
// 4..6 Cull (3 bits)
// 7..10 DeafaultState (4 bits)
// 11 ZWrite (1 bit)
// 12..15 ColorMask (4 bits)
var key = 0u;
key |= ((uint)psoOptions.Blend & 0xFu) << 0;
key |= ((uint)psoOptions.Cull & 0x7u) << 4;
key |= ((uint)psoOptions.ZTest & 0xFu) << 7;
key |= ((uint)psoOptions.ZWrite & 0x1u) << 11;
key |= ((uint)psoOptions.ColorMask & 0xFu) << 12;
value = new UInt128(passKey.value, key);
}
public bool Equals(MaterialPipelineKey other) => value == other.value;
public override bool Equals(object? obj) => obj is MaterialPipelineKey other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(MaterialPipelineKey left, MaterialPipelineKey right) => left.Equals(right);
public static bool operator !=(MaterialPipelineKey left, MaterialPipelineKey right) => !(left == right);
}
public readonly struct PassPipelineKey : IEquatable<PassPipelineKey>
{
public readonly UInt128 value;
public PassPipelineKey(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
{
if (rtvFormats.Length > 8)
{
throw new ArgumentException($"RTV formats length exceeds maximum supported count of {8}.");
}
// layout:
// 0..64 8 RTV formats (8 bits each)
// 64..72 DSV format (8 bits)
var rtvPart = 0UL;
for (var i = 0; i < rtvFormats.Length; i++)
{
rtvPart |= ((ulong)(byte)rtvFormats[i]) << (i * 8);
}
value = new UInt128(rtvPart, (ulong)dsvFormat);
}
public bool Equals(PassPipelineKey other) => value == other.value;
public override bool Equals(object? obj) => obj is PassPipelineKey other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(PassPipelineKey left, PassPipelineKey right) => left.Equals(right);
public static bool operator !=(PassPipelineKey left, PassPipelineKey right) => !(left == right);
}
public ref struct GraphicsPSODescriptor
{
public ShaderPassKey PassId
{
get; set;
}
public PipelineState PipelineOption
{
get; set;
}
public ReadOnlySpan<TextureFormat> RtvFormats
{
get; set;
}
public TextureFormat DsvFormat
{
get; set;
}
}
public readonly struct CBufferPropertyInfo
{
public string Name
{
get; init;
}
public uint StartOffset
{
get; init;
}
public uint Size
{
get; init;
}
}
public readonly struct CBufferInfo
{
public string Name
{
get; init;
}
public uint RegisterSlot
{
get; init;
}
public uint RegisterSpace
{
get; init;
}
public uint SizeInBytes
{
get; init;
}
public IReadOnlyList<CBufferPropertyInfo> Properties
{
get; init;
}
}
public struct RenderDesc
{
public float4x4 ViewMatrix
{
get; set;
}
public float4x4 ProjectionMatrix
{
get; set;
}
public float4 CameraPosition
{
get; set;
}
// The "Target" (Where to write pixels)
public Handle<Texture> Target
{
get; set;
}
public Handle<Texture> DepthTarget
{
get; set;
}
public RectDesc Viewport
{
get; set;
}
//public RenderPathID RenderPath;
//public LayerMask CullingMask;
}
public struct ViewportDesc
{
public float X
{
get; set;
}
public float Y
{
get; set;
}
public float Width
{
get; set;
}
public float Height
{
get; set;
}
public float MinDepth
{
get; set;
}
public float MaxDepth
{
get; set;
}
}
public struct RectDesc
{
public uint Left
{
get; set;
}
public uint Top
{
get; set;
}
public uint Right
{
get; set;
}
public uint Bottom
{
get; set;
}
}
public struct SubResourceData
{
public unsafe void* pData;
public uint rowPitch;
public uint slicePitch;
}
public struct PassRenderTargetDesc
{
public Handle<Texture> Texture
{
get; set;
}
public Color128 ClearColor
{
get; set;
}
}
public struct PassDepthStencilDesc
{
public Handle<Texture> Texture
{
get; set;
}
public float ClearDepth
{
get; set;
}
public byte ClearStencil
{
get; set;
}
}
public struct BarrierDesc
{
public Handle<GPUResource> Resource
{
get; set;
}
public ResourceState StateBefore
{
get; set;
}
public ResourceState StateAfter
{
get; set;
}
}
public struct ResourceDesc
{
[StructLayout(LayoutKind.Explicit)]
private struct resource_union
{
[FieldOffset(0)]
public TextureDesc textureDescription;
[FieldOffset(0)]
public BufferDesc bufferDescription;
}
private resource_union _desc;
public TextureDesc TextureDescription
{
readonly get => _desc.textureDescription;
set => _desc.textureDescription = value;
}
public BufferDesc BufferDescription
{
readonly get => _desc.bufferDescription;
set => _desc.bufferDescription = value;
}
public static ResourceDesc Buffer(BufferDesc desc)
{
return new ResourceDesc
{
BufferDescription = desc
};
}
public static ResourceDesc Texture(TextureDesc desc)
{
return new ResourceDesc
{
TextureDescription = desc
};
}
}
/// <summary>
/// Render Target description
/// Supports either color OR depth rendering, not both
/// </summary>
public struct RenderTargetDesc
{
/// <summary>
/// Width of the render Target
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the render Target
/// </summary>
public uint Height
{
get; set;
}
/// <summary>
/// Slice of the render Target
/// </summary>
public uint Slice
{
get; set;
}
/// <summary>
/// Type of render Target
/// </summary>
public RenderTargetType Type
{
get; set;
}
/// <summary>
/// Target texture Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get; set;
}
/// <summary>
/// Creation flags for the render Target
/// </summary>
public RenderTargetCreationFlags CreationFlags
{
get; set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get; set;
}
/// <summary>
/// Number of samples for MSAA
/// </summary>
public uint SampleCount
{
get; set;
}
/// <summary>
/// Creates a color render Target
/// </summary>
public static RenderTargetDesc Color(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution | RenderTargetCreationFlags.GenerateMips,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Color,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
/// <summary>
/// Creates a depth render Target
/// </summary>
public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Depth,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TextureDesc ToTextureDescripton()
{
var usage = Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
if (CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
{
usage |= TextureUsage.UnorderedAccess;
}
return new TextureDesc
{
Width = Width,
Height = Height,
Slice = Slice,
Format = Format,
Dimension = Dimension,
MipLevels = MipLevels,
Usage = usage,
};
}
}
/// <summary>
/// Texture description
/// </summary>
public struct TextureDesc
{
/// <summary>
/// Width of the texture
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the texture
/// </summary>
public uint Height
{
get; set;
}
/// <summary>
/// Slice of the texture
/// </summary>
public uint Slice
{
get; set;
}
/// <summary>
/// Texture Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get;
set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get; set;
}
/// <summary>
/// Texture usage flags
/// </summary>
public TextureUsage Usage
{
get; set;
}
}
/// <summary>
/// Describes the parameters used to configure a texture sampler for graphics rendering operations.
/// </summary>
public record struct SamplerDesc
{
public TextureFilterMode FilterMode
{
get; set;
}
public TextureAddressMode AddressU
{
get; set;
}
public TextureAddressMode AddressV
{
get; set;
}
public TextureAddressMode AddressW
{
get; set;
}
public ComparisonFunction ComparisonFunc
{
get; set;
}
public float MipLODBias
{
get; set;
}
public uint MaxAnisotropy
{
get; set;
}
public float MinLOD
{
get; set;
}
public float MaxLOD
{
get; set;
}
}
/// <summary>
/// Buffer description
/// </summary>
public struct BufferDesc
{
/// <summary>
/// Size of the buffer in bytes
/// </summary>
public ulong Size
{
get; set;
}
public uint Stride
{
get; set;
}
/// <summary>
/// Buffer usage flags
/// </summary>
public BufferUsage Usage
{
get; set;
}
/// <summary>
/// Memory space for the buffer
/// </summary>
public ResourceMemoryType MemoryType
{
get; set;
}
}
public struct CommandError
{
public int CommandIndex
{
get; set;
}
public string CommandName
{
get; set;
}
public ErrorStatus Status
{
get; set;
}
}
/// <summary>
/// Swap chain description
/// </summary>
public struct SwapChainDesc
{
/// <summary>
/// Width of the swap chain
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the swap chain
/// </summary>
public uint Height
{
get; set;
}
public float ScaleX
{
get; set;
}
public float ScaleY
{
get; set;
}
/// <summary>
/// Back buffer Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Target for presentation (window handle or composition Target)
/// </summary>
public SwapChainTarget Target
{
get; set;
}
}
/// <summary>
/// Swap chain Target (window handle or composition surface)
/// </summary>
public struct SwapChainTarget
{
/// <summary>
/// Target space
/// </summary>
public SwapChainTargetType Type
{
get; set;
}
/// <summary>
/// Window handle for HWND targets
/// </summary>
public nint WindowHandle
{
get; set;
}
/// <summary>
/// Composition surface for UWP/WinUI targets
/// </summary>
public object? CompositionSurface
{
get; set;
}
public static SwapChainTarget FromWindowHandle(nint hwnd)
{
return new SwapChainTarget
{
Type = SwapChainTargetType.WindowHandle,
WindowHandle = hwnd,
CompositionSurface = null
};
}
public static SwapChainTarget FromCompositionSurface(object surface)
{
return new SwapChainTarget
{
Type = SwapChainTargetType.Composition,
WindowHandle = 0,
CompositionSurface = surface
};
}
}
public enum SwapChainTargetType
{
WindowHandle,
Composition
}
[Flags]
public enum ResourceState
{
Common = 0,
VertexAndConstantBuffer = 1 << 0,
IndexBuffer = 1 << 1,
RenderTarget = 1 << 2,
UnorderedAccess = 1 << 3,
DepthWrite = 1 << 4,
DepthRead = 1 << 5,
PixelShaderResource = 1 << 6,
CopyDest = 1 << 7,
CopySource = 1 << 8,
GenericRead = 1 << 9,
IndirectArgument = 1 << 10,
NonPixelShaderResource = 1 << 11,
Present = 0,
}
public enum CommandQueueType
{
Graphics,
Compute,
Copy
}
public enum CommandBufferType
{
Graphics,
Compute,
Copy
}
public enum PipelineType
{
Graphics,
Compute
}
[Flags]
public enum RenderTargetCreationFlags
{
None = 0,
AllowUAV = 1 << 0,
AllowMSAA = 1 << 1,
DynamicallyResolution = 1 << 2,
GenerateMips = 1 << 3
}
public enum ResourceMemoryType
{
Default, // GPU memory
Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory
}
[Flags]
public enum TextureUsage
{
None = 0,
ShaderResource = 1 << 0,
RenderTarget = 1 << 1,
DepthStencil = 1 << 2,
UnorderedAccess = 1 << 3
}
public enum TextureDimension
{
Unknown = -1,
None = 0,
Texture2D = 1,
Texture3D = 2,
TextureCube = 3,
Texture2DArray = 4,
TextureCubeArray = 5
}
public enum RenderTargetType
{
Color,
Depth
}
// TODO: Support compressed formats (BCn, ASTC, ETC2, etc)
public enum TextureFormat
{
Unknown,
R8G8B8A8_UNorm,
B8G8R8A8_UNorm,
R16G16B16A16_Float,
R32G32B32A32_Float,
D24_UNorm_S8_UInt,
D32_Float
}
[Flags]
public enum BufferUsage
{
None = 0,
Vertex = 1 << 0,
Index = 1 << 1,
IndirectArgument = 1 << 7,
Constant = 1 << 2,
ShaderResource = 1 << 3,
UnorderedAccess = 1 << 4,
Structured = 1 << 5,
Raw = 1 << 6,
Upload = 1 << 8,
Readback = 1 << 9,
}
public enum IndexType
{
UInt16,
UInt32
}
public enum PrimitiveTopology
{
Point,
Line,
Triangle,
}
public enum TextureFilterMode
{
Point,
Bilinear,
Trilinear,
Anisotropic
}
public enum TextureAddressMode
{
Repeat,
Mirror,
Clamp,
Border,
MirrorOnce
}
public enum ComparisonFunction
{
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always
}