Files
GhostEngine/Ghost.Shader.Concept/KeywordSet.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

162 lines
4.8 KiB
C#

using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
namespace Ghost.Shader.Concept;
/// <summary>
/// Compact representation of enabled keywords using bitsets.
/// Supports up to 256 global and 256 local keywords with O(1) operations.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public unsafe struct KeywordSet : IEquatable<KeywordSet>
{
private const int GlobalBits = 256;
private const int LocalBits = 256;
private const int GlobalLongs = GlobalBits / 64;
private const int LocalLongs = LocalBits / 64;
private fixed ulong _globalBits[GlobalLongs];
private fixed ulong _localBits[LocalLongs];
public void Enable(ShaderKeyword keyword)
{
fixed (ulong* global = _globalBits, local = _localBits)
{
if (keyword.Scope == KeywordScope.Global)
SetBit(global, GlobalLongs, keyword.Id);
else
SetBit(local, LocalLongs, keyword.Id);
}
}
public void Disable(ShaderKeyword keyword)
{
fixed (ulong* global = _globalBits, local = _localBits)
{
if (keyword.Scope == KeywordScope.Global)
ClearBit(global, GlobalLongs, keyword.Id);
else
ClearBit(local, LocalLongs, keyword.Id);
}
}
public readonly bool IsEnabled(ShaderKeyword keyword)
{
fixed (ulong* global = _globalBits, local = _localBits)
{
if (keyword.Scope == KeywordScope.Global)
return GetBit(global, GlobalLongs, keyword.Id);
else
return GetBit(local, LocalLongs, keyword.Id);
}
}
public void Clear()
{
fixed (ulong* global = _globalBits, local = _localBits)
{
for (int i = 0; i < GlobalLongs; i++)
global[i] = 0;
for (int i = 0; i < LocalLongs; i++)
local[i] = 0;
}
}
public void SetGlobal(KeywordSet* other)
{
fixed (ulong* dest = _globalBits)
{
for (int i = 0; i < GlobalLongs; i++)
dest[i] = other->_globalBits[i];
}
}
public void SetLocal(KeywordSet* other)
{
fixed (ulong* dest = _localBits)
{
for (int i = 0; i < LocalLongs; i++)
dest[i] = other->_localBits[i];
}
}
public static unsafe KeywordSet Merge(KeywordSet* a, KeywordSet* b)
{
KeywordSet result;
KeywordSet* pResult = &result;
for (int i = 0; i < GlobalLongs; i++)
pResult->_globalBits[i] = a->_globalBits[i] | b->_globalBits[i];
for (int i = 0; i < LocalLongs; i++)
pResult->_localBits[i] = a->_localBits[i] | b->_localBits[i];
return result;
}
public readonly ulong ComputeHash()
{
ulong hash = 0xcbf29ce484222325; // FNV-1a offset
const ulong prime = 0x100000001b3;
fixed (ulong* global = _globalBits, local = _localBits)
{
for (int i = 0; i < GlobalLongs; i++)
{
hash ^= global[i];
hash *= prime;
}
for (int i = 0; i < LocalLongs; i++)
{
hash ^= local[i];
hash *= prime;
}
}
return hash;
}
public readonly bool Equals(KeywordSet other)
{
fixed (ulong* thisGlobal = _globalBits, thisLocal = _localBits)
{
for (int i = 0; i < GlobalLongs; i++)
if (thisGlobal[i] != other._globalBits[i])
return false;
for (int i = 0; i < LocalLongs; i++)
if (thisLocal[i] != other._localBits[i])
return false;
}
return true;
}
public override readonly bool Equals(object? obj) => obj is KeywordSet other && Equals(other);
public override readonly int GetHashCode() => (int)ComputeHash();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetBit(ulong* bits, int count, int index)
{
if (index < 0 || index >= count * 64) return;
int longIndex = index / 64;
int bitIndex = index % 64;
bits[longIndex] |= (1UL << bitIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ClearBit(ulong* bits, int count, int index)
{
if (index < 0 || index >= count * 64) return;
int longIndex = index / 64;
int bitIndex = index % 64;
bits[longIndex] &= ~(1UL << bitIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetBit(ulong* bits, int count, int index)
{
if (index < 0 || index >= count * 64) return false;
int longIndex = index / 64;
int bitIndex = index % 64;
return (bits[longIndex] & (1UL << bitIndex)) != 0;
}
}