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

225 lines
5.9 KiB
C#

using System.Runtime.InteropServices;
namespace Ghost.Shader.Concept;
/// <summary>
/// Material property types supported by the system.
/// </summary>
public enum MaterialPropertyType : byte
{
Float,
Float2,
Float3,
Float4,
Int,
Matrix4x4,
Texture2D,
TextureCube
}
/// <summary>
/// Metadata for a material property.
/// </summary>
public readonly struct MaterialPropertyInfo
{
public readonly string Name;
public readonly MaterialPropertyType Type;
public readonly int Offset;
public readonly int Size;
public MaterialPropertyInfo(string name, MaterialPropertyType type, int offset, int size)
{
Name = name;
Type = type;
Offset = offset;
Size = size;
}
}
/// <summary>
/// Thread-safe storage for material properties using linear memory layout.
/// Optimized for fast updates and GPU buffer uploads.
/// </summary>
public unsafe sealed class MaterialPropertyBlock : IDisposable
{
private byte* _data;
private int _capacity;
private int _size;
private readonly object _lock = new();
private readonly Dictionary<string, MaterialPropertyInfo> _properties = new();
public int Size => _size;
public IntPtr DataPtr => (IntPtr)_data;
public MaterialPropertyBlock(int initialCapacity = 1024)
{
_capacity = initialCapacity;
_data = (byte*)Marshal.AllocHGlobal(_capacity);
_size = 0;
}
~MaterialPropertyBlock()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_data != null)
{
Marshal.FreeHGlobal((IntPtr)_data);
_data = null;
}
}
private void EnsureCapacity(int required)
{
if (_capacity >= required) return;
int newCapacity = Math.Max(_capacity * 2, required);
byte* newData = (byte*)Marshal.AllocHGlobal(newCapacity);
Buffer.MemoryCopy(_data, newData, newCapacity, _size);
Marshal.FreeHGlobal((IntPtr)_data);
_data = newData;
_capacity = newCapacity;
}
public void SetFloat(string name, float value)
{
lock (_lock)
{
var info = GetOrCreateProperty(name, MaterialPropertyType.Float, sizeof(float));
*(float*)(_data + info.Offset) = value;
}
}
public void SetVector2(string name, float x, float y)
{
lock (_lock)
{
var info = GetOrCreateProperty(name, MaterialPropertyType.Float2, sizeof(float) * 2);
float* ptr = (float*)(_data + info.Offset);
ptr[0] = x;
ptr[1] = y;
}
}
public void SetVector3(string name, float x, float y, float z)
{
lock (_lock)
{
var info = GetOrCreateProperty(name, MaterialPropertyType.Float3, sizeof(float) * 3);
float* ptr = (float*)(_data + info.Offset);
ptr[0] = x;
ptr[1] = y;
ptr[2] = z;
}
}
public void SetVector4(string name, float x, float y, float z, float w)
{
lock (_lock)
{
var info = GetOrCreateProperty(name, MaterialPropertyType.Float4, sizeof(float) * 4);
float* ptr = (float*)(_data + info.Offset);
ptr[0] = x;
ptr[1] = y;
ptr[2] = z;
ptr[3] = w;
}
}
public void SetInt(string name, int value)
{
lock (_lock)
{
var info = GetOrCreateProperty(name, MaterialPropertyType.Int, sizeof(int));
*(int*)(_data + info.Offset) = value;
}
}
public void SetMatrix4x4(string name, ReadOnlySpan<float> matrix)
{
if (matrix.Length != 16)
throw new ArgumentException("Matrix must have 16 elements", nameof(matrix));
lock (_lock)
{
var info = GetOrCreateProperty(name, MaterialPropertyType.Matrix4x4, sizeof(float) * 16);
fixed (float* src = matrix)
{
Buffer.MemoryCopy(src, _data + info.Offset, info.Size, info.Size);
}
}
}
public bool TryGetFloat(string name, out float value)
{
lock (_lock)
{
if (_properties.TryGetValue(name, out var info) && info.Type == MaterialPropertyType.Float)
{
value = *(float*)(_data + info.Offset);
return true;
}
value = 0;
return false;
}
}
public void CopyTo(byte* destination, int maxSize)
{
lock (_lock)
{
int copySize = Math.Min(_size, maxSize);
Buffer.MemoryCopy(_data, destination, maxSize, copySize);
}
}
public void CopyFrom(MaterialPropertyBlock source)
{
lock (_lock)
{
lock (source._lock)
{
EnsureCapacity(source._size);
Buffer.MemoryCopy(source._data, _data, _capacity, source._size);
_size = source._size;
_properties.Clear();
foreach (var kvp in source._properties)
{
_properties[kvp.Key] = kvp.Value;
}
}
}
}
private MaterialPropertyInfo GetOrCreateProperty(string name, MaterialPropertyType type, int size)
{
if (_properties.TryGetValue(name, out var existing))
{
if (existing.Type != type)
throw new InvalidOperationException($"Property {name} type mismatch: expected {existing.Type}, got {type}");
return existing;
}
// Align to 16 bytes for GPU compatibility
int offset = (_size + 15) & ~15;
int alignedSize = (size + 15) & ~15;
EnsureCapacity(offset + alignedSize);
var info = new MaterialPropertyInfo(name, type, offset, alignedSize);
_properties[name] = info;
_size = offset + alignedSize;
return info;
}
}