- 原文地址:Next-generation 3D Graphics on the Web
- 原文做者:Dean Jackson
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:reid3290
- 校對者:leviding,H2O-2
今天,蘋果 WebKit 團隊提議在 W3C 成立一個新的社區羣組(Community Group)來討論 Web 端三維圖形的將來和開發一款支持現代 GPU 特性(包括底層圖像處理和通用計算)的標準 API。W3C 社區容許你們自由參與進來,並且咱們也誠邀瀏覽器開發商、GPU 硬件提供商、軟件開發者和 Web 社區加入咱們。html
權當拋磚引玉,咱們分享了一個 API 提案和一個針對 WebKit 開源項目的 API 原型。咱們但願這是一個有益的開始,並期待隨着社區討論的進行 API 會不斷髮展進化。前端
更新:如今有一個實現和演示 WebGPU 的 demo。react
讓咱們來看看咱們成立這個社區羣組的來龍去脈,以及這個新組與現有 Web 圖形 API(如 WebGL)的關係。android
有一段時間,基於 Web 標準的技術能夠生成具備靜態內容的頁面,而其中惟一的圖形則是嵌入的圖片。不久以後,Web 開始增長更多開發人員能夠經過 JavaScript 訪問的功能。最終,咱們須要一個徹底可編程的圖形 API,以使腳本能夠實時建立圖像。所以,「canvas」 元素及其相關的 2D 渲染 API 誕生於 WebKit,隨後迅速普及到其餘瀏覽器引擎中,而且很快標準化了。ios
隨着時間的推移,Web 應用程序和內容漸趨豐富和複雜,並開始觸及平臺的瓶頸。以遊戲爲例,其性能和視覺質量相當重要。在瀏覽器中開發遊戲的需求是有的,但大多數遊戲使用的是 GPU 提供的 3D 圖形 API。Mozilla 和 Opera 公佈了一些從 「canvas」 元素中暴露出 3D 渲染上下文的實驗,其結果很是具備吸引力,所以社區決定一塊兒將你們均可以實現的內容進行標準化。git
全部的瀏覽器引擎協做建立了 WebGL,這是在 Web 上渲染 3D 圖形的標準。它基於 OpenGL ES —— 一種面向嵌入式系統的跨平臺圖形 API。這個起點是正確的,由於它能夠輕鬆地在全部瀏覽器中實現相同的 API,並且大多數瀏覽器引擎都在支持 OpenGL 的系統上運行。即便系統沒有直接支持 OpenGL,像 ANGLE 這樣的項目也能夠在其餘技術之上進行仿真,畢竟這種 API 的抽象級別是很高的。隨着 OpenGL 的發展,WebGL 也能夠跟着發展。github
WebGL 已經在開放平臺上賦予了開發人員圖形處理器的功能,全部主流瀏覽器都支持 WebGL 1,使得能夠在 Web 上開發出高質量的遊戲(console-quality games),也促進了 three.js 等第三方庫的蓬勃發展。此後,該標準發展成爲 WebGL 2,包括 WebKit 在內的全部主流瀏覽器引擎都承諾對它提供支持。web
在 WebGL 發展的同時,GPU 技術也在發展進步,並且已經建立了新的軟件 API,可以更好地反映現代 GPU 的設計特性。這些新 API 的抽象級別比較低,而且因爲其下降了開銷,一般來講比 OpenGL 的性能更好。該領域的主要技術平臺有微軟的 Direct3D 十二、蘋果的 Metal 和 Khronos Group 的 Vulkan。雖然這些技術的設計理念都是類似的,但惋惜的是沒有一項技術是跨平臺可用的。編程
那麼這對 Web 意味着什麼呢?從充分利用 GPU 的角度來說,這些新技術無疑是將來的發展方向。Web 平臺想要成功必須定義一種容許多個系統上實現的通用標準,而如今已經有幾個在架構上稍有差異的圖形 API 了。要開發一款能夠加速圖形和計算的現代化底層技術,必須設計一個能夠在多種系統(包括上面提到的那些系統)上實現的 API。隨着圖形技術的蓬勃發展,繼續遵循像 OpenGL 這樣的某個特定 API 標準顯然是不可行的。canvas
相反,咱們須要評估和設計一個新的 Web 標準:它可以提供一組核心功能,以及一個支持多種系統圖形技術和平臺的 API,此外還要保障 Web 所要求的保密性和安全性。
再者,咱們還須要考慮如何在圖形處理以外使用 GPU,以及新標準如何與其餘 Web 技術協同工做。該標準應該暴露現代 GPU 的通用計算功能。其設計架構應符合 Web 的既定模式以便開發和使用。它須要可以與其餘重要的新興 Web 標準(如 WebAssembly 和 WebVR)協同工做。最重要的是,這個標準的制定應該是一個開放的過程,容許行業專家和更普遍的網絡社區參與。
W3C 爲這種狀況提供了社區羣組平臺。「Web 端的 GPU」 社區羣組現已開放會員註冊。
幾年前咱們就預估了下一代圖形 API 的發展狀況,並着手在 WebKit 中設計原型以驗證咱們能夠將很是低級別的 GPU API 暴露給 Web 同時還能夠得到有價值的性能提高。咱們獲得了一些很是鼓舞人心的實驗結果,因此咱們將原型分享給了 W3C 社區羣組。咱們也準備將代碼部署到 WebKit 中,因此你很快就能夠本身去嘗試了。咱們並不奢望這一 API 自己能成爲最後的標準,社區也有可能根本就不會從它入手,可是咱們認爲編寫代碼的工做自己是頗有價值的。其餘瀏覽器引擎也已經開發了相似的原型。與社區合做併爲計算機圖形提出一個偉大的新技術想必是一件十分使人激動的事情。
下文將詳細闡述咱們的實驗,咱們將它稱爲 「WebGPU」。
不出意料,WebGPU 的接口是經過 「canvas」 元素來訪問的。
let canvas = document.querySelector("canvas");
let gpu = canvas.getContext("webgpu");複製代碼
WebGPU 比 WebGL 要更加面向對象化,事實上這也是性能提高的原因之一。WebGPU 容許你建立和存儲表示狀態的對象和能夠處理一組命令的對象,而無需在每次繪製操做以前設置狀態。這樣,咱們能夠在狀態建立時就執行一些驗證工做,從而減小繪圖時的工做量。
WebGPU 上下文暴露了圖形命令和並行計算命令。假設須要繪製一些圖形,這須要用到圖形管道。圖形管道中最重要的元素是着色器(shaders),它們是在 GPU 上運行用以處理幾何數據併爲每一個像素的繪製提供顏色的程序。着色器一般用專門用於圖形的編程語言進行編寫。
決定 Web API 使用何種着色語言是件有趣的事情,由於有不少因素須要考慮。咱們須要一種功能強大的語言,要求編程儘可能簡單、能序列化爲可高效傳輸的格式,並要求能夠由瀏覽器進行驗證以確保着色器的安全性。業內有部分人傾向於使用能夠從許多源格式生成的着色器表示,這有點相似於彙編語言。同時,在「查看源代碼」方面 Web 可謂發展迅速,對人而言代碼的可讀性仍是很重要的。咱們指望關於着色語言的討論成爲標準化過程當中最有趣的部分之一,咱們也十分願意聽取社區的意見。
就 WebGPU 原型而言,咱們決定暫不考慮着色語言的問題,而是直接採用一種現存的語言。由於咱們當時的工做是創建在蘋果的平臺上的,因此咱們選擇了Metal Shading Language。那接下來的問題就是如何將着色器加載到 WebGPU 了。
let library = gpu.createLibrary( /* 源代碼 */ );
let vertexFunction = library.functionWithName("vertex_main");
let fragmentFunction = library.functionWithName("fragment_main");複製代碼
咱們使用 gpu
對象從源代碼加載並編譯着色器,生成一個 WebGPULibrary
。着色器代碼自己並不重要 —— 其實就是一個很是簡單的頂點(vertex)和片斷(fragment)的組合。一個 WebGPULibrary
能夠容納多個着色器函數,所以咱們經過函數名稱取出將要在管道中用到的相應函數。
如今咱們就能夠建立管道了。
// 管道的一些細節。
let pipelineDescriptor = new WebGPURenderPipelineDescriptor();
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.colorAttachments[0].pixelFormat = "BGRA8Unorm";
let pipelineState = gpu.createRenderPipelineState(pipelineDescriptor);複製代碼
傳入所需描述信息(包括使用的頂點、片斷着色器以及圖像格式)便可從上下文中獲得一個新的 WebGPURenderPipelineState
對象。
繪圖操做要求使用緩衝區向渲染管道提供數據,例如幾何座標、顏色、法向量等等,而 WebGPUBuffer
則是容納這些數據的對象。
let vertexData = new Float32Array([ /* some data */ ]);
let vertexBuffer = gpu.createBuffer(vertexData);複製代碼
此例中,咱們有一個 Float32Array
,它包含了須要在幾何圖形中繪製的每一個頂點的數據。咱們從 Float32Array
建立一個 WebGPUBuffer
,該緩衝區會在以後的繪圖操做中用到。
諸如此類的頂點數據不多發生變化,但也有些數據是幾乎每次繪製時都會發生變化的。像這種不變的數據被稱爲 uniforms。表示相機位置的當前變換矩陣便是 uniform 的一個很常見的例子。WebGPUBuffer
也可用於 uniform,但此處咱們但願在建立以後將其寫入緩衝區。
// 將 "buffer" 看做是一個以前分配好的 WebGPUBuffer。
// buffer.contents 暴露一個 ArrayBufferView,咱們將其
// 解析爲一個 32 位的浮點數數組。
let uniforms = new Float32Array(buffer.contents);
// 設置所需 uniform。
uniforms[42] = Math.PI;複製代碼
這樣作的好處之一是 JavaScript 開發人員能夠將 ArrayBufferView 封裝在帶有自定義 getter 和 setter 的類或代理對象(Proxy object)中,這樣外部接口看起來像典型的 JavasScript 對象同樣。而後,包裝器對象會更新緩衝區正在使用的底層數組中的相應部分。
在通知 WebGPU 上下文繪圖以前還須要設置一些狀態,這包括渲染的目標位置(最終將在 canvas
中顯示的 WebGPUTexture
)以及紋理(texture)初始化和使用狀況的描述信息。這些狀態存儲在 WebGPURenderPassDescriptor
中。
// 從上下文獲取下一幀所指望的紋理信息。
let drawable = gpu.nextDrawable();
let passDescriptor = new WebGPURenderPassDescriptor();
passDescriptor.colorAttachments[0].loadAction = "clear";
passDescriptor.colorAttachments[0].storeAction = "store";
passDescriptor.colorAttachments[0].clearColor = [0.8, 0.8, 0.8, 1.0];
passDescriptor.colorAttachments[0].texture = drawable.texture;複製代碼
首先,咱們向 WebGPU 上下文請求一個表示下一可繪幀的對象,此對象最終會被複制到 canvas 元素中去。完成繪圖代碼後,咱們要通知 WebGPU 以便其顯示繪圖結果並準備下一個可繪幀。
從初始化 WebGPURenderPassDescriptor
的代碼中能夠看出,咱們不會在繪圖操做正在進行的時候從紋理中讀取信息(由於 loadAction
的值是 clear
),而是在繪圖操做完成以後才使用該紋理(由於 storeAction
的值是 store
),此外代碼還指定了紋理的填充顏色。
接下來,咱們建立用於保存實際繪製操做的對象。一個 WebGPUCommandQueue
有一組 WebGPUCommandBuffers
。咱們使用 WebGPUCommandEncoder
將操做推送到 WebGPUCommandBuffer
中去。
let commandQueue = gpu.createCommandQueue();
let commandBuffer = commandQueue.createCommandBuffer();
// 使用以前建立的描述符。
let commandEncoder = commandBuffer.createRenderCommandEncoderWithDescriptor(
passDescriptor);
// 告知編碼器使用何種狀態(例如:着色器)。
commandEncoder.setRenderPipelineState(pipelineState);
// 最後,編碼器還須要知道使用哪一個緩衝區。
commandEncoder.setVertexBuffer(vertexBuffer, 0, 0);複製代碼
至此,咱們已經設置好了一個渲染管道,其中包含若干着色器、一個用於保存幾何信息的緩衝區、一個用於保存繪製操做的隊列以及一個能夠提交到該隊列的編碼器。如今只需將實際繪圖命令推入編碼器便可。
// 咱們知道咱們的緩衝區有 3 個頂點,
// 咱們但願繪製出一個填充的三角形。
commandEncoder.drawPrimitives("triangle", 0, 3);
commandEncoder.endEncoding();
// 全部繪圖命令已經提交。通知 WebGPU
// 一旦隊列處理完畢即刻顯示 canvas 中的繪圖結果。
commandBuffer.presentDrawable(drawable);
commandBuffer.commit();複製代碼
像大多數 3D 圖形的示例代碼同樣,繪製一個簡單的形狀看起來要寫不少代碼,但其實並不是如此。這些現代 API 有一個優勢 —— 其大部分代碼都是在建立能夠重用以繪製其餘內容的對象。例如,通常渲染上下文只須要一個 WebGPUCommandQueue
實例,又者能夠爲不一樣的着色器提早建立多個 WebGPURenderPipelineState
對象。此外,瀏覽器還能夠在前期進行不少驗證工做,從而減小繪圖操做過程當中的開銷。
但願本文可讓你對 WebGPU 提案有一個大體瞭解。儘管由 W3C 社區羣組最終肯定的 API 可能同此提案有很大不一樣,但咱們相信不少通常的設計原則都是通用的。
蘋果的 WebKit 團隊已經建議爲 Web 端 GPU 創建一個 W3C 社區羣組做爲工做論壇,同時也請你加入咱們一塊兒定義 GPU 的下一代標準。咱們的建議獲得了其餘瀏覽器引擎開發商、GPU 供應商、框架開發人員等業內同仁的積極迴應。在行業的支持下,咱們誠邀全部對 Web GPU 感興趣或有專長的人加入社區羣組。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。