LoginSignup
24

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-30

この記事はWebGL Advent Calendar 2019の1日目の記事です。

内容が古くなっているので、最新はこちら(WGSL版)を参照ください。

はじめに

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

WebGPU とは

WebGPUWebGLWebGL2 の後継とされているグラフィックス 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 グループが提案する計算/グラフィックス用のシェーダ中間言語です。GLSLHLSL をコンパイルした際の中間形式(バイナリ)になります。
ChromeFirefox では WebGPU のシェーダとして SPIR-V を採用しています。
WHLSL は Apple が提案するシェーダ言語です。

WebGPU の有効化方法

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

ブラウザ 対応するバックエンドのライブラリ WebGPU 有効化方法
Chrome Canary Dawn chrome://flags/#enable-unsafe-web
Firefox Nightly gfx-rs dom.webgpu.enable
Safari Technology Preview webkit Develop -> Experimental Features -> WebGPU

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

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

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

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 試してみるテスト ※ 実行には WebGPU 対応ブラウザが必要です。

参考までに、WebGL の最小サンプルは、下記を参照ください。
[Qiita] 30行で WebGL を試してみるテスト

あとがき

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24