Help us shape the future of Go! Take the 2026 Go Developer Survey

spirv

package
v0.21.2 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 3 Imported by: 1

README

SPIR-V Binary Writer

This package provides a low-level SPIR-V binary writer for constructing SPIR-V modules programmatically.

Status

✅ Implemented - Binary writer foundation complete 🔄 Next Step - IR to SPIR-V translation

Features

ModuleBuilder

The main API for building SPIR-V modules:

builder := spirv.NewModuleBuilder(spirv.Version1_3)

// Required setup
builder.AddCapability(spirv.CapabilityShader)
builder.SetMemoryModel(spirv.AddressingModelLogical, spirv.MemoryModelGLSL450)

// Types
floatType := builder.AddTypeFloat(32)
vec4Type := builder.AddTypeVector(floatType, 4)

// Entry points
funcID := builder.AddFunction(...)
builder.AddEntryPoint(spirv.ExecutionModelFragment, funcID, "main", nil)

// Generate binary
binary := builder.Build()
Supported Operations
Type Operations
  • AddTypeVoid() - Void type
  • AddTypeBool() - Boolean type
  • AddTypeFloat(width) - Float types (16, 32, 64-bit)
  • AddTypeInt(width, signed) - Integer types
  • AddTypeVector(componentType, count) - Vector types (vec2, vec3, vec4)
  • AddTypeMatrix(columnType, columnCount) - Matrix types
  • AddTypeArray(elementType, length) - Array types
  • AddTypeStruct(memberTypes...) - Struct types
  • AddTypePointer(storageClass, baseType) - Pointer types
  • AddTypeFunction(returnType, paramTypes...) - Function types
Constant Operations
  • AddConstant(typeID, values...) - Integer/boolean constants
  • AddConstantFloat32(typeID, value) - 32-bit float constant
  • AddConstantFloat64(typeID, value) - 64-bit float constant
  • AddConstantComposite(typeID, constituents...) - Composite constants
Variable Operations
  • AddVariable(pointerType, storageClass) - Global variable
  • AddVariableWithInit(pointerType, storageClass, initID) - Variable with initializer
Function Operations
  • AddFunction(funcType, returnType, control) - Function definition
  • AddFunctionParameter(typeID) - Function parameter
  • AddLabel() - Basic block label
  • AddReturn() - Return from void function
  • AddReturnValue(valueID) - Return with value
  • AddFunctionEnd() - End of function
Entry Point & Execution Mode
  • AddEntryPoint(execModel, funcID, name, interfaces) - Shader entry point
  • AddExecutionMode(entryPoint, mode, params...) - Execution configuration
Debug Operations
  • AddName(id, name) - Debug name for ID
  • AddMemberName(structID, member, name) - Debug name for struct member
  • AddString(text) - Debug string
Decoration Operations
  • AddDecorate(id, decoration, params...) - Decorate type/variable
  • AddMemberDecorate(structID, member, decoration, params...) - Decorate struct member
Constants
Execution Models
  • ExecutionModelVertex - Vertex shader
  • ExecutionModelFragment - Fragment shader
  • ExecutionModelGLCompute - Compute shader
Storage Classes
  • StorageClassUniformConstant - Uniform/constant storage
  • StorageClassInput - Shader input
  • StorageClassOutput - Shader output
  • StorageClassUniform - Uniform buffer
  • StorageClassWorkgroup - Shared/workgroup memory
  • StorageClassPrivate - Private storage
  • StorageClassFunction - Function local storage
  • StorageClassPushConstant - Push constants
  • StorageClassStorageBuffer - Storage buffer
Decorations
  • DecorationLocation - Location decoration
  • DecorationBinding - Binding decoration
  • DecorationDescriptorSet - Descriptor set decoration
  • DecorationBuiltIn - Built-in decoration
  • DecorationBlock - Block decoration
  • DecorationOffset - Struct member offset

SPIR-V Binary Format

Header (5 words)
Word 0: 0x07230203 (Magic number)
Word 1: Version (major.minor << 16 | patch << 8)
Word 2: Generator ID
Word 3: Bound (max ID + 1)
Word 4: Schema (reserved, 0)
Instruction Format
Word 0: (WordCount << 16) | Opcode
Words 1-N: Operands
Module Sections (in order)
  1. Capabilities
  2. Extensions
  3. Extended instruction imports
  4. Memory model
  5. Entry points
  6. Execution modes
  7. Debug strings
  8. Debug names
  9. Annotations (decorations)
  10. Types and constants
  11. Global variables
  12. Functions

Examples

See example_test.go for complete examples:

  • Minimal module
  • Module with types
  • Fragment shader structure

Testing

# Run tests
GOROOT="/c/Program Files/Go" go test ./spirv/...

# Run examples
GOROOT="/c/Program Files/Go" go test -v -run Example ./spirv/...

Implementation Notes

ID Allocation
  • IDs start at 1 (0 is invalid)
  • AllocID() returns sequential IDs
  • bound field tracks max ID + 1
String Encoding
  • UTF-8 null-terminated
  • Padded to 4-byte boundary
  • Stored as little-endian words
Float Encoding
  • AddConstantFloat32() uses IEEE 754 single precision
  • AddConstantFloat64() uses IEEE 754 double precision (2 words)
Binary Generation
  • Little-endian byte order
  • Word-aligned (4 bytes)
  • Sections written in SPIR-V specification order

Next Steps

The next phase of implementation will add:

  1. IR to SPIR-V Translation (spirv/backend.go)

    • Convert naga IR types to SPIR-V types
    • Convert naga IR expressions to SPIR-V instructions
    • Convert naga IR statements to SPIR-V control flow
    • Map naga IR address spaces to SPIR-V storage classes
  2. Type Deduplication

    • Cache created types to avoid duplicates
    • Efficient type lookup
  3. Expression Emission

    • Arithmetic operations
    • Logical operations
    • Load/store operations
    • Function calls
  4. Control Flow

    • If/else blocks
    • Loops
    • Switch statements
    • Structured control flow

References

License

Same as gogpu/naga project.

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

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

func Compile added in v0.21.0

func Compile(module *ir.Module, options Options) ([]byte, TranslationInfo, error)

Compile translates module to a SPIR-V module, returned as its little-endian 32-bit words (Rust naga's back::spv::write_vec, as bytes).

type Version

type Version struct {
	Major uint8
	Minor uint8
}

Version represents a SPIR-V version.

Directories

Path Synopsis
internal
codegen
Package codegen implements SPIR-V code generation from naga IR.
Package codegen implements SPIR-V code generation from naga IR.

Jump to

Keyboard shortcuts

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