在纯 Go 中构建 Shader 编译器:naga 达到 v0.4.0
Source: Dev.to

TL;DR
naga 将 WGSL(WebGPU 着色语言)编译为 SPIR‑V 字节码。
- 17 000 行纯 Go。
- 无 CGO。无外部依赖。只需
go build。
v0.4.0 现已支持带原子操作和屏障的计算着色器
- 完整的类型推断系统
- 203 条测试,约 61 % 覆盖率
- 可用于图形和计算工作负载的生产就绪
为什么要构建着色器编译器?
如果你在 Go 中进行 GPU 编程,你一定遇到过这样的障碍:着色器编译总是需要外部工具(Rust 的 naga、Google 的 glslc、NVIDIA 的工具链)。这意味着:
- 额外的构建依赖
- 平台特定的二进制文件
- CGO 或子进程调用
- 部署复杂
GoGPU 生态的目标是消除这些摩擦。一个纯 Go 的着色器编译器意味着:
go build ./...
# 就这么简单。无需 cmake、Rust 工具链、DLL。
发展历程:v0.1.0 → v0.4.0
v0.1.0 — 基础(约 10 K 行代码)
- WGSL 词法分析器,识别 140+ 记号
- 递归下降解析器
- 中间表示(33 种表达式,16 种语句)
- SPIR‑V 二进制写入器,包含 100+ 操作码
结果:顶点和片段着色器能够成功编译。
v0.2.0 — 类型系统(约 2 K 行代码)
难点:类型推断。SPIR‑V 要求对所有内容显式标注类型,而 WGSL 允许推断:
let x = 1.0; // f32
let v = vec3(1.0); // vec3
let n = normalize(v); // vec3,依据函数返回值推断
实现了完整的类型解析引擎,能够在每个表达式中追踪类型。
v0.3.0 — 纹理(约 3 K 行代码)
加入纹理操作——图形编程的核心:
@fragment
fn main(@location(0) uv: vec2) -> @location(0) vec4 {
return textureSample(myTexture, mySampler, uv);
}
实现了 SPIR‑V 图像操作,如 OpSampledImage、OpImageSampleImplicitLod 等。
v0.4.0 — 计算着色器(约 2 K 行代码)
最新发布带来了 GPU 计算能力:
@group(0) @binding(0)
var counter: atomic;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3) {
atomicAdd(&counter, 1u);
workgroupBarrier();
}
关键新增
- 存储缓冲区访问模式:
read、read_write - 工作组共享内存:
var - 9 种原子操作:add、sub、min、max、and、or、xor、exchange、compare‑exchange
- 3 种屏障类型:workgroup、storage、texture
- 取地址运算符:
&用于原子指针
架构
┌─────────────────────────────────────────────────────┐
│ WGSL 源码 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 词法分析器(140+ 记号) │
│ wgsl/lexer.go — ~400 行代码 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 解析器(递归下降) │
│ wgsl/parser.go — ~1400 行代码 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ AST → IR 降级 │
│ wgsl/lower.go — ~1100 行代码 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 中间表示(IR) │
│ 33 种表达式类型,16 种语句类型 │
│ 类型推断 + 去重 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ SPIR‑V 后端 │
│ spirv/backend.go — ~1800 行代码 │
│ 100+ 操作码,GLSL.std.450 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ SPIR‑V 二进制 │
│ 兼容 Vulkan 的字节码 │
└─────────────────────────────────────────────────────┘
使用方式
作为库
import "github.com/gogpu/naga"
func main() {
source := `
@vertex
fn main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4 {
return vec4(0.0, 0.0, 0.0, 1.0);
}
`
spirv, err := naga.Compile(source)
if err != nil {
log.Fatal(err)
}
// spirv 已可用于 Vulkan
}
命令行工具
go install github.com/gogpu/naga/cmd/nagac@latest
nagac shader.wgsl -o shader.spv
nagac -debug shader.wgsl -o shader.spv # 带调试名称
带警告的编译
v0.4.0 引入了未使用变量检测:
result, err := naga.LowerWithWarnings(ast)
for _, w := range result.Warnings {
fmt.Printf("Warning: %s at line %d\n", w.Message, w.Span.Line)
}
以下划线 _ 开头的变量会被有意忽略(类似 Go 的风格)。
支持的特性
| 类别 | 特性 |
|---|---|
| 类型 | f32、f64、i32、u32、bool、vec2‑4、mat2x2‑4x4、数组、结构体、原子类型 |
| 纹理 | texture_2d、texture_3d、texture_cube、sampler |
| 着色器 | @vertex、@fragment、@compute |
| 绑定 | @location、@group/@binding、@builtin |
| 存储 | uniform、storage(read/read_write)、workgroup |
| 函数 | 50+ 内建函数(数学、几何、插值、原子、屏障) |
| 控制流 | if/else、for、while、loop、break、continue |
下一步计划
v0.5.0
- GLSL 后端 – 输出 GLSL 以兼容 OpenGL
- 源码映射 – 调试信息将 SPIR‑V 映射回 WGSL
- 优化 passes – 常量折叠、死代码消除
v1.0.0
- 完整符合 WGSL 规范
- HLSL/MSL 后端,支持 DirectX/Metal
- 生产级硬化
性能
编译速度很快。普通着色器的编译时间不足 5 ms。完整测试套件(203 条)运行约 2 秒。
尚未对 Rust 的 naga 做基准对比,但我们的目标不是追求原始速度,而是提供一个 纯 Go 的解决方案,能够无缝集成到 Go 项目中。