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.
- ResourceArrayBindings
Collapses the per-element resource bindings a Shader Model 5
RDEFcarries for an ARRAY of textures or samplers into the one binding per array that Shader Model 4 reflection reports, which is the view realmgfxcbuilds its DirectX_11 parameter table from (issue #339).Measured 2026-10-02 with d3dcompiler_47 (
/Gec /O3, mgfxc's own flags) onTexture2D Tex[2] : register(t0)sampled asTex[0]andTex[1]:ps_4_0(andps_4_0_level_9_3): ONE bindingTex, BindPoint 0, BindCount 2. The BindPoint is the array's base register and the BindCount its declared size whichever elements are used:Tex[1]alone still reflectsText0 count 2, andTex[0] + Tex[3]of a 4-array reflectsText0 count 4. An array declared atregister(t1)reflectsText1 count 2 after the texture at t0.ps_5_0: one binding PER ELEMENT, named with the index, each BindCount 1:Tex[0]t0,Tex[1]t1 (andTex[1]t1 alone when only element 1 is read). vkd3d-shader's DXBC follows the same convention, andD3DReflectreports these records as they are stored.
mgfxccompiles a DirectX_11 shader at the model the author wrote (ps_4_0in every MonoGame template), walks the texture bindings and makes one sampler record per binding whose parameter is named by the binding, so a texture array is ONE effect parameter (Tex) with ONE record at the array's base slot (3.8.4.1 and 3.8.5 alike). ShadowDusk compiles DirectX 11 at Shader Model 5 (Vkd3dShaderCompiler/D3DCompileRequest), so its RDEF has the per-element records; before this collapse they became parametersTex[0],Tex[1], ... that no author ever names (effect.Parameters["Tex"]was null). The collapse reproduces the Shader Model 4 view from the Shader Model 5 records: the base name, the base slot (slot - index, the same for every element), and the element count asArrayLength. Non-array bindings pass through untouched, so no byte of an array-free effect moves. The DXIL reflector (DirectX 12) already reports an array this way (BindCount), so the two DirectX extractors now agree.
- 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
A placeholder that was meant to cross-check texture and sampler bind slots against a SPIR-V module. It was never implemented and always returns Empty.
- 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.