ChartGPU源码级揭秘:WGSL着色器、Pipeline Cache与submitBatcher如何实现单帧提交
【免费下载链接】ChartGPUBeautiful, open source, WebGPU-based charting library项目地址: https://gitcode.com/gh_mirrors/ch/ChartGPU
ChartGPU 是一个基于 WebGPU 的开源图表库,它能把千万级数据点的绘制压力交给 GPU。本文带你深入源码,看看三件关键武器——WGSL 着色器、Pipeline Cache 与 submitBatcher——如何协作,把一帧内 N 个图表的绘制指令合并成一次queue.submit,这正是 ChartGPU 流畅渲染流式大盘的核心秘密。
一帧渲染的完整旅程:先懂"脏标记"按需渲染
在进入 GPU 细节前,先理解一帧是怎么被触发的。ChartGPU 的渲染循环由 RenderScheduler.ts 驱动,它实现了按需渲染(render-on-demand):
requestRender()只打一个dirty标记;同一 JS 回合内的多次请求会自动合并成一帧- 只有 dirty 时才会执行回调,空闲时渲染循环完全静止,不浪费任何 GPU 时间
- 动画结束后如果没有新的 dirty 请求,循环自动进入 idle 状态
这套机制是后面所有优化的地基:既然一帧可能包含多个图表的绘制,那么"一帧只提交一次"就变得顺理成章。
WGSL 着色器:每一段线段都是 GPU 的活
ChartGPU 的所有图表类型——折线、面积、K 线、热力图、3D 点云——背后都是src/shaders/目录下的 WGSL 着色器文件,共 20+ 个.wgsl。以折线图为例,line.wgsl 的思路非常巧妙:
- 每个实例画一段线段(
point[i] → point[i+1]),顶点着色器在屏幕空间把线段膨胀成四边形 - 片元着色器用SDF(有向距离场)+ smoothstep 做抗锯齿,线条在任意缩放下都平滑
- uniform 块里还内嵌了 log 坐标投影、ring 缓冲区寻址、LOD 抽稀等能力,全部在 GPU 端完成
着色器以?raw方式直接作为字符串导入(见 createLineRenderer.ts 首行),类型声明在 wgsl-raw.d.ts。这意味着 WGSL 源码在构建期就是纯文本——这个特性为下一节的缓存机制埋下伏笔。
Pipeline Cache:昂贵的编译只付一次
WebGPU 中createShaderModule和createRenderPipeline是相对昂贵的操作。一个仪表盘里 6 个折线图如果用同一套 shader,朴素的写法会编译 6 次。
PipelineCache.ts 就是为解决这个问题而生:
- 着色器模块按 WGSL 源码字符串去重,并用 FNV-1a 64 位哈希生成稳定 ID(PipelineCache.ts),避免把整段源码塞进管线缓存键
- 渲染/计算管线按"身份定义字段"生成紧凑缓存键——顶点/片元布局、混合、深度模板、图元拓扑等逐一规范化后拼接,等价描述符必然得到等价键
- 缓存绑定到单个
GPUDevice,设备丢失时自动清空并重置统计;getStats()可随时查看命中率
公共入口在 createPipelineCache.ts,行为可参考单测 ChartGPU.pipelineCache.test.ts:同一段 WGSL 第二次调用直接命中缓存,同构管线第二次getOrCreateRenderPipeline不再触发编译。
submitBatcher:N 个图表合并成一次 queue.submit
现在到了"单帧提交"的主角。WebGPU 驱动对每次device.queue.submit都有非平凡的校验与 fence 开销。多图表仪表盘如果各自提交,一帧就是 N 次 submit。
submitBatcher.ts 用一个精巧的微任务批处理解决了它:
enqueueDeviceSubmit(device, commandBuffer):各图表编码完成后不立即提交,而是把命令缓冲推入该 device 的待处理队列,并用queueMicrotask安排一次统一冲刷- 同一个 JS 回合内所有
renderFrame()结束后的 submit 自动折叠成一次queue.submit([cb0..cbN]),同时保持 FIFO 顺序 destroyBufferAfterSubmit(submitBatcher.ts):序列缓冲区扩容时,旧 buffer 的销毁会延迟到本批提交完成之后,既保住合批收益,又杜绝 use-after-destroyflushDeviceSubmit:图表销毁前必须同步冲刷,防止悬空的微任务提交引用已释放纹理的命令缓冲
它的单测在 submitBatcher.test.ts。而 GPUContext.ts 支持注入共享GPUDevice,让一个仪表盘里所有图表天然共享同一 device——这正是 submitBatcher 能跨图表合批的前提。
串起来看:从数据到像素的单帧链路
把三块拼起来,ChartGPU 一帧的生命周期是:
脏标记触发帧→ 各图表用 Pipeline Cache 命中已有管线、零重复编译 → 数据写入缓冲、编码命令缓冲 → submitBatcher 在微任务边界把所有图表的命令缓冲合并成一次
queue.submit→ GPU 并行渲染,脏 buffer 延迟销毁
想亲手验证,可以从这几处入手:
- 性能文档与基准:docs/performance.md、benchmarks/render-performance-benchmark.ts
- 3500 万点压力测试示例:examples/ultimate-benchmark/main.ts
- 快速上手:docs/GETTING_STARTED.md 与 docs/API.md
- 流式大盘实战:examples/streaming-dashboard/main.ts
下次当你看到 ChartGPU 让一个 6 图表仪表盘在 60fps 下丝滑滚动时,可以知道:每一帧背后,只发生了一次提交。
【免费下载链接】ChartGPUBeautiful, open source, WebGPU-based charting library项目地址: https://gitcode.com/gh_mirrors/ch/ChartGPU
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考