声明式WebGPU与S表达式
摘要
Pngine是一个用于WebGPU的声明式格式与运行时,它使用S表达式来简化WebGPU配置的声明与验证,支持跨平台共享,并能导出为HTML或嵌入运行时的PNG等格式。
<p><a href="https://lobste.rs/s/hypo37/declarative_webgpu_with_s_expressions">评论</a></p>
查看缓存全文
缓存时间: 2026/08/23 21:23
# 基于 S 表达式的声明式 WebGPU 实现
来源:https://hugodaniel.com/posts/declarative-webgpu-with-s-expressions/
可部署与共享的 WebGPU 接线方案
2026年8月23日
关于作者 (https://hugodaniel.com/)
新手入门 (https://hugodaniel.com/start-here/)
所有文章 (https://hugodaniel.com/posts)
订阅源 (https://hugodaniel.com/atom.xml)
这是我过去两年半开发的 WebGPU 声明式格式与运行时——**pngine**。以下代码展示了如何用 S 表达式编写与 WebGPU 规范 (https://www.w3.org/TR/webgpu/) 1:1 对应的接线配置:
```lisp
(shader-module :name code :code """
@vertex fn vertexMain(
@builtin(vertex_index) VertexIndex : u32
) -> @builtin(position) vec4f {
var pos = array(
vec2(0.0, 0.5),
vec2(-0.5, -0.5),
vec2(0.5, -0.5)
);
return vec4f(pos[VertexIndex], 0.0, 1.0);
}
@fragment fn fragMain() -> @location(0) vec4f {
return vec4(1.0, 0.0, 0.0, 1.0);
}
""")
(render-pipeline :name pipeline :layout auto
(vertex :module code :entry vertexMain)
(fragment :module code :entry fragMain
(target :format preferred-canvas-format))
)
(render-pass :name trianglePass
(color-attachment :view context-current-texture
:clear-value [0 0 0 0]
:load-op clear
:store-op store)
:pipeline pipeline
(draw :vertex-count 3))
(frame :name main :perform [trianglePass])
```
这段代码通过 S 表达式构建了简单的红色三角形,实现了与 WebGPU 规范完全对应的声明式接线。
## 1-2-3 Pngine 的核心能力
Pngine 具备三大核心功能:
- 允许以跨平台方式声明并部署 WebGPU 接线方案(包含着色器与 CPU/WASM 初始化),不仅限于浏览器环境,也支持 rust wgpu,并已适配 Android/iOS 播放器
- 通过 WGSL 反射和规范检查验证声明的 WebGPU 配置,无需实时上下文即可生成错误与警告提示,并提供 LSP 支持实现实时校验
- 可将所有内容导出为单个 .html 文件、.zip 包或 .png 文件,支持将可执行的 PNG 文件直接交付给他人(PNG 本身不自动执行,仅包含字节码,需配合微型播放器运行)
最后一点正是 **pngine** 名称的由来——S 表达式会被编译为二进制表示,由微型运行时解释执行。运行时与负载数据可嵌入 PNG 附加数据块,而 PNG 文件本身仍作为预览图像呈现。
完整说明请访问 [专属页面](https://pngine.com/)。本项目采用 CC0 协议,代码托管于 [GitHub](https://github.com/HugoDaniel/pngine) 用于问题追踪与讨论,发布版本源自作者自建仓库。
## 为何需要这样的工具?
共享定制化的 WebGPU 接线方案长期存在技术门槛。我需要单个文件能同时描述 WebGPU 管线/缓冲区/通道等结构,以及着色器模块与 WGSL 代码。虽然现有通用编程语言都能实现,但我追求更高层的抽象——一种可作为其他工具基础、能深度解析(供人或机器使用)且无干扰的解决方案。
[SJON](https://hugodaniel.com/pages/sjon) 为我提供了构建 DSL 的途径,可通过 S 表达式实现自动验证与一致性交付。此方案专注于 WebGPU 接线部分,着色器代码仍保持原生 WGSL 格式。
程序的可读数据表征是 pngine 的核心理念,这种特性使得程序结构本身成为常规结构化数据,从而支持预先验证、回放、检查、压缩、序列化与机器生成——所有操作都作用于同一数据表示。
## 打包你的接线方案
分享 WebGPU 管线需要预先约定大量规范:使用 TypeScript/JavaScript/Rust 哪种语言?句柄如何表示与传递?管线用于动画、游戏还是其他场景?这些选择直接影响接线方案的打包与共享方式。
Pngine 旨在最小化这些差异,允许用统一方式打包不同用途的 WebGPU 接线方案以适应多样化目标环境。
## 多数 WebGPU 接线具有静态特性
WebGPU 规范以卓越的一致性与优雅设计,解决了图形领域日益混沌复杂的接口问题。该规范着力消除隐式状态,将功能提升至精心设计的 API 调用与配置对象。
对比前文三角形示例的等效 JavaScript 代码(该代码本身由 pngine 生成):
```javascript
// (shader-module :name code ...)
const code = device.createShaderModule({
code: `
@vertex fn vertexMain(
@builtin(vertex_index) VertexIndex : u32
) -> @builtin(position) vec4f {
var pos = array(
vec2(0.0, 0.5),
vec2(-0.5, -0.5),
vec2(0.5, -0.5)
);
return vec4f(pos[VertexIndex], 0.0, 1.0);
}
@fragment fn fragMain() -> @location(0) vec4f {
return vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
// (render-pipeline :name pipeline ...)
const pipeline = device.createRenderPipeline({
layout: "auto",
vertex: {
module: code,
entryPoint: "vertexMain"
},
fragment: {
module: code,
entryPoint: "fragMain",
targets: [{ format }]
},
primitive: {
topology: "triangle-list"
},
});
// (frame :name main :perform [trianglePass])
const encoder = device.createCommandEncoder();
// (render-pass :name trianglePass ...)
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: [0, 0, 0, 0],
loadOp: "clear",
storeOp: "store",
}],
});
pass.setPipeline(pipeline);
pass.draw(3); // (draw :vertex-count 3)
pass.end();
device.queue.submit([encoder.finish()]);
```
这段代码通过创建、接线、排序三个步骤完成 WebGPU 工作流。将 WebGPU 接线方案声明化后,各模块可摆脱顺序依赖,以声明形式存在并灵活重组。
在 pngine 中,`frame` 是少数需要严格排序的环节,其 `:perform` 列表中的名称对应文件其他位置定义的通道:
```lisp
(frame :name main :perform [
update-uniforms
update-textures
sdf-pass
post-processing
])
```
## 与管线协同工作
[Shadertoy](https://www.shadertoy.com/) 是优秀的着色器共享平台,拥有丰富的示例代码与活跃社区。进阶用户可尝试 [compute.toys](https://compute.toys/),支持多计算着色器协同工作,并通过宏扩展配置管线参数。
如今各类着色器工具层出不穷,但 pngine 的定位不同——它不是交互式开发环境(尽管提供 LSP 支持),而是作为基础平台,支持用户在其上构建场景图、材质节点等高级抽象工具。
### 从三角形到复杂效果
简单示例仅展示基础功能,以下案例体现 pngine 的实际潜力:
**粒子喷泉系统**
[下载 PNG 文件](https://hugodaniel.com/images/pngine-particle-fountain.png)
包含 2048 个 GPU 粒子的完整系统:缓冲区池化管理,从喷射口喷射,受重力影响,生命周期结束后以随机速度重生。该示例中属性缓冲区同时作为模拟写入目标。
**实例化植被渲染**
[下载 PNG 文件](https://hugodaniel.com/images/pngine-instanced-trees.png)
单次绘制调用渲染 400 棵低多边形树木,包含风力摆动效果与天空深度测试。每棵树由 12 个顶点构成(主干四边形+两个叶丛三角形),400 个实例数据由计算通道一次性填充,最终通过单条绘制指令完成:
`(draw :vertex-count 12 :instance-count NUM_TREES)`
这两个案例的 PNG 文件本身即包含完整程序、演示海报及运行时。
## 打包器与观察者
上述 PNG 文件包含附加二进制数据块,存放实际的 WebGPU 播放器与负载数据,使得需要加载的 JS 代码极其精简。这是导出 pngine .sjon 文件的可选方案,适用于小型静态应用。此外还支持生成 .html 文件、.zip 包或纯字节码。
pngine 中的 "png" 致敬了最初的设计灵感:让图像同时作为预览海报和字节码/运行时载体。
## 结语
土壤基质需满足三大功能:为根系提供结构支撑、保水透气、储存与输送养分。对我而言,pngine 正是 WebGPU 与图形学的生长基质——作为专注于 WebGPU 的底层实验平台,它既限制在特定领域,又支持通用交互与 CPU(WASM) 代码接入着色器缓冲区,并提供终端/LSP/编辑器环境下的实时验证能力。
这为更高级的实验提供了扎根生长的空间,更支持带有自动 PNG 预览的着色器共享方案。
立即体验 [pngine](https://pngine.com/),或查阅 [源代码](https://github.com/HugoDaniel/pngine)。
相似文章
WebGPU实战教程
一本免费的互动书籍,教授使用JavaScript和WebGPU进行图形编程,涵盖从基础到高级主题,如GPU计算和高斯泼溅(Gaussian splatting)。
[P] 读了太多架构手册后,我构建了一个可移植的GPU ISA [P]
一个名为WAVE的可移植GPU ISA,将内核编译为通用二进制文件,并翻译成特定厂商的后端(Metal、PTX、HIP、SYCL),已在多个GPU上验证结果。
@xenovacom: 使用 WebGPU,你可以构建出惊人的东西。当这个立方体最终落入洞中时,我就停止制作演示。
这条推文突显了 WebGPU 在创建出色的交互式演示方面的潜力,其中包含一个关于立方体落入洞中的趣味挑战。
SDXL 在浏览器中本地运行于 WebGPU,开源
Stable Diffusion XL (SDXL) 现在可以使用 WebGPU 在浏览器中本地运行,通过开源代码直接在设备上生成高质量 AI 图像。
Gooey:面向 Zig 的 GPU 加速 UI 框架
Gooey 是一个面向 Zig 的 GPU 加速 UI 框架,通过 Metal、Vulkan/Wayland 和 WebGPU/WASM 支持 macOS、Linux 和浏览器。它提供声明式 UI、动画、主题、无障碍和零外部依赖。