Documentation
¶
Overview ¶
Package spirv provides SPIR-V code generation from naga IR.
SPIR-V is the standard intermediate language for GPU shaders, used by Vulkan, OpenCL, and other APIs.
Architecture ¶
This package is a thin public API layer. All codegen, builder, and spec logic lives in spirv/internal/codegen (following the DXIL pattern).
IR to SPIR-V Backend ¶
Compile translates naga IR modules to SPIR-V binary format:
binary, info, err := spirv.Compile(irModule, spirv.Options{})
if err != nil {
log.Fatal(err)
}
The binary writer, opcodes and the other SPIR-V enumerations are internal, as in Rust naga (back::spv); the public surface is Compile, its Options and TranslationInfo, Version, Capability and the binding map types.
References ¶
SPIR-V Specification: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html
Concurrency ¶
Every exported function is safe for concurrent use. Compile never modifies its input module, so one module can be compiled for several targets at once.
Example (BackendCompile) ¶
Example_backendCompile demonstrates compiling an IR module to SPIR-V.
package main
import (
"fmt"
"github.com/gogpu/naga/ir"
"github.com/gogpu/naga/spirv"
)
func main() {
// Create a simple IR module with types and constants
module := &ir.Module{
Types: []ir.Type{
// f32 scalar type
{Name: "f32", Inner: ir.ScalarType{Kind: ir.ScalarFloat, Width: 4}},
// vec3<f32> vector type
{
Name: "vec3f",
Inner: ir.VectorType{
Size: ir.Vec3,
Scalar: ir.ScalarType{Kind: ir.ScalarFloat, Width: 4},
},
},
},
// A constant's value is its Init: an expression in GlobalExpressions.
GlobalExpressions: []ir.Expression{
{Kind: ir.Literal{Value: ir.LiteralF32(1.0)}}, // 0
{Kind: ir.Literal{Value: ir.LiteralF32(2.0)}}, // 1
{Kind: ir.Literal{Value: ir.LiteralF32(3.0)}}, // 2
{Kind: ir.ExprCompose{Type: 1, Components: []ir.ExpressionHandle{0, 1, 2}}}, // 3
},
Constants: []ir.Constant{
{Name: "x", Type: 0, Init: 0}, // const x: f32 = 1.0;
{Name: "y", Type: 0, Init: 1}, // const y: f32 = 2.0;
{Name: "z", Type: 0, Init: 2}, // const z: f32 = 3.0;
{Name: "position", Type: 1, Init: 3}, // const position: vec3<f32> = vec3(1.0, 2.0, 3.0);
},
GlobalVariables: []ir.GlobalVariable{},
Functions: []ir.Function{},
EntryPoints: []ir.EntryPoint{},
}
// Configure backend options
options := spirv.Options{
Version: spirv.Version1_3,
Debug: true, // Include debug names
}
binary, _, err := spirv.Compile(module, options)
if err != nil {
panic(err)
}
// Verify magic number
magic := uint32(binary[0]) | uint32(binary[1])<<8 | uint32(binary[2])<<16 | uint32(binary[3])<<24
fmt.Printf("SPIR-V magic: 0x%08x\n", magic)
fmt.Printf("Binary size: %d bytes\n", len(binary))
fmt.Println("Compilation successful!")
}
Output: SPIR-V magic: 0x07230203 Binary size: 172 bytes Compilation successful!
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( Version1_0 = Version{1, 0} Version1_1 = Version{1, 1} Version1_2 = Version{1, 2} Version1_3 = Version{1, 3} Version1_4 = Version{1, 4} Version1_5 = Version{1, 5} Version1_6 = Version{1, 6} )
Common SPIR-V versions.
Functions ¶
This section is empty.
Types ¶
type BindingInfo ¶ added in v0.20.0
type BindingInfo struct {
// DescriptorSet is the SPIR-V DescriptorSet decoration.
DescriptorSet uint32
// Binding is the SPIR-V Binding decoration.
Binding uint32
// BindingArraySize, when set on a binding array, declares it with this
// many elements instead of its WGSL size. It is also the length bounds
// checks use for an unbounded binding array, which otherwise has none.
BindingArraySize *uint32
}
BindingInfo is where a resource goes in SPIR-V. Mirrors Rust naga's spv::BindingInfo.
type Capability ¶
type Capability uint32
Capability is a SPIR-V capability, by its number in the SPIR-V specification (spirv::Capability in Rust). The constants name the ones the backend writes or callers commonly allow; any other value of the specification can be converted.
const ( CapabilityMatrix Capability = 0 CapabilityShader Capability = 1 CapabilityFloat16 Capability = 9 CapabilityFloat64 Capability = 10 CapabilityInt64 Capability = 11 CapabilityInt16 Capability = 22 CapabilityImageGatherExtended Capability = 25 CapabilityInt8 Capability = 39 CapabilityLinkage Capability = 5 CapabilityInt64Atomics Capability = 12 CapabilityClipDistance Capability = 32 CapabilityImageCubeArray Capability = 34 CapabilitySampleRateShading Capability = 35 CapabilitySampled1D Capability = 43 CapabilityImage1D Capability = 44 CapabilitySampledCubeArray Capability = 45 CapabilityStorageImageExtendedFormats Capability = 49 CapabilityImageQuery Capability = 50 CapabilityDerivativeControl Capability = 51 CapabilityStorageBuffer16BitAccess Capability = 4433 CapabilityUniformAndStorageBuffer16BitAccess Capability = 4434 CapabilityStorageInputOutput16 Capability = 4436 CapabilityMultiView Capability = 4439 CapabilityFragmentBarycentricKHR Capability = 5284 CapabilityShaderNonUniform Capability = 5301 CapabilityAtomicFloat32AddEXT Capability = 6033 CapabilityDotProductInput4x8BitPacked Capability = 6018 CapabilityDotProduct Capability = 6019 CapabilityGroupNonUniform Capability = 61 CapabilityGroupNonUniformVote Capability = 62 CapabilityGroupNonUniformArithmetic Capability = 63 CapabilityGroupNonUniformBallot Capability = 64 CapabilityGroupNonUniformShuffle Capability = 65 CapabilityGroupNonUniformShuffleRel Capability = 66 CapabilityGroupNonUniformQuad Capability = 68 CapabilityGeometry Capability = 2 CapabilitySubgroupBallotKHR Capability = 4423 CapabilityInt64ImageEXT Capability = 5016 )
Common capabilities.
type Options ¶
type Options struct {
// Version is the SPIR-V version of the module. The zero value is 1.0,
// the only version every Vulkan device accepts without knowing the
// device, as Rust naga's default; wgpu-hal derives it from the device
// (Vulkan 1.0 → 1.0, 1.1 → 1.3, 1.2 → 1.5, 1.3 → 1.6, 1.4 with
// VK_KHR_spirv_1_4). The backend never raises it: a feature that needs a
// newer version (subgroup operations need 1.3) is an error, as Rust
// naga's SpirvVersionTooLow. Below 1.4, values copied between the
// layout-free Workgroup types and the decorated ones are copied member by
// member instead of with OpCopyLogical.
Version Version
// ExtraCapabilities are declared in addition to those the module needs
// (no Rust naga counterpart).
ExtraCapabilities []Capability
// Debug includes debug information.
Debug bool
// UseStorageInputOutput16 passes f16 entry point inputs and outputs as
// f16 with the StorageInputOutput16 capability. The zero value converts
// them to and from f32, which every device accepts; the capability needs
// SHADER_F16 and device support, so it is an opt-in. Rust naga's
// use_storage_input_output_16 defaults to true; wgpu-hal passes whether
// the device supports it.
UseStorageInputOutput16 bool
// ForcePointSize adds a BuiltIn PointSize output variable with value 1.0.
ForcePointSize bool
// AdjustCoordinateSpace negates the y of every Position output after the
// entry point stores it (Rust naga's ADJUST_COORDINATE_SPACE). Vulkan
// flips y with a negative viewport height, so the zero value leaves
// positions as they are; set it for a device that ignores a negative
// height (wgpu-hal's IGNORED_NEGATIVE_VIEWPORT_HEIGHT workaround). Rust's
// Options::default() has it on.
AdjustCoordinateSpace bool
// SkipLoopBounding leaves loops as they are. By default every loop gets
// a decrementing 64-bit counter that breaks out of it, so an infinite
// loop, undefined behavior that can hang the device, ends. Rust naga's
// force_loop_bounding defaults to true, and wgpu-hal passes true.
SkipLoopBounding bool
// BoundsCheckPolicies selects how out-of-bounds indices and image loads
// are handled. The zero value is backend.BoundsCheckRestrict everywhere,
// which WebGPU requires; Rust naga's default is Unchecked, and wgpu-hal
// passes the policies of the device. Every field honors every policy:
// Restrict with OpExtInst UMin (index) and clamped coordinates (image
// loads), ReadZeroSkipWrite with a comparison and a selection, Unchecked
// with no code. The exceptions: an unbounded binding array under
// Restrict or ReadZeroSkipWrite needs its size in BindingMap
// (BindingArraySize), and ReadZeroSkipWrite cannot guard a runtime-sized
// array or a binding array of acceleration structures; Compile fails
// then.
BoundsCheckPolicies backend.BoundsCheckPolicies
// EntryPoint selects the one entry point the module gets, as Rust
// naga's spv::PipelineOptions: the module then leaves out the globals
// that entry point does not use and the functions it cannot call. nil
// writes every entry point. One the module does not have is an error
// wrapping backend.ErrEntryPointNotFound.
EntryPoint *backend.EntryPointSelector
// AllowedCapabilities is the set of capabilities the module may use
// (Rust naga's capabilities): one it needs outside the set is an error.
// nil allows every capability.
AllowedCapabilities map[Capability]struct{}
// SkipRayQueryInitTracking leaves out the state variable every ray query
// gets to prevent misuse: by default rayQueryInitialize checks its ray
// (tmin <= tmax and the like) and skips the query when it is invalid, and
// rayQueryProceed does nothing on a query that was not initialized. Rust
// naga's ray_query_initialization_tracking defaults to true, and
// wgpu-hal passes true.
SkipRayQueryInitTracking bool
// BindingMap maps WGSL @group/@binding to the SPIR-V DescriptorSet and
// Binding decorations, and can give a binding array its size. Mirrors
// Rust naga's spv::Options::binding_map. A nil map keeps every resource
// at its WGSL @group/@binding. A non-nil map, empty too, must list every
// resource the module declares: one without an entry is an error
// wrapping backend.ErrMissingBinding, unless FakeMissingBindings is set.
BindingMap map[ir.ResourceBinding]BindingInfo
// FakeMissingBindings keeps a resource that a non-nil BindingMap leaves
// out at its WGSL @group/@binding instead of failing. Rust naga's
// fake_missing_bindings defaults to true; wgpu-hal passes false, the
// zero value here.
FakeMissingBindings bool
// ZeroInitializeWorkgroupMemory is how compute entry points zero their
// workgroup variables, as WebGPU requires. The zero value is
// backend.ZeroInitWorkgroupPolyfill, which works on every device; every
// mode is honored.
ZeroInitializeWorkgroupMemory backend.ZeroInitializeWorkgroupMemoryMode
// ClampFragDepth, when set, clamps the depth a fragment entry point
// writes to the range in two f32 push constants, min then max, at byte
// offset ClampFragDepth.Offset, for which the module declares a push
// constant block: WebGPU clamps frag_depth to the viewport's depth
// range, and Vulkan doesn't. The caller pushes the viewport's minDepth
// and maxDepth there. Rust naga's CLAMP_FRAG_DEPTH clamps to 0..1;
// Dawn's ClampFragDepth transform reads the range as this does. An
// entry point that writes frag_depth and has push constants of its own
// finds the range in its own block instead, members appended after its
// data, as an entry point has at most one push constant block, so Offset
// has to be past that data. Every stage that uses such a block then has
// the range in its block too.
ClampFragDepth *backend.ClampFragDepth
}
Options configures SPIR-V generation.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions returns the zero Options, the safe defaults: SPIR-V 1.0, Restrict bounds checks, bounded loops, f16 inputs and outputs through f32.
type TranslationInfo ¶ added in v0.21.0
type TranslationInfo struct {
// EntryPointNames maps the name of each entry point the module holds to
// its OpEntryPoint name, the same in SPIR-V.
EntryPointNames map[string]string
}
TranslationInfo describes what Compile wrote.