Documentation
¶
Overview ¶
Package backend holds cross-backend constants and helpers shared between naga's text backends (HLSL) and binary backends (DXIL) that both target the D3D ecosystem. Centralizing these values here prevents the HLSL writer and the DXIL emitter from drifting — a drift that directly causes D3D12 pipeline-state creation failures at the input-layout boundary (see BUG-DXIL-028).
Anything placed here must be:
- a spec-level output convention, not IR semantics
- referenced by at least two sibling backends that otherwise would duplicate a literal
- stable enough that external tooling (wgpu/hal, gg, ui) can rely on it
Index ¶
- Constants
- Variables
- func EndsWithDigit(s string) bool
- func IsASCIIAlphanumeric(c rune) bool
- func MemberInterfaceLess(a, b MemberInterfaceKey) bool
- func NeedsTrailingUnderscore(name string) bool
- func SortFlatBindings(bindings []ir.Binding, types []ir.TypeHandle, isVSInput bool)
- func SortedArgIndices(args []ir.FunctionArgument) []int
- func SortedMemberIndices(members []ir.StructMember) []int
- type MemberInterfaceKey
- type MemberInterfaceKind
- type PackedElement
- type PackedMember
- type SigElementInfo
- type SigPackInterp
- type SigPackKind
Constants ¶
const LocationSemantic = "LOC"
LocationSemantic is the semantic-name prefix for user-defined @location(N) vertex-shader inputs and inter-stage varyings in both HLSL source and DXIL input/output signatures.
It is a naga ecosystem convention. DXIL does not prescribe a name for arbitrary (non-SV_*) semantics; the backends choose one and every consumer must agree.
Current consumers (must match this value):
- naga/hlsl/types.go — HLSL source emission
- naga/dxil/dxil.go — PSV signature element name
- naga/dxil/internal/emit — !dx.entryPoints signature name
- naga/dxil/internal/container — ISG1/OSG1 SemanticMapping
- wgpu/hal/dx12/pipeline.go — D3D12_INPUT_ELEMENT_DESC.SemanticName
A mismatch between the DXIL input signature and the D3D12 input layout causes CreateGraphicsPipelineState to return E_INVALIDARG. IDxcValidator cannot detect this because the DXIL container is internally consistent; the break only surfaces at the container-to-pipeline boundary.
See BUG-DXIL-028 for the discovery trail.
Variables ¶
var HLSLCaseInsensitiveKeywords = map[string]struct{}{
"asm": {},
"decl": {},
"pass": {},
"technique": {},
"texture1d": {},
"texture2d": {},
"texture3d": {},
"texturecube": {},
}
HLSLCaseInsensitiveKeywords contains keywords that are case-insensitive in HLSL. These need special handling to avoid conflicts.
var HLSLReservedKeywords = map[string]struct{}{}/* 556 elements not displayed */
HLSLReservedKeywords contains all HLSL reserved keywords (case-sensitive). Shared between HLSL codegen (namer) and DXIL backend (NeedsTrailingUnderscore). Based on Microsoft HLSL documentation and Rust naga implementation.
Functions ¶
func EndsWithDigit ¶
EndsWithDigit checks if a string ends with an ASCII digit.
func IsASCIIAlphanumeric ¶
IsASCIIAlphanumeric checks if a rune is an ASCII letter or digit. Matches Rust's char::is_ascii_alphanumeric().
func MemberInterfaceLess ¶
func MemberInterfaceLess(a, b MemberInterfaceKey) bool
MemberInterfaceLess reports whether key a should come before key b in the graphics output ordering. Defined here so callers don't have to inline the three-way comparison.
func NeedsTrailingUnderscore ¶
NeedsTrailingUnderscore reports whether a variable name requires a trailing underscore suffix in HLSL output. The HLSL namer (matching Rust naga's proc::Namer) appends "_" when the sanitized name ends with an ASCII digit or collides with an HLSL reserved keyword. The DXIL backend uses this to produce metadata resource names that match what DXC would generate from the HLSL roundtrip.
func SortFlatBindings ¶
func SortFlatBindings(bindings []ir.Binding, types []ir.TypeHandle, isVSInput bool)
SortFlatBindings sorts parallel (bindings, types) slices in graphics interface order: @location bindings first (ascending by location index), then @builtin bindings (ascending by builtin enum value).
This is needed when multiple struct-typed arguments each contribute members to the flat input binding list. SortedMemberIndices handles within-struct ordering, but cross-argument ordering requires a final sort of the concatenated result. Without this, a builtin from an earlier argument (e.g., @builtin(position) from VertexOutput) appears before a location from a later argument (e.g., @location(1) from NoteInstance), producing wrong register assignments for fragment input signatures.
isVSInput should be true for vertex shader inputs (InputAssembler packing). DXC keeps VS inputs in declaration order — system values first, then locations — not the locations-first order used for fragment/geometry inputs. When isVSInput is true, this function is a no-op.
func SortedArgIndices ¶
func SortedArgIndices(args []ir.FunctionArgument) []int
SortedArgIndices returns the indices into the args slice in sorted emission order (locations first, builtins last), without mutating the original slice. Used by the DXIL backend to iterate entry-point arguments in the same order as the ISG1 signature, so loadInput sigID values match the register layout.
func SortedMemberIndices ¶
func SortedMemberIndices(members []ir.StructMember) []int
SortedMemberIndices returns the indices into the members slice in sorted emission order, without mutating the original slice. The DXIL backend uses these indices to walk both the signature builder and the storeOutput emitter in lockstep.
Returned slice has length len(members). If members is empty, returns nil.
Types ¶
type MemberInterfaceKey ¶
type MemberInterfaceKey struct {
Kind MemberInterfaceKind
Location uint32
Builtin ir.BuiltinValue
}
MemberInterfaceKey is the per-member sort key used to order struct members for graphics output emission.
func NewMemberInterfaceKey ¶
func NewMemberInterfaceKey(binding *ir.Binding) MemberInterfaceKey
NewMemberInterfaceKey builds a sort key from a struct member binding.
type MemberInterfaceKind ¶
type MemberInterfaceKind int
MemberInterfaceKind enumerates the binding categories in sort priority order.
const ( // MemberLocation is a @location(N) binding — sorted first by Location. MemberLocation MemberInterfaceKind = iota // MemberBuiltin is a @builtin(...) binding — sorted second by Builtin enum. MemberBuiltin // MemberOther is the catch-all (nil binding, unrecognized) — sorted last. MemberOther )
type PackedElement ¶
type PackedElement struct {
OrigIdx int // index into the original members slice (matches SortedMemberIndices)
Register uint32 // assigned register row (StartRow)
StartCol uint8 // starting component within the row, 0..3
ColCount uint8 // number of components consumed, 1..4
Rows uint8 // number of register rows consumed, 1..N (N=array length for ClipDistance)
}
PackedElement is the per-element register layout produced by PackSignatureElements. Together with the shared SortedMemberIndices order it tells every signature producer (OSG1/ISG1, PSV0 PSVSignatureElement, !dx.entryPoints metadata, storeOutput / loadInput rowIndex) where each element lives in the register file.
All packed values live in a single 4-component register row:
StartCol + ColCount <= 4
Multi-row elements (e.g. arrays for SV_ClipDistance / SV_CullDistance) always start at column 0 and consume Rows register slots; that case is represented with Rows > 1.
func PackSignatureElements ¶
func PackSignatureElements(elems []SigElementInfo, isInput bool) []PackedElement
PackSignatureElements assigns (Register, StartCol) to every entry in elems using DXC's signature-packing rules. Pure function, no side effects.
The returned slice has the same length and ordering as elems — caller indexes into it the same way it would index into elems or SortedMemberIndices.
Algorithm (mirrors DXC DxilSignatureAllocator::PackPrefixStable):
- SV_Position consumes its own row of 4 components (Allocated, StartCol=0).
- SV_ClipDistance / SV_CullDistance consume Rows contiguous rows starting at StartCol=0 (also unpacked with anything else).
- Other SV_* builtins consume their own row each (StartCol=0).
- System-managed PS elements get Register=0xFFFFFFFF, StartCol=0, Rows=1 — they do not consume any output row.
- User @location elements are packed by interpolation group, greedy first-fit within a 4-column row, in input (sorted) order. A new row is started whenever the current one has no room for the element's columns or its interpolation mode differs from the row's group.
isInput is currently unused — VS input also packs (DXIL.rst: VSIn uses PackingKind::InputAssembler which is "no packing" — incremental row assignment) but for fragment input (PackingKind::Vertex) packing applies. Callers that hit a real divergence between input/output packing in the future can branch on the flag without changing the API.
type PackedMember ¶
type PackedMember struct {
PackedElement
HasBinding bool // false if the member has no binding (caller must skip)
}
PackedMember pairs a struct member's original index with its assigned register layout. Length and ordering match SortedMemberIndices for the same struct, so callers can iterate as:
for sigID, pm := range packed {
member := members[pm.OrigIdx]
storeOutput(sigID, pm.Register, pm.StartCol, ...)
}
func PackStructMembers ¶
func PackStructMembers( irMod *ir.Module, members []ir.StructMember, stage ir.ShaderStage, isOutput bool, isVSInput bool, interpFn func(loc ir.LocationBinding) SigPackInterp, ) []PackedMember
PackStructMembers sorts the struct members in graphics-interface order (locations first, builtins last) and returns the packed register layout per member in that sorted order. interpFn maps a LocationBinding to its DXIL InterpolationMode enum value used for grouping.
stage and isOutput drive system-managed classification (SV_Depth on PS output is system-managed; SV_SampleIndex on PS input is system-managed).
isVSInput=true selects "no packing" mode (each element on its own row at column 0) per DXIL.rst PackingKind::InputAssembler. All other graphics signatures use the standard packed layout.
type SigElementInfo ¶
type SigElementInfo struct {
Kind SigPackKind
ColCount uint8 // 1..4 for single-row, 1 for multi-row scalar arrays (column width per row)
Rows uint8 // 1 for single-row, N for scalar arrays
Interp SigPackInterp // matters only when Kind == SigPackLocation
SemanticIdx uint32 // matters only when Kind == SigPackTargetOutput (Register = SemanticIdx)
}
SigElementInfo is the per-member input to PackSignatureElements.
func SigElementInfoForBinding ¶
func SigElementInfoForBinding( irMod *ir.Module, binding ir.Binding, typeHandle ir.TypeHandle, stage ir.ShaderStage, isOutput bool, interpFn func(loc ir.LocationBinding) SigPackInterp, ) SigElementInfo
SigElementInfoForBinding builds a SigElementInfo from a naga binding plus its IR type. Mirrors the bind-to-semantic mapping used by dxil/internal/emit/emitter.go makeSigInfo and dxil/dxil.go bindingToSignatureElements so all 6 signature producers see the same classification.
stage and isOutput are required because some bindings change packing class based on direction (SV_SampleIndex on PS input is system-managed; on nothing else). interpFn is provided so the caller can plug in its own interpolation-mode resolver — keeps this package free of any DXIL-emit dependency.
type SigPackInterp ¶
type SigPackInterp uint8
SigPackInterp is the DXC InterpolationMode enum value used to keep like-with-like when packing user locations. Two location elements may share a register row only if they have the same interpolation mode.
type SigPackKind ¶
type SigPackKind uint8
SigPackKind categorizes a signature element for the packing algorithm. The packing rules in DXC's DxilSignatureAllocator group elements by kind and never pack two elements from different kinds in the same row.
const ( // SigPackLocation is a user @location varying — packed by interpolation // group, greedy first-fit within a row. Used for VS output / PS input. SigPackLocation SigPackKind = iota // SigPackTargetOutput is a fragment-stage SV_Target output. Packing rule // is "Register = SemanticIndex" (DXIL.rst PackingKind::Target) — each // SV_Target gets its own row matching its semantic index, no packing // across targets. SigPackTargetOutput // SigPackBuiltinSVPosition is SV_Position — always its own row of 4 components. SigPackBuiltinSVPosition // SigPackBuiltinScalarArray covers SV_ClipDistance / SV_CullDistance which // are multi-row arrays of scalars and are not packed with anything. SigPackBuiltinScalarArray // SigPackBuiltinSystemValue covers other SV_* builtins (VertexID, InstanceID, // IsFrontFace, PrimitiveID, etc.) — each on its own row. SigPackBuiltinSystemValue // SigPackBuiltinSystemManaged covers system-managed PS-stage elements // (SV_Depth family, SV_Coverage, SV_StencilRef, SV_SampleIndex on PS input) // which carry StartRow=-1 / 0xFF and do not consume a register row at all. SigPackBuiltinSystemManaged // SigPackOther is the catch-all for elements without a binding — they do // not appear in the signature and are allocated nothing. SigPackOther )