Add experimental 'DeclareMinimumPrecisionSupport' compile option - #928
Add experimental 'DeclareMinimumPrecisionSupport' compile option#928Sergio0694 wants to merge 3 commits into
Conversation
Adds support for appending (or updating) the 'SFI0' shader feature info blob in a DXBC container, along with the modified MD5 checksum used by DXBC containers. Recomputing the checksum is required, as FXC APIs such as 'D3DSetBlobPart' validate it and reject any container that was modified after being compiled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a62113de-a3b2-457b-abce-70ee666c9e6f
Setting 'EnableLinking' embeds an export function in the compiled bytecode, but Direct2D will generally still not link effects created from it. Additionally declaring minimum precision support has been observed to make linking engage, which can remove one rendering pass, and the intermediate surface that goes with it, for each effect that ends up being linked. This option appends a shader feature info blob declaring 'D3D_SHADER_FEATURE_MINIMUM_PRECISION' to the compiled bytecode. The compiled shader instructions are left untouched, so no computation is lowered to a reduced precision. This relies on behavior that is neither documented nor guaranteed, so the option is marked as experimental. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a62113de-a3b2-457b-abce-70ee666c9e6f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a62113de-a3b2-457b-abce-70ee666c9e6f
There was a problem hiding this comment.
Pull request overview
This PR introduces an experimental D2D1CompileOptions.DeclareMinimumPrecisionSupport flag for ComputeSharp.D2D1 shader compilation. When used together with EnableLinking, it patches the produced DXBC container to declare minimum precision support via an SFI0 blob (and recomputes the DXBC checksum), which has been observed to make Direct2D effect shader linking engage.
Changes:
- Added DXBC container patching utilities (including checksum recomputation) to inject/modify the
SFI0shader feature info blob. - Wired the new experimental flag through both runtime compilation and source-generator-linked compilation paths.
- Added tests covering the +20 byte delta behavior and round-tripping through generated descriptors.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ComputeSharp.D2D1.Tests/D2D1ShaderCompilerTests.cs | Adds unit tests for the new compile option and a DXBC well-formedness helper used by tests. |
| tests/ComputeSharp.D2D1.Tests/D2D1PixelShaderTests.cs | Adds an end-to-end generator path test ensuring the compile option round-trips and affects bytecode size as expected. |
| src/ComputeSharp.D2D1/Shaders/Translation/Dxbc.cs | Introduces DXBC inspection/patching logic to append/update the SFI0 blob for minimum precision support. |
| src/ComputeSharp.D2D1/Shaders/Translation/Dxbc.Checksum.cs | Implements the DXBC checksum recomputation algorithm required after patching containers. |
| src/ComputeSharp.D2D1/Shaders/Translation/D3DCompiler.cs | Applies the optional DXBC patching before embedding export blobs for linking. |
| src/ComputeSharp.D2D1/Shaders/Interop/D2D1ShaderCompiler.cs | Wires the new option through the public shader compiler API path. |
| src/ComputeSharp.D2D1/Attributes/Enums/D2D1CompileOptions.cs | Adds the experimental DeclareMinimumPrecisionSupport enum flag and documentation. |
| src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj | Links the new DXBC helper source files into the source generator project build. |
Comments suppressed due to low confidence (1)
tests/ComputeSharp.D2D1.Tests/D2D1ShaderCompilerTests.cs:360
- Same as above:
bytecodeWithRetentionis a misleading name (this is the minimum precision support option being toggled). Consider renaming for clarity.
ReadOnlyMemory<byte> bytecodeWithRetention = D2D1ShaderCompiler.Compile(
InvertEffectSource.AsSpan(),
"PSMain".AsSpan(),
D2D1ShaderProfile.PixelShader40Level93,
(D2D1CompileOptions.Default & ~D2D1CompileOptions.EnableLinking) | D2D1CompileOptions.DeclareMinimumPrecisionSupport);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| namespace ComputeSharp.D2D1.Shaders.Translation; | ||
|
|
||
| /// <inheritdoc/> | ||
| partial class Dxbc |
| uint blobCount = BinaryPrimitives.ReadUInt32LittleEndian(bytecode.Slice(28)); | ||
|
|
||
| for (int i = 0; i < blobCount; i++) | ||
| { | ||
| uint blobOffset = BinaryPrimitives.ReadUInt32LittleEndian(bytecode.Slice(32 + (i * 4))); | ||
| uint blobSize = BinaryPrimitives.ReadUInt32LittleEndian(bytecode.Slice((int)blobOffset + 4)); | ||
|
|
||
| if (blobOffset + 8 + blobSize > bytecode.Length) | ||
| { | ||
| return false; | ||
| } | ||
| } |
| ReadOnlyMemory<byte> bytecodeWithRetention = D2D1ShaderCompiler.Compile( | ||
| InvertEffectSource.AsSpan(), | ||
| "PSMain".AsSpan(), | ||
| D2D1ShaderProfile.PixelShader40Level93, | ||
| D2D1CompileOptions.Default | D2D1CompileOptions.DeclareMinimumPrecisionSupport); | ||
|
|
||
| // The only difference is the appended shader feature info blob: one entry in the table of | ||
| // blob offsets (4 bytes), the header of the blob (8 bytes), and its payload (8 bytes). | ||
| Assert.AreEqual(bytecode.Length + 20, bytecodeWithRetention.Length); | ||
|
|
||
| // Compiling succeeds only if D3DSetBlobPart accepted the patched container, and the resulting | ||
| // bytecode is only usable if the checksum was recomputed over the patched contents. | ||
| Assert.IsTrue(IsWellFormedDxbcContainer(bytecodeWithRetention.Span)); | ||
| } |
|
I had Claude do a big experiment to see if it could determine if this was actually causing linking to engage and improving performance. tl;dr: nope, might even be a little slower for some reason, but I suspect that's mostly per-run variance/noise Real-world results:
|
| OS | Windows 11 Pro, build 26200 |
| GPUs | NVIDIA GeForce RTX 5090 (driver 32.0.16.1656, 2026-08-19), AMD Radeon(TM) Graphics iGPU (driver 32.0.21036.18, 2025-11-11), Microsoft Basic Render Driver (WARP) |
| Host app | Paint.NET (in-development), net10.0-windows, .NET SDK 11.0.100-preview.5 |
| ComputeSharp.D2D1 | 3.2.1-pr928 built from the PR head; full ComputeSharp.D2D1.Tests suite passed locally (148/148, 4 skips) |
| Shader profile / options | PixelShader50 (FXC), PackMatrixRowMajor | OptimizationLevel3 | WarningsAreErrors | EnableLinking assembly-wide (Paint.NET's shipping configuration), ± DeclareMinimumPrecisionSupport |
Paint.NET implements its own ID2D1EffectImpl/ID2D1DrawTransform stack and uses ComputeSharp.D2D1 as the shader compiler/metadata provider; every pixel shader is loaded via D2D1PixelShader.LoadBytecode<T>() from the source-generated descriptors, so the flag exercises the generator path end to end.
Methodology
- A/B lever: one package build; the flag is toggled by a compile-time define on the Paint.NET side that adds
DeclareMinimumPrecisionSupportto the assembly-wide[D2DCompileOptions]for the whole tree. The only difference between the two binaries is theSFI0blob in the shader bytecode. The harness verifies the variant at startup by parsing the DXBC container of shaders from two different assemblies (guards against stale builds); the ON build was confirmed to carrySFI0in all shader-bearing assemblies. - 69 scenarios per GPU: 2 controls (a single linkable shader; a complex-sampling Gaussian blur), synthetic all-linkable chains (
sqrt×2/4/8/16,saturate×8 — every stageTrivialSampling+ one simple input), Paint.NET's canvas mip-composite graph at 2/4/8/16 layers (a chain of 4-input + 2-inputTrivialSampling/all-simple shader transforms), two fixed framework chains (channel replace, gamma transfer), and 56 real Paint.NET effect configurations (HSL, Levels, Curves, SketchBlur, distortions, generators, …). - Measurement: 1920×1080 render into an offscreen target, timed at two target precisions (float32 and 8-bpc unorm, since D2D's minimum-precision conversion can depend on the target). Median of 30 iterations (10/5 for slow scenarios), each iteration fenced by a CPU readback (the readback is a constant cost on both sides, so it slightly dilutes percentage deltas).
- Drift control: both binaries preserved side by side and run interleaved —
off₁ → on₁ → off₂ → on₂. The primary comparison isoff₂vson₂; the same-variant pairs (off₁vsoff₂,on₁vson₂) give the measurement noise floor. An earlier non-interleaved attempt produced a ±30% "effect" that tracked scenario order rather than linkability — the noise floors below are why I trust these numbers and not those. - Pixel comparison: 1024×1024 renders at both precisions, SHA-256 over raw pixels, full dumps retained for delta analysis if hashes differ (they never did).
Results: pixel output
Byte-identical everywhere. All 69 scenarios × 3 GPUs × 2 target precisions produced identical hashes with the flag off vs on. The same-variant reruns were also byte-identical, i.e. the pipeline is fully deterministic, so the comparison has no noise term at all. The PR's caveat that D2D might route the bytecode through a minimum-precision conversion did not materialize on any of this hardware — including WARP, and including numerically sensitive content (16 chained sqrts over float32, HSL configurations at extreme parameter values, steep Curves clamping).
Results: performance
Aggregate of per-scenario f32 medians (lower is better):
| GPU | OFF | ON | Δ | Noise floor (same-variant reruns) |
|---|---|---|---|---|
| NVIDIA RTX 5090 | 361.1 ms | 393.2 ms | +8.9% | +0.7% (off₁→off₂), +4.9% (on₁→on₂) |
| AMD Radeon iGPU | 10,661.7 ms | 10,671.0 ms | +0.1% | +0.1%, +0.1% |
| WARP | 165,038.0 ms | 167,852.8 ms | +1.7% | not collected |
The RTX numbers drift by up to ~5% between runs of the same binary, so its +8.9% is at most a few percent of real effect and possibly none. The iGPU aggregate — dominated by long, extremely stable scenarios (±0.1% reproducibility) — is the cleanest hardware signal: exactly zero change. WARP (normally very repeatable) shows a consistent +4–13% regression across many mid-size scenarios, which I'd treat as the most credible evidence of a real (if small) cost to the flag.
The scenarios linking should love
f32 medians, OFF → ON (Δ). Every stage in these chains is D2D1PixelOptions.TrivialSampling with all-simple inputs, compiled with EnableLinking — the exact shape effect shader linking exists to optimize:
| Scenario | RTX 5090 | Radeon iGPU | WARP |
|---|---|---|---|
control: single sqrt (nothing to merge) |
3.55 → 3.53 (−0.5%) | 7.51 → 7.96 (+6.1%) | 9.84 → 9.13 (−7.2%) |
| control: Gaussian blur σ=15 (unlinkable) | 4.08 → 4.35 (+6.8%) | 57.05 → 57.02 (−0.1%) | 871.1 → 875.9 (+0.6%) |
chain sqrt ×2 |
3.32 → 3.30 (−0.7%) | 8.34 → 9.39 (+12.6%) | 14.18 → 13.53 (−4.6%) |
chain sqrt ×4 |
3.70 → 3.46 (−6.6%) | 10.97 → 10.81 (−1.4%) | 23.86 → 24.02 (+0.7%) |
chain sqrt ×8 |
3.79 → 3.57 (−5.8%) | 14.33 → 14.85 (+3.6%) | 44.38 → 44.14 (−0.5%) |
chain sqrt ×16 |
4.10 → 4.38 (+6.8%) | 21.88 → 22.45 (+2.6%) | 83.01 → 83.89 (+1.1%) |
chain saturate ×8 |
3.55 → 3.69 (+3.9%) | 14.02 → 14.58 (+4.0%) | 42.80 → 43.13 (+0.8%) |
| mip-composite, 2 layers | 3.14 → 3.42 (+8.8%) | 7.50 → 8.34 (+11.1%) | 14.96 → 14.74 (−1.5%) |
| mip-composite, 4 layers | 4.40 → 4.25 (−3.3%) | 15.88 → 18.14 (+14.2%) | 48.43 → 49.62 (+2.5%) |
| mip-composite, 8 layers | 4.49 → 5.22 (+16.1%) | 32.84 → 32.80 (−0.1%) | 112.6 → 114.2 (+1.4%) |
| mip-composite, 16 layers | 5.74 → 6.87 (+19.7%) | 65.39 → 65.38 (±0.0%) | 244.3 → 245.9 (+0.7%) |
| replace-channel chain | 3.83 → 3.88 (+1.2%) | 10.80 → 11.03 (+2.1%) | 24.34 → 25.14 (+3.3%) |
| gamma-transfer chain | 3.67 → 4.33 (+18.1%) | 11.81 → 11.55 (−2.2%) | 45.95 → 46.48 (+1.2%) |
(RTX per-scenario deltas of this size appear between identical binaries too — see noise floor; the iGPU's longer scenarios are reproducible to ±0.1% and show nothing.)
Why I believe linking is not engaging
The 16-deep sqrt chain, unlinked, needs 15 intermediate surfaces. At 1920×1080 float32 that's ~33 MB per intermediate — on the order of 1 GB of write+read traffic per frame. On the iGPU (shared-DDR bandwidth), eliminating that would be worth many milliseconds out of a 22 ms frame; even partial linking would be unmissable. Measured effect: +2.6% (slower). Same story for the 16-layer mip composite (31 shader transforms, ±0.0% on the iGPU at ±0.1% reproducibility).
So on this hardware, either the SFI0 blob is still not sufficient to make D2D link these transforms, or D2D links them and discards all the benefit — and the intermediate-bandwidth math makes the first explanation far more likely.
Two caveats on that conclusion:
- This is timing-based inference, not a pass count. I have an ETW capture layer wired into the harness (D2D/DXGI/D3D11/shader-cache providers with per-scenario event windows) but haven't yet done the elevated run; that would settle it directly.
- Paint.NET's transforms also call
SetInstructionCountHint,SetInputDescription, andSetOutputBufferon every draw info. If any of those independently disqualifies a transform from linking, the flag would be moot for this codebase regardless — worth keeping in mind before generalizing from Paint.NET to other consumers.
Bottom line
For Paint.NET's workload, on this hardware: the flag is perfectly safe for output correctness (byte-identical pixels everywhere, both precisions, all three rasterizers), but it produced no measurable linking benefit and a probable small regression (clearest on WARP). I wouldn't enable it as-is — but the experiment doesn't invalidate the PR's premise so much as show that the SFI0 declaration alone isn't enough to get D2D to link at least this style of custom-effect graph. Happy to re-run with ETW pass counts, on other driver versions, or against a reduced repro if that would help pin down what D2D is objecting to.
Full per-scenario data: primary comparison (flag OFF vs ON, all 3 GPUs, f32 + 8-bpc)
=== Shader linking A/B comparison: DeclareMinimumPrecisionSupport OFF vs ON ===
OFF: C:\temp\pr928-results\off2 (2026-08-31 15:57:36Z), ON: C:\temp\pr928-results\on2 (2026-08-31 16:58:26Z)
scenario f32 off f32 on delta u8 off u8 on delta pixels(f32 / u8)
[NVIDIA GeForce RTX 5090] control single-sqrt 3.547m 3.530m -0.5% 1.240m 1.258m +1.5% identical / identical
[NVIDIA GeForce RTX 5090] control gaussian-blur-15 4.077m 4.354m +6.8% 1.981m 2.322m +17.2% identical / identical
[NVIDIA GeForce RTX 5090] chain sqrt-d2 3.323m 3.301m -0.7% 1.303m 1.221m -6.3% identical / identical
[NVIDIA GeForce RTX 5090] chain sqrt-d4 3.699m 3.456m -6.6% 1.458m 1.382m -5.2% identical / identical
[NVIDIA GeForce RTX 5090] chain sqrt-d8 3.791m 3.569m -5.8% 1.633m 1.671m +2.3% identical / identical
[NVIDIA GeForce RTX 5090] chain sqrt-d16 4.101m 4.381m +6.8% 2.058m 2.090m +1.6% identical / identical
[NVIDIA GeForce RTX 5090] chain saturate-d8 3.547m 3.685m +3.9% 1.579m 1.596m +1.1% identical / identical
[NVIDIA GeForce RTX 5090] mip-composite L2 3.144m 3.421m +8.8% 1.170m 1.416m +21.1% identical / identical
[NVIDIA GeForce RTX 5090] mip-composite L4 4.396m 4.253m -3.3% 1.875m 1.625m -13.3% identical / identical
[NVIDIA GeForce RTX 5090] mip-composite L8 4.492m 5.217m +16.1% 2.460m 3.243m +31.8% identical / identical
[NVIDIA GeForce RTX 5090] mip-composite L16 5.740m 6.872m +19.7% 3.642m 4.227m +16.1% identical / identical
[NVIDIA GeForce RTX 5090] replace-channel R 3.834m 3.880m +1.2% 1.686m 1.805m +7.1% identical / identical
[NVIDIA GeForce RTX 5090] gamma-transfer2 3.668m 4.332m +18.1% 1.693m 2.156m +27.4% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnAddNoise (fixed seed) 3.685m 3.981m +8.1% 1.667m 1.947m +16.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnBrightnessContrast (B=+0.4, C=+0.3) 3.725m 4.079m +9.5% 1.731m 2.200m +27.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnDropShadow 4.797m 5.141m +7.2% 3.268m 3.389m +3.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnEmboss 3.640m 3.985m +9.5% 2.168m 1.869m -13.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnFragment 4.271m 4.117m -3.6% 2.094m 2.714m +29.6% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnGlow 4.170m 4.592m +10.1% 2.099m 2.314m +10.2% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnHueSaturationLightness (H=30, S=1.2) 3.800m 4.252m +11.9% 2.271m 1.954m -14.0% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnInkSketch 4.454m 5.375m +20.7% 2.893m 2.782m -3.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnLevels 3.716m 4.075m +9.7% 1.760m 1.770m +0.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnMotionBlur 3.477m 4.089m +17.6% 1.201m 1.627m +35.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnOilPainting 6.464m 7.165m +10.8% 4.871m 5.119m +5.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnOutline 7.030m 8.714m +24.0% 4.970m 6.319m +27.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnPencilSketch 4.672m 4.713m +0.9% 1.994m 2.325m +16.6% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnPixelate 3.527m 3.942m +11.8% 1.693m 1.733m +2.4% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnReduceNoise 4.223m 4.564m +8.1% 2.320m 2.542m +9.6% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnRelief 3.935m 4.066m +3.3% 1.644m 1.869m +13.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur 5.810m 5.972m +2.8% 3.522m 3.831m +8.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Smoothness=10 (iterative) 9.079m 10.031m +10.5% 7.627m 7.664m +0.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Smoothness=20 (max iterations) 14.496m 14.875m +2.6% 12.936m 12.913m -0.2% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Radius=50 11.818m 12.079m +2.2% 10.094m 10.166m +0.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Percentile=0.2 (erode) 5.416m 6.335m +17.0% 3.401m 3.684m +8.3% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Percentile=0.85 (dilate) 5.500m 5.705m +3.7% 3.350m 3.991m +19.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur EdgeMode=Clamp 5.575m 5.906m +6.0% 3.724m 3.875m +4.0% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur EdgeMode=Wrap 5.382m 6.163m +14.5% 3.366m 3.896m +15.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur EdgeMode=Transparent 5.893m 6.121m +3.9% 3.687m 4.120m +11.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur InputColorContext=sRGB (linearizes) 6.543m 7.312m +11.7% 4.622m 5.146m +11.3% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Smoothness=10 + InputColorContext=sRGB 12.926m 13.532m +4.7% 11.358m 11.478m +1.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSoftenPortrait 4.567m 5.147m +12.7% 2.532m 2.810m +11.0% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnSurfaceBlur 3.952m 4.193m +6.1% 1.714m 2.065m +20.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnVignette 3.591m 4.127m +14.9% 1.550m 1.979m +27.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnClouds [generator] (fixed seed) 3.702m 3.864m +4.4% 1.525m 1.341m -12.0% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnJuliaFractal [generator] 4.105m 4.181m +1.8% 1.718m 1.902m +10.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnMandelbrotFractal [generator] 4.906m 5.591m +14.0% 2.893m 3.273m +13.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnHSL S=2.0 -> Sx4, forces S>1 [saturated primaries] 3.086m 3.339m +8.2% 1.091m 1.456m +33.4% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnHSL H=-170 forces H<0 [saturated primaries] 3.380m 3.518m +4.1% 1.265m 1.398m +10.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnHSL H=+170 S=2.0, H>1 wrap + S>1 [saturated primaries] 3.289m 3.501m +6.5% 1.109m 1.405m +26.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnHSL H=-170 S=2.0 L=-0.5, combined extreme [saturated primaries] 3.231m 3.906m +20.9% 1.088m 1.419m +30.4% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnHSL S=2.0 -> Sx4 [gradient input] 3.869m 4.400m +13.7% 1.735m 2.128m +22.6% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnLevels in[30,220] out[10,245] gamma0.4 (all channels) 3.806m 3.819m +0.3% 1.470m 1.580m +7.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnLevels in[30,220] out[10,245] gamma2.2 (all channels) 3.393m 3.780m +11.4% 1.326m 1.515m +14.2% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnLevels per-channel windows + gamma 3.665m 4.251m +16.0% 1.567m 1.819m +16.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnCurves RGB per-channel (S-curve R, darken G, brighten B) 3.701m 4.010m +8.3% 1.387m 1.466m +5.7% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnCurves Luminosity strong S-curve 3.176m 3.532m +11.2% 1.133m 1.365m +20.5% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnCurves RGB steep high-contrast (pushes to clamp boundaries) 3.239m 3.764m +16.2% 1.192m 1.417m +18.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnMandelbrot deepzoom Zoom=250 Factor=1 Angle=30 Quality=4 12.355m 12.967m +4.9% 10.536m 10.724m +1.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnMandelbrot Zoom=5 Factor=3 Angle=45 Quality=8 (max samples) 15.377m 15.888m +3.3% 13.359m 14.243m +6.6% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnBulge 6.555m 7.139m +8.9% 3.746m 3.677m -1.8% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnCrystalize 5.578m 6.447m +15.6% 3.127m 3.667m +17.2% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnDents 5.646m 6.849m +21.3% 4.049m 3.797m -6.2% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnFrostedGlass 4.556m 5.392m +18.3% 2.440m 2.687m +10.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnPolarInversion 6.174m 7.276m +17.8% 3.596m 4.676m +30.1% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnRadialBlur 8.359m 9.436m +12.9% 4.437m 5.465m +23.2% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnRotateZoom 5.988m 6.250m +4.4% 3.760m 3.964m +5.4% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnTileReflection 6.188m 7.391m +19.4% 3.464m 5.067m +46.3% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnTwist 6.126m 7.654m +24.9% 4.292m 4.763m +11.0% identical / identical
[NVIDIA GeForce RTX 5090] pdn PdnZoomBlur 6.205m 6.419m +3.4% 4.046m 4.138m +2.3% identical / identical
[AMD Radeon(TM) Graphics] control single-sqrt 7.505m 7.960m +6.1% 3.380m 3.718m +10.0% identical / identical
[AMD Radeon(TM) Graphics] control gaussian-blur-15 57.050m 57.015m -0.1% 53.489m 53.435m -0.1% identical / identical
[AMD Radeon(TM) Graphics] chain sqrt-d2 8.341m 9.389m +12.6% 5.607m 5.167m -7.8% identical / identical
[AMD Radeon(TM) Graphics] chain sqrt-d4 10.970m 10.813m -1.4% 6.794m 7.325m +7.8% identical / identical
[AMD Radeon(TM) Graphics] chain sqrt-d8 14.331m 14.845m +3.6% 10.502m 11.273m +7.3% identical / identical
[AMD Radeon(TM) Graphics] chain sqrt-d16 21.878m 22.447m +2.6% 18.485m 19.157m +3.6% identical / identical
[AMD Radeon(TM) Graphics] chain saturate-d8 14.022m 14.583m +4.0% 10.765m 11.328m +5.2% identical / identical
[AMD Radeon(TM) Graphics] mip-composite L2 7.500m 8.335m +11.1% 4.086m 4.112m +0.6% identical / identical
[AMD Radeon(TM) Graphics] mip-composite L4 15.881m 18.138m +14.2% 12.280m 12.603m +2.6% identical / identical
[AMD Radeon(TM) Graphics] mip-composite L8 32.844m 32.801m -0.1% 29.286m 29.203m -0.3% identical / identical
[AMD Radeon(TM) Graphics] mip-composite L16 65.389m 65.378m -+0.0% 62.608m 62.601m -+0.0% identical / identical
[AMD Radeon(TM) Graphics] replace-channel R 10.800m 11.032m +2.1% 6.881m 7.232m +5.1% identical / identical
[AMD Radeon(TM) Graphics] gamma-transfer2 11.810m 11.553m -2.2% 7.386m 8.216m +11.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnAddNoise (fixed seed) 11.998m 11.724m -2.3% 8.789m 8.031m -8.6% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnBrightnessContrast (B=+0.4, C=+0.3) 10.255m 10.559m +3.0% 6.612m 6.883m +4.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnDropShadow 27.256m 26.594m -2.4% 22.515m 22.556m +0.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnEmboss 14.445m 14.463m +0.1% 10.506m 10.862m +3.4% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnFragment 11.657m 11.940m +2.4% 8.517m 8.308m -2.5% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnGlow 22.589m 22.657m +0.3% 19.453m 19.281m -0.9% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnHueSaturationLightness (H=30, S=1.2) 12.388m 12.343m -0.4% 8.750m 8.848m +1.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnInkSketch 42.318m 42.144m -0.4% 38.971m 39.004m +0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnLevels 9.821m 9.893m +0.7% 6.342m 6.458m +1.8% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnMotionBlur 9.244m 9.294m +0.5% 5.601m 5.714m +2.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnOilPainting 312.199m 311.061m -0.4% 308.688m 307.235m -0.5% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnOutline 158.085m 157.871m -0.1% 154.782m 154.185m -0.4% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnPencilSketch 20.444m 20.727m +1.4% 17.189m 17.209m +0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnPixelate 8.075m 8.588m +6.4% 4.503m 4.873m +8.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnReduceNoise 60.161m 60.177m +0.0% 56.898m 56.708m -0.3% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnRelief 14.216m 14.469m +1.8% 10.571m 10.744m +1.6% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur 242.303m 242.125m -0.1% 239.101m 238.548m -0.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur Smoothness=10 (iterative) 781.021m 780.585m -0.1% 778.091m 777.538m -0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur Smoothness=20 (max iterations) 1551.319m 1551.146m -+0.0% 1548.408m 1547.663m -+0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur Radius=50 1131.735m 1130.439m -0.1% 1127.668m 1127.323m -+0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur Percentile=0.2 (erode) 243.448m 243.324m -0.1% 240.245m 239.797m -0.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur Percentile=0.85 (dilate) 242.593m 242.135m -0.2% 238.529m 238.890m +0.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur EdgeMode=Clamp 242.173m 242.320m +0.1% 238.825m 238.674m -0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur EdgeMode=Wrap 242.171m 242.087m -+0.0% 239.007m 238.752m -0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur EdgeMode=Transparent 243.799m 243.562m -0.1% 240.195m 240.097m -+0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur InputColorContext=sRGB (linearizes) 247.684m 247.255m -0.2% 243.905m 243.941m +0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSketchBlur Smoothness=10 + InputColorContext=sRGB 797.796m 798.095m +0.0% 795.038m 794.097m -0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSoftenPortrait 36.163m 36.071m -0.3% 32.813m 32.731m -0.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnSurfaceBlur 34.347m 34.398m +0.1% 30.786m 31.185m +1.3% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnVignette 12.874m 12.727m -1.1% 8.858m 9.538m +7.7% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnClouds [generator] (fixed seed) 12.741m 12.603m -1.1% 8.941m 9.343m +4.5% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnJuliaFractal [generator] 16.522m 17.154m +3.8% 13.974m 13.957m -0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnMandelbrotFractal [generator] 189.921m 189.964m +0.0% 186.894m 186.648m -0.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnHSL S=2.0 -> Sx4, forces S>1 [saturated primaries] 5.420m 5.496m +1.4% 2.402m 2.490m +3.7% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnHSL H=-170 forces H<0 [saturated primaries] 5.158m 5.316m +3.1% 2.266m 2.550m +12.5% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnHSL H=+170 S=2.0, H>1 wrap + S>1 [saturated primaries] 4.743m 5.277m +11.3% 2.295m 2.542m +10.8% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnHSL H=-170 S=2.0 L=-0.5, combined extreme [saturated primaries] 5.241m 5.621m +7.3% 2.451m 2.574m +5.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnHSL S=2.0 -> Sx4 [gradient input] 12.310m 12.760m +3.7% 8.863m 9.528m +7.5% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnLevels in[30,220] out[10,245] gamma0.4 (all channels) 9.442m 10.159m +7.6% 5.593m 6.389m +14.2% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnLevels in[30,220] out[10,245] gamma2.2 (all channels) 9.717m 9.921m +2.1% 6.219m 6.154m -1.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnLevels per-channel windows + gamma 9.538m 10.317m +8.2% 6.061m 6.658m +9.8% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnCurves RGB per-channel (S-curve R, darken G, brighten B) 7.741m 7.893m +2.0% 3.729m 4.075m +9.3% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnCurves Luminosity strong S-curve 7.696m 7.575m -1.6% 3.555m 3.849m +8.3% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnCurves RGB steep high-contrast (pushes to clamp boundaries) 7.557m 8.078m +6.9% 3.660m 4.088m +11.7% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnMandelbrot deepzoom Zoom=250 Factor=1 Angle=30 Quality=4 1212.592m 1212.303m -+0.0% 1209.135m 1209.382m +0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnMandelbrot Zoom=5 Factor=3 Angle=45 Quality=8 (max samples) 1580.577m 1580.335m -+0.0% 1577.401m 1577.316m -+0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnBulge 27.957m 28.533m +2.1% 13.000m 12.466m -4.1% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnCrystalize 32.773m 33.214m +1.3% 16.351m 16.230m -0.7% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnDents 34.203m 34.520m +0.9% 20.960m 20.967m +0.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnFrostedGlass 16.401m 16.228m -1.1% 9.763m 9.572m -2.0% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnPolarInversion 39.810m 40.544m +1.8% 24.375m 24.601m +0.9% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnRadialBlur 82.782m 83.543m +0.9% 77.865m 77.554m -0.4% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnRotateZoom 80.999m 80.226m -1.0% 72.206m 71.837m -0.5% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnTileReflection 51.753m 51.770m +0.0% 25.326m 25.135m -0.8% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnTwist 39.438m 39.934m +1.3% 24.000m 24.418m +1.7% identical / identical
[AMD Radeon(TM) Graphics] pdn PdnZoomBlur 85.725m 86.653m +1.1% 82.248m 82.192m -0.1% identical / identical
[Microsoft Basic Render Driver] control single-sqrt 9.836m 9.127m -7.2% 7.683m 7.582m -1.3% identical / identical
[Microsoft Basic Render Driver] control gaussian-blur-15 871.076m 875.926m +0.6% 865.407m 869.861m +0.5% identical / identical
[Microsoft Basic Render Driver] chain sqrt-d2 14.177m 13.531m -4.6% 12.692m 12.693m +0.0% identical / identical
[Microsoft Basic Render Driver] chain sqrt-d4 23.863m 24.021m +0.7% 22.817m 23.222m +1.8% identical / identical
[Microsoft Basic Render Driver] chain sqrt-d8 44.382m 44.139m -0.5% 42.675m 43.129m +1.1% identical / identical
[Microsoft Basic Render Driver] chain sqrt-d16 83.005m 83.886m +1.1% 82.168m 82.374m +0.3% identical / identical
[Microsoft Basic Render Driver] chain saturate-d8 42.804m 43.127m +0.8% 41.126m 41.133m +0.0% identical / identical
[Microsoft Basic Render Driver] mip-composite L2 14.956m 14.737m -1.5% 13.988m 13.835m -1.1% identical / identical
[Microsoft Basic Render Driver] mip-composite L4 48.430m 49.623m +2.5% 46.440m 47.677m +2.7% identical / identical
[Microsoft Basic Render Driver] mip-composite L8 112.634m 114.164m +1.4% 111.192m 112.575m +1.2% identical / identical
[Microsoft Basic Render Driver] mip-composite L16 244.279m 245.870m +0.7% 242.710m 244.440m +0.7% identical / identical
[Microsoft Basic Render Driver] replace-channel R 24.342m 25.136m +3.3% 22.947m 23.768m +3.6% identical / identical
[Microsoft Basic Render Driver] gamma-transfer2 45.946m 46.480m +1.2% 45.198m 45.157m -0.1% identical / identical
[Microsoft Basic Render Driver] pdn PdnAddNoise (fixed seed) 74.022m 74.329m +0.4% 72.507m 72.974m +0.6% identical / identical
[Microsoft Basic Render Driver] pdn PdnBrightnessContrast (B=+0.4, C=+0.3) 24.629m 24.202m -1.7% 22.788m 22.805m +0.1% identical / identical
[Microsoft Basic Render Driver] pdn PdnDropShadow 232.225m 236.463m +1.8% 234.140m 238.182m +1.7% identical / identical
[Microsoft Basic Render Driver] pdn PdnEmboss 140.538m 140.497m -+0.0% 138.378m 138.870m +0.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnFragment 148.449m 156.358m +5.3% 146.463m 151.930m +3.7% identical / identical
[Microsoft Basic Render Driver] pdn PdnGlow 187.496m 197.633m +5.4% 187.742m 199.111m +6.1% identical / identical
[Microsoft Basic Render Driver] pdn PdnHueSaturationLightness (H=30, S=1.2) 47.284m 48.641m +2.9% 46.182m 47.536m +2.9% identical / identical
[Microsoft Basic Render Driver] pdn PdnInkSketch 545.357m 567.658m +4.1% 536.453m 560.357m +4.5% identical / identical
[Microsoft Basic Render Driver] pdn PdnLevels 32.979m 32.872m -0.3% 30.205m 31.291m +3.6% identical / identical
[Microsoft Basic Render Driver] pdn PdnMotionBlur 63.440m 65.216m +2.8% 62.610m 64.620m +3.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnOilPainting 2776.961m 2913.117m +4.9% 2772.466m 2925.241m +5.5% identical / identical
[Microsoft Basic Render Driver] pdn PdnOutline 2800.156m 2959.068m +5.7% 2807.173m 2970.751m +5.8% identical / identical
[Microsoft Basic Render Driver] pdn PdnPencilSketch 133.403m 138.689m +4.0% 131.314m 136.157m +3.7% identical / identical
[Microsoft Basic Render Driver] pdn PdnPixelate 18.965m 19.829m +4.6% 17.883m 18.294m +2.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnReduceNoise 1097.220m 1149.722m +4.8% 1094.701m 1146.099m +4.7% identical / identical
[Microsoft Basic Render Driver] pdn PdnRelief 132.821m 138.413m +4.2% 130.957m 137.108m +4.7% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur 2559.605m 2812.051m +9.9% 2554.572m 2814.633m +10.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur Smoothness=10 (iterative) 8476.314m 8522.020m +0.5% 8469.183m 8496.210m +0.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur Smoothness=20 (max iterations) 16912.221m 17423.571m +3.0% 17534.707m 17915.402m +2.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur Radius=50 11829.452m 12117.819m +2.4% 12178.992m 12086.062m -0.8% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur Percentile=0.2 (erode) 2623.465m 2634.154m +0.4% 2617.902m 2627.032m +0.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur Percentile=0.85 (dilate) 2650.845m 2636.601m -0.5% 2665.280m 2622.588m -1.6% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur EdgeMode=Clamp 2675.074m 2728.496m +2.0% 2682.675m 2743.778m +2.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur EdgeMode=Wrap 2661.705m 2774.666m +4.2% 2597.428m 2742.363m +5.6% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur EdgeMode=Transparent 2613.972m 2755.918m +5.4% 2575.664m 2736.983m +6.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur InputColorContext=sRGB (linearizes) 2670.107m 2814.891m +5.4% 2659.805m 2824.983m +6.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnSketchBlur Smoothness=10 + InputColorContext=sRGB 8767.625m 9329.651m +6.4% 8180.622m 9264.451m +13.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnSoftenPortrait 378.011m 423.312m +12.0% 377.049m 416.355m +10.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnSurfaceBlur 813.283m 868.865m +6.8% 812.521m 892.069m +9.8% identical / identical
[Microsoft Basic Render Driver] pdn PdnVignette 65.011m 73.389m +12.9% 64.389m 68.701m +6.7% identical / identical
[Microsoft Basic Render Driver] pdn PdnClouds [generator] (fixed seed) 162.060m 182.993m +12.9% 160.743m 180.687m +12.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnJuliaFractal [generator] 223.626m 231.675m +3.6% 222.727m 229.677m +3.1% identical / identical
[Microsoft Basic Render Driver] pdn PdnMandelbrotFractal [generator] 3823.952m 3935.421m +2.9% 3831.045m 3938.888m +2.8% identical / identical
[Microsoft Basic Render Driver] pdn PdnHSL S=2.0 -> Sx4, forces S>1 [saturated primaries] 3.374m 4.831m +43.2% 1.621m 2.210m +36.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnHSL H=-170 forces H<0 [saturated primaries] 3.275m 4.506m +37.6% 1.673m 2.218m +32.6% identical / identical
[Microsoft Basic Render Driver] pdn PdnHSL H=+170 S=2.0, H>1 wrap + S>1 [saturated primaries] 3.321m 5.139m +54.7% 1.712m 2.198m +28.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnHSL H=-170 S=2.0 L=-0.5, combined extreme [saturated primaries] 3.301m 5.017m +52.0% 1.636m 2.322m +42.0% identical / identical
[Microsoft Basic Render Driver] pdn PdnHSL S=2.0 -> Sx4 [gradient input] 45.073m 48.910m +8.5% 44.195m 47.405m +7.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnLevels in[30,220] out[10,245] gamma0.4 (all channels) 29.199m 32.828m +12.4% 28.430m 31.265m +10.0% identical / identical
[Microsoft Basic Render Driver] pdn PdnLevels in[30,220] out[10,245] gamma2.2 (all channels) 29.313m 32.116m +9.6% 28.330m 31.425m +10.9% identical / identical
[Microsoft Basic Render Driver] pdn PdnLevels per-channel windows + gamma 29.269m 32.582m +11.3% 28.319m 31.086m +9.8% identical / identical
[Microsoft Basic Render Driver] pdn PdnCurves RGB per-channel (S-curve R, darken G, brighten B) 19.654m 21.849m +11.2% 18.202m 20.468m +12.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnCurves Luminosity strong S-curve 20.873m 22.528m +7.9% 19.560m 20.971m +7.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnCurves RGB steep high-contrast (pushes to clamp boundaries) 19.571m 21.919m +12.0% 18.215m 20.129m +10.5% identical / identical
[Microsoft Basic Render Driver] pdn PdnMandelbrot deepzoom Zoom=250 Factor=1 Angle=30 Quality=4 32001.169m 32625.968m +2.0% 32507.756m 31873.149m -2.0% identical / identical
[Microsoft Basic Render Driver] pdn PdnMandelbrot Zoom=5 Factor=3 Angle=45 Quality=8 (max samples) 42453.463m 42061.478m -0.9% 42595.949m 42042.829m -1.3% identical / identical
[Microsoft Basic Render Driver] pdn PdnBulge 143.373m 139.958m -2.4% 153.057m 152.745m -0.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnCrystalize 360.554m 353.501m -2.0% 341.550m 333.312m -2.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnDents 424.083m 416.363m -1.8% 427.620m 426.922m -0.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnFrostedGlass 142.682m 141.669m -0.7% 142.127m 141.609m -0.4% identical / identical
[Microsoft Basic Render Driver] pdn PdnPolarInversion 215.407m 211.456m -1.8% 220.876m 220.714m -0.1% identical / identical
[Microsoft Basic Render Driver] pdn PdnRadialBlur 4500.628m 4435.796m -1.4% 4493.909m 4441.286m -1.2% identical / identical
[Microsoft Basic Render Driver] pdn PdnRotateZoom 178.259m 176.551m -1.0% 179.637m 176.205m -1.9% identical / identical
[Microsoft Basic Render Driver] pdn PdnTileReflection 281.596m 276.030m -2.0% 268.316m 265.263m -1.1% identical / identical
[Microsoft Basic Render Driver] pdn PdnTwist 227.948m 224.642m -1.5% 238.655m 234.172m -1.9% identical / identical
[Microsoft Basic Render Driver] pdn PdnZoomBlur 3960.567m 3839.225m -3.1% 4059.521m 3847.459m -5.2% identical / identical
Timing (f32 medians): 3 scenarios ≥5% faster with the flag, 77 ≥5% slower; aggregate 176060.8 ms -> 178917.0 ms (+1.6%).
NVIDIA GeForce RTX 5090: aggregate 361.1 ms -> 393.2 ms (+8.9%), 2 faster / 45 slower (≥5%).
AMD Radeon(TM) Graphics: aggregate 10661.7 ms -> 10671.0 ms (+0.1%), 0 faster / 10 slower (≥5%).
Microsoft Basic Render Driver: aggregate 165038.0 ms -> 167852.8 ms (+1.7%), 1 faster / 22 slower (≥5%).
Pixels (f32): 207 scenarios byte-identical, 0 differ.
Noise floor summary: two runs of the SAME flag-OFF binary
Timing (f32 medians): 17 scenarios ≥5% faster in the second run, 24 ≥5% slower; aggregate 11010.3 ms -> 11022.8 ms (+0.1%).
NVIDIA GeForce RTX 5090: aggregate 358.5 ms -> 361.1 ms (+0.7%), 15 faster / 13 slower (≥5%).
AMD Radeon(TM) Graphics: aggregate 10651.9 ms -> 10661.7 ms (+0.1%), 2 faster / 11 slower (≥5%).
Pixels (f32): 138 scenarios byte-identical, 0 differ.
Noise floor summary: two runs of the SAME flag-ON binary
Timing (f32 medians): 6 scenarios ≥5% faster in the second run, 49 ≥5% slower; aggregate 11035.9 ms -> 11064.2 ms (+0.3%).
NVIDIA GeForce RTX 5090: aggregate 374.7 ms -> 393.2 ms (+4.9%), 6 faster / 33 slower (≥5%).
AMD Radeon(TM) Graphics: aggregate 10661.2 ms -> 10671.0 ms (+0.1%), 0 faster / 16 slower (≥5%).
Pixels (f32): 138 scenarios byte-identical, 0 differ.
|
Via Claude again, had it do ETW based analysis: Follow-up: direct pass-count evidence — linking does not engage, with or without
|
| Scenario | psPasses OFF | psPasses ON | Unlinked graph predicts | PSInvocations OFF → ON |
|---|---|---|---|---|
control: single sqrt |
1.52 | 1.52 | 1 | 3,145,728 → 3,145,728 |
chain sqrt ×2 |
2.52 | 2.52 | 2 | 5,219,328 → 5,219,328 |
chain sqrt ×4 |
4.52 | 4.52 | 4 | 9,366,528 → 9,366,528 |
chain sqrt ×8 |
8.52 | 8.52 | 8 | 17,660,928 → 17,660,928 |
chain sqrt ×16 |
16.52 | 16.52 | 16 | 34,249,728 → 34,249,728 |
chain saturate ×8 |
8.52 | 8.52 | 8 | 17,660,928 → 17,660,928 |
| mip-composite, 16 layers | 29.52 | 29.52 | 29¹ | 61,206,528 → 61,206,528 |
| gamma-transfer chain | 5.52 | 5.52 | 5 | 11,440,128 → 11,440,128 |
| control: Gaussian blur σ=15 (unlinkable) | 5.55 | 5.55 | multi-pass | 11,501,616 → 11,501,616 |
The constant ~0.52 offset on every row is the readback/format-conversion pass and is identical everywhere. ¹ 15 color-accumulate + 15 coverage-accumulate passes, with the final coverage pass elided by D2D because nothing consumes it — the graph executes exactly as authored, pass for pass.
VSInvocations (per-pass full-screen geometry) and CSInvocations are also identical OFF vs ON in all 55 scenarios. Full table below.
Recall the shaders here are the ideal linking candidates: compiled with EnableLinking (export function embedded), D2D1_PIXEL_OPTIONS_TRIVIAL_SAMPLING, all inputs simple, pixel shaders only, ps_5_0 — and in the ON build additionally carrying the SFI0 minimum-precision blob in every shader-bearing assembly.
What this means
- On this configuration (Win11 26200, RTX 5090, driver 32.0.16.1656), the
SFI0declaration does not cause D2D to link these custom-effect transforms. Combined with the earlier data — byte-identical pixels everywhere, flat timing, identical intermediate-surface traffic — the flag is a complete no-op for this workload, neither helping nor breaking anything. - This measures my effect stack, and there are app-specific suspects I haven't ruled out (Paint.NET sets
SetInstructionCountHint,SetInputDescription, andSetOutputBufferon every transform's draw info; any of those could conceivably disqualify linking independently of the bytecode). - The open question this doesn't answer: does effect shader linking still work at all on current Windows builds? The next experiment I'd run is the same pipeline-statistics measurement over a chain of native built-in effects (e.g. two chained ColorMatrix effects, which are authored linkable) — if even those execute as separate passes, linking is dead machine-wide and no amount of bytecode decoration could ever activate it. If they do link, the interesting delta is between the built-ins' registration path and a ComputeSharp.D2D1 custom effect's.
The pipeline-statistics approach is easy to lift into any repro (create your own D3D11 device for the D2D device, wrap DrawImage + readback in a D3D11_QUERY_PIPELINE_STATISTICS Begin/End, divide PSInvocations by output pixels), and I'd suggest it as the standard way to validate any future variation of this experiment — it's the difference between inferring linking from timing and just counting the passes.
Full pipeline-statistics table (all 55 scenarios, OFF vs ON)
=== Pipeline statistics (one f32 render; psPasses = PSInvocations / output pixels) ===
scenario psPasses off psPasses on PSInvocations off -> on VS off VS on CS off CS on
[NVIDIA GeForce RTX 5090] control single-sqrt 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
[NVIDIA GeForce RTX 5090] control gaussian-blur-15 5.55 5.55 11,501,616 -> 11,501,616 330 330 0 0
[NVIDIA GeForce RTX 5090] chain sqrt-d2 2.52 2.52 5,219,328 -> 5,219,328 186 186 0 0
[NVIDIA GeForce RTX 5090] chain sqrt-d4 4.52 4.52 9,366,528 -> 9,366,528 330 330 0 0
[NVIDIA GeForce RTX 5090] chain sqrt-d8 8.52 8.52 17,660,928 -> 17,660,928 618 618 0 0
[NVIDIA GeForce RTX 5090] chain sqrt-d16 16.52 16.52 34,249,728 -> 34,249,728 1,194 1,194 0 0
[NVIDIA GeForce RTX 5090] chain saturate-d8 8.52 8.52 17,660,928 -> 17,660,928 618 618 0 0
[NVIDIA GeForce RTX 5090] mip-composite L2 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
[NVIDIA GeForce RTX 5090] mip-composite L4 5.52 5.52 11,440,128 -> 11,440,128 402 402 0 0
[NVIDIA GeForce RTX 5090] mip-composite L8 13.52 13.52 28,028,928 -> 28,028,928 978 978 0 0
[NVIDIA GeForce RTX 5090] mip-composite L16 29.52 29.52 61,206,528 -> 61,206,528 2,130 2,130 0 0
[NVIDIA GeForce RTX 5090] replace-channel R 4.52 4.52 9,366,528 -> 9,366,528 330 330 0 0
[NVIDIA GeForce RTX 5090] gamma-transfer2 5.52 5.52 11,440,128 -> 11,440,128 402 402 0 0
[NVIDIA GeForce RTX 5090] pdn PdnAddNoise (fixed seed) 3.52 3.52 7,292,928 -> 7,292,928 258 258 0 0
[NVIDIA GeForce RTX 5090] pdn PdnBrightnessContrast (B=+0.4, C=+0.3) 4.52 4.52 9,366,528 -> 9,366,528 330 330 0 0
[NVIDIA GeForce RTX 5090] pdn PdnDropShadow 9.00 9.00 18,662,903 -> 18,662,903 702 702 0 0
[NVIDIA GeForce RTX 5090] pdn PdnEmboss 3.54 3.54 7,333,440 -> 7,333,440 366 366 0 0
[NVIDIA GeForce RTX 5090] pdn PdnFragment 4.59 4.59 9,509,145 -> 9,509,145 414 414 0 0
[NVIDIA GeForce RTX 5090] pdn PdnGlow 7.57 7.57 15,700,080 -> 15,700,080 654 654 0 0
[NVIDIA GeForce RTX 5090] pdn PdnHueSaturationLightness (H=30, S=1.2) 6.52 6.52 13,513,728 -> 13,513,728 474 474 0 0
[NVIDIA GeForce RTX 5090] pdn PdnInkSketch 13.57 13.57 28,141,680 -> 28,141,680 1,086 1,086 0 0
[NVIDIA GeForce RTX 5090] pdn PdnLevels 3.52 3.52 7,292,928 -> 7,292,928 258 258 0 0
[NVIDIA GeForce RTX 5090] pdn PdnMotionBlur 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
[NVIDIA GeForce RTX 5090] pdn PdnOilPainting 1.65 1.65 3,428,348 -> 3,428,348 186 186 522,240 522,240
[NVIDIA GeForce RTX 5090] pdn PdnOutline 13.66 13.66 28,316,918 -> 28,316,918 1,062 1,062 1,520,640 1,520,640
[NVIDIA GeForce RTX 5090] pdn PdnPencilSketch 10.54 10.54 21,863,040 -> 21,863,040 870 870 0 0
[NVIDIA GeForce RTX 5090] pdn PdnPixelate 1.77 1.77 3,674,256 -> 3,674,256 186 186 0 0
[NVIDIA GeForce RTX 5090] pdn PdnReduceNoise 3.62 3.62 7,499,328 -> 7,499,328 366 366 0 0
[NVIDIA GeForce RTX 5090] pdn PdnRelief 3.54 3.54 7,333,440 -> 7,333,440 366 366 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur 6.77 6.77 14,031,552 -> 14,031,552 582 582 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Smoothness=10 (iterative) 13.77 13.77 28,551,660 -> 28,551,660 1,086 1,086 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Smoothness=20 (max iterations) 23.77 23.77 49,298,928 -> 49,298,928 1,806 1,806 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Radius=50 7.05 7.05 14,615,364 -> 14,615,364 582 582 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Percentile=0.2 (erode) 6.77 6.77 14,031,552 -> 14,031,552 582 582 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Percentile=0.85 (dilate) 6.77 6.77 14,031,552 -> 14,031,552 582 582 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur EdgeMode=Clamp 6.73 6.73 13,953,195 -> 13,953,195 522 522 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur EdgeMode=Wrap 6.77 6.77 14,031,552 -> 14,031,552 582 582 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur EdgeMode=Transparent 7.98 7.98 16,547,912 -> 16,547,912 630 630 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur InputColorContext=sRGB (linearizes) 10.77 10.77 22,325,952 -> 22,325,952 870 870 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSketchBlur Smoothness=10 + InputColorContext=sRGB 24.77 24.77 51,361,260 -> 51,361,260 1,878 1,878 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSoftenPortrait 11.63 11.63 24,121,728 -> 24,121,728 942 942 0 0
[NVIDIA GeForce RTX 5090] pdn PdnSurfaceBlur 3.58 3.58 7,415,616 -> 7,415,616 366 366 0 0
[NVIDIA GeForce RTX 5090] pdn PdnVignette 6.52 6.52 13,513,728 -> 13,513,728 474 474 0 0
[NVIDIA GeForce RTX 5090] pdn PdnClouds [generator] (fixed seed) 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
[NVIDIA GeForce RTX 5090] pdn PdnHSL S=2.0 -> Sx4, forces S>1 [saturated primaries] 0.13 0.13 273,664 -> 273,664 48 48 0 0
[NVIDIA GeForce RTX 5090] pdn PdnHSL H=-170 forces H<0 [saturated primaries] 0.13 0.13 273,664 -> 273,664 48 48 0 0
[NVIDIA GeForce RTX 5090] pdn PdnHSL H=+170 S=2.0, H>1 wrap + S>1 [saturated primaries] 0.13 0.13 273,664 -> 273,664 48 48 0 0
[NVIDIA GeForce RTX 5090] pdn PdnHSL H=-170 S=2.0 L=-0.5, combined extreme [saturated primaries] 0.13 0.13 273,664 -> 273,664 48 48 0 0
[NVIDIA GeForce RTX 5090] pdn PdnHSL S=2.0 -> Sx4 [gradient input] 6.52 6.52 13,513,728 -> 13,513,728 474 474 0 0
[NVIDIA GeForce RTX 5090] pdn PdnLevels in[30,220] out[10,245] gamma0.4 (all channels) 3.52 3.52 7,292,928 -> 7,292,928 258 258 0 0
[NVIDIA GeForce RTX 5090] pdn PdnLevels in[30,220] out[10,245] gamma2.2 (all channels) 3.52 3.52 7,292,928 -> 7,292,928 258 258 0 0
[NVIDIA GeForce RTX 5090] pdn PdnLevels per-channel windows + gamma 3.52 3.52 7,292,928 -> 7,292,928 258 258 0 0
[NVIDIA GeForce RTX 5090] pdn PdnCurves RGB per-channel (S-curve R, darken G, brighten B) 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
[NVIDIA GeForce RTX 5090] pdn PdnCurves Luminosity strong S-curve 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
[NVIDIA GeForce RTX 5090] pdn PdnCurves RGB steep high-contrast (pushes to clamp boundaries) 1.52 1.52 3,145,728 -> 3,145,728 114 114 0 0
|
More from Claude. Built-in effects do successfully link, which I think we already knew Control experiment: native built-ins DO link — the custom effects are what's being refusedOne more follow-up, and it inverts the tentative conclusion from my last comment. I speculated that effect shader linking might simply be dead on modern Windows, which would have made The experimentSame pipeline-statistics instrument as before (
The second one is the rigor check: chained color matrices could in principle be collapsed algebraically (concatenate the matrices) rather than via shader linking, but gamma transfer is non-linear, so Results
An 8-deep native chain costs exactly the same pixel-shader invocations as a single effect — the whole graph fuses into one pass. The heterogeneous chain fuses too, so this is genuine effect shader linking, not matrix algebra. (Results identical in the flag-OFF and flag-ON builds, as expected — these are Microsoft's shaders, untouched by the flag. Pixels byte-identical as always. The ~0.52 constant is the same readback-conversion pass as in all previous tables.) For contrast, from the previous comment, on the same machine in the same process:
What this means
Where the disqualifier might liveThe delta between "links" and "doesn't link" is now the delta between a built-in effect and my custom-effect stack. Candidates, roughly in the order I'd investigate:
Worth reconciling with this PR's original motivation: if the "declaring minimum precision support makes linking engage" observation was made on a different stack — e.g. ComputeSharp's own Either way: the pipeline-statistics pass counter has turned out to be the right microscope for this whole question — one integer per scenario, deterministic to the invocation, and it has now cleanly separated "linking is off" from "linking is on but not for you." |
Summary
Adds a new experimental
D2D1CompileOptionsflag that declares minimum precision support in the compiled shader bytecode.EnableLinkingalready compiles an export function and embeds it in the bytecode, which is what Direct2D effect shader linking needs. In practice though, Direct2D will generally still not link effects created from that bytecode, so every effect in a chain costs its own rendering pass and its own intermediate surface.Additionally declaring minimum precision support in the bytecode has been observed to make linking engage. This PR exposes that as an opt-in flag, so apps that can benefit from it are able to enable it and measure it, while the default compilation path stays exactly as it is today.
What the flag does
When
DeclareMinimumPrecisionSupportis set (andEnableLinkingis also set), the compiled bytecode gets a shader feature info blob (SFI0) declaringD3D_SHADER_FEATURE_MINIMUM_PRECISION.The compiled shader instructions are left completely untouched, so no computation is lowered to a reduced precision. Only the container metadata changes. The resulting bytecode is 20 bytes larger: 4 bytes for the new entry in the table of blob offsets, 8 bytes for the blob header, and 8 bytes for the payload.
The flag is a no-op without
EnableLinking, since there is no export function to link in that case. This is covered by a test asserting byte-identical output.Implementation
The new
Dxbctype appends (or updates, if one is already present) theSFI0blob in a DXBC container, and then recomputes the container checksum.Recomputing the checksum is required rather than optional:
D3DSetBlobPart, which is used right after to embed the export function, validates the checksum of its input and fails withE_FAILif the container was modified after being compiled. The checksum is the MD5 algorithm from RFC 1321 with a modified handling of the final block, as published in INF-0004 - Validator Hashing.Because
D3DSetBlobPartperforms that validation, it doubles as a strong correctness check: the tests only pass if the checksum implementation is byte-for-byte correct.API surface
The flag is not part of
DefaultorOptimizeForSize, so nothing changes for existing code.Alternatives that were tried and rejected
Two simpler approaches were measured first, and neither works:
min16floatin HLSL. FXC optimizes the annotation away, producing byte-identical bytecode with noSFI0blob at all. Verified onps_4_0_level_9_3,ps_4_0andps_5_0. Anything inert enough to be safe gets eliminated, and anything that survives does so precisely because it changes the numerics, so there is no usable middle ground.D3DCOMPILE_PARTIAL_PRECISION(/Gpp). Rejected outright by FXC belowps_5_0: "error X3534: partial precision is not supported for target ps_4_0_level_9_3. Min-precision types may offer similar functionality."Patching the container directly is therefore the only reliable option, which is what this PR does.
Caveats
This relies on behavior that is neither documented nor guaranteed, and that may stop working at any time. That is why the option is marked with
[Experimental]and is opt-in rather than being folded intoDefault.Direct2D may also still run the bytecode through a minimum precision conversion depending on the target being rendered to, so shaders using this option should be validated to still produce correct results, and the option should only be kept if it measurably improves performance for a given workload.
Testing
D2D1ShaderCompilerTests.CompileInvertEffectWithDeclareMinimumPrecisionSupport— asserts the exact 20 byte size delta and that the result is a well formed DXBC container with a consistent size and set of blob offsets.D2D1ShaderCompilerTests.CompileInvertEffectWithDeclareMinimumPrecisionSupportAndNoLinking— asserts the option is ignored withoutEnableLinking(byte-identical output).D2D1PixelShaderTests.LoadBytecode_DeclareMinimumPrecisionSupportIsAppliedCorrectly— covers the source generator path end to end, including that the option round-trips through the generated descriptor.Full
ComputeSharp.D2D1.Testssuite passes (148 passed, 4 pre-existing skips).Commits
Add 'Dxbc' helpers to patch shader feature flags— container patching and checksum, no behavior change.Add experimental 'DeclareMinimumPrecisionSupport' compile option— the option and its wiring into both compilation paths.Add tests for 'DeclareMinimumPrecisionSupport'— test coverage.