Uniform模块架构
Uniform Module 刻意保持微小:一个 UniformBuffer 类,几个类型辅助函数,以及 UniformUtils 中的少量函数。它们共同强制执行 WebGPU 的统一缓冲布局约束,并保持 CPU↔GPU 传输的可预测性。
设计目标
- 缓存优先 (Cache-first) – 在 CPU 上保留 Uniform 数据的
ArrayBuffer副本,以便在一次queue.writeBuffer调用之前可以进行多次更新。 - 共享布局 (Shared layouts) – 暴露
UniformBuffer.bindGroupLayout(),以便每个管线都可以重用相同的统一绑定组布局(@binding(0)统一缓冲,在 VS/FS/CS 中可见)。 - 对齐保证 (Alignment guarantees) – 提供辅助函数 (
UniformUtils.alignSize,packVec,packMat4),以便相机/渲染设置结构体始终遵守 WebGPU 的 16 字节规则。 - 最小表面积 (Minimal surface area) – 没有花哨的池化或映射;只需构造一次并重用。
数据生命周期
new UniformBuffer(device, initBytes)
│
├─ 创建 GPU 缓冲 (UNIFORM | COPY_DST)
├─ 将初始字节复制到 CPU 缓存 (_data)
└─ 使用 queue.writeBuffer 上传初始内容
setData(view)
│
├─ 将字节复制到 _data (CPU 缓存)
└─ 数据已更新但尚未同步到 GPU (隐式"脏"状态)
flush(device?)
│
└─ dev.queue.writeBuffer(buffer, 0, _data)
destroy()
│
└─ buffer.destroy()
datagetter 返回用于检查/调试的克隆ArrayBuffer。dataBytessetter 整体替换缓存(当 ONNX 通过暂存缓冲覆盖模型参数 Uniform 时使用)。clone()简单地使用缓存的字节构造另一个UniformBuffer。
内存布局说明
WebGPU 要求统一缓冲为 16 字节对齐,并遵守类似 std140 的填充规则:
- 标量占用 4 字节,但如果位于结构体中,仍位于 16 字节槽内。
vec2– 8 字节,vec3– 填充到 16 字节(被视为vec4)。mat4x4以列主序存储,总共 64 字节。
UniformUtils 负责处理这些细节:
UniformUtils.alignSize(100); // → 112
UniformUtils.packVec([1,2,3], 3); // → Float32Array([1,2,3,0])
UniformUtils.packMat4(matrix16); // 确保 16 个元素
UniformUtils.createAlignedBuffer(n); // 返回填充到 16 字节的 ArrayBuffer
绑定组布局
UniformBuffer.bindGroupLayout(device) 总是返回相同的布局:
可见性位覆盖 VS/FS/CS,因此相同的布局适用于预处理、排序和渲染管线。每个 UniformBuffer 使用该共享布局构造自己的 BindGroup。
集成模式
相机 + 渲染设置
GaussianRenderer 和 GaussianPreprocessor 各自拥有两个 Uniform:
cameraUniforms(272 字节: view, viewInv, proj, projInv, viewport, fov)settingsUniforms(~80 字节: 裁剪框, 高斯缩放, SH 阶数, 开关)
在编码计算或渲染 Pass 之前,它们调用 setData(...) 随后调用 flush()。
模型参数
每个 PointCloud 构造一个 modelParamsUniforms = new UniformBuffer(device, 128-byte struct),其镜像了 WGSL 的期望(变换、偏移量、缩放、精度元数据)。预处理通过 updateModelParamsBuffer 或 updateModelParamsWithOffset 更新此缓冲,并在分派计算之前 flush。
ONNX 计数器
动态点云传递一个额外的 countBuffer;预处理 flush 模型参数 Uniform,然后使用 copyBufferToBuffer 覆盖字节偏移量 68 处的 num_points 槽位。由于 Uniform 缓存镜像 GPU 数据,渲染器稍后可以检查 modelParamsUniforms.data 进行调试。
错误检查
setData在view.byteLength !== size时抛出异常,防止意外的部分写入。dataBytessetter 也强制执行相同的大小检查。packMat4除非正好提供 16 个元素,否则抛出异常。
未来钩子
当前的实现是有意简化的。如果我们将来需要缓冲池或持久映射,我们可以扩展 UniformBuffer 以接受自定义用法标志(已通过 UniformConfig 支持)并添加 UniformPool。目前,简单性使得每帧 Uniform 更新变得琐碎且易于推理。