我在 2017 年時曾經寫過關於 WebGL 與 Three.js 的主題,近幾年新的標準 WebGPU 逐步成形,已經可以在部分瀏覽器上使用了。今天就來談談 WebGL 與 WebGPU 吧!
對於 WebGL 和 WebGPU 我的理解有限,也沒有辦法深入談太多,真要說起來它算是我接下來想探索但還來不及探索的主題,因此這篇文章可能會寫的比較雜一點,敬請見諒。
WebGL 大約是在 2011 年出現的,當時網頁已經可以用 canvas 來畫圖,但卻沒有進一步控制 GPU 的手段。WebGL,背後其實是對 OpenGL 的包裝,由 Khronos Group 進行規範跟標準化。
其中,大家最常聽見的名詞就是 vertex shader 跟 fragment shader,以及撰寫 shader 的語言 GLSL。然而,為什麼要用 GLSL,一個跟 JavaScript 大相逕庭的語言來寫呢?
以我的理解來看 OpenGL 只是一個 API 指令集,真正的實作是給 GPU 廠商決定。每個 GPU 通常會有對應的驅動程式,負責將 OpenGL 或 GLSL 的程式碼轉譯為 GPU 的指令。因此如果是在 OpenGL 上運作的話,就必須用 GLSL 來撰寫。
如果有實際撰寫過 WebGL 的程式碼,應該會發現整個心智模型跟我們一般在寫程式碼是完全不一樣的。首先要準備好頂點資料傳進去 GPU buffer、準備好 texture 傳入 buffer,最後才是實際執行。
由於 GPU 的執行速度很快(或者說 GPU 非常擅長平行運算),所以每次 I/O 都會增加 GPU 延遲。因此與其一個個傳資料到 buffer 裡面,一次全部運算的效率更高。
從上面的解釋也可以知道為什麼要獨立撰寫 shader 了,我們可以想像同一個 shader 程式碼,它會在每個像素上執行,背後其實就是在 GPU 裏頭數千個、數萬個核心上同時執行。不像一般的程式碼是循序執行。
這部分在 webglfundamentals 有更多解說。
在 WebGL 出現的時期,當時 GPU 只是用來作渲染的,然而以現在來看,這樣的 API 設計其實有點尷尬。
在 Machine Learning、挖礦風潮、Nerual Network 盛行之下,人們發現 GPU 不僅可以用來渲染,而且還對這種需要大量計算矩陣乘法的操作非常在行。也就是所謂 GPGPU(通用 GPU)。再加上 OpenGL 已經 deprecated,也有新的 GPU APIs 正在誕生,像是蘋果的 Metal 以及 Vulkan 等等,所以 WebGPU 也就誕生了。
如果有撇過一眼 WebGPU 的程式碼,應該會覺得寫起來更加囉嗦了,而且有非常多奇怪的 symbol 導致第一時間很難理解它在幹嘛。
WebGPU 不是在 OpenGL 上疊床架屋,而是讓開發者可以更進一步存取底層的 GPU API,讓開發者可以更彈性地使用 GPU。
例如在 WebGL 當中渲染 pipeline,固定是 vertex shader 和 fragment shader,因此如果要作比較通用型的計算,例如我想要只是要算個矩陣時,這樣寫起來就有點麻煩。
WebGPU 可以自訂 pipeline,例如 rendering pipeline 或 compute pipeline。
範例參考 MDN:
struct VertexOut {
@builtin(position) position : vec4f,
@location(0) color : vec4f
}
@vertex
fn vertex_main(@location(0) position: vec4f,
@location(1) color: vec4f) -> VertexOut
{
var output : VertexOut;
output.position = position;
output.color = color;
return output;
}
@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
{
return fragData.color;
}
@group(0) @binding(0)
var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(
@builtin(global_invocation_id)
global_id : vec3u,
@builtin(local_invocation_id)
local_id : vec3u,
) {
// Avoid accessing the buffer out of bounds
if (global_id.x >= ${BUFFER_SIZE}) {
return;
}
output[global_id.x] =
f32(global_id.x) * 1000. + f32(local_id.x);
}
然後分別透過 device.createShaderModule()
跟 device.createComputePipeline()
來呼叫。這樣一來,如果只是想要單純計算的話,就可以用 compute pipeline 來執行。
不管是 WebGPU 還是 WebGL,因為他們不同於以往的心智模型,所有具有非常高的學習門檻。但是我覺得去理解不同的手法,並且試圖理解他們為什麼要那麼做事非常有趣的事情。
在實務上從頭寫 WebGL 的機率比較小一些,通常都是用 Three.js 或其他成熟的套件,開發者只要專心寫 shader 跟傳資料就可以了。
現在有了 WebGPU 之後,存取 GPU 變得更方便了,或許未來我們可以看到更多深度學習的應用在網頁上。
雖然如此,一旦掌握了核心觀念,然後不用管麻煩的資料傳遞的話,個人覺得 shader 寫起來也蠻有趣的,推薦給想要做出酷酷效果的大家!