WebGPU入門(一)--認識 WebGPU

1. WebGpu的前輩WebGL

要說起webGPU就不得不提起一下他的前輩webgl.這樣才能更好知道WebGPU 取代 WebGL 為什么是大勢所趨钉蒲。

?說到 WebGL执解,就不得不說說 OpenGL。在早期的個人電腦中,使用最廣泛的 3D 圖形渲染技術是 Direct3D 和 OpenGL。Direct3D 是微軟 DirectX 技術的一部分荣挨,主要用于 Windows 平臺。 OpenGL 作為一種開源的跨平臺技術朴摊,贏得了眾多開發(fā)者的青睞默垄。

? 后來一個特殊的版本——OpenGL ES,它專為嵌入式計算機甚纲、智能手機口锭、家用游戲機和其他設備而設計。它從 OpenGL 中刪除了許多舊的和無用的功能,同時添加了新功能鹃操。例如去掉了矩形等多余的多邊形韭寸,只保留點、線荆隘、三角形等基本圖形恩伺。這使它在保持輕巧的同時仍然足夠強大以渲染精美的 3D 圖形。

? 而 WebGL 是從 OpenGL ES 派生出來的椰拒,它專注于 Web 的 3D 圖形渲染晶渠。
下圖展示了它們之間的關系:


image.png

WebGL 歷史

image.png

?從上圖可以看出,WebGL 已經很老了燃观。不僅因為它存在已久褒脯,還因為它的標準是從 OpenGL 繼承而來的。OpenGL 的設計理念可以追溯到 1992 年仪壮,而這些古老的理念其實與今天 GPU 的工作原理非常不符。

? 對于瀏覽器開發(fā)者來說胳徽,需要適配 GPU 的不同特性积锅,這給他們帶來了很多不便。雖然這些對于上層開發(fā)人員來說是看不到的养盗。

?從上圖可以看出缚陷,2014 年蘋果發(fā)布了 Metal。 Steve Jobs 是 OpenGL ES 的支持者往核,他認為這是行業(yè)的未來箫爷。所以當時蘋果設備上的游戲都依賴 OpenGL ES(比如憤怒的小鳥,水果忍者)聂儒,都是很經典的游戲虎锚。

?但喬布斯去世后,蘋果放棄了 OpenGL ES衩婚,開發(fā)了新的圖形框架 Metal窜护。

?微軟在 2015 年也發(fā)布了自己的 D3D12【Direct3D 12】圖形框架。緊隨其后的是 Khronos Group非春,圖形界的國際組織柱徙,類似于前端圈子里的 W3C、TC39奇昙。而 WebGL 是它的標準护侮。甚至也逐漸淡化了 WebGL,轉而支持現在的 Vulkan储耐。

?迄今為止羊初,Metal、D3D12 [Direct3D 12] 和 Vulkan 并列為現代三大圖形框架什湘。這些框架充分釋放了 GPU 的可編程能力凳忙,讓開發(fā)者可以最大限度的自由控制 GPU业踏。

?同樣重要的是要注意,當今的主流操作系統(tǒng)不再支持 OpenGL 作為主要支持涧卵。這意味著今天編寫的每一行 WebGL 代碼都有 90% 的機會不被 OpenGL 繪制勤家。它在 Windows 計算機上使用 DirectX 繪制,在 Mac 計算機上使用 Metal 繪制柳恐。

?可見 OpenGL 已經過期了伐脖。但這并不意味著它會消失。繼續(xù)在嵌入式乐设、科學研究等特殊領域發(fā)揮作用讼庇。

?WebGL 也是如此,大量的適配工作使得推進困難重重近尚,于是推出了 WebGPU蠕啄。

2. WebGPU介紹

WebGPU 是由 W3C GPU for the Web 社區(qū)組所發(fā)布的規(guī)范,目標是允許網頁代碼以高性能且安全可靠的方式訪問 GPU 功能戈锻。

  • WebGPU API 使 web 開發(fā)人員能夠使用底層系統(tǒng)的 GPU(圖形處理器)進行高性能計算并繪制可在瀏覽器中渲染的復雜圖形歼跟。
  • WebGPU 是 WebGL 的繼任者,為現代 GPU 提供更好的兼容格遭、支持更通用的 GPU 計算哈街、更快的操作以及能夠訪問到更高級的 GPU 特性。
  • 封裝了現代圖形API(Dx12拒迅、Vulkan骚秦、Metal),提供給Web 3D程序員璧微,為 Web釋放了更多的GPU 硬件的功能作箍。

3. WebGPU架構原理

image.png

WebGPU 使用Adapter來實現從操作系統(tǒng)的本機圖形 API 到 WebGPU 的轉換層。對于使用者是通過LogicalDevice來實現對GPU的抽象前硫,由于瀏覽器是可以運行多個 Web 應用程序的單一 OS 級應用程序蒙揣,因此需要多路復用,以便每個 Web 應用程序感覺就像它擁有對 GPU 的唯一控制權开瞭。這就是邏輯設備抽象的作用懒震。

