從 WebGL 到 WebGPU

法蘭索瓦博福
François Beaufort

身為 WebGL 開發人員,您可能會對 WebGPU 的問候程度感到相當困惑,也很期待開始使用 WebGPU,這是 WebGL 的後續版本,可為網路帶來先進的圖形 API 發展。

知道 WebGL 和 WebGPU 共用許多核心概念,可說是令人安心。這兩種 API 可讓您在 GPU 上執行稱為著色器的小型程式。WebGL 支援頂點和片段著色器,而 WebGPU 也支援運算著色器。WebGL 使用 OpenGL 著色語言 (GLSL),WebGPU 使用 WebGPU 著色語言 (WGSL)。雖然兩種語言不同,但基本概念大致上相同。

因此,本文將著重介紹 WebGL 和 WebGPU 的一些差異,協助您快速上手。

全域狀態

WebGL 具有許多全球狀態。部分設定會套用至所有算繪作業,例如繫結的紋理和緩衝區。您可以透過呼叫各種 API 函式來設定這個全域狀態。除非您變更,否則此狀態將持續有效。WebGL 中的全域狀態是主要錯誤來源,因為使用者很容易忘記變更全域設定。此外,全域狀態也讓程式碼共用變得困難,因為開發人員必須小心謹慎,不要以可能影響程式碼其他部分的方式意外變更全域狀態。

WebGPU 是無狀態 API,不會維持全域狀態。而是使用管道的概念封裝所有在 WebGL 中的全域算繪狀態。管道包含要使用的混合、拓撲和屬性等資訊。管道無法變更。如要變更部分設定,必須建立另一個管道。WebGPU 也會使用指令編碼器將指令批次處理,並依照記錄順序執行。這在陰影對應中非常實用,例如,在一次傳遞物件時,應用程式可以記錄多個指令串流,每個光源的陰影對應各有一個指令串流。

總而言之,WebGPU 的全域狀態模型建立了強大、不易且脆弱的可組合函式和應用程式,因此大幅減少了開發人員向 GPU 傳送指令時需要追蹤的狀態量。

已停止同步處理

在 GPU 上,以同步方式傳送指令並等待期間通常效率不彰,因為這樣可能會清除管道並造成對話框。特別是在 WebGPU 和 WebGL 中,這些元件會使用多程序架構,以及 GPU 驅動程式在 JavaScript 以外的程序中執行。

舉例來說,在 WebGL 中呼叫 gl.getError() 時,需要從 JavaScript 程序到 GPU 程序和備份之間同步 IPC。這可能會在兩個程序進行通訊時,CPU 端會出現泡泡。

WebGPU 的設計完全非同步,可避免出現這些泡泡。錯誤模型和所有其他作業都是非同步進行。舉例來說,在建立紋理時,即使紋理實際上發生錯誤,這項作業仍會立即成功。您只能以非同步方式找出錯誤。這種設計可避免跨程序通訊泡泡,且能提供應用程式穩定的效能。

計算著色器

運算著色器是在 GPU 上執行,執行一般用途運算的程式。這些 API 僅適用於 WebGPU,不適用於 WebGL。

有別於頂點和片段著色器,它們不限於圖形處理,而且可用於機器學習、物理模擬和科學運算等多種工作。運算著色器會同時與數百個或數千個執行緒平行執行,因此非常高效地處理大型資料集。如要進一步瞭解 GPU 運算和更多詳細資訊,請參閱這篇關於 WebGPU 的詳盡的文章

影片畫面處理

使用 JavaScript 和 WebAssembly 處理影片畫面有一些缺點:將資料從 GPU 記憶體複製到 CPU 記憶體,以及使用工作站和 CPU 執行緒可達成的有限平行處理能力。WebGPU 沒有這些限制,因為與 WebCodecs API 緊密整合,因此非常適合處理視訊畫面。

以下程式碼片段說明如何在 WebGPU 中匯入 VideoFrame 做為外部紋理,然後加以處理。歡迎試用這個示範影片

// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...

(function render() {
  const videoFrame = new VideoFrame(video);
  applyFilter(videoFrame);
  requestAnimationFrame(render);
})();

function applyFilter(videoFrame) {
  const texture = device.importExternalTexture({ source: videoFrame });
  const bindgroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture }],
  });
  // Finally, submit commands to GPU
}

預設應用程式可攜性

WebGPU 會強制您要求 limits。根據預設,requestDevice() 傳回的 GPUDevice 可能與實體裝置的硬體功能不符,而非所有 GPU 的合理且最低的常見分母。透過要求開發人員要求裝置限制,WebGPU 可確保應用程式能夠在更多裝置上執行。

畫布處理

在您建立 WebGL 結構定義後,WebGL 會自動管理畫布,並提供情境屬性 (例如 Alpha、antialias、colorSpace、depth、saveDrawingBuffer 或 stencil)。

