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));
}
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.

2 participants