Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
pull_request:
branches: [main]

permissions:
contents: read
id-token: write

jobs:
test:
name: Test - ${{ matrix.os }}
Expand Down Expand Up @@ -44,11 +48,10 @@ jobs:
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
use_oidc: true
files: ./coverage.txt
flags: unittests
name: codecov-naga
fail_ci_if_error: true

lint:
name: Lint
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.17.14] - 2026-06-08

### Added (GLSL)

- **GLSL version-aware binding support (BUG-GLES-005)** — GLSL backend now
provides full infrastructure for runtime binding fallback on GL < 4.2 drivers
(e.g., WSL2 Mesa d3d12 with GL 4.1 / GLSL 410). Follows Rust wgpu-hal
`device.rs:438-461` pattern.
- `SupportsExplicitLocations()` method on `Version` — gates `layout(binding=N)`
emission on GLSL >= 420 (desktop) or >= 310 (ES). Matches Rust naga
`mod.rs:213`.
- `UniformInfo` struct — reflection data for uniform/storage blocks (block name,
binding, storage flag). Populated during GLSL code generation.
- `TranslationInfo.Uniforms` field — carries uniform block reflection to the
HAL for post-link `glGetUniformBlockIndex`/`glUniformBlockBinding` assignment.
- `VersionES300` constant — safe minimum for OpenGL ES contexts.
- Writer collects `uniformInfos` during `writeUniformBlock`, `writeUniformVariable`,
and `writeStorageVariable` for the runtime binding fallback path.

## [0.17.13] - 2026-05-08

### Fixed (DXIL)
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
- **Function Calls** — `OpFunctionCall` support for modular WGSL shaders with helper functions
- **SPIR-V Backend** — Vulkan-compatible bytecode generation (**87/87 exact Rust naga parity**): integer div/mod safety wrappers, image bounds checking (Restrict/ReadZeroSkipWrite), ray query helpers, force loop bounding, workgroup zero-init polyfill, NonUniform decorations, capability-aware instruction emission
- **MSL Backend** — Metal Shading Language output for macOS/iOS (**91/91 exact Rust naga parity**), vertex pulling transform, external textures, override pipeline constants
- **GLSL Backend** — OpenGL Shading Language for OpenGL 3.3+, ES 3.0+ (**68/68 exact Rust naga parity**), dead code elimination, ProcessOverrides, image bounds checking
- **GLSL Backend** — OpenGL Shading Language for OpenGL 3.3+, ES 3.0+ (**68/68 exact Rust naga parity**), dead code elimination, ProcessOverrides, image bounds checking, version-aware `layout(binding=N)` emission (`SupportsExplicitLocations` for GL 4.2+/ES 3.1+), `UniformInfo` reflection for runtime binding fallback on older GL drivers
- **HLSL Backend** — High-Level Shading Language for DirectX 11/12 (**72/72 exact Rust naga parity**)
- **DXIL Backend** (experimental) — Direct DXIL generation from naga IR (**161/170 IDxcValidator validation, 94.7%**; **105/208 DXC golden parity, diff=0**; **gg production: 61/61 entry points VALID (100%)**; visual: renders circles + text on D3D12). LLVM 3.7 bitcode with dx.op intrinsics, DXBC container. Vertex, fragment, compute, and mesh shaders (SM 6.0-6.5). CBV/SRV/UAV (read-only storage as SRV, read-write as UAV), atomics (i32/i64/f32 + image), barriers, ray query (35 intrinsics), wave/subgroup ops (13 intrinsics), texture sampling (8 variants), matrix scalarization, pack/unpack, helper functions. Optimization passes: DCE (mark-and-sweep), SROA (struct decomposition), mem2reg (SSA promotion), single-store local promotion, loadInput DCE (per-member backwards reachability), workgroup struct decomposition, function inlining (early-return wrapping), strength reduction (mul→shl, urem→and, sub→add), constant folding. `Options.BindingMap` for WGSL→DXIL `(space, register)` remap (wgpu root signature compatibility). Eliminates FXC/DXC dependency. `dxil.Compile()` API. ~50K LOC, 330+ unit tests. World's first Pure Go DXIL generator.
- **Type Conversions** — Scalar constructors `f32(x)`, `u32(y)`, `i32(z)` with correct SPIR-V opcodes
Expand Down Expand Up @@ -204,7 +204,7 @@ spirvBytes, err := naga.GenerateSPIRV(module, spirvOpts)
## Architecture

