目錄
- 1 為什么是 WebGPU 而不是 WebGL 3.0
- 顯卡驅動
- 圖形 API 的簡單年表
- WebGL 能運行在各個瀏覽器的原因
- WebGPU 的名稱由來
- 2 與 WebGL 比較編碼風格
- OpenGL 的編碼風格
- CPU 負載問題
- WebGPU 的裝配式編碼風格
- 廚子戲法
- 3 多執行緒與強大的通用計算(GPGPU)能力
- WebWorker 多執行緒
- 通用計算(GPGPU)
- 4 瀏覽器的實作
- 5 未來
- 參考資料
這篇講講歷史,不太適合直奔主題的朋友們,
1 為什么是 WebGPU 而不是 WebGL 3.0
你若往 Web 圖形技術的底層去深究,一定能追溯到上個世紀 90 年代提出的 OpenGL 技術,也一定能看到,WebGL 就是基于 OpenGL ES 做出來的這些資訊,OpenGL 在那個顯卡羸弱的年代發揮了它應有的價值,
顯卡驅動
我們都知道現在的顯卡都要安裝顯卡驅動程式,通過顯卡驅動程式暴露的 API,我們就可以操作 GPU 完成圖形處理器的操作,
問題就是,顯卡驅動和普通編程界的匯編一樣,底層,不好寫,于是各大廠就做了封裝 —— 碼界的基操,
圖形 API 的簡單年表
OpenGL 就是干這個的,負責上層介面封裝并與下層顯卡驅動打交道,但是,眾所周知,它的設計風格已經跟不上現代 GPU 的特性了,
Microsoft 為此做出來最新的圖形API 是 Direct3D 12,Apple 為此做出來最新的圖形API 是 Metal,有一個著名的組織則做出來 Vulkan,這個組織名叫 Khronos,D3D12 現在在發光發熱的地方是 Windows 和 PlayStation,Metal 則是 Mac 和 iPhone,Vulkan 你可能在安卓手機評測上見得多,這三個圖形 API 被稱作三大現代圖形API,與現代顯卡(無論是PC還是移動設備)的聯系很密切,
WebGL 能運行在各個瀏覽器的原因
噢,忘了一提,OpenGL 在 2006 年把丟給了 Khronos 管,現在各個作業系統基本都沒怎么裝這個很老的圖形驅動了,
那問題來了,基于 OpenGL ES 的 WebGL 為什么能跑在各個作業系統的瀏覽器?
因為 WebGL 再往下已經可以不是 OpenGL ES 了,在 Windows 上現在是通過 D3D 轉譯到顯卡驅動的,在 macOS 則是 Metal,只不過時間越接近現在,這種非親兒子式的實作就越發困難,
蘋果的 Safari 瀏覽器最近幾年才珊珊支持 WebGL 2.0,而且已經放棄了 OpenGL ES 中 GPGPU 的特性了,或許看不到 WebGL 2.0 的 GPGPU 在 Safari 上實作了,果子哥現在正忙著 Metal 和更偉大的 M 系列自研芯片呢,
WebGPU 的名稱由來
所以,綜上所述,下一代的 Web 圖形介面不叫 WebGL 3.0 的原因,你清楚了嗎?已經不是 GL 一脈的了,為了使現代巨頭在名稱上不打架,所以采用了更貼近硬體名稱的 WebGPU,WebGPU 從根源上和 WebGL 就不是一個時代的,無論是編碼風格還是性能表現上,
題外話,OpenGL 并不是沒有學習的價值,反而它還會存在一段時間,WebGL 也一樣,
2 與 WebGL 比較編碼風格
WebGL 實際上可以說是 OpenGL 的影子,OpenGL 的風格對 WebGL 的風格影響巨大,
學習過 WebGL 介面的朋友都知道一個東西:gl 變數,準確的說是 WebGLRenderingContext 物件,WebGL 2.0 則是 WebGLRenderingContext2.
OpenGL 的編碼風格
無論是操作著色器,還是操作 VBO,亦或者是創建一些 Buffer、Texture 物件,基本上都得通過 gl 變數一條一條函式地走程序,順序是非常講究的,例如,下面是創建兩大著色器并執行編譯、連接的代碼:
const vertexShaderCode = `
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
`
const fragmentShaderCode = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0.5, 1);
}
`
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertexShader, vertexShaderCode)
gl.compileShader(vertexShader)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragmentShader, fragmentShaderCode)
gl.compileShader(fragmentShader)
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
// 還需要顯式指定你需要用哪個 program
gl.useProgram(program)
// 繼續操作頂點資料并觸發繪制
// ...
創建著色器、賦予著色器代碼并編譯的三行 js WebGL 呼叫,可以說是必須這么寫了,頂多 vs 和 fs 的創建編譯順序可以換一下,但是又必須在 program 之前完成這些操作,
CPU 負載問題
有人說這無所謂,可以封裝成 JavaScript 函式,隱藏這些程序細節,只需傳遞引數即可,是,這是一個不錯的封裝,很多 js 庫都做過,并且都很實用,
但是,這仍然有難以逾越的鴻溝 —— 那就是 OpenGL 本身的問題,
每一次呼叫 gl.xxx 時,都會完成 CPU 到 GPU 的信號傳遞,改變 GPU 的狀態,是立即生效的,熟悉計算機基礎的朋友應該知道,計算機內部的時間和硬體之間的距離有多么重要,世人花了幾十年時間無不為信號傳遞付出了努力,上述任意一條 gl 函式改變 GPU 狀態的程序,大致要走完 CPU ~ 總線 ~ GPU 這么長一段距離,
我們都知道,辦事肯定是一次性備齊材料的好,不要來來回回跑那么多遍,而 OpenGL 就是這樣子的,有人說為什么要這樣而不是改成一次發送的樣子?歷史原因,OpenGL 盛行那會兒 GPU 的作業沒那么復雜,也就不需要那么超前的設計,
綜上所述,WebGL 是存在 CPU 負載隱患的,是由于 OpenGL 這個狀態機制決定的,
現代三大圖形API 可不是這樣,它們更傾向于先把東西準備好,最后提交給 GPU 的就是一個完整的設計圖紙和緩沖資料,GPU 只需要拿著就可以專注辦事,
WebGPU 的裝配式編碼風格
WebGPU 雖然也有一個總管家一樣的物件 —— device,型別是 GPUDevice,表示可以操作 GPU 設備的一個高層級抽象,它負責創建操作圖形運算的各個物件,最后裝配成一個叫 “CommandBuffer(指令緩沖,GPUCommandBuffer)”的物件并提交給佇列,這才完成 CPU 這邊的勞動,
所以,device.createXXX 創建程序中的物件時,并不會像 WebGL 一樣立即通知 GPU 完成狀態的改變,而是在 CPU 端寫的代碼就從邏輯、型別上確保了待會傳遞給 GPU 的東西是準確的,并讓他們按自己的坑站好位,隨時等待提交給 GPU,
在這里,指令緩沖物件具備了完整的資料資料(幾何、紋理、著色器、管線調度邏輯等),GPU 一拿到就知道該干什么,
// 在異步函式中
const device = await adapter.requestDevice()
const buffer = device.createBuffer({
/* 裝配幾何,傳遞記憶體中的資料,最終成為 vertexAttribute 和 uniform 等資源 */
})
const texture = device.createTexture({
/* 裝配紋理和采樣資訊 */
})
const pipelineLayout = device.createPipelineLayout({
/* 創建管線布局,傳遞系結組布局物件 */
})
/* 創建著色器模塊 */
const vertexShaderModule = device.createShaderModule({ /* ... */ })
const fragmentShaderModule = device.createShaderModule({ /* ... */ })
/*
計算著色器可能用到的著色器模塊
const computeShaderModule = device.createShaderModule({ /* ... * / })
*/
const bindGroupLayout = device.createBindGroupLayout({
/* 創建系結組的布局物件 */
})
const pipelineLayout = device.createPipelineLayout({
/* 傳遞系結組布局物件 */
})
/*
上面兩個布局物件其實可以偷懶不創建,系結組雖然需要系結組布局以
通知對應管線階段系結組的資源長啥樣,但是系結組布局是可以由
管線物件通過可編程階段的代碼自己推斷出來系結組布局物件的
本示例代碼保存了完整的程序
*/
const pipeline = device.createRenderPipeline({
/*
創建管線
指定管線各個階段所需的素材
其中有三個階段可以傳遞著色器以實作可編程,即頂點、片段、計算
每個階段還可以指定其所需要的資料、資訊,例如 buffer 等
除此之外,管線還需要一個管線的布局物件,其內置的系結組布局物件可以
讓著色器知曉之后在通道中使用的系結組資源是啥樣子的
*/
})
const bindGroup_0 = deivce.createBindGroup({
/*
資源打組,將 buffer 和 texture 歸到邏輯上的分組中,
方便各個程序呼叫,程序即管線,
此處必須傳遞系結組布局物件,可以從管線中推斷獲取,也可以直接傳遞系結組布局物件本身
*/
})
const commandEncoder = device.createCommandEncoder() // 創建指令緩沖編碼器物件
const renderPassEncoder = commandEncoder.beginRenderPass() // 啟動一個渲染通道編碼器
// 也可以啟動一個計算通道
// const computePassEncoder = commandEncoder.beginComputePass({ /* ... */ })
/*
以渲染通道為例,使用 renderPassEncoder 完成這個通道內要做什么的順序設定,例如
*/
// 第一道繪制,設定管線0、系結組0、系結組1、vbo,并觸發繪制
renderPassEncoder.setPipeline(renderPipeline_0)
renderPassEncoder.setBindGroup(0, bindGroup_0)
renderPassEncoder.setBindGroup(1, bindGroup_1)
renderPassEncoder.setVertexBuffer(0, vbo, 0, size)
renderPassEncoder.draw(vertexCount)
// 第二道繪制,設定管線1、另一個系結組并觸發繪制
renderPassEncoder.setPipeline(renderPipeline_1)
renderPassEncoder.setBindGroup(1, another_bindGroup)
renderPassEncoder.draw(vertexCount)
// 結束通道編碼
renderPassEncoder.endPass()
// 最后提交至 queue,也即 commandEncoder 呼叫 finish 完成編碼,回傳一個指令緩沖
device.queue.submit([
commandEncoder.finish()
])
上述程序是 WebGPU 的一般化代碼,很粗糙,沒什么細節,不過基本上就是這么個邏輯,
對通道編碼器的那部分代碼,筆者保留的比較完整,讓讀者更好觀察一個指令編碼器是如何編碼通道,并最后結束編碼創建指令緩沖提交給佇列的,
廚子戲法
用做菜來比喻,OpenGL 系的編程就好比做一道菜時需要什么調料就去拿什么調料,做好一道菜再繼續做下一道菜;而現代圖形API 則是多灶臺開火,所有材料都在合適的位置上,包括處理好的食材和輔料,即使一個廚師(CPU)都可以同時做好幾道菜,效率很高,
3 多執行緒與強大的通用計算(GPGPU)能力
WebWorker 多執行緒
WebGL 的總管家物件是 gl 變數,它必須依賴 HTML Canvas 元素,也就是說必須由主執行緒獲取,也只能在主執行緒調度 GPU 狀態,WebWorker 技術的多執行緒能力只能處理資料,比較雞肋,
WebGPU 改變了總管家物件的獲取方式,adapter 物件所依賴的 navigator.gpu 物件在 WebWorker 中也可以訪問,所以在 Worker 中也可以創建 device,也可以裝配出指令緩沖,從而實作多執行緒提交指令緩沖,實作 CPU 端多執行緒調度 GPU 的能力,
通用計算(GPGPU)
如果說 WebWorker 是 CPU 端的多執行緒,那么 GPU 本身的多執行緒也要用上,
能實作這一點的,是一個叫做“計算著色器”的東西,它是可編程管線中的一個可編程階段,在 OpenGL 中可謂是姍姍來遲(因為早期的顯卡并沒挖掘其并行通用計算的能力),更別說 WebGL 到了 2.0 才支持了,蘋果老兄甚至壓根就懶得給 WebGL 2.0 實作這個特性,
WebGPU 出廠就帶這玩意兒,通過計算著色器,使用 GPU 中 CU(Compute Unit,計算單元)旁邊的共享記憶體,速度比普通的顯存速度快得多,
有關計算著色器的資料不是特別多,目前只能看例子,在參考資料中也附帶了一篇博客,
將 GPGPU 帶入 Web 端后,腳本語言的運行時(deno、瀏覽器JavaScript,甚至未來的 nodejs 也有可能支持 WebGPU)就可以訪問 GPU 的強大并行計算能力,據說 tensorflow.js 改用 WebGPU 作為后置技術后性能有極為顯著的提升,對深度學習等領域有極大幫助,即使用戶的瀏覽器沒那么新潮,渲染編程還沒那么快換掉 WebGL,WebGPU 的通用計算能力也可以在別的領域發光發熱,更別說計算著色器在渲染中也是可以用的,
真是誘人啊!
4 瀏覽器的實作
Edge 和 Chrome 截至發文,在金絲雀版本均可以通過 flag 打開試用,
Edge 和 Chrome 均使用了 Chromium 核心,Chromium 是通過 Dawn 這個模塊實作的 WebGPU API,根據有關資料,Dawn 中的 DawnNative 部分負責與三大圖形 API 溝通,向上則給一個叫 DawnWire 的模塊傳遞資訊,DawnWire 模塊則負責與 JavaScript API 溝通,也就是你寫的 WebGPU 代碼,WGSL 也是這個部分實作的,Dawn 是 C++ 實作的,你可以在參考資料中找到連接,
FireFox 則使用了 gfx-rs 專案實作 WebGPU,顯然是 Rust 語言實作的 WebGPU,也有與 Dawn 類似的模塊設計,
Safari 則更新自家的 WebKit 實作 WebGPU,
5 未來
展望宏圖之類的話不說,但是隨著紅綠藍三家的 GPU 技術越發精湛,加上各個移動端的 GPU 逐漸起色,三大現代圖形API肯定還在發展,WebGPU 一定能在 Web 端釋放現代圖形處理器(GPU)的強大能力,無論是圖形游戲,亦或是通用并行計算帶來的機器學習、AI能力,
參考資料
- Google Dawn Page
- gfx-rs GitHub Home Page
- Get started with GPU Compute on the web
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/412954.html
標籤:其他
上一篇:影像處理技術(三)白平衡