另一方面,WebGPU 則需要您自行管理畫布。舉例來說,如要在 WebGPU 中達成反鋸齒,請建立多重取樣紋理並算繪。接著,您可以將多重取樣紋理解析為一般紋理,並將該紋理繪製到畫布。有了這項手動管理功能,您就能從單一 GPUDevice 物件輸出任意數量的畫布。反之,WebGL 只能在每個畫布上建立一種內容。

請參閱 WebGPU 多重畫布示範

提醒您,瀏覽器目前對每頁 WebGL 畫布數量設有限制。寫字時,Chrome 和 Safari 最多可同時使用 16 個 WebGL 畫布,Firefox 最多可建立 200 種 WebGL 畫布。另一方面,每頁 WebGPU 畫布數量則沒有限制。

這張螢幕截圖顯示 Safari、Chrome 和 Firefox 瀏覽器中最多的 WebGL 畫布數量
WebGL、Chrome 和 Firefox 中的 WebGL 畫布數量上限 (由左至右) - 示範

實用的錯誤訊息

WebGPU 會為 API 傳回的每則訊息提供呼叫堆疊。換句話說,您可以快速查看程式碼中錯誤的位置,適合偵錯及修正錯誤。

WebGPU 錯誤訊息除了提供呼叫堆疊之外,也相當簡單明瞭且易於採取行動。錯誤訊息通常會附上錯誤說明和錯誤修正建議。

您也可以為 WebGPU 物件提供自訂 label。接著,瀏覽器會在 GPUError 訊息、控制台警告和瀏覽器開發人員工具中使用這個標籤。

從名稱到索引

在 WebGL 中,許多事物都會透過名稱相互連結。舉例來說,您可以在 GLSL 中宣告名為 myUniform 的統一變數,並使用 gl.getUniformLocation(program, 'myUniform') 取得變數的位置。如果您不慎輸入統一變數的名稱,這項錯誤就非常實用。

另一方面,在 WebGPU 中,所有內容都是透過位元組偏移或索引完全連接 (通常稱為「位置」)。您有責任將 WGSL 和 JavaScript 程式碼的位置保持同步。

mipmap 產生

在 WebGL 中,您可以建立紋理的等級 0 mip,然後呼叫 gl.generateMipmap()。然後 WebGL 會產生其他所有 mip 層級。

在 WebGPU 中,您必須自行產生 mipmap。目前沒有內建函式可執行這項操作。如要進一步瞭解我們的決策,請參閱規格討論。您可以使用 webgpu-utils 等便利的程式庫產生 mipmap,或自己瞭解如何執行。

儲存空間緩衝區和儲存紋理

WebGL 和 WebGPU 皆支援統一緩衝區,可讓您將有限大小的常數參數傳遞至著色器。儲存空間緩衝區與統一緩衝區非常相似,且僅支援 WebGPU,而且比統一緩衝區更強大且更有彈性。

  • 傳遞至著色器的儲存空間緩衝區資料可能會比統一緩衝區大上許多。雖然規格表示統一緩衝區繫結大小上限為 64 KB (請參閱 maxUniformBufferBindingSize),但在 WebGPU 中,儲存空間緩衝區繫結的大小上限為 128 MB (請參閱 maxStorageBufferBindingSize)。

  • 儲存空間緩衝區可寫入,並支援部分不可部分完成的作業,而統一緩衝區則為唯讀性質。這樣就能導入新的演算法類別。

  • 儲存空間緩衝區繫結可支援執行階段大小陣列,藉此享有更具彈性的演算法,而著色器則必須提供統一的緩衝區陣列大小。

儲存紋理僅適用於 WebGPU,並將儲存緩衝區的紋理套用至統一緩衝區。這類 API 比一般紋理更具彈性,可支援隨機存取寫入 (以及日後的讀取)。

緩衝區和紋理變更

在 WebGL 中,您可以建立緩衝區或紋理,然後隨時使用 gl.bufferData()gl.texImage2D() 分別變更緩衝區大小。

在 WebGPU 中,緩衝區和紋理無法變更。也就是說,物件大小、使用情況或格式一經建立即無法變更。你只能變更群組內容。

聊天室慣例差異

在 WebGL 中,Z 裁剪空間範圍是 -1 到 1。在 WebGPU 中,Z 軸裁剪空間範圍是 0 到 1。這表示 z 值為 0 的物件最接近相機位置,而 z 值 1 的物件則最遠。

插圖:WebGL 和 WebGPU 的 Z 剪輯空間範圍。
在 WebGL 和 WebGPU 中裁剪空間範圍。

WebGL 使用 OpenGL 慣例,Y 軸代表 Y 軸,Z 軸則朝向檢視器。WebGPU 採用金屬法,也就是 Y 軸向下移動,Z 軸不在畫面中。請注意,Y 軸方向位於 framebuffer 座標、可視區域座標以及片段/像素座標中。在短片空間中,Y 軸方向仍與 WebGL 相同。

特別銘謝

由 Corentin Wallez、Gregg Tavares、Stephen White、Ken Russell 和 Rachel Andrew 協助撰寫這篇文章。

我也推薦使用 WebGPUFundamentals.org,進一步瞭解 WebGPU 和 WebGL 之間的差異。