4、著色器

使用過WebGL的都知道嗤详,我們要告訴GPU需要繪制什么是通過著色器和片段著色器个扰,需要你將數據緩沖區(qū)上傳到 GPU,并告訴它如何將該數據解釋為一系列三角形葱色。每個頂點占據該數據緩沖區(qū)的一塊递宅,描述該頂點在 3D 空間中的位置,但可能還包括顏色、紋理 ID办龄、法線和其他內容等輔助數據烘绽。列表中的每個頂點都由 GPU 在頂點階段處理,在每個頂點上運行頂點著色器俐填,這將應用平移安接、旋轉或透視變形。

著色器: “著色器”這個詞曾經讓我感到困惑英融,因為你可以做的不僅僅是著色盏檐。但在過去(即 1980 年代后期!)驶悟,這個術語是恰當的:它是在 GPU 上運行的一小段代碼胡野,用于決定每個像素應該是什么顏色,這樣你就可以正在渲染的對象進行著色痕鳍,實現燈光和陰影的錯覺硫豆。如今,著色器泛指在 GPU 上運行的任何程序笼呆。

GPU 現在對三角形進行光柵化熊响,這意味著 GPU 會計算出每個三角形在屏幕上覆蓋的像素。然后每個像素由片段著色器處理抄邀,它可以訪問像素坐標耘眨,也可以訪問輔助數據來決定該像素應該是哪種顏色昼榛。如果使用得當境肾,可以使用此過程創(chuàng)建令人驚嘆的 3D 圖形。

這種將數據傳遞到頂點著色器胆屿,然后到片段著色器奥喻,然后將其直接輸出到屏幕上的系統(tǒng)稱為管道,在 WebGPU 中非迹,你必須明確定義管道环鲤。

5. 管線

管線(pipeline)是一個邏輯結構,其包含在你完成程序工作的可編程階段憎兽。WebGPU 目前能夠處理兩種類型的管線:

  • 渲染管線用于渲染圖形冷离,通常渲染到 <canvas> 元素中,但它也可以在畫面之外的地方渲染圖形纯命。它有兩個主要階段:

    • 頂點著色階段:在該階段中西剥,頂點著色器(vertex shader)接受 GPU 輸入的位置數據并使用像旋轉、平移或透視等特定的效果將頂點在 3D 空間中定位亿汞。然后瞭空,這些頂點會被組裝成基本的渲染圖元,例如三角形等,然后通過 GPU 進行光柵化咆畏,計算出每個頂點應該覆蓋在 canvas 上的哪些像素南捂。
    • 片元著色階段:在該階段中,片元著色器(fragment shader)計算由頂點著色器生成的基本圖元所覆蓋的每個像素的顏色旧找。這些計算通常使用輸入溺健,如圖像(以紋理的方式)提供表面細節(jié)以及虛擬化光源的位置和顏色。
  • 計算管線用于通用計算钦讳。計算管線包含單獨的計算階段矿瘦,在該階段中,計算著色器(compute shader)接受通用的數據愿卒,在指定數量的工作組之間并行處理數據缚去,然后將結果返回到一個或者多個緩沖區(qū)。這些緩沖區(qū)可以包含任意類型的數據琼开。

image.png

6.命令編碼器

命令編碼器(mmand encoder),的意思是說你可以把你想讓 GPU 執(zhí)行所有的命令都壓到命令編碼器里面易结,讓命令編碼器把所有的這些命令編在一起,然后存成命令緩存柜候,然后再發(fā)送到 GPU 里面去執(zhí)行搞动。這樣設計的好處就是把數據編碼這種cpu擅長的事情交給cpu處理,這樣可以減少和GPU的通信開銷渣刷。提高效率鹦肿。

7.WebGPU作流程和代碼實踐

有了上面這些知識點,就可以來看看WebGPU的一個工作流程辅柴。

image.png

