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>

參考

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容