A few weeks ago, I published GoGPU: A Pure Go Graphics Library for GPU Programming — introducing an ecosystem for GPU computing in Go without CGO. Today, I want to share progress on one of the most challenging components: naga, a shader compiler written entirely in Go.
TL;DR
naga compiles WGSL (WebGPU Shading Language) to SPIR-V bytecode. 17,000 lines of pure Go. No CGO. No external dependencies. Just go build.
- v0.4.0 now supports compute shaders with atomics and barriers
- Full type inference system
- 203 tests, ~61% coverage
- Production-ready for graphics and compute workloads
Why Build a Shader Compiler?
If you've worked with GPU programming in Go, you've hit this wall: shader compilation always requires external tools. Rust's naga, Google's glslc, or NVIDIA's toolchain. That means:
- Extra build dependencies
- Platform-specific binaries
- CGO or subprocess calls
- Complex deployment
The GoGPU ecosystem aims to eliminate this friction. A Pure Go shader compiler means:
go build ./...
# That's it. No cmake, no Rust toolchain, no DLLs.
The Journey: v0.1.0 to v0.4.0
v0.1.0 — Foundation (~10K LOC)
Started with the basics:
- WGSL lexer recognizing 140+ tokens
- Recursive descent parser
- Intermediate representation (33 expression types, 16 statement types)
- SPIR-V binary writer with 100+ opcodes
Result: vertex and fragment shaders compiled successfully.
v0.2.0 — Type System (~2K LOC)
The hard part: type inference. SPIR-V requires explicit types for everything, but WGSL allows inference:
let x = 1.0; // What type? f32
let v = vec3(1.0); // vec3<f32>
let n = normalize(v); // Also vec3<f32>, inferred from function return
Built a complete type resolution engine that tracks types through every expression.
v0.3.0 — Textures (~3K LOC)
Added texture operations — the bread and butter of graphics:
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(myTexture, mySampler, uv);
}
This required implementing SPIR-V image operations: OpSampledImage, OpImageSampleImplicitLod, and friends.
v0.4.0 — Compute Shaders (~2K LOC)
The latest release brings GPU compute capabilities:
@group(0) @binding(0)
var<storage, read_write> counter: atomic<u32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
atomicAdd(&counter, 1u);
workgroupBarrier();
}
This required:
-
Storage buffer access modes:
read,read_write -
Workgroup shared memory:
var<workgroup> - 9 atomic operations: add, sub, min, max, and, or, xor, exchange, compare-exchange
- 3 barrier types: workgroup, storage, texture
-
Address-of operator:
&for atomic pointers
Architecture
┌─────────────────────────────────────────────────────┐
│ WGSL Source │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Lexer (140+ tokens) │
│ wgsl/lexer.go — ~400 LOC │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Parser (recursive descent) │
│ wgsl/parser.go — ~1400 LOC │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ AST → IR Lowering │
│ wgsl/lower.go — ~1100 LOC │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Intermediate Representation │
│ 33 expression types, 16 statement types │
│ Type inference + deduplication │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ SPIR-V Backend │
│ spirv/backend.go — ~1800 LOC │
│ 100+ opcodes, GLSL.std.450 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ SPIR-V Binary │
│ Vulkan-compatible bytecode │
└─────────────────────────────────────────────────────┘
Usage
As a Library
import "github.com/gogpu/naga"
func main() {
source := `
@vertex
fn main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
`
spirv, err := naga.Compile(source)
if err != nil {
log.Fatal(err)
}
// spirv is ready for Vulkan
}
CLI Tool
go install github.com/gogpu/naga/cmd/nagac@latest
nagac shader.wgsl -o shader.spv
nagac -debug shader.wgsl -o shader.spv # with debug names
With Warnings
v0.4.0 introduces unused variable detection:
result, err := naga.LowerWithWarnings(ast)
for _, w := range result.Warnings {
fmt.Printf("Warning: %s at line %d\n", w.Message, w.Span.Line)
}
Variables prefixed with _ are intentionally ignored (Go-style).
Supported Features
| Category | Features |
|---|---|
| Types | f32, f64, i32, u32, bool, vec2-4, mat2x2-4x4, arrays, structs, atomics |
| Textures | texture_2d, texture_3d, texture_cube, sampler |
| Shaders | @vertex, @fragment, @compute |
| Bindings | @location, @group/@binding, @builtin |
| Storage | uniform, storage (read/read_write), workgroup |
| Functions | 50+ built-in (math, geometric, interpolation, atomic, barrier) |
| Control | if/else, for, while, loop, break, continue |
What's Next
v0.5.0
- GLSL backend — output to GLSL for OpenGL compatibility
- Source maps — debug info mapping SPIR-V back to WGSL
- Optimization passes — constant folding, dead code elimination
v1.0.0
- Full WGSL specification compliance
- HLSL/MSL backends for DirectX/Metal
- Production hardening
Performance
Compilation is fast. A typical shader compiles in under 5ms. The entire test suite (203 tests) runs in ~2 seconds.
No benchmarks yet against Rust's naga, but the goal isn't to be faster — it's to be pure Go and integrate seamlessly into Go toolchains.
Part of the GoGPU Ecosystem
naga is one piece of a larger vision:
| Project | Description | Status |
|---|---|---|
| gogpu/gogpu | Graphics framework | v0.3.0 |
| gogpu/wgpu | Pure Go WebGPU | v0.3.0 |
| gogpu/naga | Shader compiler | v0.4.0 |
| gogpu/gg | 2D graphics | Planned |
| gogpu/ui | GUI toolkit | Planned |
Together, this gives Go a complete GPU computing stack — from low-level shaders to high-level graphics, all without CGO.
Try It
go get github.com/gogpu/naga@v0.4.0
Or check out the repository: github.com/gogpu/naga
Contributing
Areas where contributions would help:
- Test cases — Real shaders from production apps
- GLSL backend — Help with the v0.5.0 target
- Documentation — Examples, tutorials
- Edge cases — WGSL features we haven't covered
Building a shader compiler from scratch has been one of the most educational projects I've worked on. Parsing, type systems, code generation — it's a crash course in compiler design.
If you're interested in GPU programming, graphics, or just want to see what 17K lines of Go can do, check out the GoGPU organization.
Star the repos if you find them useful. And if you hit any issues, open a GitHub issue — that's how we make this better.
We Need Testers with Real Projects
Here's the honest truth: naga has been tested against our own shaders and synthetic test cases. But compilers are only as good as the code they're tested against.
If you have real WGSL shaders from production projects, we want them.
What we're looking for:
- Shaders from actual games or applications
- Complex compute shaders with real algorithms
- Edge cases that might break our parser
- Anything that works with other compilers but fails with naga
How to help:
- Try compiling your shaders with
nagac - If something fails — open an issue with the shader code
- If it works — let us know! We'd love to add it to our test suite (with attribution)
Even anonymous shader submissions help. The more real-world code we test against, the more robust naga becomes.
# Quick test
go install github.com/gogpu/naga/cmd/nagac@v0.4.0
nagac your_shader.wgsl -o test.spv
# Did it work? Did it fail? Either way, we want to know.
Open an issue: github.com/gogpu/naga/issues
Links:
Top comments (0)