目錄
- 1. 核心概念
- ① 配接器和設備
- ② 緩沖、紋理、采樣器
- ③ 系結組
- ④ 著色器與管線
- ⑤ 編碼器與佇列
- 2. 重要機制
- ① 緩沖映射機制
- ② 時間線
1. 核心概念
這部分不會詳細展開,以后寫教程時會深入,以下只是核心概念,是絕大多數 WebGPU 原生程式要接觸的,并不是全部,
① 配接器和設備
配接器,也就是 GPUAdapter,指代真正的物理顯卡,WebGPU 給了個物件來代替它:
const adapter = await navigator.gpu.requestAdapter()
它提供了一個最重要行為,請求設備物件 GPUDevice:
const device = await adapter.requestDevice()
那么什么是 Device?其實,顯卡很忙,
WebGPU 程式只是三大圖形 API 中某個的“上層封裝”,除了 WebGPU,呼叫三大圖形 API 的程式遠不止,游戲、三維建模工具、視頻編解碼器,都有可能會呼叫,甚至會直接調取 GPU 廠商給的 SDK 或驅動程式,
顯然,作為顯卡“本身”,配接器為了極高效率地作業,喂給它的資料資源和指令最好就是翻譯過的,盡可能專注地執行計算 —— 就像大老板不可能日理萬機一樣,最好給到老板的決策資料,就是經過整理的,他要做的就是使用他多年的經驗快速決策、簽字(效率高的老總 = RTX4090,超市小老板 = GT1030),
那么,誰負責與各個部門(各個對顯卡有需要的程式)負責人溝通具體業務呢?
我認為是老總的全權代理人,一般是秘書 + 副總經理,
不同封裝有不同的概念,至少在 WebGPU 中,這個代理人叫做“設備”,GPUDevice,它幾乎就是顯卡的分身,WebGPU 程式中所要調取的資源、創建的物件、要觸發的行為,都交給設備物件實作,

每個 WebGPU 程式應該都有自己的 GPUDevice,不同的設備物件創建的 Buffer、Texture 等資源是不互通的,而配接器呢,一般情況下是同一個,除非你短時間內把電腦的顯卡給更改過,前一會兒是獨顯,過一會兒可能是核顯了(這段話還有待技術驗證,僅為我不負責任的猜測),
如果你寫過原生的 WebGL,你可能會聯想到 gl 背景關系變數了,沒錯,設備物件大部分時候就是 gl 背景關系的作用,但是是有本質區別的,
② 緩沖、紋理、采樣器
緩沖、紋理,即 GPUBuffer、GPUTexture 均是 GPU 顯存中的資料物件,能在客戶端代碼(如果沒特別說明,就是指瀏覽器端的 JavaScript)組織、創建、上載資料、相互轉化、反讀資料,
WebGPU 進行渲染繪圖時,Canvas 是一個特殊的 GPUTexture,
采樣器則是著色器程式對紋理采樣時的引數封裝,
看起來是 WebGL 類似物件 WebGLBuffer、WebGLTexture 以及紋理采樣函式的“升級”,實際上呼叫時提供了更細致的傳參,在資料上載、紋理與緩沖相互轉化、再從顯存讀取到記憶體的“映射機制”上卻大有不同,
這三個物件被稱作“資源”,均由 GPUDevice 創建,
③ 系結組
系結組,我更愿意稱之為“資源系結組”,即 GPUBindGroup;資源即“緩沖、紋理、采樣器”的任意組合,
使用系結組,允許把一組你需要的資源“打組”,傳進著色器代碼中,它與下面的“管線”是緊密相關的,
為什么要打組呢?為什么我不能寫個函式,按我需要把 GPUBuffer、GPUTexture、GPUSampler 挨個像 WebGL 一樣系結到某個系結點呢?
有兩個原因:
- 性能角度:打組本身就是減少 CPU 到 GPU 信號通訊的一種方式,想想你的硬碟,是連續大檔案傳得快,還是細碎的小檔案快?
- 復用角度:不同的著色行為可能會用一樣的資源集合,此時同一個系結組就可以復用;想一想,肉餡兒塞進包子里叫肉包,包進餃子皮里就是肉餃子了,

