backend

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 3 Imported by: 0

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

View Source
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

View Source
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.

View Source
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 added in v0.17.12

func EndsWithDigit(s string) bool

EndsWithDigit checks if a string ends with an ASCII digit.

func IsASCIIAlphanumeric added in v0.17.12

func IsASCIIAlphanumeric(c rune) bool

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 added in v0.17.12

func NeedsTrailingUnderscore(name string) bool

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 added in v0.17.6

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
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL