mirror of
https://github.com/stride3d/stride.git
synced 2026-10-12 02:23:23 +00:00
shaders: fix float codegen for parameter keys (fixes #3250)
Fold scientific-notation exponents into the literal value (was dropped, producing wrong constants in generated keys and SPIR-V). Emit C# defaults from the typed AST instead of ToString string-surgery, so scalar/vector suffixes and type mapping (double->Double, half->float, int->Int) are correct.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Stride.Shaders.Core;
|
||||
using Stride.Shaders.Parsing.Analysis;
|
||||
using Stride.Shaders.Parsing.SDFX.AST;
|
||||
@@ -119,52 +120,22 @@ public class EffectCodeWriter : ShaderWriter
|
||||
}
|
||||
}
|
||||
|
||||
var csharpType = variableType ?? ToCSharpType(parameterType);
|
||||
|
||||
Write($"public static readonly {parameterKeyType}ParameterKey<");
|
||||
if (variableType == null)
|
||||
VisitNode(new TypeName(parameterType.ToString(), default) { Type = parameterType });
|
||||
else
|
||||
Write(variableType);
|
||||
Write(csharpType);
|
||||
Write("> ");
|
||||
Write(name);
|
||||
Write(" = ");
|
||||
if (variableMap == null)
|
||||
{
|
||||
Write($"ParameterKeys.New{parameterKeyType}<");
|
||||
if (variableType == null)
|
||||
VisitNode(new TypeName(parameterType.ToString(), default) { Type = parameterType });
|
||||
else
|
||||
Write(variableType);
|
||||
Write(csharpType);
|
||||
Write(">(");
|
||||
if (initialValue != null)
|
||||
{
|
||||
var initialValueString = initialValue.ToString()!;
|
||||
|
||||
if (initialValueString != "null")
|
||||
{
|
||||
var initialValueType = type.ToId();
|
||||
|
||||
if (type is ArrayType)
|
||||
initialValueString = initialValueType + initialValueString;
|
||||
|
||||
// Rename float2/3/4 to Vector2/3/4
|
||||
if (initialValueString.StartsWith("float2", StringComparison.Ordinal) ||
|
||||
initialValueString.StartsWith("float3", StringComparison.Ordinal) ||
|
||||
initialValueString.StartsWith("float4", StringComparison.Ordinal))
|
||||
{
|
||||
initialValueString = initialValueString.Replace("float", "new Vector");
|
||||
}
|
||||
else if (type is ArrayType)
|
||||
{
|
||||
initialValueString = "new " + initialValueType;
|
||||
}
|
||||
if (isProcessingColor)
|
||||
{
|
||||
initialValueString = initialValueString.Replace("Vector3", "Color3");
|
||||
initialValueString = initialValueString.Replace("Vector4", "Color4");
|
||||
}
|
||||
}
|
||||
Write(initialValueString);
|
||||
}
|
||||
// Array value keys use slots and can't carry an array default (NewValue<T> takes a single
|
||||
// struct), so only scalar/vector/matrix defaults are emitted; array initializers are dropped.
|
||||
if (initialValue != null && type is not ArrayType && initialValue.ToString() != "null")
|
||||
WriteInitialValue(initialValue, type);
|
||||
Write(")");
|
||||
}
|
||||
else
|
||||
@@ -176,6 +147,84 @@ public class EffectCodeWriter : ShaderWriter
|
||||
isProcessingColor = false;
|
||||
}
|
||||
|
||||
// Maps a resolved shader type to the C# type used for its parameter key and default value.
|
||||
private string ToCSharpType(SymbolType type) => type switch
|
||||
{
|
||||
ScalarType s => ScalarCSharpType(s.Type),
|
||||
// half cbuffer members are unsupported (reflection rejects them); key as float so the generated C# stays valid.
|
||||
VectorType { BaseType.Type: Scalar.Float or Scalar.Half, Size: var n } => isProcessingColor && n is 3 or 4 ? $"Color{n}" : $"Vector{n}",
|
||||
VectorType { BaseType.Type: Scalar.Double, Size: var n } => $"Double{n}",
|
||||
VectorType { BaseType.Type: Scalar.Int or Scalar.UInt, Size: var n } => $"Int{n}",
|
||||
MatrixType { Rows: 4, Columns: 4, BaseType.Type: Scalar.Float } => "Matrix",
|
||||
ArrayType a => $"{ToCSharpType(a.BaseType)}[]",
|
||||
BufferType or StructuredBufferType or AppendStructuredBufferType or ConsumeStructuredBufferType or ByteAddressBufferType => "Buffer",
|
||||
TextureType => "Texture",
|
||||
SamplerType => "SamplerState",
|
||||
_ => type.ToString(),
|
||||
};
|
||||
|
||||
private static string ScalarCSharpType(Scalar scalar) => scalar switch
|
||||
{
|
||||
Scalar.Boolean => "bool",
|
||||
Scalar.Int => "int",
|
||||
Scalar.UInt => "uint",
|
||||
Scalar.Int64 => "long",
|
||||
Scalar.UInt64 => "ulong",
|
||||
Scalar.Half or Scalar.Float => "float",
|
||||
Scalar.Double => "double",
|
||||
_ => scalar.ToString(),
|
||||
};
|
||||
|
||||
// Emits a constant default value as C#, formatting each numeric literal per its target scalar type.
|
||||
private void WriteInitialValue(Expression value, SymbolType type)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case BoolLiteral b:
|
||||
Write(b.Value ? "true" : "false");
|
||||
break;
|
||||
case NumberLiteral number when type is ScalarType scalar:
|
||||
Write(FormatScalarLiteral(number, scalar.Type));
|
||||
break;
|
||||
case VectorLiteral vector when type is VectorType { BaseType: var element }:
|
||||
Write("new ").Write(ToCSharpType(type)).Write("(");
|
||||
WriteInitialValueList(vector.Values, element);
|
||||
Write(")");
|
||||
break;
|
||||
case MatrixLiteral matrix when type is MatrixType { BaseType: var element }:
|
||||
Write("new ").Write(ToCSharpType(type)).Write("(");
|
||||
WriteInitialValueList(matrix.Values, element);
|
||||
Write(")");
|
||||
break;
|
||||
default:
|
||||
// Best-effort fallback for shapes we don't model (e.g. a named constant reference).
|
||||
Write(value.ToString()!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteInitialValueList(List<Expression> values, SymbolType elementType)
|
||||
{
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
Write(", ");
|
||||
WriteInitialValue(values[i], elementType);
|
||||
}
|
||||
}
|
||||
|
||||
// Formats a scalar numeric default as a C# literal with the suffix matching the target scalar type.
|
||||
private static string FormatScalarLiteral(NumberLiteral number, Scalar scalar) => scalar switch
|
||||
{
|
||||
Scalar.Half or Scalar.Float => ((float)number.DoubleValue).ToString(CultureInfo.InvariantCulture) + "f",
|
||||
Scalar.Double => number.DoubleValue.ToString(CultureInfo.InvariantCulture) + "d",
|
||||
Scalar.Int => number.LongValue.ToString(CultureInfo.InvariantCulture),
|
||||
Scalar.UInt => number.ULongValue.ToString(CultureInfo.InvariantCulture) + "u",
|
||||
Scalar.Int64 => number.LongValue.ToString(CultureInfo.InvariantCulture) + "L",
|
||||
Scalar.UInt64 => number.ULongValue.ToString(CultureInfo.InvariantCulture) + "UL",
|
||||
_ => number.DoubleValue.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
private bool IsParameterDeclaredInContext(string parameter)
|
||||
{
|
||||
foreach (var context in contextStack)
|
||||
|
||||
@@ -94,17 +94,16 @@ public partial class IntegerLiteral(Suffix suffix, long value, TextLocation info
|
||||
// If expectedType is float, handle it:
|
||||
if (Type is ScalarType { Type: Scalar.Float })
|
||||
{
|
||||
return compiler.Context.CompileConstantLiteral(new FloatLiteral(new(32, true, true), Value, null, Info));
|
||||
return compiler.Context.CompileConstantLiteral(new FloatLiteral(new(32, true, true), Value, Info));
|
||||
}
|
||||
|
||||
return compiler.Context.CompileConstantLiteral(this);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class FloatLiteral(Suffix suffix, double value, int? exponent, TextLocation info) : NumberLiteral<double>(suffix, value, info)
|
||||
public sealed partial class FloatLiteral(Suffix suffix, double value, TextLocation info) : NumberLiteral<double>(suffix, value, info)
|
||||
{
|
||||
public int? Exponent { get; set; } = exponent;
|
||||
public static implicit operator FloatLiteral(double v) => new(new(), v, null, new());
|
||||
public static implicit operator FloatLiteral(double v) => new(new(), v, new());
|
||||
|
||||
public override void ProcessSymbol(SymbolTable table, SymbolType? expectedType = null)
|
||||
{
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public record struct PrefixParser : IParser<Expression>
|
||||
}
|
||||
if (op == Operator.Minus && lit is FloatLiteral floatLit)
|
||||
{
|
||||
parsed = new FloatLiteral(floatLit.Suffix, -floatLit.DoubleValue, floatLit.Exponent, scanner[position..scanner.Position]);
|
||||
parsed = new FloatLiteral(floatLit.Suffix, -floatLit.DoubleValue, scanner[position..scanner.Position]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+5
-8
@@ -88,24 +88,21 @@ public struct NumberParser : IParser<Literal>
|
||||
|
||||
|
||||
var value = double.Parse(scanner.Span[position..scanner.Position], CultureInfo.InvariantCulture);
|
||||
int? exponent = null;
|
||||
if (Tokens.Char('e', ref scanner, advance: true) || Tokens.Char('E', ref scanner, advance: true))
|
||||
{
|
||||
var signed = Tokens.AnyOf(["+", "-"], ref scanner, out var matched, advance: true);
|
||||
var expStart = scanner.Position;
|
||||
Tokens.AnyOf(["+", "-"], ref scanner, out _, advance: true);
|
||||
if (Tokens.Digit(ref scanner, advance: true))
|
||||
{
|
||||
while (Tokens.Digit(ref scanner, advance: true)) ;
|
||||
exponent = int.Parse(scanner.Span[expStart..scanner.Position], CultureInfo.InvariantCulture);
|
||||
if (signed && matched == "-")
|
||||
exponent = -exponent;
|
||||
// Re-parse the whole mantissa+exponent span so the exponent is folded into the value.
|
||||
value = double.Parse(scanner.Span[position..scanner.Position], CultureInfo.InvariantCulture);
|
||||
}
|
||||
else return Parsers.Exit(ref scanner, result, out parsed, position, new(SDSLErrorMessages.SDSL0001, scanner[scanner.Position], scanner.Memory));
|
||||
}
|
||||
if (Tokens.FloatSuffix(ref scanner, out var suffix, advance: true) && suffix is not null)
|
||||
parsed = new FloatLiteral(suffix.Value, value, exponent, scanner[position..scanner.Position]);
|
||||
parsed = new FloatLiteral(suffix.Value, value, scanner[position..scanner.Position]);
|
||||
else
|
||||
parsed = new FloatLiteral(new(32, true, true), value, exponent, scanner[position..scanner.Position]);
|
||||
parsed = new FloatLiteral(new(32, true, true), value, scanner[position..scanner.Position]);
|
||||
return true;
|
||||
}
|
||||
public static bool Hex<TScanner>(ref TScanner scanner, ParseResult result, [MaybeNullWhen(false)] out Literal parsed, in ParseError? orError = null)
|
||||
|
||||
@@ -609,7 +609,7 @@ public partial class SpirvBuilder
|
||||
(ScalarType { Type: Scalar.Float }, ScalarType { Type: Scalar.Int }) => InsertData(new OpConvertFToS(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
(ScalarType { Type: Scalar.Float }, ScalarType { Type: Scalar.UInt }) => InsertData(new OpConvertFToU(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
|
||||
(ScalarType { Type: Scalar.Float }, ScalarType { Type: Scalar.Boolean }) => InsertData(new OpFOrdNotEqual(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue, context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(32, true, true), 0.0, null, new()), elementSize).Id)),
|
||||
(ScalarType { Type: Scalar.Float }, ScalarType { Type: Scalar.Boolean }) => InsertData(new OpFOrdNotEqual(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue, context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(32, true, true), 0.0, new()), elementSize).Id)),
|
||||
(ScalarType { Type: Scalar.Int }, ScalarType { Type: Scalar.Boolean }) => InsertData(new OpINotEqual(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue, context.CreateConstantCompositeVectorRepeat(new IntegerLiteral(new(32, false, true), 0, new()), elementSize).Id)),
|
||||
(ScalarType { Type: Scalar.UInt }, ScalarType { Type: Scalar.Boolean }) => InsertData(new OpINotEqual(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue, context.CreateConstantCompositeVectorRepeat(new IntegerLiteral(new(32, false, false), 0, new()), elementSize).Id)),
|
||||
|
||||
@@ -620,8 +620,8 @@ public partial class SpirvBuilder
|
||||
context.CreateConstantCompositeVectorRepeat(new IntegerLiteral(new(32, false, true), 1, new()), elementSize).Id,
|
||||
context.CreateConstantCompositeVectorRepeat(new IntegerLiteral(new(32, false, true), 0, new()), elementSize).Id)),
|
||||
(ScalarType { Type: Scalar.Boolean }, ScalarType { Type: Scalar.Float }) => InsertData(new OpSelect(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue,
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(32, true, true), 1.0, null, new()), elementSize).Id,
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(32, true, true), 0.0, null, new()), elementSize).Id)),
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(32, true, true), 1.0, new()), elementSize).Id,
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(32, true, true), 0.0, new()), elementSize).Id)),
|
||||
|
||||
// Half conversions (OpFConvert for float<->half<->double, standard int conversions)
|
||||
(ScalarType { Type: Scalar.Half }, ScalarType { Type: Scalar.Float }) => InsertData(new OpFConvert(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
@@ -632,10 +632,10 @@ public partial class SpirvBuilder
|
||||
(ScalarType { Type: Scalar.Half }, ScalarType { Type: Scalar.UInt }) => InsertData(new OpConvertFToU(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
(ScalarType { Type: Scalar.Int }, ScalarType { Type: Scalar.Half }) => InsertData(new OpConvertSToF(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
(ScalarType { Type: Scalar.UInt }, ScalarType { Type: Scalar.Half }) => InsertData(new OpConvertUToF(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
(ScalarType { Type: Scalar.Half }, ScalarType { Type: Scalar.Boolean }) => InsertData(new OpFOrdNotEqual(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue, context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(16, true, true), 0.0, null, new()), elementSize).Id)),
|
||||
(ScalarType { Type: Scalar.Half }, ScalarType { Type: Scalar.Boolean }) => InsertData(new OpFOrdNotEqual(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue, context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(16, true, true), 0.0, new()), elementSize).Id)),
|
||||
(ScalarType { Type: Scalar.Boolean }, ScalarType { Type: Scalar.Half }) => InsertData(new OpSelect(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue,
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(16, true, true), 1.0, null, new()), elementSize).Id,
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(16, true, true), 0.0, null, new()), elementSize).Id)),
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(16, true, true), 1.0, new()), elementSize).Id,
|
||||
context.CreateConstantCompositeVectorRepeat(new FloatLiteral(new(16, true, true), 0.0, new()), elementSize).Id)),
|
||||
|
||||
// Bitcast (int=>uint or uint=>int)
|
||||
(ScalarType { Type: Scalar.Int }, ScalarType { Type: Scalar.UInt }) => InsertData(new OpBitcast(context.GetOrRegister(castTypeSameSize), context.Bound++, rowValue)),
|
||||
|
||||
@@ -280,8 +280,8 @@ public partial class SpirvContext
|
||||
uint i => new IntegerLiteral(new(32, false, false), i, location),
|
||||
long i => new IntegerLiteral(new(64, false, true), i, location),
|
||||
ulong i => new IntegerLiteral(new(64, false, false), (long)i, location),
|
||||
float i => new FloatLiteral(new(32, true, true), i, null, location),
|
||||
double i => new FloatLiteral(new(64, true, true), i, null, location),
|
||||
float i => new FloatLiteral(new(32, true, true), i, location),
|
||||
double i => new FloatLiteral(new(64, true, true), i, location),
|
||||
_ => throw new NotSupportedException($"Unsupported literal type: {value.GetType()}"),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user