Repository navigation
[Shaders] fix: an integer operation keeps its operands' types in a float context - #3468
Conversation
… line Checked on the translated class, each seen failing on Stride 4.4 far from its cause: a name the SDSL parser reserves (sample, half, float2, params...), recursion, a shader parameter written, pixel-only operations in a compute shader, a return inside a loop (X4555 on Direct3D 11), thread groups out of range, a compute shader without [NumThreads], a shader replacing an engine one without [Shader(Replaces = true)], an integer division in a float vector constructor (stride3d/stride#3468). The round trip is unchanged: 471 of 479 engine shaders translate back, none flagged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
It seems I also tried |
| { | ||
| NumberLiteral => true, | ||
| ParenthesisExpression parenthesis => IsLiteralOperation(parenthesis.Expression), | ||
| PrefixExpression { Operator: Operator.Plus or Operator.Minus } prefix and not CastExpression => IsLiteralOperation(prefix.Expression), |
There was a problem hiding this comment.
I think and not CastExpression is not necessary as operator would be Operator.Cast.
There was a problem hiding this comment.
Right, the prefix pattern already matches on the operator, so a cast (Operator.Cast) never matched it. The check is gone along with IsLiteralOperation: LiteralFolding.TryFold matches PrefixExpression { Operator: Plus or Minus or BitwiseNot } only, and (float)1 / 2 is covered as a case that must not be folded.
|
Thanks, both cases were real. Unsuffixed literal operations are now folded at compile time as HLSL does (64-bit integers, double floats), then take the context's type, so Unrelated issue found along the way: |
3ace3f5 to
dba5a4e
Compare
|
Rebased on #3467, which this now builds on: folding must know whether a literal was written without a suffix (only those take the context's type), and before #3467 |
dba5a4e to
7c57f96
Compare
The expected type of the context was passed to the operands of + - * / %, so in float3(i / 4, 0, 0) with a uint i the literal 4 became a float and the division was done in float (1 / 4 gave 0.25); 1 / 2 in a float gave 0.5 and 5u - 6 no longer wrapped. It was added so that constant expressions stay constants of their target type (1c722b5). The context's type now only reaches + - * over literals, whose value is the same computed in it; other operations take their operands' types, and a literal still follows the other operand's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An operation made only of unsuffixed literals is now folded at compile time, integers in 64 bits and floats in double, and the result takes the type of its context: 65536 * 65536 / 3 is 1431655765 in a uint or a float, 1 / 2 is 0 in a float, 1 << 31 >> 31 is 1. A suffixed or hexadecimal literal keeps its own type, so 5u - 6 is a uint subtraction that wraps before a float context converts it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7c57f96 to
988b7aa
Compare
Note
Follow-up of #3467, based on it. Until #3467 is merged this PR also shows its two commits; the changes of this PR are the last two: c05b216 and dba5a4e.
Why it depends on it: folding needs to know whether a literal was written without a suffix, since only those take the context's type. Before #3467 the parser could not tell:
5land an unsuffixed5got the sameSuffix, and a large unsuffixed value like3000000000could not be represented. #3467 records it (IntegerLiteral.Unsuffixed, types unsuffixed values as DXC does), and this PR adds the same flag onFloatLiteral, so1.0f / 3is a float operation while1.0 / 3is folded in double.PR Details
An integer operation keeps its operands' types inside a float context, and an operation made only of unsuffixed literals is folded as HLSL does.
BinaryExpressionpassed the expected type of its context to the operands of+ - * / %, so with auint i,float3(i / 4, 0, 0)divided in float (1 / 4gave0.25),1 / 2gave0.5and5u - 6no longer wrapped. This came with 1c722b5, so that constant expressions stay constants of their target type.Now:
+ - * / %,& | ^ << >>, unary- + ~, parentheses) is folded at compile time, integers in 64 bits and floats in double, and the result takes the context's type:65536 * 65536 / 3is 1431655765 in a uint or a float,1 / 2is 0 in a float,1 << 31 >> 31is 1. A division by zero is left to the GPU.5u,5l,1.0f,1.0h,1.0d) or hexadecimal literal keeps its own type, sofloat4(5u - 6, ...)is a uint subtraction that wraps before the conversion (4294967296).The folding lives in
LiteralFolding.cs.Tests:
LiteralFoldingTests: value and type of each folded case, and the cases that must not be folded.LiteralOperationFolding.sdsl(D3D11 + Vulkan), one bit per failed check; it fails on the previous version of this PR and passes now.IntegerOperationInFloatContext.sdslfrom the first version is kept.Stride.Shaders.Testssuite passes (1068, with [Shaders] fix: integer suffixes after a lone 0 and on hexadecimal literals #3467).Stride.Samples.Tests, D3D11, 17 tests) passes. Frames over the LPIPS threshold (animated samples) were compared to a run of master: same drift, same images.Note: FXC folds
float(4294967295u) != 4294967296.0fin double precision (true, although the float value is 4294967296), so the render test checks those values with<.Related Issue
None opened: found while converting the engine's shaders to C# and back, where an integer division inside a float constructor did not give the same result as HLSL. Repro: the tests above.
Types of changes
Checklist
Stride.Samples.Tests)