This is due to an unparenthesized macro argument in d2d1effecthelpers.hlsli.
Claude found this while we were writing some shaders, here's its write-up:
D2D.SampleInputAtOffset(int, float2) looks like an ordinary method call from C#, but it lowers to the D2DSampleInputAtOffset function-like preprocessor macro from d2d1effecthelpers.hlsli. That macro pastes its offset argument into an arithmetic expression without parentheses:
#define D2DSampleInputAtOffset(index, offset) InputTexture##index.Sample(InputSampler##index, __d2dstatic_uv##index.xy + offset * __d2dstatic_uv##index.zw)
So when the C# argument is a compound expression, operator precedence tears it apart after expansion. The shader compiles cleanly and runs — it just samples garbage coordinates, which makes this very unpleasant to debug.
Repro
float2 samplePosition = /* some absolute position */;
float2 scenePosition = D2D.GetScenePosition().XY;
float4 color = D2D.SampleInputAtOffset(0, samplePosition - scenePosition);
The generated HLSL invokes the macro with the expression verbatim:
D2DSampleInputAtOffset(0, samplePosition - scenePosition)
which the preprocessor expands to:
InputTexture0.Sample(InputSampler0, __d2dstatic_uv0.xy + samplePosition - scenePosition * __d2dstatic_uv0.zw)
* binds tighter than -, so only scenePosition gets scaled by the texel size while samplePosition is added as raw pixels. The intended expansion is:
InputTexture0.Sample(InputSampler0, __d2dstatic_uv0.xy + (samplePosition - scenePosition) * __d2dstatic_uv0.zw)
We hit this in production shader development for Paint.NET (the sampled output was garbage until the expression was bound to a local first). From the C# author's point of view there is no hint that a macro is involved, so normal expression semantics are silently violated.
D2DSampleInputAtPosition is also affected, just less often: its parameter expands inside (pos - __d2dstatic_scenePos.xy), which is safe for additive expressions but still tears for anything with lower precedence than - (e.g. a conditional a ? b : c).
Root cause
Classic macro hygiene bug — the parameter should be (offset) in the macro body. The macro text is identical in the Windows SDK header (10.0.26100.0\um\d2d1effecthelpers.hlsli, line 313) and in the copy that ComputeSharp.D2D1 embeds in its assembly, so the SDK deserves an upstream report too — but ComputeSharp doesn't need to wait for that.
Suggested fixes
Either (or both, for defense in depth):
- Patch the embedded header copy:
... + (offset) * __d2dstatic_uv##index.zw (and ((pos) - __d2dstatic_scenePos.xy) in D2DSampleInputAtPosition). One-character-class change, fixes every caller with no generator changes.
- Parenthesize emitted macro arguments in the source generator: when lowering
D2D.* intrinsics that map to function-like macros, wrap each argument expression in parentheses (D2DSampleInputAtOffset(0, (a - b))). This also protects against any other unparenthesized macro parameters, including when compiling against the stock SDK header.
Workaround (for anyone hitting this today)
Bind the expression to a local and pass the identifier:
float2 sampleOffset = samplePosition - scenePosition;
float4 color = D2D.SampleInputAtOffset(0, sampleOffset);
Environment
- ComputeSharp.D2D1 3.2.0 (macro text verified in the embedded header inside
ComputeSharp.D2D1.dll)
- Same macro in Windows SDK
10.0.26100.0 um\d2d1effecthelpers.hlsli line 313
This is due to an unparenthesized macro argument in
d2d1effecthelpers.hlsli.Claude found this while we were writing some shaders, here's its write-up:
D2D.SampleInputAtOffset(int, float2)looks like an ordinary method call from C#, but it lowers to theD2DSampleInputAtOffsetfunction-like preprocessor macro fromd2d1effecthelpers.hlsli. That macro pastes itsoffsetargument into an arithmetic expression without parentheses:So when the C# argument is a compound expression, operator precedence tears it apart after expansion. The shader compiles cleanly and runs — it just samples garbage coordinates, which makes this very unpleasant to debug.
Repro
The generated HLSL invokes the macro with the expression verbatim:
which the preprocessor expands to:
InputTexture0.Sample(InputSampler0, __d2dstatic_uv0.xy + samplePosition - scenePosition * __d2dstatic_uv0.zw)*binds tighter than-, so onlyscenePositiongets scaled by the texel size whilesamplePositionis added as raw pixels. The intended expansion is:InputTexture0.Sample(InputSampler0, __d2dstatic_uv0.xy + (samplePosition - scenePosition) * __d2dstatic_uv0.zw)We hit this in production shader development for Paint.NET (the sampled output was garbage until the expression was bound to a local first). From the C# author's point of view there is no hint that a macro is involved, so normal expression semantics are silently violated.
D2DSampleInputAtPositionis also affected, just less often: its parameter expands inside(pos - __d2dstatic_scenePos.xy), which is safe for additive expressions but still tears for anything with lower precedence than-(e.g. a conditionala ? b : c).Root cause
Classic macro hygiene bug — the parameter should be
(offset)in the macro body. The macro text is identical in the Windows SDK header (10.0.26100.0\um\d2d1effecthelpers.hlsli, line 313) and in the copy that ComputeSharp.D2D1 embeds in its assembly, so the SDK deserves an upstream report too — but ComputeSharp doesn't need to wait for that.Suggested fixes
Either (or both, for defense in depth):
... + (offset) * __d2dstatic_uv##index.zw(and((pos) - __d2dstatic_scenePos.xy)inD2DSampleInputAtPosition). One-character-class change, fixes every caller with no generator changes.D2D.*intrinsics that map to function-like macros, wrap each argument expression in parentheses (D2DSampleInputAtOffset(0, (a - b))). This also protects against any other unparenthesized macro parameters, including when compiling against the stock SDK header.Workaround (for anyone hitting this today)
Bind the expression to a local and pass the identifier:
Environment
ComputeSharp.D2D1.dll)10.0.26100.0um\d2d1effecthelpers.hlsliline 313