Chapter 99: WebGPU Advanced Features
WebGPU 的高级能力要放在浏览器安全模型、GPU 管线和 JavaScript 调度成本之间判断。读完本章后,读者应能定位一个 WebGPU 高级渲染方案中哪些工作适合交给 compute pass,哪些数据应停留在 GPU buffer 或 texture 中,哪些计时结果能够支撑优化判断,哪些功能仍处在平台实验或浏览器实现差异之内。
本章使用一个贯穿场景:浏览器中的大规模粒子与体积可视化编辑器。它每帧接收少量交互参数,在 GPU 上更新粒子、剔除不可见实例、写入间接绘制参数,再用 render pass 绘制可见粒子和体积切片。这个场景覆盖 compute pipeline、storage buffer、storage texture、indirect draw、timestamp query、JS-GPU 上传路径和 worker/offscreen canvas 这些高级主题。
WebGPU 的主线判断来自公开标准和浏览器文档。W3C 的 WGSL Candidate Recommendation Draft 描述 shader 语言和资源接口,MDN 的 WebGPU API 说明浏览器入口、安全上下文和 worker 可用性。工程文章引用这些资料时,应把它们视为版本边界:标准说明可移植语义,浏览器文档说明当前部署和兼容状态。
本章的结论可以先收束成一句话:WebGPU 高级渲染的核心能力来自“GPU 内部闭环”。数据上传进入 buffer 或 texture 后,compute pass 负责生成下一阶段资源,render pass 消耗这些资源,timestamp query 提供 GPU 侧证据,JavaScript 只提交少量命令和参数更新。这个闭环越稳定,浏览器端高级应用越接近原生显式图形 API 的组织方式。
99.1 WebGPU Ray Tracing 支持实验
WebGPU 中讨论 ray tracing 时,需要先确定“光线追踪”指向哪一种执行路径。当前 WebGPU 主线 API 面向 raster pipeline、compute pipeline、buffer、texture、bind group 和 command encoder。它可以用 compute shader 实现路径追踪、距离场追踪、屏幕空间光线步进或软件 BVH 遍历;它没有给 Web 内容暴露 Vulkan ray tracing、DirectX Raytracing 或 Metal ray tracing 那种标准化硬件光追管线入口。
在本章贯穿场景里,实验性 ray tracing 可以作为一个 compute pass 接入。粒子与体积编辑器先把体积网格、三角形代理、材质参数和相机参数放入 storage buffer 或 texture,再由 compute shader 为每个像素或每个低分辨率 sample 发射光线。compute pass 的输出写入 storage texture,随后 render pass 把这张纹理作为全屏合成输入。此时 ray tracing 的工程含义是“用 WebGPU compute 组织光线算法”,并没有获得浏览器标准层面的硬件 RT acceleration structure。
这条路径的资源关系可以写成一个稳定闭环。CPU 上传 camera、light、frame index 和少量编辑参数;GPU 读取 BVH 节点 buffer、triangle buffer、volume texture 和 random seed buffer;compute shader 输出 radiance texture 或 accumulated texture;render pass 负责 tone mapping、UI overlay 和 present。判断这个实验是否可迁移,应先看它是否依赖标准 WebGPU 对象,再看 shader 中的数据结构是否能落到 WGSL 支持的 storage buffer、storage texture 和 workgroup 执行模型。
下面的 WGSL 片段展示最小的数据接口。它省略完整 BVH 遍历,只保留资源组织方式:ray tracing 结果写入 storage texture,场景结构通过 storage buffer 读取。
struct Camera {
view_proj_inv: mat4x4<f32>,
frame_index: u32,
_pad0: vec3<u32>,
};
struct BvhNode {
bounds_min: vec3<f32>,
left_or_first: u32,
bounds_max: vec3<f32>,
count: u32,
};
@group(0) @binding(0) var<uniform> camera: Camera;
@group(0) @binding(1) var<storage, read> nodes: array<BvhNode>;
@group(0) @binding(2) var output_image: texture_storage_2d<rgba16float, write>;
@compute @workgroup_size(8, 8, 1)
fn trace_main(@builtin(global_invocation_id) gid: vec3<u32>) {
let dims = textureDimensions(output_image);
if (gid.x >= dims.x || gid.y >= dims.y) {
return;
}
let uv = vec2<f32>(gid.xy) / vec2<f32>(dims);
let color = vec4<f32>(uv, f32(camera.frame_index & 1u), 1.0);
textureStore(output_image, vec2<i32>(gid.xy), color);
}
这段代码证明的点是资源接口,而非光照质量。@compute entry point 把每个 invocation 映射到一个输出 texel,storage texture 承担跨 pass 图像结果,storage buffer 承担场景结构读取。真正的 BVH 遍历会增加节点访问、triangle intersection、material sampling 和随机采样,这些成本都会变成 storage buffer 带宽、ALU 分支和 dispatch 规模的问题。
实验性 ray tracing 的边界来自三处。第一,硬件 RT 单元是否参与执行由浏览器和底层实现决定,Web 内容通常拿不到可移植的硬件 RT 管线对象。第二,递归、动态内存分配和复杂指针图不适合直接移植到 WGSL,需要改写成数组索引、循环、stack buffer 或 stackless traversal。第三,浏览器的安全模型会限制高精度计时、未初始化内存访问和越界行为,shader 数据结构要先满足 WebGPU validation。
在粒子与体积可视化编辑器中,ray tracing 实验更适合作为局部效果:体积阴影、屏幕空间反射、低分辨率 global illumination preview、选区内路径追踪预览。把它放进主渲染路径时,应先固定分辨率、sample 数、accumulation buffer 格式和 denoise 策略,再用 timestamp query 或浏览器性能面板观察 GPU 时间。若单帧 budget 是 16.6 ms,光线 pass 占用 10 ms,交互编辑会立即失去余量;此时应降低分辨率、分帧累积或只在静止状态提升 sample 数。
99.2 Compute Pipeline 扩展应用
Compute pipeline 是 WebGPU 高级应用中最稳定的扩展入口。它把一次 GPU 计算组织成 shader module、pipeline layout、bind group、compute pass 和 dispatchWorkgroups。和 render pipeline 相比,compute pipeline 的输出目标更自由:它可以写 storage buffer、storage texture,也可以生成 indirect draw 参数,为后续 render pass 提供输入。
在贯穿场景中,compute pipeline 每帧承担三类工作。第一类是 simulation,例如粒子位置、速度、生命周期和随机扰动。第二类是 visibility processing,例如体积 brick 的屏幕占比计算、粒子 frustum culling、LOD level 选择和 compaction。第三类是 image processing,例如体积密度预积分、低分辨率光照缓存、bloom threshold 和后处理预计算。三类工作都具有同一个特征:输入和输出大部分留在 GPU 端,JavaScript 只更新少量参数。
一个 compute pass 的最小判断顺序是:先定义数据布局,再确定 workgroup size,再根据 buffer usage 选择资源状态,最后把结果接到下一阶段。数据布局决定 shader 是否能连续读取;workgroup size 决定每个 workgroup 内的并行粒度和 shared memory 使用;buffer usage 决定资源能否同时作为 storage、copy、indirect 或 vertex 输入;下一阶段决定结果是否需要额外拷贝。
下面的 JavaScript 片段展示 compute pipeline 的基础创建方式。它对应 MDN 中 createComputePipeline() 的对象关系:shader module 提供 WGSL 入口,pipeline layout 固定 bind group 布局,compute pass 绑定 pipeline 和资源后调度工作。
const particleLayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
buffer: { type: "uniform" },
},
{
binding: 1,
visibility: GPUShaderStage.COMPUTE,
buffer: { type: "storage" },
},
],
});
const particlePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({ bindGroupLayouts: [particleLayout] }),
compute: {
module: device.createShaderModule({ code: particleUpdateWGSL }),
entryPoint: "update_particles",
},
});
const pass = commandEncoder.beginComputePass();
pass.setPipeline(particlePipeline);
pass.setBindGroup(0, particleBindGroup);
pass.dispatchWorkgroups(Math.ceil(particleCount / 256));
pass.end();
这段代码的关键约束是 pipeline layout 和 WGSL binding 必须一致。@group(0) @binding(1) 的 shader 资源需要和 particleLayout 中的 binding 类型对应。若 shader 声明 var<storage, read_write>,bind group layout 也要提供可写 storage buffer;若 resource visibility 少了 compute stage,pipeline 创建或绑定阶段会失败。高级 WebGPU 工程的很多错误都可以先从“WGSL 声明、layout entries、bind group resource、usage flags”四者是否一致开始排查。
Compute pipeline 的扩展价值来自跨 pass 复用。粒子更新 pass 写 position buffer;剔除 pass 读取 position buffer 并写 visible index buffer;间接参数 pass 写 draw arguments buffer;render pass 再读取 visible index 和 draw arguments。这个组织方式减少了 CPU 读取 GPU 数据的需求,也把每帧 JavaScript 逻辑压缩成 command encoding 和少量 uniform 更新。
需要同时看两个性能边界。第一个边界是 dispatch 规模。过小的 dispatch 会让 API 提交和验证成本显得突出,过大的 dispatch 会放大带宽和分支成本。第二个边界是资源写入模式。连续写 storage buffer、按 tile 写 storage texture、按 prefix sum 生成 compacted list,三者对 cache 和内存事务的压力不同。WebGPU 代码评审时,应把 compute shader 的每个输出都标到后续使用者上,确认它真的减少了 CPU-GPU 交互或 render pass 工作量。
99.3 WebGPU Compute Storage Indirect Draw and Timestamp Integration
把 compute、storage、indirect draw 和 timestamp 放在一起,才形成 WebGPU 高级帧管线。compute pass 负责生成数据,storage buffer 和 storage texture 保存中间结果,indirect draw 让 GPU 生成绘制参数,timestamp query 给 GPU 阶段耗时提供证据。它们共同回答一个问题:一个浏览器端渲染应用能否把“决定画什么”和“实际绘制”都留在 GPU 内部。
贯穿场景中的一帧可以组织成如下路径。第一步,JavaScript 上传 frame uniform,包含相机矩阵、时间、交互参数和资源计数。第二步,compute pass 更新粒子状态并写出可见标记。第三步,compute pass 对可见标记做 compaction,生成 visible instance buffer 和 indirect draw arguments buffer。第四步,render pass 读取 visible buffer,调用 drawIndirect() 或 drawIndexedIndirect()。第五步,query resolve 把 timestamp 结果复制到可映射 buffer,调试模式下再由 CPU 读取。
这条路径可以用 Mermaid 表示。图中每个节点都对应 WebGPU 的一个对象或 pass,箭头表示资源生产和消费关系。
图中的核心路径是 C2 → I → R。如果可见实例数量由 GPU 计算得到,再通过 indirect draw 进入 render pass,CPU 就不需要每帧读取 visible count 后重新发起绘制。这个设计对大规模粒子、草地、点云、体素 brick 和可视化节点都适用。它把“剔除结果”从 JavaScript 变量改成 GPU buffer 中的参数,帧时间更容易随场景规模扩展。
Indirect draw 的 buffer 需要带有 GPUBufferUsage.INDIRECT,同时常见做法会叠加 GPUBufferUsage.STORAGE,让 compute shader 能写入参数。非索引绘制通常使用四个 32-bit 字段:vertexCount、instanceCount、firstVertex、firstInstance。索引绘制会增加 firstIndex 和 baseVertex。工程实现中应把这些字段定义成明确结构,保证 WGSL 写入顺序和 WebGPU draw command 读取顺序一致。
Storage texture 适合承载中间图像结果,例如 ray tracing 输出、体积预积分图、后处理 ping-pong texture 和屏幕空间缓存。它的优势是 shader 可以按 texel 写入,后续 render pass 可以把结果作为 sampled texture 读取。它的限制也直接:格式必须支持 storage binding,访问模式要和 WGSL 声明一致,读写同一张纹理时要关注 pass 分界和资源用途。W3C WGSL 文档中 storage texture 的格式、访问模式和 textureStore 约束,是判断 shader 可移植性的基础材料。
Timestamp query 用来回答 GPU 阶段耗时,而非 JavaScript 函数耗时。可用实现通常要求 device 开启 timestamp-query feature,并通过 GPUQuerySet 存储 timestamp。当前工程中常见的使用形态是在 compute pass 或 render pass descriptor 中设置 timestampWrites,也能在部分文档和实现中看到 writeTimestamp() 这一接口形态。写文章和写引擎时,应以目标浏览器版本、compatibility table 和实际 feature detection 为准。
下面的 JavaScript 片段展示一种调试模式下的 timestamp 组织方式。代码只表达对象关系,省略了错误处理和 feature fallback。
const querySet = device.createQuerySet({ type: "timestamp", count: 4 });
const computePass = commandEncoder.beginComputePass({
timestampWrites: {
querySet,
beginningOfPassWriteIndex: 0,
endOfPassWriteIndex: 1,
},
});
computePass.setPipeline(cullPipeline);
computePass.setBindGroup(0, cullBindGroup);
computePass.dispatchWorkgroups(cullGroupCount);
computePass.end();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [colorAttachment],
timestampWrites: {
querySet,
beginningOfPassWriteIndex: 2,
endOfPassWriteIndex: 3,
},
});
renderPass.setPipeline(particleRenderPipeline);
renderPass.setVertexBuffer(0, visibleInstanceBuffer);
renderPass.drawIndirect(indirectArgsBuffer, 0);
renderPass.end();
commandEncoder.resolveQuerySet(querySet, 0, 4, queryResolveBuffer, 0);
commandEncoder.copyBufferToBuffer(queryResolveBuffer, 0, readbackBuffer, 0, 32);
这段代码说明了 timestamp 的正确使用位置。query 写入跟随 GPU command buffer 执行顺序,resolve 也是 GPU 命令,CPU 读取需要等待提交完成并映射 readback buffer。若每帧都强制等待读取,会把 GPU 异步执行拉回 CPU 同步路径。稳定做法是在 profiling 模式下采样,延迟几帧读取,并把结果和 pass label、资源规模、分辨率一起记录。
集成这四类功能时,排查顺序应固定。先检查 buffer usage 是否包含所有用途;再检查 bind group layout 和 WGSL 声明;然后检查 indirect args 字段顺序和 offset 对齐;接着检查 pass 之间是否存在正确的命令顺序;最后再看 timestamp 结果是否和浏览器性能面板、帧率变化相符。这个顺序能把“没有画出来”和“画得慢”分开处理。
99.4 WebGPU JS-GPU Interaction and Upload Cost Control
WebGPU 高级应用的主要开销经常来自 JavaScript 与 GPU 之间的交互次数,而非某一行 shader 算术。浏览器需要验证对象、跟踪资源、调度命令、管理安全边界和提交到底层图形 API。每帧大量创建 pipeline、bind group、buffer,或频繁读取 GPU 结果,都会把显式 GPU 管线重新变成 CPU 驱动瓶颈。
贯穿场景中的上传数据可以分成三层。第一层是低频资源,例如粒子初始数组、体积数据、材质表、几何 buffer 和大型纹理。它们应在加载阶段创建并上传,运行期只更新变化范围。第二层是中频资源,例如编辑器中被修改的体积 brick、选择集、颜色映射表和 brush 参数。它们适合按 chunk 或 dirty range 更新。第三层是高频参数,例如 camera、time、mouse、viewport 和 frame index。它们适合放进小 uniform buffer 或 ring buffer,每帧顺序写入。
queue.writeBuffer() 和 queue.writeTexture() 是常见上传入口。它们适合少量或中等规模更新,调用次数和总字节数都要受控。大型流式数据可以使用 mapped buffer、staging buffer 或分块上传,但映射和拷贝也会引入同步边界。判断上传方案时,应同时记录三项数据:每帧调用次数、上传字节数、上传后多久被 GPU 消费。
下面的示例展示一种高频 uniform ring buffer。它把每帧参数写到不同 offset,减少覆盖仍在 GPU 使用的数据的风险。
const frameStride = 256;
const framesInFlight = 3;
const frameUniformBuffer = device.createBuffer({
size: frameStride * framesInFlight,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
function uploadFrameUniform(frameIndex, uniformBytes) {
const slot = frameIndex % framesInFlight;
const offset = slot * frameStride;
device.queue.writeBuffer(frameUniformBuffer, offset, uniformBytes);
return offset;
}
这段代码的关键点是 offset 生命周期。CPU 每帧写入一个 slot,GPU 在稍后的 command buffer 中读取同一 slot。若引擎每帧覆盖同一段 uniform buffer,而浏览器或底层队列仍在使用旧 command,驱动可能插入隐式同步或复制。ring buffer 用空间换稳定提交,让上传路径和 GPU 消费路径更容易分析。
Pipeline 和 bind group 也会带来 JS-GPU 交互成本。高级 WebGPU 应用应缓存 shader module、pipeline layout、render pipeline、compute pipeline、sampler 和 bind group layout。材质变化不应导致每帧重复创建 pipeline;更稳的做法是把材质参数放进 storage buffer 或 uniform buffer,用少量 pipeline variant 覆盖 blend、depth、vertex layout 和 shader feature 组合。pipeline cache 命中率可以通过对象创建次数和帧内 command encoding 时间观察。
Texture size 是另一个直接影响上传和采样成本的变量。体积可视化、视频纹理、编辑器画布和后处理链都容易制造超出预算的 texture。判断 texture 是否合理,应看它的分辨率、格式、mipmap、sample count、usage flags 和生命周期。编辑器中的 preview texture 可以使用低分辨率;最终输出或静止帧再提升采样质量。这样可以把交互阶段的带宽压力和最终画质阶段分开。
Worker 和 OffscreenCanvas 能把部分 command encoding、资源准备和 UI 主线程分离。MDN 文档说明 WebGPU 可通过 Navigator.gpu 或 worker 内的 WorkerNavigator.gpu 作为入口,但可用性仍受浏览器实现影响。工程上,worker 适合处理资源解码、数据整理、命令构建和非 UI 的 GPU 提交;DOM 交互、输入事件和布局仍需要主线程配合。引入 worker 后,应观察消息传递开销和 transferable 对象使用方式。
上传成本控制的可复用顺序是:先把资源按频率分层;再缓存长期对象;然后把高频小参数合并成 ring buffer;接着把中频修改压缩成 dirty range;最后把 readback 放到调试路径或异步统计路径。这个顺序能直接回答“这一帧为什么 JS 时间高”:是对象创建太多、上传太碎、同步读取太频繁,还是 worker 与主线程之间复制了过多数据。
99.5 Web 端高级渲染应用
Web 端高级渲染应用的设计目标是让浏览器承担交互入口,让 GPU 承担可并行的数据处理和图像生成。粒子系统、体渲染、科学可视化、节点编辑器、材质预览器和轻量 DCC 工具都可以使用 WebGPU,但它们的资源预算和调试方式需要按帧管线拆开,而非只看平均 FPS。
粒子系统适合展示 compute pipeline 的收益。CPU 只上传发射器参数和交互力场,GPU 负责更新百万级粒子的状态、剔除不可见粒子、生成 visible list,再用 instancing 或 indirect draw 绘制。性能瓶颈通常出现在 storage buffer 带宽、排序或透明混合 overdraw 上。若粒子透明且覆盖面积很大,fragment 阶段和 blend cost 会超过 simulation cost,继续优化 compute shader 收益有限。
体渲染适合展示 texture 和采样预算。数据可能来自 3D texture、2D texture array 或压缩 brick buffer。渲染路径可能使用 ray marching、slice rendering 或 multi-resolution bricking。判断体渲染性能时,应把采样步数、texture format、cache locality、empty space skipping、transfer function 更新和后处理分开记录。编辑器交互阶段可以降低步数或分辨率,静止阶段再提高采样密度。
可视化和编辑器项目更关注交互延迟。一次鼠标拖动可能触发 selection buffer 更新、GPU picking、属性面板刷新和渲染重绘。若每次交互都触发 GPU readback,UI 会感受到同步等待。更稳的结构是把 picking 结果延迟一帧读取,把 hover preview 留在 GPU 端显示,把属性面板更新和主渲染循环解耦。这样可以让视觉反馈先到达屏幕,再处理精确数据回传。
资源预算需要用 frame time 表达。60 Hz 的单帧预算约为 16.6 ms,120 Hz 的单帧预算约为 8.3 ms。一个 WebGPU 编辑器可以把预算拆成 JS input 与 UI、command encoding、compute simulation、render pass、postprocess、present 和 readback 七段。timestamp query 能覆盖 GPU 段,Performance API 和浏览器 profiler 能覆盖 JS 段,二者结合才能解释完整帧时间。
下面是一组面向高级 WebGPU 应用的预算表。它作为诊断入口,把每类应用的主要风险落到可观察证据上。
| 应用类型 | GPU 主路径 | 主要资源 | 首要风险 | 观察证据 |
|---|---|---|---|---|
| 粒子系统 | compute update → indirect draw | storage buffer、vertex buffer | overdraw、buffer 带宽、排序成本 | pass timestamp、visible count、fragment 时间 |
| 体渲染 | ray marching → compositing | 3D texture、storage texture | 采样步数、纹理带宽、分辨率 | GPU 时间、采样次数、texture size |
| 科学可视化 | compute filter → render overlay | storage buffer、uniform、color map | 数据上传、筛选成本、UI 同步 | upload bytes、dispatch 时间、readback 次数 |
| 编辑器视口 | selection/update → render | index buffer、object table、pick buffer | 交互延迟、对象重建、同步读取 | JS profile、command count、mapAsync 等待 |
这张表的用途是建立排查入口。粒子系统掉帧时,先看可见数量和 fragment 时间;体渲染掉帧时,先看采样步数和 texture 带宽;可视化应用卡顿时,先看上传字节数和筛选 pass;编辑器响应慢时,先看主线程 profile 和 readback 等待。不同应用共用同一套 WebGPU 对象,但瓶颈入口并不相同。
高级 WebGPU 项目还要处理设备能力差异。adapter 的 features 和 limits 决定 timestamp query、texture format、workgroup size、storage binding 数量和最大 buffer size。启动阶段应集中查询能力,生成 profile,再按 profile 选择渲染路径。高端桌面可以启用高分辨率体渲染、较大 workgroup 和更多 storage buffer;移动平台可以使用更低分辨率、更少 pass、更保守的 texture format 和更少的同步点。
最终的工程判断是:WebGPU 高级功能的成败不取决于单个 API 是否“高级”,而取决于整帧资源路径是否闭合。compute 生成的数据要能被 render 直接消费;storage texture 要能进入合成;indirect draw 要减少 CPU 参与;timestamp 要能定位 GPU 阶段;JS 上传要保持小而集中。只要这条路径稳定,浏览器端就能承载粒子、体渲染、可视化和编辑器这类复杂图形系统。
最小自检任务
你正在设计一个 WebGPU 粒子可视化页面,目标是在浏览器中显示 500,000 个粒子。每帧相机参数由 JavaScript 更新,粒子位置在 GPU 上更新,不可见粒子由 GPU 剔除,最后通过 indirect draw 绘制。请给出这条帧管线的资源路径,并说明你会如何定位卡顿来自 JS 上传、compute pass、render pass 还是 GPU readback。
答案要点
资源路径应从 JavaScript 的 frame uniform 开始。相机、时间和交互参数写入 uniform buffer 或 ring buffer;compute pass 读取旧粒子 storage buffer,写入新粒子状态和可见标记;第二个 compute pass 读取可见标记,写出 visible instance buffer 和 indirect args buffer;render pass 绑定粒子渲染 pipeline,读取 visible buffer,并通过 drawIndirect() 消费 indirect args buffer;可选 timestamp query 记录 compute 和 render pass 的 GPU 时间。
定位 JS 上传问题时,先看每帧 queue.writeBuffer() 或 queue.writeTexture() 调用次数、上传字节数和对象创建次数。若 command encoding 前的 JS profile 已经很高,优先压缩上传、缓存 pipeline 和 bind group,并把高频参数合并到 ring buffer。定位 compute 问题时,看粒子更新和剔除 pass 的 timestamp、dispatch 规模、storage buffer 读写量和分支情况。定位 render 问题时,看可见粒子数量、屏幕覆盖面积、blend 状态和 fragment 时间。定位 readback 问题时,看是否每帧等待 mapAsync() 或 query 结果;稳定结构应延迟读取,并把 readback 放到调试或统计路径。
关键边界是 feature 和 limit。indirect args buffer 需要 GPUBufferUsage.INDIRECT,compute 写入还需要 storage usage;timestamp 需要查询 adapter/device feature;workgroup size、storage buffer 数量和最大 buffer size 要从 limits 中取得。若某个浏览器缺少 timestamp query,仍可用浏览器 profiler、帧率曲线和阶段开关做粗粒度定位。
本章知识点总结
- 高级主线:WebGPU 高级应用应围绕 GPU 内部闭环组织,compute 生成数据,render 消费数据,JS 只提交命令和少量参数。
- 光追边界:WebGPU 当前主线能力支持用 compute shader 实现路径追踪实验,但 Web 内容没有标准化硬件光追管线入口。
- Compute 入口:Compute pipeline 由 shader module、pipeline layout、bind group、compute pass 和 dispatchWorkgroups 共同定义。
- 资源一致:WGSL binding、bind group layout、resource usage 和 pipeline visibility 必须一致,很多验证错误都来自这四者不匹配。
- 间接绘制:Indirect draw 让 GPU 生成绘制参数并交给 render pass 消费,适合粒子、点云、草地和可视化实例。
- Storage 结果:Storage buffer 适合结构化数据,storage texture 适合中间图像,两者都要按后续 pass 的消费方式设计。
- 计时证据:Timestamp query 回答 GPU 阶段耗时,CPU 读取 query 结果需要异步和延迟,频繁同步读取会改变帧管线。
- 上传分层:低频资源、中频 dirty range 和高频 frame uniform 应分开管理,高频参数适合使用 ring buffer。
- 对象缓存:Pipeline、bind group layout、sampler 和 bind group 应长期复用,减少帧内对象创建和验证成本。
- Worker 边界:Worker 与 OffscreenCanvas 可以分离部分 GPU 工作和主线程 UI,但消息传递和浏览器兼容性仍要进入预算。
- 应用预算:粒子看 overdraw 和 buffer 带宽,体渲染看采样步数和 texture 带宽,编辑器看交互延迟和 readback 等待。
- 能力查询:Adapter features 和 limits 决定 timestamp、texture format、workgroup size 和 buffer 规模,启动阶段应生成设备 profile。
- 排查顺序:先看上传和对象创建,再看 compute dispatch,再看 render pass 覆盖与混合,最后看 readback 和 query 同步。