天天看點

WebGPU[1] 三角形

代碼見: https://github.com/onsummer/my-dev-notes/tree/master/webgpu-Notes/01-triangle

如果本篇的代碼不能跑了,請聯系我或自己看看文檔試試修改。

釋出日:2021年3月31日;更新日 2021年5月6日

入口:navigator.gpu

WebGL1 或 WebGL2 是從

canvas.getContext('webgl')

這樣擷取一個上下文對象來進行一切操作的。

而 WebGPU 則直接從浏覽器對象中擷取一個附件一樣的東西:

navigator.gpu

,如果你在控制台擷取這玩意兒擷取不到,說明你沒開實驗特性或浏覽器壓根不支援 WebGPU。

這就很像從主機裡掏出一張顯示卡一樣。

擴充卡 & 裝置

代碼最開始要從這個 gpu 對象裡請求一個“擴充卡(adapter,是 er 不是 or)”,然後從擴充卡裡請求一個“裝置(device)”。

const adapter = await navigator.gpu.requestAdapter()
const device = await adapter.requestDevice()
           

擴充卡,指的是實體顯示卡。聰明的你一定能猜到,除了N卡,還有高通骁龍上面的 SoC 圖形處理器,是以這個

requestAdapter()

是可以傳遞參數的。

裝置,即把實體顯示卡進行邏輯對象化。當調用

requestDevice()

時,允許請求一些顯示卡的擴充特性,就像 WebGL 會通過請求擴充來引入額外的功能一樣。

後續代碼中,大量的操作均以函數調用的方式,由裝置發出。這個裝置對象,就類似 WebGL 的 context。差別的地方,就是“裝置”它是顯示卡功能的集合體,更接近顯示卡本身,而 context 隻是與顯示卡對話的中間人,是一個上下文對象。

休息一下!

交換鍊、渲染管線、WebGPU上下文、編碼器

不了解 WebGPU 的渲染流程,就沒辦法進行下一步的。

關于交換鍊的描述,這裡 講得比我好。

  • canvas -建立-> WebGPU上下文 -建立-> 交換鍊 -建立-> canvas像素記憶體視圖(視圖用于操作像素記憶體,輔助交換鍊的交換工作,将顯示卡上渲染好的資料寫入像素記憶體)
  • 裝置 -建立-> “指令”編碼器 -建立-> “渲染通道”編碼器

渲染進行時,渲染通道編碼器 會将 “渲染通道描述對象”,通過 交換鍊 繪制到 canvas 上。

編碼器如何通路canvas?通過

this.swapChain.getCurrentTexture().createView()

建立出來的對象來通路 canvas 上的像素記憶體,把顯存裡繪制好的資料寫入 canvas 上的像素記憶體。

const textureView = swapChain.getCurrentTexture().createView()
const renderPassDescriptor = {
  colorAttachments: [{
    attachment: textureView,
    loadValue: {
      r: 0.0, g: 0.0, b: 0.0, a: 1.0
    }
  }]
}
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor)
           

“指令”編碼器通過傳遞進來的參數與交換鍊上的 canvas 像素記憶體作綁定,傳回一個 “渲染通道”編碼器,“渲染通道”編碼器則通過設定渲染管線、調用函數來完成繪制。

passEncoder.setPipeline(pipeline)
passEncoder.draw(3, 1, 0, 0)
           

最後,結束此渲染通道,結束指令編碼器,所有指令扔給裝置。

passEncoder.endPass()
device.queue.submit([commandEncoder.finish()])
           

提示

若要進行代碼結構優化,不想堆屎山,恭喜你,一個未來未來的架構師正在發芽,目前進度:建立檔案夾。

優化的第一步,先把以下三個對象提升生命周期:

  • 擴充卡
  • 裝置
  • 交換鍊

渲染一次(幀)所需的着色器、編碼器、管線和資料可能不盡一樣,但是以上仨貨基本不變。

現在頂點資料寫死在着色器中,後續會從記憶體中讀取并傳入,類似 WebGLBuffer。

大緻結構可布局如下:

const adapter = // 請求擴充卡
const gtx4090ti = // 請求裝置
const swapChain = // 從canvas上下文中擷取交換鍊

// 經典的 rAF
function requestNewFrame() {
  const pipeline = // 建立管線
  const cmdEncoder = // 建立指令編碼器
  const passEncoder = // 建立渲染通道編碼器
  /*
    passEncoder 設定繪制指令
  */
  // 搞定了目前幀的所有準備,送出給裝置

  requestAnimationFrame(requestNewFrame)
}

requestNewFrame()
           

或者使用異步的寫法

async function init(/*參數*/) {
  const adapter = await ...// 請求擴充卡
	const gtx4090ti = await ...// 請求裝置
	const swapChain = // 從canvas上下文中擷取交換鍊
  
  const requestNewFrame = () => {
    /*
     建立管線、編碼器,使用編碼器進行繪制
    */
    requestAnimationFrame(requestNewFrame)
  }
  return requestNewFrame
}

init(/*傳參*/).then(requestFn => {
  requestFn()
})