iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Software Development

渲染與GPU編程系列 第 19

Day 18|Compute Shader on Website:WebGPU 的 Storage Buffer(SBO)與 Compute Shader 入門(零基礎友善版)#1

  • 分享至 

  • xImage
  •  

規劃有點改變,接下來幾篇會改成講"Compute Shader on Website"。

今天要把兩件常被搞混的東西講清楚:
Storage Buffer(SBO) 是「給 GPU 讀寫的大倉庫」;Compute Shader 是「不畫圖、只算數」的 GPU 程式。
我們先用簡單的比喻建立直覺,再動手做一個範例:把陣列丟進 GPU,用 compute shader 算完再把結果拿回來。


1)先把名詞變得很直覺

  • Buffer 是什麼?
    一條在 GPU 的連續記憶體。你可以把它想成「一長條資料夾」:

    • VERTEX/INDEX:專門給繪圖管線的頂點/索引資料。
    • UNIFORM:小小一張設定表(只讀、容量有限,常見限制 ~64KB)。
    • STORAGE(SBO)大容量、可以 讀/寫 的資料夾,放任意結構化資料最方便。
  • 為什麼要 SBO?
    當資料很大(上 MB)、或你要在 GPU 寫回結果(像模擬、排序、粒子、貼圖運算),就需要 Storage Buffer。Uniform 無法寫、容量也不夠。

  • Compute Shader 是什麼?
    一段在 GPU 上平行運算的程式,不經過光柵化、材質這些「畫圖」流程。
    你告訴 GPU:「開 多少個工人(workgroups),每個工人處理哪一段資料」,計算結果通常寫回 SBOStorage Texture


2)SBO vs Uniform:怎麼選?

特性 Uniform Buffer Storage Buffer(SBO)
讀寫 只讀 可讀可寫(或宣告為只讀以利最佳化)
容量 小(常見 ~64KB/綁定) 大(MB 級),受硬體 limits 約束
用途 常數參數、矩陣 大陣列/結構體、結果回寫、粒子、排序
WGSL 宣告 @group/binding var<uniform> var<storage, read> / var<storage, read_write>

新手口訣:要寫回、或資料很大 → Storage Buffer;只是一些小參數 → Uniform。


3)SBO 的型別與對齊(不踩雷小抄)

  • WebGPU/WGSL 在 Storage Buffer 使用類似 std430 的對齊規則:

    • f32/u32/i32 對齊 4 bytes。
    • vec2<f32> 對齊 8;vec3/vec4<f32> 對齊 16vec3 也看齊 16)。
    • struct 的對齊是成員裡 最大對齊;大小會 補齊到該對齊倍數
  • 陣列的步幅(arrayStride)通常會是元素對齊的倍數(要小心 vec3)。

  • 最簡做法:

    • 結構成員盡量用 vec4、或加註 @align(16),就不會被 vec3 的 16 對齊坑到。
    • 真的不確定,先用 vec4 取代 vec3

4)Compute Shader:Workgroup 與索引怎麼算?

  • 你設定 @workgroup_size(64),表示「每隊 64 人」。
  • 呼叫 dispatchWorkgroups(G) 就會啟動 G 隊 × 64 人
  • 每個人(thread)會拿到一個 global_invocation_id(像員工編號),常用它來當陣列索引。
  • 若資料長度不是 workgroup 大小的整數倍,記得做越界檢查(上面 WGSL 的 if (i < ...))。

延伸:下一篇會講 workgroup 之間怎麼協作(workgroup 記憶體、workgroupBarrier())、原子操作、以及把 compute 算的資料拿去 render pass 畫圖。


5)SBO 也能用在「渲染管線」裡(快看一眼)

很多人以為 Storage Buffer 只給 compute;其實你在 Vertex/Fragment Shader 也能讀(甚至寫)。
最常見用途:大量實例(Instancing)的變換矩陣。例如依 @builtin(instance_index) 在 VS 讀第 i 個矩陣:

WGSL(片段)

struct Mat { m: mat4x4<f32>; };
struct Mats { data: array<Mat>; };

@group(0) @binding(0) var<storage, read> instMats: Mats;

@vertex
fn vs_main(@location(0) pos: vec3f,
           @builtin(instance_index) iid: u32) -> @builtin(position) vec4f {
  let M = instMats.data[iid].m;
  return M * vec4f(pos, 1.0);
}

JS 端把一整串矩陣塞進 Storage Buffer,就能一次畫出上千個分身,比把資料塞進很多小 Uniform 更彈性。


6)常見錯誤&秒修

  1. Attachment 相容性錯誤(在 render pass 才會遇到)
    管線有 depthStencil 就必須在 render pass 提供 depthStencilAttachment;不需要就兩邊都拿掉。
  2. 讀不到結果
    忘了把 bufC copyMAP_READ 的 staging buffer;或 await mapAsync 之前就讀。
  3. out-of-bounds
    global_invocation_id.x 超過陣列長度。記得 if (i < N)
  4. BindGroup 不匹配
    WGSL 的 @group/@binding 要和 JS 的 getBindGroupLayout(0) / entries 對得上。
  5. 格式/對齊怪怪的
    在 Storage Buffer 的 struct 裡避免 vec3;改用 vec4 或加 @align(16)

8)一句話總結

Storage Buffer = GPU 的大倉庫(可讀可寫,放大量結構化資料);
Compute Shader = 叫很多小工平行算(不畫圖也能用 GPU)。
把兩者串起來,你就能做各種高效處理:物理模擬、影像濾鏡、排序、幾何產生…
今天先用向量加法暖身;下一篇我們會更深入 Compute Shader 的工作群組、同步、共享記憶體與效能小撇步


上一篇
Day 17|第一個 WebGPU 專案:環境與初始化
下一篇
Day 19|WebGPU Compute Shader 的基礎範例
系列文
渲染與GPU編程22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言