前端工程

WebGPU Compute Shader:見落とされたGPU計算の切り札

WebGPUと聞いて多くの人がまず思うのは「WebGLの代替」です。しかし、WebGPUを単なる次世代グラフィックスAPIとして使うだけでは、大きな見落としがあります——Compute ShaderこそがWebGPUの真の切り札です。

WebGPUのCompute Shaderは、ブラウザに初めてネイティブなGPGPU能力をもたらしました。これは漸進的な改善ではなく、パラダイムシフトです。


WGSLコア構文

WebGPUはGLSLに代わる新しいシェーダー言語WGSLを導入しました:

@group(0) @binding(0) var<storage, read> inputData: array<f32>;
@group(0) @binding(1) var<storage, read_write> outputData: array<f32>;

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let idx = global_id.x;
    if (idx >= arrayLength(&inputData)) { return; }
    outputData[idx] = inputData[idx] * 2.0;
}

Workgroup Sizeの推奨値

ハードウェア 推奨サイズ 理由
NVIDIA 128-256 Warpサイズが32
AMD 64-128 Wavefrontサイズが64
Intel 32-64 サブグループサイズが小さい
安全なデフォルト 64 全ハードウェア互換

Compute Shader vs WebGL Transform Feedback

項目 WebGL TF WebGPU Compute
プログラミングモデル レンダリングパイプラインの偽装 ネイティブ計算
データ転送 テクスチャ/VBO Storage Buffer
スレッド制御 頂点数によるシミュレーション 正確なdispatch制御
データ読み戻し 遅いreadPixels 高速なmapAsync
ランダム書き込み 非対応 アトミック操作+ランダム書き込み
共有メモリ なし Workgroup Shared Memory

実践:Compute Shaderによるガウシアンブラー

JavaScriptで1080p画像のガウシアンブラーを処理するには約450msかかりますが、Compute Shaderなら約5msで完了——約50倍高速です。

@group(0) @binding(0) var srcTexture: texture_2d<f32>;
@group(0) @binding(1) var dstTexture: texture_storage_2d<rgba8unorm, write>;
@group(0) @binding(2) var<uniform> params: Params;

struct Params {
    width: u32,
    height: u32,
    radius: f32,
}

@compute @workgroup_size(16, 16)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let pixel_coords = vec2<u32>(global_id.x, global_id.y);
    if (pixel_coords.x >= params.width || pixel_coords.y >= params.height) { return; }

    let sigma = params.radius / 3.0;
    var color = vec4<f32>(0.0);
    var total_weight = 0.0;
    let radius_i = i32(params.radius);

    for (var dx: i32 = -radius_i; dx <= radius_i; dx++) {
        for (var dy: i32 = -radius_i; dy <= radius_i; dy++) {
            let weight = exp(-(f32(dx)*f32(dx) + f32(dy)*f32(dy)) / (2.0*sigma*sigma));
            let sx = clamp(i32(pixel_coords.x) + dx, 0, i32(params.width) - 1);
            let sy = clamp(i32(pixel_coords.y) + dy, 0, i32(params.height) - 1);
            color += textureLoad(srcTexture, vec2<u32>(sx, sy), 0) * weight;
            total_weight += weight;
        }
    }
    textureStore(dstTexture, pixel_coords, color / total_weight);
}

パフォーマンス比較

手法 1080p (r=10) 4K (r=10)
JavaScript ~450ms ~1800ms
Web Worker (4スレッド) ~130ms ~520ms
WebGLフラグメント ~12ms ~45ms
WebGPU Compute ~5ms ~18ms

実践:10万パーティクルシミュレーション(60fps)

10万個のパーティクルを毎フレーム更新するのはCPUでは不可能です。

struct Particle {
    pos: vec2<f32>,
    vel: vec2<f32>,
    color: vec4<f32>,
    life: f32,
}

@group(0) @binding(0) var<storage, read_write> particles: array<Particle>;

@compute @workgroup_size(256)
fn updateParticles(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let idx = global_id.x;
    if (idx >= arrayLength(&particles)) { return; }
    var p = particles[idx];
    p.vel.y += -9.8 * 0.016;
    p.vel *= 0.999;
    p.pos += p.vel * 0.016;
    if (p.pos.y < 0.0) { p.pos.y = 0.0; p.vel.y *= -0.7; }
    p.life -= 0.016;
    if (p.life <= 0.0) {
        p.pos = vec2<f32>(0.5, 0.8);
        p.life = 3.0;
    }
    particles[idx] = p;
}

重要なテクニック:同じバッファをSTORAGE | VERTEXとして使用し、Compute→Renderのゼロコピーを実現します。


ブラウザ互換性

ブラウザ サポート バージョン
Chrome ✅ 完全対応 113+
Edge ✅ 完全対応 113+
Firefox ⚠️ 実験的 Nightly
Safari ⚠️ 部分対応 17.4+

WebGPU + Web Worker連携パターン

メインスレッド (WebGPU Compute) → mapAsync → SharedArrayBuffer → Web Worker (ロジック)

GPUが重い計算を担当し、CPUがロジック判断を行うシナリオに最適です。


まとめ

WebGPU Compute ShaderはWebプラットフォームに真のGPGPUをもたらします。Chrome 113+で安定サポートされ、エコシステムも急速に成熟しています。今後はWebGPU SubgroupsRay Tracingの提案に注目です。

ブラウザローカルツールを無料で試す →

#WebGPU#Compute Shader#GPU计算#WGSL#并行计算