Vala プログラミング

WebGPU プログラミング

おなが@京都先端科学大

Babylon.js WebGPU Compute Shader ( Chrome Canary )

Babylon.js のWebGPUで Compute Shader が使えるようになりました。
( v5.0.0-alpha で動作します。)

Compute Shaders | Babylon.js Documentation
https://doc.babylonjs.com/advanced_topics/shaders/computeShader
f:id:onagat12:20210522185026j:plain:w400:h180

この画面からSimple compute shaders example を実行しようとすると、
以下の createStorageBuffer に関するエラーが出て実行できません。
f:id:onagat12:20210522184113p:plain:w400:h160

下のサイトを参考にして、実行できるようにトライしてみました。
・Babylon.js で WebGPU を試してみるテスト - Qiita
 https://qiita.com/cx20/items/e46bd22f0d9e47e81df1
・Babylon.jsでWebGPU試してみる : IZUN∀ TEC - livedoor
 http://blog.livedoor.jp/mizuki_izuna/archives/24967180.html

Playground 画面からプログラムをコピーし、html の script 部分にそまま
貼り付けて実行しました。
実行結果
f:id:onagat12:20210522185522j:plain
f:id:onagat12:20210522185617j:plain
(下: マウスで移動した様子)

コンソール画面
f:id:onagat12:20210522185740j:plain
・compute shader の計算結果が表示されています。
・使用しているライブラリのバージョン( v5.0.0-aplha.22 )も表示されます。

