LoginSignup
82
43

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-12-13

この記事はWeb グラフィックス Advent Calendar 2020の13日目の記事です。
※ 2023年1月7日、内容が古くなっていた為、記事の内容を最新化しました。
  変更箇所は「主な変更内容(2020年12月~2022年12月)」を参照ください。

はじめに

WebGPUWebGLWebGL2 の後継とされているグラフィックス API です。
今のところリリース時期としては 2023年5月 Chrome 113 を目標 としているようです。
API の仕様は固まりつつありますが、まだ仕様変更等が入る可能性があることにご注意ください。

ここでは、主に、シェーダ機能「WGSL」(WebGPU Shading Language)について試してみようと思います。

2019年時点は WebGPU 用のシェーダの方針が決まっていなかった為、glslang と呼ばれるライブラリを用いて GLSLSPIR-V(シェーダ中間言語)にコンパイルして使用する形が取られていました。
現在は、Chrome Canary で新しいシェーダ WGSL が実験的に使えるようになってきた為、それを使ってみようと思います。

WGSL とは

WGSLWebGPU Shading Language の略で、その名の通り WebGPU 用のシェーダです。
GLSL では C 言語ライクな構文でしたが WGSL では Rust に似た構文が採用されています。
2019年から2022年にかけて何回か構文の仕様変更がありました。
以下のサンプルコードは現時点の最新の仕様の内容で更新しています。

GLSL と WGSL のコードの比較

箇所 GLSL WGSL
VSコード image.png image.png
FSコード image.png image.png

WebGPU の有効化方法

WebGPU は Chrome CanaryFirefox Nightly でフラグを有効化することで試すことができます。
ただし 2023年1月時点では、WGSLChrome Canary でしか動作しないようです。
なお、これらの機能は、実験的な機能である為、有効化したまま、信頼できない 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 コード

以下、シェーダに WGSL を使用した場合のサンプルコードです。動作確認は Chrome Canary 111 で行いました。他の環境では動作しない可能性がありますのでご注意ください。

index.html
<canvas id="c" width="512" height="512"></canvas>

<script id="vs" type="x-shader/x-vertex">
struct VertexOutput {
    @builtin(position) Position : vec4<f32>
}

@vertex
fn main(
    @location(0) position : vec3<f32>
) -> VertexOutput {
    var output : VertexOutput;
    output.Position = vec4<f32>(position, 1.0);
    return output;
}
</script>

<script id="fs" type="x-shader/x-fragment">
@fragment
fn main() -> @location(0) vec4<f32> {
    return vec4<f32>(0.0, 0.0, 1.0, 1.0);
}
</script>
index.js
const vs = document.getElementById("vs").textContent;
const fs = document.getElementById("fs").textContent;
init();

async function init() {
    const gpu = navigator["gpu"];
    const adapter = await gpu.requestAdapter();
    const device = await adapter.requestDevice();

    const c = document.getElementById("c");
    c.width = window.innerWidth;
    c.height = window.innerHeight;
    const ctx = c.getContext("webgpu");
    const format = gpu.getPreferredCanvasFormat();
    ctx.configure({
        device: device,
        format: format,
        alphaMode: "opaque"
    });
    const pipeline = device.createRenderPipeline({
        layout: "auto",
        vertex: {
            module: device.createShaderModule({
                code: vs
            }),
            entryPoint: "main",
            buffers: [
                {
                    arrayStride: 3 * 4,
                    attributes: [
                        {
                            // position
                            shaderLocation: 0,
                            offset: 0,
                            format: "float32x3"
                        }
                    ]
                }
            ]
        },
        fragment: {
            module: device.createShaderModule({
                code:  fs
            }),
            entryPoint: "main",
            targets: [
                {
                    format: format
                }
            ]
        },
        primitive: {
            topology: "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
    ];
	  let vertexBuffer = makeVertexBuffer(device, new Float32Array(positions));

    const render =  function () {
        const commandEncoder = device.createCommandEncoder();
        const textureView = ctx.getCurrentTexture().createView();
        const renderPassDescriptor = {
            colorAttachments: [{
                view: textureView,
                loadOp: "clear",
                clearValue: {r: 1, g: 1, b: 1, a: 1},
                storeOp: "store"
            }]
        };
        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(pipeline);
        passEncoder.setVertexBuffer(0, vertexBuffer);
        passEncoder.draw(3, 1, 0, 0);
        passEncoder.end();
        device.queue.submit([commandEncoder.finish()]);
    }
    requestAnimationFrame(render);
}

function makeVertexBuffer(device, data) {
    const verticesBuffer = device.createBuffer({
        size: data.byteLength,
        usage: GPUBufferUsage.VERTEX,
        mappedAtCreation: true,
    });
    new Float32Array(verticesBuffer.getMappedRange()).set(data);
    verticesBuffer.unmap();
    return verticesBuffer;
}

実際に動作するコードは jsfiddle にアップしてありますので、ご利用ください。 ※ 実行には WebGPU 対応ブラウザが必要です。

[jsfiddle] 100行で WebGPU 試してみるテスト(GLSL版)
[jsfiddle] 100行で WebGPU 試してみるテスト(WGSL版)

主な変更内容(2020年12月~2022年12月)

■ WGSL 部分
変更内容:
GLSL で言うところの attribute 属性はシェーダの main 関数の引き数で受け取れるようになりました。
struct(構造体)が指定できるようになりました。
・属性を指定する書き方が Java のアノテーションのような書き方になりました。
 例)

変更前 変更後
[[location(0)]] @location(0)
[[builtin(position)]] @builtin(position)
[[stage(vertex)]] @vertex
[[stage(fragment)]] @fragment

image.png

■ WebGPU API 部分
変更内容:
・コンテキストを取得する際の名称が gpupresentwebgpu に変更されました。
・ピクセルフォーマットが gpu.getPreferredCanvasFormat() で取得できるようになりました。
・Render Pipline の設定で colorStates: の指定が不要になりました。
・Render Pass の colorAttachments: の指定方法が変更になりました。loadOpe / storeOp の指定が必要になりました。
・冗長だった名称が簡素化されました。
 例)

変更前 変更後
vertexStage: vertex:
fragmentStage: fragment:
colorStates: 廃止
vertexBuffers: buffers:
primitiveTopology: primitive:
frontFrace: primitive: に移動
cullMode: primitive: に移動
context.configureSwapChain() context.configure()
passEncoder.endPass() passEncoder.end()
device.defaultQueue device.queue

image.png

その他のサンプル

手前味噌ですが、いくつかの基本的なサンプルについて GLSL 版と WGSL 版のサンプルを下記に置いてありますので興味ある方はどうぞ。

image.png

おわりに

何故シェーダ言語が GLSL ベースではなく Rust ベースの新しい言語になったかは、少し闇が深そうなので、触れないでおきます。。
API やシェーダの仕様は固まりつつあるので、WebGPU の新しいシェーダ言語 WGSL に今のうちから慣れておくのも良いのではないでしょうか。

参考情報

■ WebGPU 仕様
https://www.w3.org/TR/webgpu/
■ WGSL 仕様
https://www.w3.org/TR/WGSL/
■ WebGPU Samples (WebGPU サンプル)
https://github.com/austinEng/webgpu-samples
■ WebGPU - Chrome Developers(開発ステータス)
https://developer.chrome.com/docs/web-platform/webgpu/
■ Raw WebGPU (WebGPU のチュートリアル)
https://alain.xyz/blog/raw-webgpu

82
43
0

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
82
43