```
naga/ ~192K LOC total
naga/ ~323K LOC total
├── wgsl/ # WGSL frontend (~19.5K LOC)
│ ├── token.go # Token types (120+)
│ ├── lexer.go # Tokenizer
Expand Down Expand Up @@ -333,11 +333,11 @@ naga/ ~192K LOC total

| Backend | Status | Target Platform |
|---------|--------|-----------------|
| SPIR-V | ✅ **87/87 Rust parity** | Vulkan |
| SPIR-V | ✅ **87/87 Rust parity**, 172/172 spirv-val | Vulkan |
| MSL | ✅ **91/91 Rust parity** | Metal (macOS/iOS) |
| GLSL | ✅ **68/68 Rust parity** | OpenGL 3.3+, ES 3.0+ |
| GLSL | ✅ **68/68 Rust parity**, version-aware binding | OpenGL 3.3+, ES 3.0+ |
| HLSL | ✅ **72/72 Rust parity** | DirectX 11/12 |
| DXIL | **161/170 IDxcValidator (94.7%)** | DirectX 12 (SM 6.0-6.5, experimental) |
| DXIL | **161/170 IDxcValidator (94.7%)**, 105 DXC golden | DirectX 12 (SM 6.0-6.5, experimental) |

See [ROADMAP.md](ROADMAP.md) for detailed development plans.

Expand Down
15 changes: 9 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@

---

## Current State: v0.17.4 (2026-04-21)
## Current State: v0.17.14 (2026-06-08)

✅ **Production-ready** shader compiler (~192K LOC) with **complete Rust naga parity**,
✅ **Production-ready** shader compiler (~323K LOC) with **complete Rust naga parity**,
**100% SPIR-V binary validation**, and **experimental DXIL backend**:

### What We Have

- **Full WGSL frontend** — Lexer (120+ tokens), parser, AST → IR lowerer
- **6 backend outputs — ALL at 100% validation:**
- SPIR-V: 165/165 spirv-val (Vulkan)
- SPIR-V: 172/172 spirv-val (Vulkan)
- MSL: 91/91 Rust naga parity (Metal)
- GLSL: 68/68 Rust naga parity (OpenGL)
- GLSL: 68/68 Rust naga parity (OpenGL), version-aware binding (GL 3.3–4.6, ES 3.0–3.2)
- HLSL: 72/72 Rust naga parity (DirectX 11/12)
- DXIL: **161/170 IDxcValidator (94.7%)**, 104/208 DXC golden parity (DirectX 12, SM 6.0-6.5) — world's first Pure Go DXIL generator
- DXIL: **161/170 IDxcValidator (94.7%)**, 105/208 DXC golden parity (DirectX 12, SM 6.0-6.5) — world's first Pure Go DXIL generator
- IR: 144/144 Rust naga parity
- **DXIL backend** (~50K LOC, 330+ unit tests) — VS/PS/CS/MS, CBV/SRV/UAV (read-only storage → SRV, read-write → UAV), atomics (i32/i64/f32 + image), barriers, ray query (35 intrinsics), wave ops (13 intrinsics), mesh shaders (SM 6.5), texture sampling (8 variants), matrix scalarization, pack/unpack, helper functions. Optimization passes: DCE, SROA, mem2reg, single-store local promotion, loadInput DCE, function inlining, strength reduction. `Options.BindingMap` for wgpu root signature compatibility. Eliminates FXC/DXC dependency. Verified 2400+ frames at 60 FPS on D3D12. Renders circles + text in gg production integration. Rust naga has NOT implemented this (open issue since 2020)
- **100+ WGSL built-in functions** — math, geometric, bit manipulation, packing, derivatives
Expand Down Expand Up @@ -82,7 +82,7 @@
| Goal | Status | Notes |
|------|--------|-------|
| Complete Rust naga parity | ✅ Done | All 5 layers at 100% |
| SPIR-V binary validation | ✅ Done | 164/164 pass spirv-val |
| SPIR-V binary validation | ✅ Done | 172/172 pass spirv-val |
| Compiler optimizations | ✅ Done | −32% allocs, −34% bytes |
| Ray tracing | ✅ Done | Ray query types, acceleration structures |
| Subgroup operations | ✅ Done | Ballot, shuffle, broadcast, quad |
Expand Down Expand Up @@ -172,6 +172,9 @@

| Version | Date | Highlights |
|---------|------|------------|
| **v0.17.14** | 2026-06 | GLSL version-aware binding: `SupportsExplicitLocations`, `UniformInfo` reflection, runtime binding fallback for GL < 4.2 (BUG-GLES-005) |
| **v0.17.13** | 2026-05 | DXIL PHI node ordering fix, coverage waves 3-4, ~60% overall |
| **v0.17.12** | 2026-05 | ARCH-001 internal packages refactor, 13 panics→errors |
| **v0.16.4** | 2026-04 | GLSL workgroup zero-init per-element loop (12KB → compact) |
| **v0.16.3** | 2026-04 | HLSL FXC workgroup zero-init fix (330× faster). First in industry. |
| **v0.16.2** | 2026-04 | HLSL 72/72 parity (100%). ForceLoopBounding architecture fix. +14 shaders. |
Expand Down
6 changes: 4 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ dependencies.
## Package Structure

```
naga/ ~189K LOC total
naga/ ~323K LOC total
├── naga.go # Public API: Compile, Parse, Lower, Validate, GenerateSPIRV
├── wgsl/ # WGSL frontend (~20K LOC)
│ ├── wgsl.go # Public API: Parse, Lower (real types)
Expand Down Expand Up @@ -387,14 +387,16 @@ type Backend struct {

### GLSL (OpenGL) — 68/68 Rust parity

- Version targeting: `#version 330`, `#version 430`, `#version 300 es`
- Version targeting: `#version 330`, `#version 430`, `#version 300 es` (any GL 3.3–4.6, ES 3.0–3.2)
- Attribute binding: `layout(location=N) in/out`
- Uniform blocks: `layout(std140, binding=N) uniform BlockName { ... }`
- Storage blocks: `layout(std430, binding=N) buffer BlockName { ... }`
- Compute: `layout(local_size_x=X, ...) in`
- BindingMap: remaps (group, binding) to flat GL binding indices (`group*16 + binding`)
- Combined texture-sampler: separate WGSL `texture_2d` + `sampler` merged into GLSL `sampler2D` at texture's binding
- TextureMappings in TranslationInfo: maps each combined sampler2D to its texture/sampler bindings (used by GLES HAL for SamplerBindMap)
- **Version-aware `layout(binding=N)` emission**: `SupportsExplicitLocations()` gates binding qualifiers on GLSL ≥ 420 (desktop) or ≥ 310 (ES), matching Rust naga `mod.rs:213`. On older GL, bindings are omitted and must be assigned at runtime by the HAL
- **UniformInfo reflection**: writer collects `UniformInfo` structs (block name, binding, storage flag) during code generation, exposed via `TranslationInfo.Uniforms` for post-link `glGetUniformBlockIndex`/`glUniformBlockBinding` fallback
- Dead code elimination via `dominates_global_use` reachability
- ProcessOverrides for pipeline constants
- Image bounds checking
Expand Down
45 changes: 45 additions & 0 deletions glsl/glsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ func (v Version) SupportsCompute() bool {
return v.Major > 4 || (v.Major == 4 && v.Minor >= 30)
}

// SupportsExplicitLocations returns true if explicit layout locations for bindings
// are supported (layout(binding=N) qualifiers).
// Desktop GLSL 420+, ES 310+.
// Matches Rust naga Version::supports_explicit_locations.
func (v Version) SupportsExplicitLocations() bool {
if v.ES {
return v.Major > 3 || (v.Major == 3 && v.Minor >= 10)
}
return v.Major > 4 || (v.Major == 4 && v.Minor >= 20)
}

// SupportsStorageBuffers returns true if this version supports storage buffers.
func (v Version) SupportsStorageBuffers() bool {
if v.ES {
Expand Down Expand Up @@ -180,6 +191,23 @@ type TextureMapping struct {
SamplerBinding *ir.ResourceBinding
}

// UniformInfo describes a GLSL uniform or storage buffer block for reflection.
// Used by the HAL runtime binding fallback on GL < 4.2 where layout(binding=N)
// is unavailable and bindings must be assigned after linking via GL calls.
// Matches Rust naga ReflectionInfo.uniforms.
type UniformInfo struct {
// BlockName is the GLSL block name (e.g., "Uniforms_block_0Vertex").
// Used with glGetUniformBlockIndex (uniform buffers) or
// glGetShaderStorageBlockIndex (storage buffers).
BlockName string

// Binding is the source (group, binding) from the IR.
Binding ir.ResourceBinding

// IsStorage is true for storage buffers (SSBO), false for uniform buffers (UBO).
IsStorage bool
}

// TranslationInfo contains metadata about the translation.
type TranslationInfo struct {
// EntryPointNames maps original entry point names to generated GLSL names.
Expand All @@ -200,6 +228,11 @@ type TranslationInfo struct {
// and sampler source bindings. Used by GLES HAL to build SamplerBindMap
// (bind GL sampler to texture's unit, not sampler's own binding).
TextureMappings map[string]TextureMapping

// Uniforms lists uniform/storage buffer blocks with their GLSL block
// names and source bindings. Used by GLES HAL for runtime binding
// fallback on GL < 4.2. Matches Rust naga ReflectionInfo.uniforms.
Uniforms []UniformInfo
}

// DefaultOptions returns sensible default options for GLSL generation.
Expand Down Expand Up @@ -264,6 +297,17 @@ func fromCodegenTranslationInfo(ci codegen.TranslationInfo) TranslationInfo {
}
}
}
var uniforms []UniformInfo
if len(ci.Uniforms) > 0 {
uniforms = make([]UniformInfo, len(ci.Uniforms))
for i, u := range ci.Uniforms {
uniforms[i] = UniformInfo{
BlockName: u.BlockName,
Binding: u.Binding,
IsStorage: u.IsStorage,
}
}
}
return TranslationInfo{
EntryPointNames: ci.EntryPointNames,
UsedExtensions: ci.UsedExtensions,
Expand All @@ -274,5 +318,6 @@ func fromCodegenTranslationInfo(ci codegen.TranslationInfo) TranslationInfo {
},
TextureSamplerPairs: ci.TextureSamplerPairs,
TextureMappings: texMappings,
Uniforms: uniforms,
}
}
25 changes: 25 additions & 0 deletions glsl/internal/codegen/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,23 @@ type TextureMapping struct {
SamplerBinding *ir.ResourceBinding
}

// UniformInfo describes a GLSL uniform or storage buffer block for reflection.
// Used by the HAL runtime binding fallback on GL < 4.2 where layout(binding=N)
// is unavailable and bindings must be assigned after linking via GL calls.
// Matches Rust naga ReflectionInfo.uniforms.
type UniformInfo struct {
// BlockName is the GLSL block name (e.g., "Uniforms_block_0Vertex").
// Used with glGetUniformBlockIndex (uniform buffers) or
// glGetShaderStorageBlockIndex (storage buffers).
BlockName string

// Binding is the source (group, binding) from the IR.
Binding ir.ResourceBinding

// IsStorage is true for storage buffers (SSBO), false for uniform buffers (UBO).
IsStorage bool
}

// TranslationInfo contains metadata about the translation.
type TranslationInfo struct {
// EntryPointNames maps original entry point names to generated GLSL names.
Expand All @@ -280,6 +297,13 @@ type TranslationInfo struct {
// (bind GL sampler to texture's unit, not sampler's own binding).
// Matches Rust naga ReflectionInfo.texture_mapping.
TextureMappings map[string]TextureMapping

// Uniforms maps global variable block names to their binding info.
// Used by GLES HAL for runtime binding fallback on GL < 4.2 where
// layout(binding=N) is unavailable. After glLinkProgram, the HAL
// queries block indices by name and assigns bindings via GL calls.
// Matches Rust naga ReflectionInfo.uniforms.
Uniforms []UniformInfo
}

// Compile generates GLSL source code from an IR module.
Expand Down Expand Up @@ -333,6 +357,7 @@ func Compile(module *ir.Module, options Options) (string, TranslationInfo, error
RequiredVersion: w.requiredVersion,
TextureSamplerPairs: w.textureSamplerPairs,
TextureMappings: textureMappings,
Uniforms: w.uniformInfos,
}

return w.String(), info, nil
Expand Down
34 changes: 34 additions & 0 deletions glsl/internal/codegen/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ type Writer struct {
globalBlockName map[ir.GlobalVariableHandle]string // block name
globalInstanceName map[ir.GlobalVariableHandle]string // instance variable name

// uniformInfos collects reflection data for uniform/storage blocks.
// Populated during writeUniformBlock and writeStorageVariable.
// Used to build TranslationInfo.Uniforms for the runtime binding fallback
// on GL < 4.2. Matches Rust naga's reflection_names_globals.
uniformInfos []UniformInfo

// Reachability set for dead code elimination.
// When set, only reachable types, constants, globals, and functions
// are emitted in the output. Built by collectReachable for the
Expand Down Expand Up @@ -1167,6 +1173,12 @@ func (w *Writer) writeUniformVariable(name, typeName string, global ir.GlobalVar
} else {
w.WriteLine("layout(std140) uniform %s { %s %s%s; };", blockName, baseType, instanceName, arraySuffix)
}
// Record for runtime binding fallback (GL < 4.2).
w.uniformInfos = append(w.uniformInfos, UniformInfo{
BlockName: blockName,
Binding: *global.Binding,
IsStorage: false,
})
} else {
w.WriteLine("uniform %s %s;", typeName, name)
}
Expand All @@ -1183,6 +1195,12 @@ func (w *Writer) writeUniformBlock(name, typeName string, global ir.GlobalVariab
} else {
w.WriteLine("layout(std140) uniform %s { %s %s; };", blockName, typeName, instanceName)
}
// Record for runtime binding fallback (GL < 4.2).
w.uniformInfos = append(w.uniformInfos, UniformInfo{
BlockName: blockName,
Binding: *global.Binding,
IsStorage: false,
})
} else {
w.WriteLine("uniform %s {", blockName)
w.PushIndent()
Expand Down Expand Up @@ -1341,6 +1359,14 @@ func (w *Writer) writeStorageVariable(name, typeName string, global ir.GlobalVar
} else {
w.WriteLine("%s%sbuffer %s { %s %s; };", layoutPrefix, readOnly, blockName, typeName, instanceName)
}
// Record for runtime binding fallback (GL < 4.2).
if global.Binding != nil {
w.uniformInfos = append(w.uniformInfos, UniformInfo{
BlockName: blockName,
Binding: *global.Binding,
IsStorage: true,
})
}
return
}
}
Expand All @@ -1349,6 +1375,14 @@ func (w *Writer) writeStorageVariable(name, typeName string, global ir.GlobalVar
baseType := w.getBaseTypeName(global.Type)
arraySuffix := w.getArraySuffix(global.Type)
w.WriteLine("%s%sbuffer %s { %s %s%s; };", layoutPrefix, readOnly, blockName, baseType, instanceName, arraySuffix)
// Record for runtime binding fallback (GL < 4.2).
if global.Binding != nil {
w.uniformInfos = append(w.uniformInfos, UniformInfo{
BlockName: blockName,
Binding: *global.Binding,
IsStorage: true,
})
}
}

// writeVaryingDeclarations writes entry point in/out declarations at module level.
Expand Down
Loading