babylon.js 等のライブラリは、preview 版を 使用しています。
f:id:onagat12:20210522185823j:plain:w400:h190
(Babylon.js https://github.com/BabylonJS/Babylon.js の readme)

shader には WGSL が使用できます。

プログラム
Simple_compute_shaders.html

<!DOCTYPE html>
<html>
<head>
    <title>Babylon.js WebGPU Simple Compute Shaders(playground)</title>
    <script src="https://preview.babylonjs.com/babylon.max.js"></script>
    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
</head>
<body>
<canvas id="renderCanvas" width="800" height="650"></canvas>

<script>
async function init() {
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.WebGPUEngine(canvas);
    await engine.initAsync();

    // from playground
    var createScene = function () {
        // This creates a basic Babylon Scene object (non-mesh)
        var scene = new BABYLON.Scene(engine);

        // This creates and positions a free camera (non-mesh)
        var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);

        // This targets the camera to scene origin
        camera.setTarget(BABYLON.Vector3.Zero());

        // This attaches the camera to the canvas
        camera.attachControl(canvas, true);

        // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
        var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

        // Default intensity is 1. Let's dim the light a small amount
        light.intensity = 0.7;

        // Our built-in 'sphere' shape.
        var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);

        // Move the sphere upward 1/2 its height
        sphere.position.y = 1;

        // Our built-in 'ground' shape.
        var ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);

        // -------- COMPUTE 1 -------------------------
        //
        const cs1 = new BABYLON.ComputeShader("myCompute", engine, { computeSource: copyTextureComputeShader }, { bindingsMapping:
            {
                "dest": { group: 0, binding: 0 },
                "src": { group: 0, binding: 2 }
            }
        });

        const src = new BABYLON.Texture("textures/ground.jpg", scene);
        const dest = new BABYLON.RawTexture.CreateRGBAStorageTexture(null, 512, 512, scene, false, false);

        cs1.setTexture("src", src);
        cs1.setStorageTexture("dest", dest);

        cs1.dispatchWhenReady(dest.getSize().width, dest.getSize().height, 1).then(() => {
            dest.readPixels().then((data) => {
                //console.log(data);
            });
        });

        const mat = new BABYLON.StandardMaterial("mat", scene);
        mat.diffuseTexture = dest;

        ground.material = mat;

        // -------- COMPUTE 2 -------------------------
        //
        const cs2 = new BABYLON.ComputeShader("myCompute2", engine, { computeSource: clearTextureComputeShader }, { bindingsMapping:
            {
                "tbuf": { group: 0, binding: 0 },
                "params": { group: 0, binding: 1 }
            }
        });

        const dest2 = new BABYLON.RawTexture.CreateRGBAStorageTexture(null, 512, 512, scene, false, false);

        const uBuffer = new BABYLON.UniformBuffer(engine);

        uBuffer.updateColor4("color", new BABYLON.Color3(1, 0.6, 0.8), 1);
        uBuffer.update();

        cs2.setStorageTexture("tbuf", dest2);
        cs2.setUniformBuffer("params", uBuffer);

        cs2.dispatchWhenReady(dest2.getSize().width, dest2.getSize().height, 1);

        const mat2 = new BABYLON.StandardMaterial("mat2", scene);
        mat2.diffuseTexture = dest2;

        sphere.material = mat2;

        // -------- COMPUTE 3 -------------------------
        //
        const cs3 = new BABYLON.ComputeShader("myCompute3", engine, { computeSource: matrixMulComputeShader }, { bindingsMapping:
            {
                "firstMatrix": { group: 0, binding: 0 },
                "secondMatrix": { group: 0, binding: 1 },
                "resultMatrix": { group: 0, binding: 2 }
            }
        });

        const firstMatrix = new Float32Array([
            2 /* rows */, 4 /* columns */,
            1, 2, 3, 4,
            5, 6, 7, 8
        ]);

        const bufferFirstMatrix = new BABYLON.StorageBuffer(engine, firstMatrix.byteLength);
        bufferFirstMatrix.update(firstMatrix);

        const secondMatrix = new Float32Array([
            4 /* rows */, 2 /* columns */,
            1, 2,
            3, 4,
            5, 6,
            7, 8
        ]);

        const bufferSecondMatrix = new BABYLON.StorageBuffer(engine, secondMatrix.byteLength);
        bufferSecondMatrix.update(secondMatrix);

        const bufferResultMatrix = new BABYLON.StorageBuffer(engine, Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1]));

        cs3.setStorageBuffer("firstMatrix", bufferFirstMatrix);
        cs3.setStorageBuffer("secondMatrix", bufferSecondMatrix);
        cs3.setStorageBuffer("resultMatrix", bufferResultMatrix);

        cs3.dispatchWhenReady(firstMatrix[0], secondMatrix[1]).then(() => {
            bufferResultMatrix.read().then((res) => {
                // we know the result buffer contains floats
                const resFloats = new Float32Array(res.buffer);
                console.log(resFloats);
            });
        });

        return scene;
    };

    const clearTextureComputeShader = `
    [[group(0), binding(0)]] var tbuf : [[access(write)]] texture_storage_2d<rgba8unorm>;

    [[block]] struct Params {
        color : vec4<f32>;
    };
    [[group(0), binding(1)]] var<uniform> params : Params;

    [[stage(compute), workgroup_size(1, 1, 1)]]

    fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) {
        textureStore(tbuf, vec2<i32>(global_id.xy), params.color);
    }
    `;

    const copyTextureComputeShader = `
    [[group(0), binding(0)]] var dest : [[access(write)]] texture_storage_2d<rgba8unorm>;
    [[group(0), binding(1)]] var samplerSrc : sampler;
    [[group(0), binding(2)]] var src : texture_2d<f32>;

    [[stage(compute), workgroup_size(1, 1, 1)]]

    fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) {
        let dims : vec2<f32> = vec2<f32>(textureDimensions(src, 0));
        let pix : vec4<f32> = textureSampleLevel(src, samplerSrc, vec2<f32>(global_id.xy) / dims, 0.0);
        textureStore(dest, vec2<i32>(global_id.xy), pix);
    }
    `;

    const matrixMulComputeShader = `
    [[block]] struct Matrix {
      size : vec2<f32>;
      numbers: array<f32>;
    };

    [[group(0), binding(0)]] var<storage> firstMatrix : [[access(read)]] Matrix;
    [[group(0), binding(1)]] var<storage> secondMatrix : [[access(read)]] Matrix;
    [[group(0), binding(2)]] var<storage> resultMatrix : [[access(write)]] Matrix;

    [[stage(compute)]] fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) {
        resultMatrix.size = vec2<f32>(firstMatrix.size.x, secondMatrix.size.y);

        let resultCell : vec2<u32> = vec2<u32>(global_id.x, global_id.y);
        var result : f32 = 0.0;
        for (var i : u32 = 0u; i < u32(firstMatrix.size.y); i = i + 1u) {
            let a : u32 = i + resultCell.x * u32(firstMatrix.size.y);
            let b : u32 = resultCell.y + i * u32(secondMatrix.size.y);
            result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b];
        }

        let index : u32 = resultCell.y + resultCell.x * u32(secondMatrix.size.y);
        resultMatrix.numbers[index] = result;
    }
    `;

    const scene = createScene();
    engine.runRenderLoop(() => {
        scene.render();
    });
};

init();
</script>
</body>
</html>