vgpu渲染捆绑(Render Bundle)完全指南:一次录制无限回放,大幅降低渲染开销
【免费下载链接】vgpuModular cross-runtime WebGPU library for shaders, 3D scenes, GPU tensors, neural networks, and math viz项目地址: https://gitcode.com/gh_mirrors/vgpu/vgpu
vgpu 是一个模块化的跨运行时 WebGPU 库,用于着色器、3D 场景、GPU 张量与神经网络。本文带你快速入门 vgpu 的渲染捆绑(Render Bundle):把一组绘制命令一次性录制,之后每帧只需回放即可跳过重复的命令编码,从而大幅降低渲染开销。
🎯 为什么需要渲染捆绑?
在典型的 WebGPU 渲染循环里,每一帧都要重新编码所有流水线、绑定组和绘制调用——哪怕场景完全没有变化。当绘制数量多起来时,这部分纯 CPU 开销很容易成为帧率瓶颈。
vgpu 的渲染捆绑正是为此设计的:调用bundle(gpu, { target }, cb)把一组绘制命令一次性录制为Bundle,之后在每帧里用pass.bundles(scene)回放,成本几乎为零。
⚡ 核心机制:冻结什么,不冻结什么
理解 bundle 的关键在于分清它"冻住"与"放行"的内容:
| 冻结(录制时确定) | 放行(每次回放仍生效) |
|---|---|
| 目标渲染签名:颜色格式、深度格式、采样数 | uniform 数据:set()更新照常流入 |
| 绑定组身份(bind group identity) | 目标尺寸:窗口缩放后无需重录 |
| 绘制命令序列 | indirect draw 参数缓冲:GPU 每次回放重读 |
一句话记忆:录制不变的部分,set()会动的部分。
🚀 快速上手:三步录制你的第一个渲染捆绑
const scene = bundle(gpu, { target: canvasTarget }, (b) => { b.draw(ocean); b.draw(boat); }); // 编码只发生这一次,无需等待帧 frameLoop(gpu, (frame) => { ocean.set({ params: { time: time.time } }); // uniform 依然动画 frame.pass(canvasTarget, (pass) => pass.bundles(scene)); // 回放,不重编码 });录制可以在帧外进行(比如加载阶段),vgpu 只读取 surface 已配置的渲染签名,不会获取画布纹理、不会调整画布尺寸,更不会提交任何 GPU 工作。真正产生绘制的是回放,它始终发生在frame()或frameLoop()内部。
⏱️ 性能进阶:预编译管线,让录制零卡顿
bundle(gpu)在调用时立刻编码,如果某个绘制对应的管线还没缓存,它会同步编译——这是 vgpu 中唯一会阻塞等待管线创建的地方。
解决方案是先"预热":对每个绘制的目标签名执行await effect.compile(signature),再录制。此时录制过程不再创建任何管线,完全无感。target选项还接受纯签名对象(如{ colors: [format] }),让你能在真实画布 surface 创建之前就完成预热与录制。
🔄 静态回放 + 动态绘制混排
同一个 pass 里,回放与实时编码可以并肩工作:
frame.pass(canvasTarget, (pass) => { pass.bundles(scene); // 静态部分:回放 pass.draw(cursor); // 动态部分:新鲜编码叠加其上 });注意两个"必须留在动态侧"的例外:使用了blendConstant或带ref的stencil的绘制无法录制(bundle 编码器无法设置这类 pass 级状态),需要用pass.draw()实时编码。
📐 窗口缩放无需重新录制
bundle 是按渲染签名(而非尺寸)匹配回放目标的,所以画布 resize 之后,同一个 bundle 继续有效——千万不要仅仅因为目标变大/变小就重录。
但采样关系是另一回事:如果 bundle 采样的纹理被替换了(例如跟随画布缩放的离屏目标被 resize),原 bundle 会失效,回放时抛出VGPU-R3-BUNDLE-STALE。此时按固定顺序替换即可:
- 录制新 bundle;
- 切换引用;
- 对新 bundle 成功后再
dispose()旧 bundle。
顺序很重要——如果录制失败,旧 bundle 仍在位,场景不会闪断。
🧹 如何释放不再需要的 bundle
- 不做任何事也可以:bundle 的存活不依赖它录制的绘制与资源,失去最后一个引用后会被垃圾回收;
dispose():当你想在明确时点释放(替换 bundle、视图销毁)时调用。它同步且幂等,只会解除 vgpu 的注册与引用,绝不销毁被借用的绘制、几何、纹理和缓冲区。
dispose()之后读取bundle.gpu或回放会抛出VGPU-BUNDLE-DISPOSED;在pass.bundles(a, b)中只要有一个条目已 dispose,整个列表都不会回放。
⚖️ 什么时候不需要捆绑?
录制本身不是免费的,每帧只有几个绘制时,编码开销微乎其微。vgpu 的性能阶梯是:
effect.draw(target)—— 单个 pass 直接绘制;frame(gpu)—— 把多个 pass 批量进一次 submit;bundle(gpu)—— 跳过那些从不变化的重编码。
绘制多、循环热,bundle 才开始回本。
🧊 实战示例:batch-rendering 批量渲染
官方示例把 cube、pyramid、octahedron、icosahedron 四种几何体打包进一个顶点缓冲,4096 个实例只录制成一个bundle,每帧回放时仅用set()更新相机矩阵:
const recorded = bundle(gpu, { target: colorTarget, label: "batch-rendering-primitives" }, (b) => { for (const drawable of draws) b.draw(drawable); }); // 每帧:p.bundles(scene.bundle)完整实现可见 scene-pipeline.ts。
📚 延伸资料
- 概念详解:concepts-render-bundles.docs.md
- Bundle API 参考:bundle.docs.md
- 可运行入门示例:example.ts(录制 → 回放 → 验证 uniform 更新)
- 帧循环概念:concepts-frames.docs.md
【免费下载链接】vgpuModular cross-runtime WebGPU library for shaders, 3D scenes, GPU tensors, neural networks, and math viz项目地址: https://gitcode.com/gh_mirrors/vgpu/vgpu
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考