根據這個流程箩溃,我們就可以上手來寫一個簡單的demo,通過編碼來體會它的工作機制碌嘀。直接上代碼涣旨。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas></canvas>
    <script type="module">
      const triangleVert = `
        @vertex
        fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
            var pos = array<vec2<f32>, 3>(
                vec2<f32>(0.0, 0.5),
                vec2<f32>(-0.5, -0.5),
                vec2<f32>(0.5, -0.5)
            );
            return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
        }
      `;
      const redFrag = `
        @fragment
        fn main() -> @location(0) vec4<f32> {
            return vec4<f32>(1.0, 0.0, 0.0, 1.0);
        }
       `;
      // initialize webgpu device & config canvas context
      async function initWebGPU(canvas) {
        if (!navigator.gpu) throw new Error("Not Support WebGPU");
        const adapter = await navigator.gpu.requestAdapter({
          powerPreference: "high-performance",
          // powerPreference: 'low-power'
        });
        if (!adapter) throw new Error("No Adapter Found");

        const device = await adapter.requestDevice({
          requiredFeatures: ["texture-compression-bc"],
          requiredLimits: {
            maxStorageBufferBindingSize:
              adapter.limits.maxStorageBufferBindingSize,
          },
        });

        // 獲取canvas
        const context = canvas.getContext("webgpu");

        // 獲取瀏覽器默認的顏色格式
        const format = navigator.gpu.getPreferredCanvasFormat();

        const devicePixelRatio = window.devicePixelRatio || 1;
        canvas.width = canvas.clientWidth * devicePixelRatio;
        canvas.height = canvas.clientHeight * devicePixelRatio;
        const size = { width: canvas.width, height: canvas.height };
        context.configure({
          // json specific format when key and value are the same
          device,
          format,
          // prevent chrome warning
          alphaMode: "opaque",
        });
        return { device, context, format, size };
      }

      // create a simple pipiline
      async function initPipeline(device, format) {
        const descriptor = {
          layout: "auto",
          vertex: {
            module: device.createShaderModule({
              code: triangleVert,
            }),
            entryPoint: "main",
          },
          primitive: {
            topology: "triangle-list", // try point-list, line-list, line-strip, triangle-strip?
          },
          fragment: {
            module: device.createShaderModule({
              code: redFrag,
            }),
            entryPoint: "main",
            targets: [
              {
                format: format,
              },
            ],
          },
        };
        return await device.createRenderPipelineAsync(descriptor);
      }
      // create & submit device commands
      function draw(device, context, pipeline) {
        const commandEncoder = device.createCommandEncoder();
        const view = context.getCurrentTexture().createView();
        const renderPassDescriptor = {
          colorAttachments: [
            {
              view: view,
              clearValue: { r: 0, g: 0, b: 0, a: 1.0 },
              loadOp: "clear", // clear/load
              storeOp: "store", // store/discard
            },
          ],
        };
        const passEncoder =
          commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(pipeline);
        // 3 vertex form a triangle
        passEncoder.draw(3);
        passEncoder.end();
        // webgpu run in a separate process, all the commands will be executed after submit
        device.queue.submit([commandEncoder.finish()]);
      }

      async function run() {
        const canvas = document.querySelector("canvas");
        if (!canvas) throw new Error("No Canvas");
        const { device, context, format } = await initWebGPU(canvas);
        const pipeline = await initPipeline(device, format);
        // start draw
        draw(device, context, pipeline);

        // re-configure context on resize
        window.addEventListener("resize", () => {
          canvas.width = canvas.clientWidth * devicePixelRatio;
          canvas.height = canvas.clientHeight * devicePixelRatio;
          // don't need to recall context.configure() after v104
          draw(device, context, pipeline);
        });
      }
      run();
    </script>
    <style>
      html,
      body {
        margin: 0;
        width: 100%;
        height: 100%;
        background: #000;
        color: #fff;
        display: flex;
        text-align: center;
        flex-direction: column;
        justify-content: center;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </body>
</html>

參考

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市股冗,隨后出現的幾起案子霹陡,更是在濱河造成了極大的恐慌,老刑警劉巖止状,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烹棉,死亡現場離奇詭異,居然都是意外死亡怯疤,警方通過查閱死者的電腦和手機浆洗,發(fā)現死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旅薄,“玉大人辅髓,你說我怎么就攤上這事泣崩。” “怎么了洛口?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵矫付,是天一觀的道長。 經常有香客問我第焰,道長买优,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任挺举,我火速辦了婚禮杀赢,結果婚禮上,老公的妹妹穿的比我還像新娘湘纵。我一直安慰自己脂崔,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布梧喷。 她就那樣靜靜地躺著砌左,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铺敌。 梳的紋絲不亂的頭發(fā)上汇歹,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音偿凭,去河邊找鬼产弹。 笑死,一個胖子當著我的面吹牛弯囊,可吹牛的內容都是我干的痰哨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼常挚,長吁一口氣:“原來是場噩夢啊……” “哼作谭!你這毒婦竟也來了稽物?” 一聲冷哼從身側響起奄毡,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贝或,沒想到半個月后吼过,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡咪奖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年盗忱,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羊赵。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡趟佃,死狀恐怖扇谣,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情闲昭,我是刑警寧澤罐寨,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站序矩,受9級特大地震影響鸯绿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜簸淀,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一瓶蝴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧租幕,春花似錦舷手、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至珠叔,卻和暖如春蝎宇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祷安。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工姥芥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汇鞭。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓凉唐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親霍骄。 傳聞我的和親對象是個殘疾皇子台囱,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容