Table of Contents

Class D3d9BytecodePatcher

Namespace
ShadowDusk.Core
Assembly
ShadowDusk.Core.dll

Post-pass over vkd3d's D3D9 SM2/SM3 token streams that canonicalizes the two instruction forms MojoShader rejects but vkd3d 1.17 emits (found by the Phase 39 rung-3/4 FNA harness — real FNA load failures where the fxc oracle loads fine):

  1. texkill with a partial destination writemask (vkd3d writes e.g. .x; fxc always writes .xyzw and MojoShader hard-fails anything else: "TEXKILL writemask must be .xyzw").
  2. texld whose coordinate source MojoShader rejects: a swizzle below SM3 ("TEXLD src0 must not swizzle" for ps_1_x/ps_2_x; SM3 allows it), or a source modifier at ANY SM2+ major ("TEXLD src0 must have no modifiers").
  3. def float literals with |f| ≥ 2³² — vkd3d's discard sentinel is −2³² (0xCF800000), and MojoShader's MOJOSHADER_printFloat converts the magnitude through a 32-bit unsigned long on Windows (LLP64), overflowing to ±0.0 in the translated source — so texkill's < 0 test never fires (Dissolve rendered un-discarded). Clamped in place to the same-signed largest float BELOW 2³² (±4294967040.0, 0x4F7FFFFF), which MojoShader prints exactly; the sentinel's only observable property is its sign. fxc's largest def literals are ±1, so the oracle never trips this. Empirically proven pixel-identical in real FNA by the rung-4 harness's clamped-Dissolve experiment.

The rewrite is semantics-preserving, not a blind mask/swizzle flip: the offending operand is routed through a fresh temporary — mov rK, reg.<replicated-masked-components> followed by the canonical texkill rK.xyzw (each tested lane now holds one of the originally-tested values), or mov rK, src0.<swizzle> followed by texld dst, rK, s#. Blindly widening the texkill mask would test garbage lanes; this never does.

D3D9 SM2+ instruction tokens carry an explicit operand-count field and the format has no byte-offset branches, so inserting instructions is purely mechanical; comment blocks (the CTAB) pass through byte-identical. Streams below SM2 are returned unchanged: MojoShader's ps_1_x rules differ wholesale (and ps_1_x tokens carry no instruction-length fields to walk), and the FNA pipeline rejects literal SM1 profiles upstream (SD0300), so this pass never has to reason about them.

Applied ONLY on the FNA path — the DirectX SM5 path never sees this code, and the blob handed to the fx_2_0 writer stays exactly what MojoShader will consume.

public static class D3d9BytecodePatcher
Inheritance
D3d9BytecodePatcher
Inherited Members

Methods

PatchForMojoShader(byte[], string)

Patches bytecode for MojoShader compatibility. Returns the original array when nothing needs patching (the common case — zero-copy).

public static Result<byte[], ShaderError> PatchForMojoShader(byte[] bytecode, string sourceFile)

Parameters

bytecode byte[]
sourceFile string

Returns

Result<byte[], ShaderError>