系結組是由 GPUDevice 創建的,是由第 ⑤ 小節中的 可編程通道編碼器 呼叫并與管線實際一起運作的,
④ 著色器與管線
著色器即 GPUShaderModule,管線一般指 GPURenderPipeline、GPUComputePipeline 兩個,
著色器支持把任意著色器混在一段字串中,頂點著色器、片元著色器、計算著色器可以共用一個 GPUShaderModule 物件,只需指定入口函式,這點與 WebGL 分開創建 VS、FS 是不一樣的,
管線可不是 WebGLProgram 的升級,雖然 gl.useProgram 和 passEncoder.setPipeline 在行為上有類似的作用,即切換到指定的行為程序,但是,在 WebGPU 中這兩個管線物件,除了附著對應的著色器物件外,還限定著管線不同階段對應的狀態引數,有三個狀態引數對應著兩大管線:
-
vertex、fragment
-
compute
例如:
/*
---------
這里不詳細展開,僅作為簡略
---------
*/
const positionAttribDesc: GPUVertexAttribute = {
shaderLocation: 0, // wgsl - @location(0)
offset: 0,
format: 'float32x3'
}
const colorAttribDesc: GPUVertexAttribute = {
shaderLocation: 1, // wgsl - @location(1)
offset: 0,
format: 'float32x3'
}
const positionBufferDesc: GPUVertexBufferLayout = {
attributes: [positionAttribDesc],
arrayStride: 4 * 3, // sizeof(float) * 3
}
const colorBufferDesc: GPUVertexBufferLayout = {
attributes: [colorAttribDesc],
arrayStride: 4 * 3, // sizeof(float) * 3
}
// --- 創建 state 引數物件
const vertexState: GPUVertexState = {
module: shaderModule,
entryPoint: 'vs_main',
buffers: [positionBufferDesc, colorBufferDesc]
}
const fragmentState: GPUFragmentState = {
module: shaderModule,
entryPoint: 'fs_main',
targets: [{
format: navigator.gpu.getPreferredCanvasFormat()
}],
}
const primitiveState: GPUPrimitiveState = {
topology: 'triangle-list'
}
// --- 渲染管線 ---
const renderPipeline = device.createRenderPipeline({
layout: 'auto',
vertex: vertexState,
fragment: fragmentState,
primitive: primitiveState
})
// --- 計算管線 ---
const computePipeline = device.createComputePipeline({
layout: 'auto',
compute: {
module: shaderModule,
entryPoint: 'cs_main',
}
})
對應 GPUVertexState、GPUFragmentState、GPUComputeState 型別;上面說到系結組是與管線緊密相關的,這幾個狀態引數物件,與系結組中的各個資源物件有著對應關系,
著色器模塊物件和管線物件也是由 GPUDevice 創建的,管線物件甚至提供了異步創建的方法,
⑤ 編碼器與佇列
WebGPU 使用“編碼器”去“記錄”一幀內要做什么事情,譬如切換管線、設定接下來要用什么緩沖、系結組,進而要進行什么操作(繪圖或觸發并行計算),
這有什么好處?
編碼器“記錄”這些行為,是在 CPU 側,也就是 JavaScript 完成的,這就解決了 WebGL 全域狀態物件的問題:改變一個狀態,就要發起一潭訓多條 GL 函式的呼叫(盡管使用擴展或在 WebGL 2.0 用各種技術進行了彌補,但是也不能實際解決問題),
編碼記錄完成后,會在 CPU 這邊生成一個叫做“指令緩沖”物件,把當前幀的所有指令緩沖一次性提交給一個佇列,那么當前幀就結束了戰斗,
合情合理,大部分的邏輯組織交給更擅長處理這些事情的 CPU 完成,最后集中發射給 GPU,這就是 WebGPU 于 WebGL 的一大優點,
編碼器有哪些?
上面一段文字比較粗略,
首先,為了區分繪圖操作、GPU 通用計算操作,WebGPU 使用“渲染通道編碼器”、“計算通道編碼器”,也就是 GPURenderPassEncoder、GPUComputePassEncoder 來實作各自的行為編碼、記錄;以渲染通道編碼為例:

上圖參考自博客 Raw WebGPU,
而能創建這兩個特定 GPU 計算的“通道編碼器”的,叫做“指令編碼器”,也就是 GPUCommandEncoder:

指令編碼器除了承載上面兩個通道編碼器的編碼結果外,還額外提供了資源的拷貝行為、查詢行為的編碼,例如紋理與緩沖物件之間的互相拷貝等:

在實際的代碼中,是按 GPUCommandEncoder 呼叫某個方法的順序進行記錄的,例如 beginRenderPass()、copyBufferToTexture() 等,
佇列與指令緩沖
指令編碼器的 finish 方法回傳一個指令緩沖物件,即 GPUCommandBuffer,這個可以提交給佇列物件 GPUQueue,佇列物件是設備物件上的一個實體欄位,
排列在佇列上的除了指令緩沖,還有佇列自己發出的“佇列時間線”上的行為,例如寫入緩沖資料、寫入紋理資料等,圖示如下:

2. 重要機制
① 緩沖映射機制
緩沖映射,簡單的說就是使得記憶體、顯存中的緩沖資料可以交換著用的一種機制,詳細的文章可以參考:
# WebGPU 中的緩沖映射機制
② 時間線
WebGPU 規范中不同的行為也許發生在的層面是不一樣的,每個層面在運作的程序中都有它自己的時間線,規范給出了三條時間線:
-
內容時間線:內容時間線上的行為,大多數是 JavaScript 物件的創建、JavaScript 方法的呼叫,這是最上面的一層;
-
設備時間線:此“設備”非
GPUDevice;設備時間線上的行為,大多數是指瀏覽器底層 WebGPU 實作中的變化,這類行為的層級低于 JavaScript 的執行,操作的是“內部物件”,卻還沒到 GPU 執行的部分,例如生成指令緩沖; -
佇列時間線:此“佇列”非
GPUQueue;佇列時間線上發生的行為,通常就是指 GPU 中具體任務的執行,例如繪制、資源上載、資源復制、通用計算調度等,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/500760.html
標籤:其他
