Table of Contents

Namespace ShadowDusk.Core.Reflection

Classes

AnnotationReflection

A reflected HLSL annotation (a name = value pair) on an effect parameter.

BindingSlotMap

Maps texture and sampler names to their bind slots, used to align reflected resource names with the slots emitted in the compiled shader.

CombinedSamplerPair

One (texture, sampler) pair that SPIRV-Cross's build_combined_image_samplers pass collapses into a single GLSL combined-sampler uniform. Both names are the HLSL identifiers (the SPIR-V OpName of the backing variables), which is the key BOTH reflection paths agree on — the native DXIL oracle and the pure-managed SpirvReflector assign different raw binding numbers, so a binding-keyed join would not be host-independent.

ConstantBufferReflection

A reflected constant (uniform) buffer: its name, size, bind slot, and variables.

CtabConstant

One constant from a D3D9 shader's CTAB constant table: the bridge between an HLSL global and the register(s) the SM1–3 bytecode reads it from. Class, Type carry the raw D3DXPARAMETER_CLASS/D3DXPARAMETER_TYPE values — numerically identical to MojoShader's symbol class/type and to the fx_2_0 typedef encoding (see docs/fx2-binary-format.md §6.1).

CtabReader

Reads the CTAB constant-table comment out of a legacy D3D9 SM1–3 token stream — the reflection source for the FNA fx_2_0 path. Layout per docs/fx2-binary-format.md §11 (derived from MojoShader's parse_constant_table, which is what FNA itself runs on these bytes at load time, and the documented D3DXSHADER_CONSTANTTABLE structs).

Only the leading comment block(s) directly after the version token are scanned: that is where fxc and vkd3d both place the CTAB, and scanning the instruction stream would risk misreading raw IEEE-float immediates (e.g. from def) as comment tokens.

CtabTable

A parsed D3D9 CTAB constant table — the SM1–3 analog of DXIL/SPIR-V reflection. It is what MojoShader itself uses at load time to bind effect parameters to shader registers, which makes it the faithful reflection source for the FNA fx_2_0 path.

D3DReflectionMaps

The single source of truth for the raw D3D reflection numeric value → ShadowDusk enum lookup tables (D3D_SHADER_VARIABLE_CLASS, D3D_SHADER_VARIABLE_TYPE, and D3D_SRV_DIMENSION). Both reflection readers consume these so the numeric mappings cannot drift apart: the pure-managed RdefReader passes the raw values it reads from the DXBC container, while the DXIL extractor casts its Vortice/D3D12 reflection enums (whose underlying values are these same D3D constants).

These helpers only own the value → enum table. Each caller keeps its own unmapped-value policy: the DXIL extractor throws on an unmapped class/type, whereas RdefReader reports failure (false) — that difference is deliberate and is preserved at the call sites, not unified here.

ParameterReflection

A flattened effect parameter as exposed to the MonoGame runtime (the user-settable surface, e.g. via Effect.Parameters["Foo"]), with its name, optional semantic, type shape, and annotations.

RdefReader

Pure-managed DXBC reflection: parses the DXBC container's RDEF (resource definition) and ISGN/OSGN (signature) chunks into the same ReflectedEffect shape D3DReflect produced — the SM4/SM5 sibling of CtabReader (Phase 18 Track A). This removes the Windows-only d3dcompiler_47 P/Invoke from the DirectX pipeline, so DX11 .mgfx compiles run on Linux/macOS (and, being dependency-free managed code, in WASM).

Layout sources: the DXBC container / RDEF format is stable and documented (Wine's d3dcompiler reflection implementation, vkd3d-shader's tpf/dxbc writers, and public DXBC container format references). Field semantics were verified byte-level against d3dcompiler_47's own emission and ID3D11ShaderReflection's readback; oracle parity is enforced by DxbcReflectionParityTests (managed output deeply equal to D3DReflect's for both d3dcompiler_47 and vkd3d DXBC, on Windows).

Behavioral quirks preserved deliberately (the bar is "identical to D3DReflect + the previous extractor", not "looks right"):

  • Array variables report a 16-byte-aligned size (the previous extractor rounded D3DReflect's unpadded trailing element up; see VariableReflection).
  • Empty constant buffers are dropped — mgfxc emits no cbuffer record for an empty $Globals (e.g. a texture-only PS), so the DX .mgfx must match.
  • D3DReflect fixes up signature system values fxc stores as 0 by semantic name (SV_TargetTarget, SV_DepthDepth, …) — reproduced in FixUpSystemValue(uint, string).
ReflectedEffect

The complete reflected metadata for one compiled shader: its constant buffers, texture and sampler bindings, input/output signatures, and flattened effect parameters. Produced by IShaderReflector and consumed when writing the .mgfx container.

SamplerReflection

A reflected sampler binding: its name, bind slot, and associated texture.

SignatureParameterReflection

One entry in a shader stage's input or output signature, describing a semantic-bound attribute (its semantic, register, system-value role, component type, and write mask).

SpirvCombinedSamplerPairs

Derives, in exactly the order SPIRV-Cross declares them, the combined-sampler uniforms that the SPIR-V → GLSL transpile will emit — purely managed, straight from the SPIR-V words.

Why this exists. The MonoGame OpenGL runtime binds a texture unit to a sampler by GLSL uniform NAME (glGetUniformLocation("ps_s{k}")), and MonoGameGlslRewriter names those ps_s{k} in emitted-declaration order. SPIRV-Cross emits one combined sampler per (texture, sampler) pair, so neither the reflected texture list nor the reflected sampler list is the list the .mgfx sampler table has to mirror: N textures read through one shared SamplerState is N uniforms but one reflected sampler, and the mirror shape (one texture, N samplers) is N uniforms but one reflected texture. Keying the table on either list silently binds the wrong texture (Phase 51 A7).

Why not just call SPIRV-Cross. The pass already computes this list, but spvc_compiler_get_combined_image_samplers is not one of the 11 functions src/ShadowDusk.Wasm/wwwroot/spirv-cross/spirv-cross.wasm exports, and that module is an out-of-band emscripten build. Reading it natively would fix the desktop path only and break the CLI-vs-WASM byte-identity promise for this shape. Deriving it from the SPIR-V is host-independent by construction, the same reason RdefReader and SpirvReflector exist.

The ordering rule is transcribed, not guessed (read from the pinned SPIRV-Cross tree the WASM module is built from, tag vulkan-sdk-1.4.335.0):

  • Compiler::build_combined_image_samplers (spirv_cross.cpp) runs traverse_all_reachable_opcodes from the single entry-point function: blocks in binary order, ops in binary order, recursing into each OpFunctionCall target with a parameter-to-argument remapping pushed for the callee's scope.
  • The only trigger is OpSampledImage. Its image and sampler operands are resolved back to global variables through remap_parametermaybe_get_backing_variable (back through OpLoad / OpAccessChain), and the pair is appended if not already present. So the order is first-use order, deduplicated — unrelated to declaration order, bind slots, or binding numbers.
  • The synthesized variable ids come from ir.increase_bound_by(2), so they are monotonic in first-use order and sort after every original module id; CompilerGLSL::emit_resources walks variables in id order and skips every separate image/sampler when vulkan_semantics is off. Hence emitted declaration order IS first-use pair order.

The emitted GLSL cannot be used instead. SPIRV-Cross's readable SPIRV_Cross_Combined<Image><Sampler> name is applied by its command-line tool, not by the pass, so through the C API the uniforms arrive as bare _<id> (uniform sampler2D _40;) carrying no pair identity at all.

Every shape this model does not cover fails loudly with SD0217 rather than producing a plausible-but-wrong order, because a subtly wrong order is exactly the silent mis-bind this class exists to eliminate.

SpirvReflector

Pure-managed IShaderReflector that derives a ReflectedEffect directly from SPIR-V bytecode — no native DXIL / ID3D12ShaderReflection path, so it runs inside the .NET WASM browser host (Phase 19).

It mirrors the field-population semantics of the native DXIL oracle (ShadowDusk.HLSL.Reflection.DxilReflectionExtractor) for the OpenGL SM3 PS-only corpus: constant-buffer layouts (offsets, sizes, class/type, rows/columns/elements, 16-byte packing) and texture / sampler bind slots.

Signatures. InputSignature and OutputSignature are intentionally left EMPTY: SPIR-V discards HLSL semantic strings (TEXCOORD0, SV_Target, …) — it keeps only numeric Location decorations — so the original signatures cannot be recovered. The PS-only MonoGame corpus does not need them for .mgfx output.

SpirvVertexInputReflector

Builds the .mgfx per-shader vertex ATTRIBUTE TABLE from a vertex shader's SPIR-V, mirroring real mgfxc's Vulkan writer (ShaderProfile.Vulkan.CreateShader): the inputs are ordered by Location, each semantic maps to a MonoGame VertexElementUsage byte plus its semantic index, and an input that spans several locations (a matrix, an array) contributes one entry per location with a running index.

This table IS load-bearing on the new native backend (correction, Phase 54 follow-up, 2026-07-23). An earlier version of this remark claimed MonoGame's native backends build their vertex input layout positionally and never read this table — that is false. The shared managed VertexInputLayout.GenerateInputElements (used by every native backend, including DirectX12) iterates this exact table to match declared vertex-buffer elements against the shader's required inputs; an empty table silently yields a zero-element input layout (its "missing input" check only runs inside the per-attribute loop, so it never fires when the table itself is empty), which then fails DirectX12's CreateGraphicsPipelineState — called lazily right before the first Draw — with E_INVALIDARG. Confirmed by reading MonoGame's real v3.8.5 source directly (VertexInputLayout.Native.cs, Shader.Native.cs). Always populate this table for a native-backend vertex shader; never assume it is decorative.

The Name and Location fields are written as "" / 0 — exactly what mgfxc emits for a Vulkan shader (its GL profile is the one that populates them); Usage/Index are what GenerateInputElements actually matches on.

SpvReflectionVerifier

Cross-checks texture and sampler bind slots against a SPIR-V module, producing a BindingSlotMap. The SPIRV-Cross-backed verification is not yet implemented; the current implementation returns Empty so the reflection pipeline can still run for DXIL-only use cases.

TextureReflection

A reflected texture binding: its name, bind slot, and dimensionality.

VariableReflection

A single variable packed inside a ConstantBufferReflection: its name, byte layout within the buffer, type shape, and (for structs) nested members.

VertexSemanticMapper

Maps an HLSL vertex-input semantic name to MonoGame's VertexElementUsage byte plus its semantic index. Shared by every backend that builds a per-shader vertex attribute table (SPIR-V for Vulkan, DXIL for DirectX12) so the mapping cannot drift between them.

Interfaces

IShaderReflector

Derives a ReflectedEffect from a single compiled shader blob.

Unlike the native DXIL path (ID3D12ShaderReflection), an implementation of this interface is expected to be pure-managed so it can run inside the .NET WASM browser host (Phase 19), where no native reflection library is available.

No stage parameter is taken: the execution model (vertex / pixel / …) is recovered from the blob's own entry-point metadata.

Enums

CtabRegisterSet

The D3D9 register file a CTAB constant is bound to (D3DXREGISTER_SET; the values are the on-disk u16).

EffectParameterClass

The shape class of an effect parameter, mirroring MonoGame's EffectParameterClass so reflected metadata maps directly onto the runtime.

EffectParameterType

The element type of an effect parameter, mirroring MonoGame's EffectParameterType so reflected metadata maps directly onto the runtime.

TextureDimension

The dimensionality of a reflected texture binding.