andersch.dev

<2025-04-26 Sat>

WebGPU

WebGPU is a web API for GPU computation and graphics rendering. It provides low-level access to a GPU similar to modern graphics APIs like Vulkan, Metal, and DirectX 12. It uses WGSL (WebGPU Shading Language) as its shading language.

It offers better performance and more advanced GPU features than WebGL.

Code Example

<!DOCTYPE html>
<html>
<head>
  <title>WebGPU Triangle</title>
  <style> canvas {width: 100%; height: 400px; background-color: #000;} </style>
</head>
<body>
  <canvas id="canvas"></canvas>
  <div id="status">Checking WebGPU...</div>

  <script type="module">
    async function init() {
      // 1. Check if WebGPU is supported
      if (!navigator.gpu) {
        document.getElementById("status").textContent = "WebGPU not supported!";
        return;
      }

      // 2. Get the GPU adapter
      const adapter = await navigator.gpu.requestAdapter();
      if (!adapter) {
        document.getElementById("status").textContent = "Couldn't request WebGPU adapter!";
        return;
      }

      // 3. Create the device
      const device = await adapter.requestDevice();
      if (!device) {
        document.getElementById("status").textContent = "Couldn't request WebGPU device!";
        return;
      }

      // 4. Configure canvas
      const canvas           = document.getElementById("canvas");
      const context          = canvas.getContext("webgpu");
      const canvasFormat     = navigator.gpu.getPreferredCanvasFormat();
      const devicePixelRatio = window.devicePixelRatio || 1;
      canvas.width           = canvas.clientWidth * devicePixelRatio;
      canvas.height          = canvas.clientHeight * devicePixelRatio;

      context.configure({
        device: device,
        format: canvasFormat,
        alphaMode: "premultiplied"
      });

      // 5. Create the vertex buffer (triangle vertices)
      const vertices = new Float32Array([
        0.0,  0.5, 0.0, // top
       -0.5, -0.5, 0.0, // bottom left
        0.5, -0.5, 0.0  // bottom right
      ]);

      const vertexBuffer = device.createBuffer({
        label: "Triangle vertices",
        size: vertices.byteLength,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
      });

      device.queue.writeBuffer(vertexBuffer, 0, vertices);

      // 6. Define vertex layout
      const vertexLayout = {
        arrayStride: 12, // 3 floats, 4 bytes each
        attributes: [{
          format: "float32x3",
          offset: 0,
          shaderLocation: 0 // position
        }]
      };

      // 7. Create the shader module
      const shaderModule = device.createShaderModule({
        label: "Triangle shader",
        code: `
          @vertex
          fn vertexMain(@location(0) position: vec3f) -> @builtin(position) vec4f {
            return vec4f(position, 1.0);
          }

          @fragment
          fn fragmentMain() -> @location(0) vec4f {
            return vec4f(1.0, 0.5, 0.2, 1.0); // Orange color
          }
        `
      });

      // 8. Create render pipeline
      const pipeline = device.createRenderPipeline({
        label: "Triangle render pipeline",
        layout: "auto",
        vertex: {
          module: shaderModule,
          entryPoint: "vertexMain",
          buffers: [vertexLayout]
        },
        fragment: {
          module: shaderModule,
          entryPoint: "fragmentMain",
          targets: [{
            format: canvasFormat
          }]
        }
      });

      // 9. Draw function
      function draw() {
        // Create command encoder
        const commandEncoder = device.createCommandEncoder();

        // Create render pass
        const renderPassDescriptor = {
          colorAttachments: [{
            view: context.getCurrentTexture().createView(),
            loadOp: "clear",
            storeOp: "store",
            clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }
          }]
        };

        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(pipeline);
        passEncoder.setVertexBuffer(0, vertexBuffer);
        passEncoder.draw(3); // Draw 3 vertices
        passEncoder.end();

        // Submit the commands
        device.queue.submit([commandEncoder.finish()]);
      }

      // Initial draw and set up resize handling
      draw();
      window.addEventListener("resize", () => {
        canvas.width = canvas.clientWidth * devicePixelRatio;
        canvas.height = canvas.clientHeight * devicePixelRatio;
        draw();
      });

      document.getElementById("status").textContent = "WebGPU triangle rendered successfully!";
    }

    init();
  </script>
