Namespace ShadowDusk.Core.Reflection
Classes
- AnnotationReflection
A reflected HLSL annotation (a
name = valuepair) 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_samplerspass collapses into a single GLSL combined-sampler uniform. Both names are the HLSL identifiers (the SPIR-VOpNameof 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_TYPEvalues — numerically identical to MojoShader's symbol class/type and to the fx_2_0 typedef encoding (seedocs/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'sparse_constant_table, which is what FNA itself runs on these bytes at load time, and the documentedD3DXSHADER_CONSTANTTABLEstructs).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, andD3D_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
DXBCcontainer'sRDEF(resource definition) andISGN/OSGN(signature) chunks into the same ReflectedEffect shapeD3DReflectproduced — 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.mgfxcompiles 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/dxbcwriters, and public DXBC container format references). Field semantics were verified byte-level against d3dcompiler_47's own emission andID3D11ShaderReflection's readback; oracle parity is enforced byDxbcReflectionParityTests(managed output deeply equal toD3DReflect'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; seeVariableReflection). - Empty constant buffers are dropped — mgfxc emits no cbuffer record for an empty
$Globals(e.g. a texture-only PS), so the DX.mgfxmust match. D3DReflectfixes up signature system values fxc stores as 0 by semantic name (SV_Target→Target,SV_Depth→Depth, …) — reproduced in FixUpSystemValue(uint, string).
- Array variables report a 16-byte-aligned size (the previous extractor rounded
- 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
.mgfxcontainer.
- 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}")), andMonoGameGlslRewriternames thoseps_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.mgfxsampler table has to mirror: N textures read through one sharedSamplerStateis 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_samplersis not one of the 11 functionssrc/ShadowDusk.Wasm/wwwroot/spirv-cross/spirv-cross.wasmexports, 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 reasonRdefReaderand 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) runstraverse_all_reachable_opcodesfrom the single entry-point function: blocks in binary order, ops in binary order, recursing into eachOpFunctionCalltarget 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 throughremap_parameter→maybe_get_backing_variable(back throughOpLoad/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_resourceswalks variables in id order and skips every separate image/sampler whenvulkan_semanticsis 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
SD0217rather 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 /
ID3D12ShaderReflectionpath, 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 numericLocationdecorations — so the original signatures cannot be recovered. The PS-only MonoGame corpus does not need them for.mgfxoutput.
- SpirvVertexInputReflector
Builds the
.mgfxper-shader vertex ATTRIBUTE TABLE from a vertex shader's SPIR-V, mirroring real mgfxc's Vulkan writer (ShaderProfile.Vulkan.CreateShader): the inputs are ordered byLocation, each semantic maps to a MonoGameVertexElementUsagebyte 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'sCreateGraphicsPipelineState— called lazily right before the first Draw — withE_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
NameandLocationfields are written as""/0— exactly what mgfxc emits for a Vulkan shader (its GL profile is the one that populates them);Usage/Indexare whatGenerateInputElementsactually 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
VertexElementUsagebyte 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
EffectParameterClassso reflected metadata maps directly onto the runtime.
- EffectParameterType
The element type of an effect parameter, mirroring MonoGame's
EffectParameterTypeso reflected metadata maps directly onto the runtime.
- TextureDimension
The dimensionality of a reflected texture binding.