D2D1: D2D.SampleInputAtOffset with a compound offset expression silently samples wrong coordinates
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- csharp
- Domain
- computer-graphics
Research direction
Start with the embedded d2d1effecthelpers.hlsli copy and inspect the D2DSampleInputAtOffset and D2DSampleInputAtPosition macro definitions. Check how D2D.SampleInputAtOffset and D2D.SampleInputAtPosition arguments are emitted by the source generator, then verify a compound offset expression expands with correct precedence and add or run coverage for that case.
Written by the indexing model from the issue text.
Description
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)inD2DSampleInputAtPosition). 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.0um\d2d1effecthelpers.hlsliline 313
- Dominant language
- C#
- Stars
- 3.2k
- Forks
- 148
- Avg merge
- 4h 32m
- Merged PRs (30d)
- 3
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Sergio0694/ComputeSharp
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
Sergio0694/ComputeSharp#936 · 2 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
Sergio0694/ComputeSharp#931 ·
-
PIX integration Openquestion :question: untriaged :toolbox:
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Sergio0694/ComputeSharp#926 ·
-
CopyToAsync Openproposal :bulb: untriaged :toolbox:
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Sergio0694/ComputeSharp#925 ·
-
bug :bug: untriaged :toolbox:
Difficulty 4/5 3-5 days Newbie friendliness 25/100
Sergio0694/ComputeSharp#922 · 2 comments · 1 reaction ·
All issues in Sergio0694/ComputeSharp
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100