</body>
</html>
cat << 'EOF' > "index.html"
<!DOCTYPE html>
<html>
<head>
  <title>WebGPU Triangle</title>
  <style> canvas {width: 100%; height: 400px; background-color: #000;} </style>
</head>
<body>
  <canvas id="canvas"></canvas>
  <div id="status">Checking WebGPU...</div>

  <script type="module">
    async function init() {
      // 1. Check if WebGPU is supported
      if (!navigator.gpu) {
        document.getElementById("status").textContent = "WebGPU not supported!";
        return;
      }

      // 2. Get the GPU adapter
      const adapter = await navigator.gpu.requestAdapter();
      if (!adapter) {
        document.getElementById("status").textContent = "Couldn't request WebGPU adapter!";
        return;
      }

      // 3. Create the device
      const device = await adapter.requestDevice();
      if (!device) {
        document.getElementById("status").textContent = "Couldn't request WebGPU device!";
        return;
      }

      // 4. Configure canvas
      const canvas           = document.getElementById("canvas");
      const context          = canvas.getContext("webgpu");
      const canvasFormat     = navigator.gpu.getPreferredCanvasFormat();
      const devicePixelRatio = window.devicePixelRatio || 1;
      canvas.width           = canvas.clientWidth * devicePixelRatio;
      canvas.height          = canvas.clientHeight * devicePixelRatio;

      context.configure({
        device: device,
        format: canvasFormat,
        alphaMode: "premultiplied"
      });

      // 5. Create the vertex buffer (triangle vertices)
      const vertices = new Float32Array([
        0.0,  0.5, 0.0, // top
       -0.5, -0.5, 0.0, // bottom left
        0.5, -0.5, 0.0  // bottom right
      ]);

      const vertexBuffer = device.createBuffer({
        label: "Triangle vertices",
        size: vertices.byteLength,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
      });

      device.queue.writeBuffer(vertexBuffer, 0, vertices);

      // 6. Define vertex layout
      const vertexLayout = {
        arrayStride: 12, // 3 floats, 4 bytes each
        attributes: [{
          format: "float32x3",
          offset: 0,
          shaderLocation: 0 // position
        }]
      };

      // 7. Create the shader module
      const shaderModule = device.createShaderModule({
        label: "Triangle shader",
        code: `
          @vertex
          fn vertexMain(@location(0) position: vec3f) -> @builtin(position) vec4f {
            return vec4f(position, 1.0);
          }

          @fragment
          fn fragmentMain() -> @location(0) vec4f {
            return vec4f(1.0, 0.5, 0.2, 1.0); // Orange color
          }
        `
      });

      // 8. Create render pipeline
      const pipeline = device.createRenderPipeline({
        label: "Triangle render pipeline",
        layout: "auto",
        vertex: {
          module: shaderModule,
          entryPoint: "vertexMain",
          buffers: [vertexLayout]
        },
        fragment: {
          module: shaderModule,
          entryPoint: "fragmentMain",
          targets: [{
            format: canvasFormat
          }]
        }
      });

      // 9. Draw function
      function draw() {
        // Create command encoder
        const commandEncoder = device.createCommandEncoder();

        // Create render pass
        const renderPassDescriptor = {
          colorAttachments: [{
            view: context.getCurrentTexture().createView(),
            loadOp: "clear",
            storeOp: "store",
            clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }
          }]
        };

        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(pipeline);
        passEncoder.setVertexBuffer(0, vertexBuffer);
        passEncoder.draw(3); // Draw 3 vertices
        passEncoder.end();

        // Submit the commands
        device.queue.submit([commandEncoder.finish()]);
      }

      // Initial draw and set up resize handling
      draw();
      window.addEventListener("resize", () => {
        canvas.width = canvas.clientWidth * devicePixelRatio;
        canvas.height = canvas.clientHeight * devicePixelRatio;
        draw();
      });

      document.getElementById("status").textContent = "WebGPU triangle rendered successfully!";
    }

    init();
  </script>
</body>
</html>
EOF
trap "rm index.html" EXIT

python -m http.server

# $BROWSER -new-tab localhost:8080