forked from Misaki/GhostEngine
Refactor: variant-aware shader/material pipeline overhaul
Major architectural update to graphics/material/shader system: - Introduced strongly-typed key structs (Key64/Key128) for passes, variants, and pipelines; removed legacy key types. - Implemented robust hashing and key generation utilities for efficient variant and pipeline lookup/caching. - Shader compiler now compiles/caches all keyword variants using new key system; includes handled as lists. - Switched to push constant root signature for per-draw data; updated HLSL and C# codegen accordingly. - Refactored Material, Shader, and Pass data structures for cache efficiency and variant support. - Pipeline library and PSO management now use 128-bit keys and variant-specific caching. - Replaced WorldNode with SceneNode in editor/scene graph; introduced ComponentManager for archetype/query management. - Migrated math utilities to Misaki.HighPerformance.Mathematics; updated editor controls. - Updated all HLSL and codegen for new buffer/push constant layouts and macros. - Misc: project reference cleanup, D3D12 Work Graph support, doc updates, and code modernization.
This commit is contained in:
305
Ghost.DSL/Generator/ShaderStructGenerator.cs
Normal file
305
Ghost.DSL/Generator/ShaderStructGenerator.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ghost.DSL.Generator;
|
||||
|
||||
public enum PackingRules
|
||||
{
|
||||
Exact,
|
||||
Aligned,
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum)]
|
||||
public class GenerateHLSLAttribute : Attribute
|
||||
{
|
||||
private readonly PackingRules _packingRules;
|
||||
private readonly string? _outputSource;
|
||||
|
||||
public GenerateHLSLAttribute(PackingRules packingRules, string? outputSource)
|
||||
{
|
||||
_packingRules = packingRules;
|
||||
_outputSource = outputSource;
|
||||
}
|
||||
}
|
||||
|
||||
internal static partial class ShaderStructGenerator
|
||||
{
|
||||
private struct ShaderFieldInfo
|
||||
{
|
||||
public string name;
|
||||
public Type fieldType;
|
||||
|
||||
public ShaderFieldInfo(string name, Type fieldType)
|
||||
{
|
||||
this.name = name;
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
public ShaderFieldInfo(FieldInfo fieldInfo)
|
||||
: this(fieldInfo.Name, fieldInfo.FieldType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private const int _HLSL_VECTOR_REGISTER_SIZE = 16; // 16 bytes (128 bits) for float4
|
||||
|
||||
private static void GenerateEnumHLSL(Type type, StringBuilder sb)
|
||||
{
|
||||
if (!type.IsEnum)
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullName} is not an enum.");
|
||||
}
|
||||
|
||||
var enumName = type.Name;
|
||||
//var underlyingType = Enum.GetUnderlyingType(space);
|
||||
//var underlyingTypeName = underlyingType switch
|
||||
//{
|
||||
// Type t when t == typeof(byte) || t == typeof(short) || t == typeof(int) => "int",
|
||||
// Type t when t == typeof(sbyte) || t == typeof(ushort) || t == typeof(uint) => "uint",
|
||||
// _ => throw new InvalidOperationException($"Unsupported underlying space {underlyingType.FullName} for enum {enumName}."),
|
||||
//};
|
||||
|
||||
// sb.Append(@$"
|
||||
//enum {enumName} : {underlyingTypeName}
|
||||
//{{");
|
||||
var names = Enum.GetNames(type);
|
||||
var values = Enum.GetValuesAsUnderlyingType(type);
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var name = $"{CamelCaseToUnderscoreRegex().Replace(enumName, "_$1")}_{names[i]}";
|
||||
var value = values.GetValue(i);
|
||||
// sb.Append(@$"
|
||||
//{name} = {Value},");
|
||||
sb.Append(@$"
|
||||
#define {name.ToUpperInvariant()} {value}"); // Use #define for capability. Enum is only support for newer HLSL versions.
|
||||
}
|
||||
// sb.AppendLine(@"
|
||||
//};");
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
public static int FindNextFieldThatFits(FieldInfo[] fields, bool[] looked, int startIndex, int size, out int foundIndex)
|
||||
{
|
||||
if (size <= 0)
|
||||
{
|
||||
foundIndex = -1;
|
||||
return size;
|
||||
}
|
||||
|
||||
var bestFitIndex = -1;
|
||||
var bestFitSize = 0;
|
||||
|
||||
for (var j = startIndex; j < fields.Length; j++)
|
||||
{
|
||||
if (looked[j])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var nextField = fields[j];
|
||||
var nextSize = Marshal.SizeOf(nextField.FieldType);
|
||||
if (nextSize <= size)
|
||||
{
|
||||
if (nextSize == size)
|
||||
{
|
||||
foundIndex = j;
|
||||
return nextSize;
|
||||
}
|
||||
|
||||
if (nextSize > bestFitSize)
|
||||
{
|
||||
bestFitSize = nextSize;
|
||||
bestFitIndex = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestFitIndex != -1)
|
||||
{
|
||||
foundIndex = bestFitIndex;
|
||||
return bestFitSize;
|
||||
}
|
||||
|
||||
foundIndex = -1;
|
||||
return size;
|
||||
}
|
||||
|
||||
private static void GenerateStructHLSL(Type type, PackingRules packingRules, StringBuilder sb)
|
||||
{
|
||||
if (!type.IsValueType || type.IsPrimitive)
|
||||
{
|
||||
throw new InvalidOperationException($"Type {type.FullName} is not a struct.");
|
||||
}
|
||||
|
||||
var structName = type.Name;
|
||||
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(static f => f.FieldType.IsValueType).ToArray();
|
||||
|
||||
var shaderFields = new ShaderFieldInfo[fields.Length];
|
||||
if (packingRules == PackingRules.Aligned)
|
||||
{
|
||||
var sortedFields = new List<ShaderFieldInfo>(fields.Length);
|
||||
var looked = new bool[fields.Length];
|
||||
var paddingIndex = 0;
|
||||
|
||||
// Sort the fields to align them to HLSL vector registers (16 bytes)
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
if (looked[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var field = fields[i];
|
||||
var size = Marshal.SizeOf(field.FieldType);
|
||||
|
||||
sortedFields.Add(new ShaderFieldInfo(field));
|
||||
|
||||
var registerRemaining = _HLSL_VECTOR_REGISTER_SIZE - (size % _HLSL_VECTOR_REGISTER_SIZE);
|
||||
while (true)
|
||||
{
|
||||
var nextSize = FindNextFieldThatFits(fields, looked, i + 1, registerRemaining, out var nextIndex);
|
||||
if (nextSize == 0 || nextIndex == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
looked[i] = true;
|
||||
looked[nextIndex] = true;
|
||||
|
||||
sortedFields.Add(new ShaderFieldInfo(fields[nextIndex]));
|
||||
|
||||
registerRemaining -= nextSize;
|
||||
}
|
||||
|
||||
if (registerRemaining != 0)
|
||||
{
|
||||
// Add padding if necessary
|
||||
var count = registerRemaining / sizeof(float);
|
||||
for (var p = 0; p < count; p++)
|
||||
{
|
||||
sortedFields.Add(new ShaderFieldInfo($"_padding{paddingIndex++}", typeof(float)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shaderFields = sortedFields.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
shaderFields[i] = new ShaderFieldInfo(fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(@$"
|
||||
struct {structName}
|
||||
{{");
|
||||
foreach (var field in shaderFields)
|
||||
{
|
||||
var fieldType = field.fieldType;
|
||||
var fieldName = field.name;
|
||||
|
||||
string hlslType;
|
||||
switch (fieldType)
|
||||
{
|
||||
case Type t when t == typeof(float):
|
||||
hlslType = "float";
|
||||
break;
|
||||
case Type t when t == typeof(double):
|
||||
hlslType = "double";
|
||||
break;
|
||||
case Type t when t == typeof(int):
|
||||
hlslType = "int";
|
||||
break;
|
||||
case Type t when t == typeof(uint):
|
||||
hlslType = "uint";
|
||||
break;
|
||||
case Type t when t == typeof(bool):
|
||||
hlslType = "bool";
|
||||
break;
|
||||
case Type t when t == typeof(Vector2):
|
||||
hlslType = "float2";
|
||||
break;
|
||||
case Type t when t == typeof(Vector3):
|
||||
hlslType = "float3";
|
||||
break;
|
||||
case Type t when t == typeof(Vector4):
|
||||
hlslType = "float4";
|
||||
break;
|
||||
case Type t when t == typeof(Matrix4x4):
|
||||
hlslType = "float4x4";
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (fieldType.Namespace == typeof(float2).Namespace)
|
||||
{
|
||||
if (fieldType.Name.StartsWith("float")
|
||||
|| fieldType.Name.StartsWith("double")
|
||||
|| fieldType.Name.StartsWith("int")
|
||||
|| fieldType.Name.StartsWith("uint")
|
||||
|| fieldType.Name.StartsWith("bool"))
|
||||
{
|
||||
hlslType = fieldType.Name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unsupported field type: {fieldType.FullName} in struct {structName}.");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(@$"
|
||||
{hlslType} {fieldName};");
|
||||
}
|
||||
|
||||
sb.AppendLine(@"
|
||||
};");
|
||||
}
|
||||
|
||||
public static void GenerateHLSL(ReadOnlySpan<Type> types, PackingRules packingRules, string outputSource)
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(outputSource)))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"The directory for the output source '{outputSource}' does not exist.");
|
||||
}
|
||||
|
||||
var hlslDefine = $"{Path.GetFileNameWithoutExtension(outputSource).ToUpperInvariant().Replace('.', '_')}_HLSL";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(@$"// Auto-generated HLSL code, please do not edit this file directly.
|
||||
|
||||
#ifndef {hlslDefine}
|
||||
#define {hlslDefine}");
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
GenerateEnumHLSL(type, sb);
|
||||
}
|
||||
else if (type.IsValueType && !type.IsPrimitive)
|
||||
{
|
||||
GenerateStructHLSL(type, packingRules, sb);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
sb.Append(@"
|
||||
#endif");
|
||||
|
||||
var hlslCode = sb.ToString();
|
||||
File.WriteAllText(outputSource, hlslCode);
|
||||
}
|
||||
|
||||
[GeneratedRegex("(?<=[a-z])([A-Z])")]
|
||||
private static partial Regex CamelCaseToUnderscoreRegex();
|
||||
}
|
||||
Reference in New Issue
Block a user