[忙しい人向け] 100行から始めるWebGPU（GLSL版）

この記事は[WebGL Advent Calendar 2019](http://qiita.com/advent-calendar/2019/webgl)の1日目の記事です。

内容が古くなっているので、最新は[こちら（WGSL版）](https://qiita.com/cx20/items/1f2dcfe0e7ac60fc8ce9)を参照ください。

### はじめに

`WebGPU` って何なの？長いコード見てる時間ないよ、という忙しい人向けに、贅肉をそぎ落とした簡易版のコードを書いてみました。
ここ最近、Windows 版の `Chrome Canary` でも `WebGPU` が試せるようになってきたので興味がある方はこの機会にお試しください。

### WebGPU とは

`WebGPU` は `WebGL` や `WebGL2` の後継とされているグラフィックス API です。
`WebGL` は Khronos グループが中心となって仕様策定が行われていましたが `WebGPU` は W3C コミュニティグループにより行われています。
`WebGPU` は、より低レベルな GPU アクセスを目的とした API の為、`WebGL` に比べパフォーマンスの向上が期待されています。

|ブラウザのAPI|対応するバックエンドのAPI|利用可能なシェーダ|仕様策定|
|:------------|:-------------------------|:-----------------|:-------|
|WebGL 1.0    |OpenGL ES 2.0             |GLSL ES 1.0       |Khronos |
|WebGL 2.0    |OpenGL ES 3.0             |GLSL ES 3.0       |Khronos |
|WebGPU       |Vulkan/DirectX/Metal      |SPIR-V/WHLSL (※) |W3C     |

※ WebGPU のシェーダは未決定な部分が多く今後変更が入る可能性がある為、ご注意ください。

### SPIR-V / WHLSL  とは

`SPIR-V` は Khronos グループが提案する計算/グラフィックス用のシェーダ中間言語です。`GLSL` や `HLSL` をコンパイルした際の中間形式（バイナリ）になります。
`Chrome` や `Firefox` では WebGPU のシェーダとして `SPIR-V` を採用しています。
`WHLSL` は Apple が提案するシェーダ言語です。

### WebGPU の有効化方法

WebGPU は `Chrome Canary` や `Safari Technology Preview` などで有効化することで試すことができます。
これらの機能は、実験的な機能である為、有効化したまま、信頼できない Web へのアクセスは行わないでください。

|ブラウザ                                                                           |対応するバックエンドのライブラリ          | WebGPU 有効化方法                         |
|:----------------------------------------------------------------------------------|:-----------------------------------------|:------------------------------------------|
|[Chrome Canary](https://www.google.com/chrome/canary/)                             |[Dawn](https://dawn.googlesource.com/dawn)|`chrome://flags/#enable-unsafe-web`        |
|[Firefox Nightly](https://www.mozilla.org/firefox/nightly)                         |[gfx-rs](https://github.com/gfx-rs/gfx)   |`dom.webgpu.enable`                        |
|[Safari Technology Preview](https://developer.apple.com/safari/technology-preview/)|[webkit](https://webkit.org/)             |Develop -> Experimental Features -> WebGPU |

### ポリゴン１枚を描画する WebGPU コード

お待たせしました。ようやくコードです。
とりあえずポリゴン１枚を描画する `WebGPU` コードとなっています。`WebGL` に比べて2～3倍のコード量でしょうか。

ちなみに、現状、このサンプルは `Chrome Canary` でしか動作しないのでご注意ください。 

```javascript:index.js
const ready = glslang();
ready.then(init);
const vs = `#version 450
layout(location = 0) in vec3 position;
void main() {
    gl_Position = vec4(position, 1.0);
}`;
const fs = `#version 450
layout(location = 0) out vec4 outColor;
void main() {
    outColor = vec4(0.0, 0.0, 1.0, 1.0);
}`;

async function init(glslang) {
    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    const c = document.getElementById('c');
    const ctx = c.getContext('gpupresent');
    const swapChainFormat = "bgra8unorm";
    const swapChain = ctx.configureSwapChain({device, format: swapChainFormat});
    const pipeline = device.createRenderPipeline({
        layout: device.createPipelineLayout({bindGroupLayouts: []}),
        vertexStage: {
            module: device.createShaderModule({
                code: glslang.compileGLSL(vs, 'vertex'),
                source: vs
            }),
            entryPoint: 'main'
        },
        fragmentStage: {
            module: device.createShaderModule({
                code: glslang.compileGLSL(fs, 'fragment'),
                source: fs
            }),
            entryPoint: 'main'
        },
        vertexState: {
            indexFormat: 'uint32',
            vertexBuffers: [{
                arrayStride: 12, // 3 * 4bytes
                attributes: [{
                    shaderLocation: 0,
                    offset: 0,
                    format: "float3"
                }]
            }]
        },
        colorStates: [{
            format: swapChainFormat,
            alphaBlend: {
                srcFactor: "src-alpha",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
            }
        }],
        primitiveTopology: 'triangle-list',
    });

    const positions = [ 
         0.0, 0.5, 0.0, // v0
        -0.5,-0.5, 0.0, // v1
         0.5,-0.5, 0.0  // v2
    ];
    const data = new Float32Array(positions);
    const vertexBuffer = device.createBuffer({
        size: data.byteLength,
        usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
    });
    vertexBuffer.setSubData(0, data);

	const render =  function () {
        const commandEncoder = device.createCommandEncoder();
        const textureView = swapChain.getCurrentTexture().createView();
        const renderPassDescriptor = {
            colorAttachments: [{
                attachment: textureView,
                loadValue: {r: 1.0, g: 1.0, b: 1.0, a: 1.0},
            }]
        };
        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setVertexBuffer(0, vertexBuffer);
        passEncoder.setPipeline(pipeline);
        passEncoder.draw(3, 1, 0, 0);
        passEncoder.endPass();
        device.defaultQueue.submit([commandEncoder.finish()]);
    }
    requestAnimationFrame(render);
}
```

### コードのダウンロード

実際に動作するコードは jsfiddle にアップしてありますので、ご利用ください。
[[jsfiddle] 100行で WebGPU 試してみるテスト](http://jsfiddle.net/cx20/g1wmxL46/39/) ※ 実行には WebGPU 対応ブラウザが必要です。

参考までに、WebGL の最小サンプルは、下記を参照ください。
[[Qiita] 30行で WebGL を試してみるテスト](https://qiita.com/cx20/items/0597cdbccdd370e05956) 

### あとがき

`WebGPU` は、ようやく少し試せるようになってきましたが、まだまだ実験段階で、昨日書いたサンプルが今日動かないということも多々ある状況です。安定性重視な方は、もう少し待った方が良いかと思います。
セキュリティ面やシェーダ周りの仕様など、課題が山積しているようなので、通常のブラウザ環境で利用できるようになるには、あと1～2年はかかるのではないでしょうか。
`WebGPU` の動向が知りたい方は、以下の Wiki が更新されているので参照されると良いと思います。

■ Implementation Status
https://github.com/gpuweb/gpuweb/wiki/Implementation-Status