Miłosz Orzeł

.net, js, html, arduino, java... no rants or clickbaits.

WebGPU/WGSL Hello Triangle!

INTRO

WebGPU is and ongoing effort to make browsers even more powerful platforms. When vendors finish their work (2021/2012?), web apps will be able to leverage the awesome power of modern GPU to render graphics and do general-purpose computing through dedicated API!

High-end games, computer vision, CAD, video processing, machine learning... all done in open, secure and cross-platform way of the Web. Nice. 

Oh, and if you are wondering what's the reason for creating WebGPU while WebGL exists, please check these two articles:

 

HELLO TRIANGLE

After a bit of digging, I've noticed that some examples posted online no longer work (that's expected), use shading language other than WGSL or may be a bit too complex for a newcomer... So, I've created my own Hello Triangle example (based on austinEng work) that uses just one HTML file and one JS file.

Breaking changes are likely as the specs evolve, so please check the repo for updates.

The code renders properly and runs without warnings in Chrome Canary v91.0.4457.2 x64 Win 10 with --enable-unsafe-webgpu flag set. Tested on 2021-03-25.

Here are the aforementioned files:

index.html

  
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>WebGPU/WGSL Hello Triangle!</title>
</head>

<body>
    <canvas id="canvas" width="400" height="400"></canvas>   
    <script src="script.js"></script>
</body>

</html>

script.js

(async () => {
    if (!navigator.gpu) {
        alert('Your browser does not support WebGPU or it is not enabled. More info: https://webgpu.io');
        return;
    }

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

    const canvas = document.getElementById('canvas')
    const context = canvas.getContext('gpupresent');

    const swapChainFormat = 'bgra8unorm';

    const swapChain = context.configureSwapChain({
        device,
        format: swapChainFormat
    });

    const vertexShaderWgslCode =
        `
        const pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
        vec2<f32>(0.0, 0.5),
        vec2<f32>(-0.5, -0.5),
        vec2<f32>(0.5, -0.5));
      
        [[builtin(position)]] var<out> Position : vec4<f32>;
        [[builtin(vertex_idx)]] var<in> VertexIndex : i32;
      
        [[stage(vertex)]]
        fn main() -> void {
            Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
            return;
        }
    `;

    const fragmentShaderWgslCode =
        `
        [[location(0)]] var<out> outColor : vec4<f32>;
      
        [[stage(fragment)]]
        fn main() -> void {
            outColor = vec4<f32>(0.0, 1.0, 0.0, 1.0);
            return;
        }
    `;

    const pipeline = device.createRenderPipeline({
        vertex: {
            module: device.createShaderModule({
                code: vertexShaderWgslCode
            }),
            entryPoint: 'main'
        },
        fragment: {
            module: device.createShaderModule({
                code: fragmentShaderWgslCode
            }),
            entryPoint: 'main',
            targets: [{
                format: swapChainFormat,
            }]
        },
        primitive: {
            topology: 'triangle-list',
        }
    });

    const commandEncoder = device.createCommandEncoder();
    const textureView = swapChain.getCurrentTexture().createView();

    const renderPassDescriptor = {
        colorAttachments: [{
            attachment: textureView,
            loadValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
        }]
    };

    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.setPipeline(pipeline);
    passEncoder.draw(3, 1, 0, 0);
    passEncoder.endPass();

    device.queue.submit([commandEncoder.finish()]);
})();

The code uses WebGPU with WebGPU Shading Language to render a green triangle on a gray background inside a 400x400 px canvas. 

All this stuff to get a triangle? Don't worry :) Remember that WebGPU is a low-level API. Your favorite libraries like tree.js or babylon.js will use it internally.

 

WHY WGSL?

There has been a bit of controversy surrounding the choice of creating WGSL instead of going for widely supported GLSL... You might want to check this issue and this comment in particular (from one of the main contributors to WebGPU) to see the rationale.