Table of Contents

Class FxParseResult

Namespace
ShadowDusk.HLSL.Ast
Assembly
ShadowDusk.HLSL.dll

The output of the FX9 pre-parser: stripped HLSL plus all extracted FX9 metadata.

public sealed record FxParseResult : IEquatable<FxParseResult>
Inheritance
FxParseResult
Implements
Inherited Members

Properties

DeferredLegacyIntrinsicCalls

The tex2Dlod / tex2Dbias / tex2Dproj calls this RAW-text parse left verbatim because the right rewrite depends on which #if branch compiles (one body under two conditional headers, or the bound texture declared with a different type per branch), each as the FX0012 it would be if no preprocessed re-parse could resolve it. A compile with any of these must be redone from the preprocessed source (the legacy-sampler recovery), where every conditional is resolved; DirectX 11, whose compiler would otherwise take the unrewritten call silently, does so up front. Always empty on a preprocessed parse.

public IReadOnlyList<FxParseError> DeferredLegacyIntrinsicCalls { get; init; }

Property Value

IReadOnlyList<FxParseError>

ExplicitGlSamplerSlots

TEXTURE name -> the OpenGL texture unit an explicit register(sN) on its LEGACY sampler declaration pins it to. Keyed on the texture (synthesized X_SDTexture, or the one a sampler_state block references) because that is what the GL sampler table joins on. Empty when nothing declared a register.

This exists because the register clause is otherwise destroyed before DXC sees it: the SM4 rewrite turns sampler X : register(s2); into Texture2D X_SDTexture; SamplerState X;, so neither reflection nor the SPIR-V can recover the 2. The clause is recorded here rather than preserved in the rewritten HLSL deliberately: emitting it would change what DXC compiles and move the DirectX, DX12, Vulkan and FNA bytes, none of which have a reported defect. Recording it changes the OpenGL slot allocation only.

Legacy form only, and that is measured, not an oversight (2026-08-02). mgfxc honours the annotation exactly here, because compiled at ps_3_0 a legacy sampler IS the combined sampler and register(sN) pins its SM3 sampler register. For the modern spelling it does not: given Texture2D T : register(t3); SamplerState S : register(s2); the mgfxc OpenGL build puts the pair on slot 0 regardless, allocating by texture declaration order. Recording modern registers here would therefore make us DIVERGE.

This is the reading of whatever text the parser was given, which for a compile is the raw source: a register that only exists in an inactive #if branch is counted and one spelled through a macro is missed (issue #299). The OpenGL target and the raylib converter therefore take this map from CollectGlSamplerSlots(string, string, FxParseResult), which reads it off the preprocessed view the way mgfxc does.

public IReadOnlyDictionary<string, int> ExplicitGlSamplerSlots { get; init; }

Property Value

IReadOnlyDictionary<string, int>

ParameterAnnotations

Annotation blocks attached to global parameter declarations.

public required IReadOnlyList<ParameterAnnotation> ParameterAnnotations { get; init; }

Property Value

IReadOnlyList<ParameterAnnotation>

ReservedGlSamplerSlots

OpenGL sampler registers that an explicit register(sN) on a MODERN SamplerState declaration takes out of circulation, so a synthesized combined sampler must be allocated around them rather than onto them.

Compiling for OpenGL means compiling at ps_3_0, where a texture and a sampler are ONE object sharing a single register namespace. A modern SamplerState still sits at its declared register, but the combined sampler fxc synthesizes for each (texture, sampler) pair cannot reuse it. Measured: with one texture and SamplerState S : register(s0), mgfxc emits ps_s1, not ps_s0.

This is what makes the LEGACY case stop looking like a special case. There the sampler is the combined object, so it both reserves its register and occupies it, landing the pair exactly on N. Same allocator, different starting facts.

public IReadOnlySet<int> ReservedGlSamplerSlots { get; init; }

Property Value

IReadOnlySet<int>

RestructuredHelpers

The helpers StrippedHlsl defines for tex2Dlod / tex2Dbias / tex2Dproj calls: each name, the intrinsic it stands for, and where its definition sits in StrippedHlsl (1-based line and columns, end exclusive). Lets a diagnostic about a call name the intrinsic the author wrote, while one located inside a definition (the helper's own placement) is left as the compiler wrote it.

public IReadOnlyList<RestructuredHelperDefinition> RestructuredHelpers { get; init; }

Property Value

IReadOnlyList<RestructuredHelperDefinition>

Samplers

All sampler declarations with sampler_state blocks extracted from the source.

public required IReadOnlyList<SamplerInfo> Samplers { get; init; }

Property Value

IReadOnlyList<SamplerInfo>

StrippedHlsl

HLSL source with all FX9 blocks stripped, preserving line numbers for error reporting.

public required string StrippedHlsl { get; init; }

Property Value

string

SynthesizedSamplerTextures

SAMPLER name -> the Texture2D the SM4 rewrite SYNTHESIZED for it, for every legacy sampler that binds no texture of its own (sampler2D A;, sampler A : register(s1);, sampler2D A = sampler_state { MinFilter = Point; };). The rewrite has to give the texture a name of its own (A_SDTexture), because HLSL cannot declare a Texture2D A beside the SamplerState A; but to mgfxc the legacy sampler is ONE object, and the effect parameter it emits for it is named A on every profile. The compiler uses this map to give the parameter that name back, so effect.Parameters["A"] finds the texture exactly as it does under mgfxc. Empty when nothing was synthesized (including the FNA fx_2_0 mode, which keeps the legacy declaration verbatim).

public IReadOnlyDictionary<string, string> SynthesizedSamplerTextures { get; init; }

Property Value

IReadOnlyDictionary<string, string>

Techniques

All technique blocks extracted from the source.

public required IReadOnlyList<TechniqueInfo> Techniques { get; init; }

Property Value

IReadOnlyList<TechniqueInfo>

UndeclaredStateTextures

SAMPLER name -> the texture its sampler_state block names (Texture = <T>), for every legacy sampler the rewrite turned into SamplerState whose texture the MAIN file's code never declares or mentions. mgfxc reads the name off the state block and emits a T parameter even when nothing declares T, while the rewritten T.Sample(...) needs a declaration. These are CANDIDATES: an #included file or a macro can still declare T, so the compiler adds Texture2D T; only when the preprocessed source (includes inlined, macros expanded) declares no T.

public IReadOnlyDictionary<string, string> UndeclaredStateTextures { get; init; }

Property Value

IReadOnlyDictionary<string, string>