Skip to content

Add experimental 'DeclareMinimumPrecisionSupport' compile option - #928

Draft
Sergio0694 wants to merge 3 commits into
mainfrom
dev/declare-minimum-precision-support
Draft

Add experimental 'DeclareMinimumPrecisionSupport' compile option#928
Sergio0694 wants to merge 3 commits into
mainfrom
dev/declare-minimum-precision-support

Conversation

@Sergio0694

Copy link
Copy Markdown
Owner

Summary

Adds a new experimental D2D1CompileOptions flag that declares minimum precision support in the compiled shader bytecode.

EnableLinking already 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 DeclareMinimumPrecisionSupport is set (and EnableLinking is also set), the compiled bytecode gets a shader feature info blob (SFI0) declaring D3D_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 Dxbc type appends (or updates, if one is already present) the SFI0 blob 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 with E_FAIL if 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 D3DSetBlobPart performs that validation, it doubles as a strong correctness check: the tests only pass if the checksum implementation is byte-for-byte correct.

API surface

namespace ComputeSharp.D2D1;

[Flags]
public enum D2D1CompileOptions
{
    // ...

    [Experimental("CMPSEXP0001")]
    DeclareMinimumPrecisionSupport = 1 << 29,

    StripReflectionData = 1 << 30,

    EnableLinking = 1 << 31,

    // ...
}

The flag is not part of Default or OptimizeForSize, so nothing changes for existing code.

Alternatives that were tried and rejected

Two simpler approaches were measured first, and neither works:

  • Declaring min16float in HLSL. FXC optimizes the annotation away, producing byte-identical bytecode with no SFI0 blob at all. Verified on ps_4_0_level_9_3, ps_4_0 and ps_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 below ps_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 into Default.

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 without EnableLinking (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.Tests suite passes (148 passed, 4 pre-existing skips).

Commits

  1. Add 'Dxbc' helpers to patch shader feature flags — container patching and checksum, no behavior change.
  2. Add experimental 'DeclareMinimumPrecisionSupport' compile option — the option and its wiring into both compilation paths.
  3. Add tests for 'DeclareMinimumPrecisionSupport' — test coverage.

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
@Sergio0694
Sergio0694 requested a review from Copilot July 30, 2026 22:15
@Sergio0694 Sergio0694 added the feature 🎉 A brand new feature for ComputeSharp label Jul 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 SFI0 shader 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: bytecodeWithRetention is 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
Comment on lines +400 to +411
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;
}
}
Comment on lines +332 to +345
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));
}
@rickbrew

Copy link
Copy Markdown
Collaborator

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: DeclareMinimumPrecisionSupport tested against Paint.NET's effect pipeline

I wired the PR (head c058cb6c, packed as 3.2.1-pr928 from this branch) into Paint.NET's full effect stack and ran an A/B benchmark of the new flag across every GPU in my system. TL;DR:

  • Pixel output is completely unaffected. 207 scenario/GPU combinations, two target precisions each, byte-for-byte identical with the flag on vs off — on NVIDIA, AMD, and WARP.
  • No performance win anywhere, including the scenarios purpose-built to be maximally linkable (16-deep chains of TrivialSampling + simple-input shaders). If anything there's a small regression, clearest on WARP.
  • The absence of any win on a bandwidth-starved iGPU for deep linkable chains is strong indirect evidence that Direct2D still does not engage shader linking for these effects even with the SFI0 blob present. (Direct pass-count evidence via ETW is still on my todo list.)

Environment

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 DeclareMinimumPrecisionSupport to the assembly-wide [D2DCompileOptions] for the whole tree. The only difference between the two binaries is the SFI0 blob 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 carry SFI0 in 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 stage TrivialSampling + one simple input), Paint.NET's canvas mip-composite graph at 2/4/8/16 layers (a chain of 4-input + 2-input TrivialSampling/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 is off₂ vs on₂; the same-variant pairs (off₁ vs off₂, on₁ vs on₂) 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:

  1. 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.
  2. Paint.NET's transforms also call SetInstructionCountHint, SetInputDescription, and SetOutputBuffer on 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.

@rickbrew

Copy link
Copy Markdown
Collaborator

Via Claude again, had it do ETW based analysis:

Follow-up: direct pass-count evidence — linking does not engage, with or without SFI0

Following up on my earlier benchmark comment with the direct evidence that was missing there. TL;DR: I can now show, at per-shader-invocation precision, that Direct2D executes these effect graphs fully unlinked regardless of the DeclareMinimumPrecisionSupport flag. Every transform in a chain runs as its own full-frame pass, and the flag changes the pass structure by exactly zero.

How the measurement works (recommended over ETW)

