Add shader property reflection and resource handles
Refactor shader property system to support runtime reflection via ShaderPropertyType and ShaderPropertyFieldInfo. Introduce strongly-typed Texture2D/3D and Buffer handle structs. Update ShaderPropertiesGenerator to emit field metadata and register it. Move mesh content structs to AssetManager.Mesh.cs and mark as internal. Update DSLShaderCompiler and registry for new property API. Remove obsolete files and clean up namespaces. Add sample TestShaderProperty struct.
This commit is contained in:
@@ -1,229 +0,0 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ghost.Generator
|
||||
{
|
||||
[Generator]
|
||||
public class ComponentSerializationGenerator : IIncrementalGenerator
|
||||
{
|
||||
private string GetJsonWriteCall(ITypeSymbol type, string fieldName)
|
||||
{
|
||||
switch (type.SpecialType)
|
||||
{
|
||||
case SpecialType.System_Byte:
|
||||
case SpecialType.System_SByte:
|
||||
case SpecialType.System_Int16:
|
||||
case SpecialType.System_Int32:
|
||||
case SpecialType.System_Int64:
|
||||
case SpecialType.System_Single:
|
||||
case SpecialType.System_Double:
|
||||
case SpecialType.System_Decimal:
|
||||
case SpecialType.System_UInt64:
|
||||
case SpecialType.System_UInt16:
|
||||
case SpecialType.System_UInt32:
|
||||
case SpecialType.System_IntPtr:
|
||||
case SpecialType.System_UIntPtr:
|
||||
return $@"writer.WriteNumber(""{fieldName}"", value.{fieldName});";
|
||||
case SpecialType.System_Boolean:
|
||||
return $@"writer.WriteBoolean(""{fieldName}"", value.{fieldName});";
|
||||
case SpecialType.System_Char:
|
||||
return $@"writer.WriteString(""{fieldName}"", [value.{fieldName}]);";
|
||||
case SpecialType.System_String:
|
||||
return $@"writer.WriteString(""{fieldName}"", value.{fieldName});";
|
||||
}
|
||||
|
||||
return $@"writer.WritePropertyName(""{fieldName}""); global::System.Text.Json.JsonSerializer.Serialize(writer, value.{fieldName}, options);";
|
||||
}
|
||||
|
||||
private string GetJsonReadCall(ITypeSymbol type)
|
||||
{
|
||||
switch (type.SpecialType)
|
||||
{
|
||||
case SpecialType.System_Byte: return "reader.GetByte()";
|
||||
case SpecialType.System_SByte: return "reader.GetSByte()";
|
||||
case SpecialType.System_Int16: return "reader.GetInt16()";
|
||||
case SpecialType.System_Int32: return "reader.GetInt32()";
|
||||
case SpecialType.System_Int64: return "reader.GetInt64()";
|
||||
case SpecialType.System_Single: return "reader.GetSingle()";
|
||||
case SpecialType.System_Double: return "reader.GetDouble()";
|
||||
case SpecialType.System_Decimal: return "reader.GetDecimal()";
|
||||
case SpecialType.System_UInt16: return "reader.GetUInt16()";
|
||||
case SpecialType.System_UInt32: return "reader.GetUInt32()";
|
||||
case SpecialType.System_UInt64: return "reader.GetUInt64()";
|
||||
// Note: the size of IntPtr and UIntPtr varies by platform, we use Int64/UInt64 to ensure compatibility
|
||||
case SpecialType.System_IntPtr: return "reader.GetInt64()";
|
||||
case SpecialType.System_UIntPtr: return "reader.GetUInt64()";
|
||||
case SpecialType.System_Boolean: return "reader.GetBoolean()";
|
||||
case SpecialType.System_Char: return "reader.GetString()[0]";
|
||||
case SpecialType.System_String: return "reader.GetString()";
|
||||
}
|
||||
|
||||
return $"global::System.Text.Json.JsonSerializer.Deserialize<{type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(ref reader, options)";
|
||||
}
|
||||
|
||||
private void GenerateJsonSerializer(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> symbols)
|
||||
{
|
||||
var sbWrites = new StringBuilder();
|
||||
var sbReads = new StringBuilder();
|
||||
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
|
||||
var structName = symbol.Name;
|
||||
var fullTypeName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
|
||||
// 1. Build Field Logic (Same as before)
|
||||
sbWrites.Clear();
|
||||
sbReads.Clear();
|
||||
|
||||
var fields = symbol.GetMembers()
|
||||
.OfType<IFieldSymbol>()
|
||||
.Where(f => !f.IsStatic && f.DeclaredAccessibility == Accessibility.Public);
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
// Note: GetJsonWriteCall returns a string ending with ";"
|
||||
var writeCall = GetJsonWriteCall(field.Type, field.Name);
|
||||
if (writeCall != null)
|
||||
{
|
||||
sbWrites.Append(" "); // Indentation
|
||||
sbWrites.AppendLine(writeCall);
|
||||
}
|
||||
|
||||
var readCall = GetJsonReadCall(field.Type);
|
||||
if (readCall != null)
|
||||
{
|
||||
// Note the double quotes ""{field.Name}"" for the case string
|
||||
sbReads.Append(" "); // Indentation
|
||||
sbReads.AppendLine($@"case ""{field.Name}"": result.{field.Name} = {readCall}; break;");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. The Main Template using $@
|
||||
// Watch the double braces {{ }} and double quotes "" ""
|
||||
var sourceCode = $@"// <auto-generated/>
|
||||
#nullable enable
|
||||
|
||||
namespace {namespaceName}
|
||||
{{
|
||||
public unsafe static class {structName}_Serializer
|
||||
{{
|
||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
||||
internal static void Init()
|
||||
{{
|
||||
var id = Ghost.Entities.ComponentTypeID<{fullTypeName}>.Value;
|
||||
Ghost.Engine.IO.ComponentSerializerRegistry.Register(id, SerializeBinaryUnsafe, SerializeJsonUnsafe);
|
||||
}}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// BINARY (Fast Path)
|
||||
// ---------------------------------------------------------
|
||||
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe void SerializeBinaryUnsafe(global::System.IO.BinaryWriter writer, void* value)
|
||||
{{
|
||||
writer.Write(new global::System.ReadOnlySpan<byte>(value, sizeof({fullTypeName})));
|
||||
}}
|
||||
|
||||
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static void SerializeBinary(this global::System.IO.BinaryWriter writer, ref {fullTypeName} value)
|
||||
{{
|
||||
unsafe {{ writer.Write(new global::System.ReadOnlySpan<byte>(global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref value), sizeof({fullTypeName}))); }}
|
||||
}}
|
||||
|
||||
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static void DeserializeBinary(this global::System.IO.BinaryReader reader, ref {fullTypeName} value)
|
||||
{{
|
||||
unsafe {{ reader.Read(new global::System.Span<byte>(global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref value), sizeof({fullTypeName}))); }}
|
||||
}}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// JSON WRITE
|
||||
// ---------------------------------------------------------
|
||||
public static unsafe void SerializeJsonUnsafe(System.Text.Json.Utf8JsonWriter writer, void* ptr, System.Text.Json.JsonSerializerOptions? options)
|
||||
{{
|
||||
SerializeJson(writer, ref *( {fullTypeName}*)ptr, options);
|
||||
}}
|
||||
|
||||
public static void SerializeJson(this global::System.Text.Json.Utf8JsonWriter writer, ref {fullTypeName} value, global::System.Text.Json.JsonSerializerOptions? options)
|
||||
{{
|
||||
writer.WriteStartObject();
|
||||
{sbWrites}
|
||||
writer.WriteEndObject();
|
||||
}}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// JSON READ
|
||||
// ---------------------------------------------------------
|
||||
public static {fullTypeName} DeserializeJson(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Text.Json.JsonSerializerOptions? options)
|
||||
{{
|
||||
var result = default({fullTypeName});
|
||||
|
||||
if (reader.TokenType != global::System.Text.Json.JsonTokenType.StartObject) throw new global::System.Text.Json.JsonException();
|
||||
|
||||
while (reader.Read())
|
||||
{{
|
||||
if (reader.TokenType == global::System.Text.Json.JsonTokenType.EndObject) return result;
|
||||
if (reader.TokenType != global::System.Text.Json.JsonTokenType.PropertyName) throw new global::System.Text.Json.JsonException();
|
||||
|
||||
var propName = reader.GetString();
|
||||
reader.Read();
|
||||
|
||||
switch (propName)
|
||||
{{
|
||||
{sbReads}
|
||||
default: reader.Skip(); break;
|
||||
}}
|
||||
}}
|
||||
return result;
|
||||
}}
|
||||
}}
|
||||
}}";
|
||||
|
||||
context.AddSource($"{structName}.Serializer.gen.cs", sourceCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
return;
|
||||
|
||||
var componentCandidates = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: (syntaxNode, _) => syntaxNode is StructDeclarationSyntax,
|
||||
transform: (ctx, _) =>
|
||||
{
|
||||
var structSyntax = (StructDeclarationSyntax)ctx.Node;
|
||||
|
||||
if (!(ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is INamedTypeSymbol symbol))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var compilation = ctx.SemanticModel.Compilation;
|
||||
var iComponentSymbol = compilation.GetTypeByMetadataName("Ghost.Entities.IComponent");
|
||||
|
||||
if (iComponentSymbol == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var iface in symbol.AllInterfaces)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(iface, iComponentSymbol))
|
||||
{
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(symbol => symbol != null)
|
||||
.Collect();
|
||||
|
||||
context.RegisterSourceOutput(componentCandidates, GenerateJsonSerializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,10 @@ namespace Ghost.Generator
|
||||
|
||||
var definedSymbol = $"__{info.Name.ToUpper()}_G_HLSL";
|
||||
|
||||
var fieldsBuilder = new StringBuilder();
|
||||
fieldsBuilder.AppendLine($" public static readonly global::Ghost.Core.Graphics.ShaderPropertyFieldInfo[] ReflectionData = new global::Ghost.Core.Graphics.ShaderPropertyFieldInfo[]");
|
||||
fieldsBuilder.AppendLine(" {");
|
||||
|
||||
var fields = info.TypeSymbol.GetMembers().OfType<IFieldSymbol>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
@@ -103,6 +107,7 @@ namespace Ghost.Generator
|
||||
|
||||
var hlslTypeAttribute = field.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Engine.Utilities.GenerateAsHLSLTypeAttribute");
|
||||
var hlslType = string.Empty;
|
||||
var shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Unknown";
|
||||
|
||||
if (hlslTypeAttribute == null)
|
||||
{
|
||||
@@ -110,34 +115,51 @@ namespace Ghost.Generator
|
||||
{
|
||||
case SpecialType.System_Single:
|
||||
hlslType = "float";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float";
|
||||
break;
|
||||
case SpecialType.System_Int32:
|
||||
hlslType = "int";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Int";
|
||||
break;
|
||||
case SpecialType.System_UInt32:
|
||||
hlslType = "uint";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.UInt";
|
||||
break;
|
||||
default:
|
||||
var typeName = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
switch (typeName)
|
||||
{
|
||||
case "global::System.Numerics.Vector2":
|
||||
case "global::System.Numerics.Vector2" or "global::Misaki.HighPerformance.Mathematics.float2":
|
||||
hlslType = "float2";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float2";
|
||||
break;
|
||||
case "global::System.Numerics.Vector3":
|
||||
case "global::System.Numerics.Vector3" or "global::Misaki.HighPerformance.Mathematics.float3":
|
||||
hlslType = "float3";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float3";
|
||||
break;
|
||||
case "global::System.Numerics.Vector4":
|
||||
case "global::System.Numerics.Vector4" or "global::Misaki.HighPerformance.Mathematics.float4":
|
||||
hlslType = "float4";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float4";
|
||||
break;
|
||||
case "global::System.Numerics.Matrix3x3":
|
||||
hlslType = "float3x3";
|
||||
break;
|
||||
case "global::System.Numerics.Matrix4x4":
|
||||
case "global::System.Numerics.Matrix4x4" or "global::Misaki.HighPerformance.Mathematics.float4x4":
|
||||
hlslType = "float4x4";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float4x4";
|
||||
break;
|
||||
case "global::System.Numerics.Quaternion" or "global::Misaki.HighPerformance.Mathematics.quaternion":
|
||||
hlslType = "float4";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float4";
|
||||
break;
|
||||
case "global::Ghost.Core.Graphics.Texture2DHandle":
|
||||
hlslType = "uint";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Texture2D";
|
||||
break;
|
||||
case "global::Ghost.Core.Graphics.Texture3DHandle":
|
||||
hlslType = "uint";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Texture3D";
|
||||
break;
|
||||
case "global::Ghost.Core.Graphics.BufferHandle":
|
||||
hlslType = "uint";
|
||||
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Buffer";
|
||||
break;
|
||||
case var _ when typeName.StartsWith("global::Misaki.HighPerformance.Mathematics."):
|
||||
hlslType = typeName.Substring("global::Misaki.HighPerformance.Mathematics.".Length);
|
||||
@@ -167,8 +189,12 @@ namespace Ghost.Generator
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine($" {hlslType} {field.Name};");
|
||||
|
||||
fieldsBuilder.AppendLine($" new global::Ghost.Core.Graphics.ShaderPropertyFieldInfo {{ Name = \"{field.Name}\", Type = {shaderPropType}, Offset = (int)global::System.Runtime.InteropServices.Marshal.OffsetOf<{info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(\"{field.Name}\") }},");
|
||||
}
|
||||
|
||||
fieldsBuilder.AppendLine(" };");
|
||||
|
||||
var code = $@"// <auto-generated/>
|
||||
|
||||
namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
||||
@@ -185,15 +211,17 @@ struct {info.Name}
|
||||
{codeBuilder}
|
||||
}};
|
||||
# endif // {definedSymbol}"";
|
||||
}}
|
||||
|
||||
{fieldsBuilder}
|
||||
#endif
|
||||
}}
|
||||
}}";
|
||||
|
||||
context.AddSource($"{info.TypeSymbol.Name}_HLSL.gen.cs", code);
|
||||
codeBuilder.Clear();
|
||||
|
||||
var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
|
||||
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}), {typeFullName}.ReflectionData);");
|
||||
}
|
||||
|
||||
var registerTypeName = "g_shaderproperty_registeration";
|
||||
|
||||
Reference in New Issue
Block a user