WebGL から WebGPU へ変更してスピードアップ

はじめに

Web アプリを作って スピードアップしたい場合に 有効な手段です

準備

設定の変更 アドレスに入力

chrome://flags/#enable-unsafe-webgpu 
edge://flags/#enable-unsafe-webgpu

サーバーを立ち上げて起動する

call D:\WinPython\scripts\env_for_icons.bat  %*
Start python -m http.server 8000
"C:\Program Files\Google\Chrome\Application\chrome.exe" http://localhost:8000/WebGPU.html

説明


WebGPU が動かないときのチェックリスト

  1. ブラウザの対応状況を確認
    • WebGPU は 最新の Google Chrome(バージョン 113 以降)Microsoft Edge(バージョン 113 以降) でサポート。
    • Firefox や Safari ではまだ正式対応していないことが多いから注意!
  2. WebGPU が有効になっているか確認
    • Chrome のアドレスバーに chrome://flags/#enable-unsafe-webgpu と入力して、WebGPUEnabled に設定。
    • その後、ブラウザを再起動
  3. HTTPS でアクセスしてる?
    • WebGPU は HTTPS または localhost でしか動かない。
    • ファイルを直接開く(file://)だと動かないから、ローカルサーバーを使って。
    例えば、Python を使って簡単にローカルサーバーを立てるなら: python -m http.server その後、ブラウザで http://localhost:8000 にアクセス!
  4. GPU が対応しているか確認
    • 一部の古い GPU や仮想環境では WebGPU が使えないこともある。
    • navigator.gpuundefined になっていたら、対応していない可能性がある。

デバッグ用コード追加

WebGPU が使えるかどうかをチェックするコードを追加:

if (!navigator.gpu) {
  alert("このブラウザは WebGPU をサポートしていません。最新の Chrome または Edge を使用してください。");
  return;
}

ソース

WebGL vs WebGPU 12色比較

WebGL vs WebGPU:12色三角形描画&時間比較

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>WebGL vs WebGPU 12色比較</title>
  <style>
    canvas { border: 1px solid #ccc; margin: 10px; }
    button { margin: 5px; }
  </style>
</head>
<body>
  <h2>WebGL vs WebGPU:12色三角形描画&時間比較</h2>
  <button onclick="runWebGL()">WebGLで描画</button>
  <button onclick="runWebGPU()">WebGPUで描画</button>
  <p id="result"></p>

  <canvas id="canvas-webgl" width="400" height="400" style="display:none;"></canvas>
  <canvas id="canvas-webgpu" width="400" height="400" style="display:none;"></canvas>

  <script>
    function showCanvas(idToShow) {
      document.getElementById("canvas-webgl").style.display = "none";
      document.getElementById("canvas-webgpu").style.display = "none";
      document.getElementById(idToShow).style.display = "block";
    }

    function runWebGL() {
      showCanvas("canvas-webgl");
      drawWebGL();
    }

    function runWebGPU() {
      showCanvas("canvas-webgpu");
      drawWebGPU();
    }

    function drawWebGL() {
      const canvas = document.getElementById("canvas-webgl");
      const gl = canvas.getContext("webgl");
      if (!gl) {
        alert("WebGL がサポートされていません");
        return;
      }

      const start = performance.now();

      const vert = `
        attribute vec2 position;
        uniform float angle;
        void main() {
          float rad = radians(angle);
          mat2 rot = mat2(cos(rad), -sin(rad), sin(rad), cos(rad));
          gl_Position = vec4(rot * position, 0, 1);
        }
      `;
      const frag = `
        precision mediump float;
        uniform vec3 color;
        void main() {
          gl_FragColor = vec4(color, 1.0);
        }
      `;

      function compile(type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        return shader;
      }

      const vs = compile(gl.VERTEX_SHADER, vert);
      const fs = compile(gl.FRAGMENT_SHADER, frag);
      const program = gl.createProgram();
      gl.attachShader(program, vs);
      gl.attachShader(program, fs);
      gl.linkProgram(program);
      gl.useProgram(program);

      const vertices = new Float32Array([
        0, 0.5,
        -0.2, -0.3,
        0.2, -0.3
      ]);
      const buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

      const loc = gl.getAttribLocation(program, "position");
      gl.enableVertexAttribArray(loc);
      gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);

      const angleLoc = gl.getUniformLocation(program, "angle");
      const colorLoc = gl.getUniformLocation(program, "color");

      gl.clearColor(1, 1, 1, 1);
      gl.clear(gl.COLOR_BUFFER_BIT);

      for (let i = 0; i < 12; i++) {
        const angle = i * 30;
        const hue = i / 12;
        const rgb = hsvToRgb(hue, 1, 1);
        gl.uniform1f(angleLoc, angle);
        gl.uniform3fv(colorLoc, rgb);
        gl.drawArrays(gl.TRIANGLES, 0, 3);
      }

      const end = performance.now();
      document.getElementById("result").textContent = `WebGL: ${(end - start).toFixed(2)} ms`;
    }

    async function drawWebGPU() {
      if (!navigator.gpu) {
        alert("このブラウザは WebGPU をサポートしていません。最新の Chrome または Edge を使用してください。");
        return;
      }

      const canvas = document.getElementById("canvas-webgpu");
      const adapter = await navigator.gpu.requestAdapter();
      const device = await adapter.requestDevice();
      const context = canvas.getContext("webgpu");

      const format = navigator.gpu.getPreferredCanvasFormat();
      context.configure({
        device: device,
        format: format,
        alphaMode: "opaque"
      });

      const start = performance.now();

      const vertices = [];
      const colors = [];
      for (let i = 0; i < 12; i++) {
        const angle = (i * 30) * Math.PI / 180;
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);
        const base = [
          [0, 0.5],
          [-0.2, -0.3],
          [0.2, -0.3]
        ];
        const hue = i / 12;
        const rgb = hsvToRgb(hue, 1, 1);
        for (const [x, y] of base) {
          vertices.push(
            x * cos - y * sin,
            x * sin + y * cos
          );
          colors.push(...rgb);
        }
      }

      const vertexBuffer = device.createBuffer({
        size: vertices.length * 4,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
      });
      device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(vertices));

      const colorBuffer = device.createBuffer({
        size: colors.length * 4,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
      });
      device.queue.writeBuffer(colorBuffer, 0, new Float32Array(colors));

      const shaderModule = device.createShaderModule({
        code: `
          struct VertexOutput {
            @builtin(position) position: vec4f,
            @location(0) color: vec3f
          };

          @vertex
          fn vs_main(@location(0) position: vec2f, @location(1) color: vec3f) -> VertexOutput {
            var out: VertexOutput;
            out.position = vec4f(position, 0.0, 1.0);
            out.color = color;
            return out;
          }

          @fragment
          fn fs_main(@location(0) color: vec3f) -> @location(0) vec4f {
            return vec4f(color, 1.0);
          }
        `
      });

      const pipeline = device.createRenderPipeline({
        layout: "auto",
        vertex: {
          module: shaderModule,
          entryPoint: "vs_main",
          buffers: [
            {
              arrayStride: 8,
              attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }]
            },
            {
              arrayStride: 12,
              attributes: [{ shaderLocation: 1, offset: 0, format: "float32x3" }]
            }
          ]
        },
        fragment: {
          module: shaderModule,
          entryPoint: "fs_main",
          targets: [{ format }]
        },
        primitive: { topology: "triangle-list" }
      });

      const commandEncoder = device.createCommandEncoder();
      const textureView = context.getCurrentTexture().createView();
      const renderPass = commandEncoder.beginRenderPass({
        colorAttachments: [{
          view: textureView,
          loadOp: "clear",
          clearValue: { r: 1, g: 1, b: 1, a: 1 },
          storeOp: "store"
        }]
      });

      renderPass.setPipeline(pipeline);
      renderPass.setVertexBuffer(0, vertexBuffer);
      renderPass.setVertexBuffer(1, colorBuffer);
      renderPass.draw(12 * 3);
      renderPass.end();

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

      const end = performance.now();
      document.getElementById("result").textContent = `WebGPU: ${(end - start).toFixed(2)} ms`;
    }

    function hsvToRgb(h, s, v) {
      let r, g, b;
      let i = Math.floor(h * 6);
      let f = h * 6 - i;
      let p = v * (1 - s);
      let q = v * (1 - f * s);
      let t = v * (1 - (1 - f) * s);
      switch (i % 6) {
        case 0: r = v; g = t; b = p; break;
        case 1: r = q; g = v; b = p; break;
        case 2: r = p; g = v; b = t; break;
        case 3: r = p; g = q; b = v; break;
        case 4: r = t; g = p; b = v; break;
        case 5: r = v; g = p; b = q; break;
      }
      return [r, g, b];
    }
  </script>
</body>
</html>

JavaScript

Posted by eightban