I first went down the ETW road, and it's mostly a dead end worth documenting:

  • On Windows 11 (build 26200), d2d1.dll has no render-tracing ETW provider. The only provider embedded in the binary is a TraceLogging provider named Microsoft.Windows.Graphics.Direct2D tagged with the Microsoft Telemetry group, and it emits nothing during rendering (verified by enumerating the providers registered in the live process). The old Microsoft-Windows-Direct2D manifest provider isn't registered on this build at all.
  • What ETW did show: with D3D11/DXGI/DxgKrnl providers enabled, the OFF and ON runs produced exactly equal event totals (391,418 = 391,418) and identical per-scenario counts — including IDXGIDevice2::OfferResources/ReclaimResources pairs, which are D2D offering/reclaiming its pooled intermediate surfaces. Identical intermediate-pool traffic already pointed at "no linking", but those events don't scale 1:1 with pass count, so they couldn't prove it.

The decisive instrument turned out to be much simpler: the app owns the ID3D11Device that D2D renders through, so the harness brackets each DrawImage+readback in a D3D11_QUERY_PIPELINE_STATISTICS query on the immediate context (device created single-threaded with PreventInternalThreadingOptimizations). Then:

psPasses ≈ PSInvocations / output pixels

is the effective number of full-frame pixel-shader passes. If D2D links a chain of N trivial-sampling transforms, psPasses collapses toward 1; if not, it reads N. No elevation, no providers, works on any adapter — and it's fully deterministic (the same graph produces the same invocation count to the exact integer, run after run).

Results (RTX 5090, 1920×1080 float32 target, flag verified via SFI0 sniff in both builds)

Every scenario reads exactly like an unlinked executor, and OFF → ON is identical to the invocation:

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 SFI0 declaration 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, and SetOutputBuffer on 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

@rickbrew

Copy link
Copy Markdown
Collaborator

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 refused

One 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 SFI0 (or any bytecode declaration) moot. That speculation is now tested and wrong — and the corrected picture is more useful for this PR.

The experiment

Same pipeline-statistics instrument as before (D3D11_QUERY_PIPELINE_STATISTICS around DrawImage + readback; psPasses ≈ PSInvocations / output pixels), same machine (Win11 26200, RTX 5090, driver 32.0.16.1656), same harness. New scenarios: chains of Direct2D's own built-in effects, which Microsoft authored to be linking-compatible. Two shapes:

  • CLSID_D2D1ColorMatrix chained ×1 / ×2 / ×4 / ×8 (non-identity matrix, so nothing can be special-cased away);
  • an alternating heterogeneous chain: ColorMatrix → GammaTransfer → ColorMatrix → GammaTransfer.

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 matrix ∘ gamma ∘ matrix ∘ gamma is not expressible as any single one of them. If that chain runs as one pass, it can only be shader linking.

Results

Scenario (native d2d1 built-ins) psPasses PSInvocations
ColorMatrix ×1 1.52 3,145,728
ColorMatrix ×2 1.52 3,145,728
ColorMatrix ×4 1.52 3,145,728
ColorMatrix ×8 1.52 3,145,728
ColorMatrix → Gamma → ColorMatrix → Gamma 1.5

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:

Scenario (ComputeSharp-based custom effects) psPasses OFF psPasses ON
custom sqrt chain ×8 8.52 8.52
custom sqrt chain ×16 16.52 16.52

What this means

  1. Effect shader linking is alive and working on current Windows — at least for the built-in effects, on this OS/driver/GPU combination.
  2. The ComputeSharp-based custom effects never link, with or without DeclareMinimumPrecisionSupport, despite meeting every documented requirement: compiled with EnableLinking (export function embedded in D3D_BLOB_PRIVATE_DATA), D2D1_PIXEL_OPTIONS_TRIVIAL_SAMPLING, all-simple inputs, pixel shaders only, ps_5_0.
  3. Therefore the SFI0 declaration is not the missing ingredient for this effect stack — something else disqualifies these transforms from linking before the bytecode metadata could matter.

Where the disqualifier might live

The 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:

  • My transform implementation (this is Paint.NET's own ID2D1EffectImpl/ID2D1DrawTransform stack, not ComputeSharp's D2D1PixelShaderEffect): the custom MapInputRectsToOutputRect/MapOutputRectToInputRects implementations, and the per-draw SetInstructionCountHint, SetInputDescription, and SetOutputBuffer calls — any of these could plausibly make D2D treat the transform as a linking hazard.
  • The export-function blob ComputeSharp embeds — shape/signature conventions D2D inspects when deciding linkability.
  • Effect registration differences (registration XML properties, transform graph construction).

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 D2D1PixelShaderEffect rather than a hand-rolled transform like mine — then both observations can be true simultaneously, and comparing those two paths is exactly where the answer is. The next experiment I'd run is a minimal two-effect chain built purely on D2D1PixelShaderEffect (ComputeSharp's registration + transform path), measured with the same pipeline-statistics query, flag off and on. That would cleanly split the space: if it links (with or without the flag), the disqualifier is in my transform stack and this PR's flag may still be doing real work for vanilla ComputeSharp consumers; if it doesn't, the hunt moves into the bytecode/registration layer that ComputeSharp controls.

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."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 🎉 A brand new feature for ComputeSharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants