Contents
はじめに — 目的と前提
要点
- このガイドは、JavaScript + WebGPU の基礎(レンダー三角形・簡単な Compute)を実務的な注意点を交えて説明します。
- 動作にはセキュアコンテキスト(HTTPS または localhost)が必須です。追加のオリジン制約(COOP/COEP)が必要になるケースや、サーバーのヘッダー設定についても解説します。
詳細
- WebGPU を使うにはブラウザが WebGPU API を実装している必要があります。ローカル検証は https://localhost または http://localhost: ポート経由で行うとセキュアコンテキストの要件を満たします。file:// では動作しないことがあります。
- Cross-Origin-Opener-Policy / Cross-Origin-Embedder-Policy(COOP/COEP)は、基本的な描画やコンピュートの利用に必須ではありませんが、SharedArrayBuffer を使った高度なスレッド処理や、厳密なクロスオリジン分離が必要な統合(例:外部リソースを安全に使いつつクロスオリジンに影響されない機能)を行う場合にはクロスオリジン分離が必要になります。COOP/COEP を付与すると外部リソースの扱いが変わるため、設定は慎重に行ってください(後述の例を参照)。
- Permissions-Policy(旧 Feature-Policy)が WebGPU を直接ブロックする場面は一般的ではありませんが、企業や組織のポリシーで機能が制限されている可能性があるため配信環境は確認してください。
ブラウザ対応状況と有効化方法(機能検出)
要点
- 実行時に必ず機能検出を行い、ブラウザ差分やフラグ依存に備えたフォールバックを用意してください。開発時にブラウザフラグを有効化することはできますが、フラグは不安定かつセキュリティリスクを伴うため本番環境での使用は避けてください。
基本的な機能検出例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
async function detectWebGPU() { if (!('gpu' in navigator)) { console.warn('WebGPU 未対応のブラウザです'); return null; } const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }); if (!adapter) { console.warn('GPU アダプタが取得できません'); return null; } console.log('adapter.features:', [...adapter.features]); console.log('adapter.limits:', adapter.limits); return adapter; } |
運用上の注意
- ブラウザのフラグ(例:Chrome や Canary の実験フラグ)に頼る場合、安定性やセキュリティのリスクがあるため開発・検証用途に限定してください。フラグで挙動が変わることがあるため、チームで設定手順を共有し、CI 等には組み込まないことを推奨します。
- 機能が無い場合は WebGL2 や CPU 実装へのフォールバックを用意してください(必ず実行時検出→切替)。
セキュリティとオリジン制約(COOP / COEP / Permissions-Policy)
要点
- WebGPU はセキュアコンテキスト(HTTPS または localhost)で動作します。COOP/COEP は基本的な利用では不要ですが、SharedArrayBuffer を利用するなどクロスオリジン分離を必要とする機能を使う場合に必要です。ヘッダーの設定はサーバー側で行います。
サーバー側ヘッダー例(クロスオリジン分離を有効にする場合)
|
1 2 3 |
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp |
注意事項
- 上記ヘッダーを付与すると、外部リソース(CDN など)に対して追加の対応が必要になる場合があります。外部リソース側が適切な Cross-Origin-Resource-Policy / CORP ヘッダーや CORS を提供しているか確認してください。
- COOP/COEP はサイト全体の挙動に影響するため、導入は慎重に行い、統合テストを十分に実施してください。
- Permissions-Policy(旧 Feature-Policy)は通常 WebGPU の利用に直接関与しませんが、組織ポリシーやホスティング環境で機能制限がかかっている場合は確認してください。
環境構築とサンプルの実行(ローカルでの確認)
要点
- ローカルでは HTTPS または localhost(セキュアコンテキスト)で実行してください。簡易サーバーを用意する手順を README 等に残すと再現性が高まります。
簡易起動例
|
1 2 3 4 5 |
# リポジトリをクローンして public 配下を配信する例 npx http-server ./public -p 8080 # または python -m http.server 8000 |
ブラウザでの確認ポイント
- DevTools のコンソールで navigator.gpu を確認する。
- サンプルの描画や Compute の結果が期待どおりかを検証する。
- シェーダーコンパイルのエラー/警告は DevTools とシェーダーモジュールの compilationInfo() で確認する(後述)。
WebGPU の基本API: Adapter / Device / CanvasContext
要点
- adapter の機能検出 → 必要最小限の requiredFeatures を指定して device を取得する流れが基本です。エラースコープ(push/popErrorScope)と device.lost のハンドリングは運用で必須です。
実務的なデバイス取得とエラースコープの使い方
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }); if (!adapter) throw new Error('GPU adapter が見つかりません'); const required = []; if (adapter.features.has('texture-compression-bc')) { required.push('texture-compression-bc'); } const device = await adapter.requestDevice({ requiredFeatures: required }); // pushErrorScope は同期呼び出し(戻り値なし) device.pushErrorScope('validation'); // ここで行う操作(パイプライン作成など)で発生した検証エラーを捕まえたい // 何らかの GPU 操作... // popErrorScope() の戻り値(Promise)を await してエラーを確認する const validationErr = await device.popErrorScope(); if (validationErr) { console.error('WebGPU validation error:', validationErr); } // device.lost はデバイスが lost したときに解決される Promise device.lost.then((info) => { // 実装によって含まれるフィールドは異なるため string 化してログ等に残す console.warn('GPU device が lost しました:', info); // 再初期化やユーザーへの通知を行う }); |
Canvas の設定
|
1 2 3 4 5 6 7 8 9 10 |
const canvas = document.querySelector('canvas'); const context = canvas.getContext('webgpu'); const format = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device, format, alphaMode: 'opaque' }); |
注意点
- getPreferredCanvasFormat() を使い、古い createSwapChain API は使用しないでください。
- キャンバスサイズの変更時は devicePixelRatio と clientWidth/clientHeight を元に canvas.width/height を更新し、必要に応じてレンダーターゲット再作成やリソース再生成を行ってください。
WGSL とレンダー/コンピュートパイプライン(最小例とハンズオン)
要点
- WGSL のモジュール間で型を自動共有できない点に注意してください(同じ型定義を各モジュールに再定義するか、フラグメント側で @location 指定の入力変数を直接宣言する必要があります)。
- シェーダーモジュール作成後は compilationInfo()(サポートされていれば)で明示的にログを取得し、エラー/警告を検査してください。
- パイプライン作成時の layout: 'auto' は便利ですが未対応実装があるためフォールバックを用意してください。
ハンズオン:三角形(修正版フルコード)
HTML(index.html)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!doctype html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>WebGPU Triangle</title> <style> html,body,canvas { height: 100%; margin: 0; } canvas { width: 100%; height: 100%; display: block; } </style> </head> <body> <canvas id="gpuCanvas"></canvas> <script type="module" src="./main.js"></script> </body> </html> |
JavaScript(main.js)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
const canvas = document.getElementById('gpuCanvas'); async function checkShaderCompilation(module, label) { // compilationInfo() はブラウザ実装依存。存在する場合はログを取得してエラーを検出する。 if (typeof module.compilationInfo === 'function') { try { const info = await module.compilationInfo(); for (const m of info.messages) { const pos = (m.lineNum !== undefined ? `${m.lineNum}:${m.linePos} ` : ''); if (m.type === 'error') console.error(`${label} shader error: ${pos}${m.message}`); else if (m.type === 'warning') console.warn(`${label} shader warning: ${pos}${m.message}`); else console.info(`${label} shader info: ${pos}${m.message}`); } if (info.messages.some(m => m.type === 'error')) { throw new Error(`${label} shader failed to compile`); } } catch (e) { // compilationInfo() 呼び出し自体が例外になる可能性もあるので捕捉する console.warn(`${label} compilationInfo() failed:`, e); } } else { console.warn('compilationInfo() is not available on this browser. Check DevTools for shader errors.'); } } async function init() { if (!('gpu' in navigator)) { console.error('WebGPU はこのブラウザで利用できません'); return; } const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }); if (!adapter) { console.error('GPU アダプタの取得に失敗しました'); return; } const device = await adapter.requestDevice(); const context = canvas.getContext('webgpu'); const format = navigator.gpu.getPreferredCanvasFormat(); function resizeCanvasToDisplaySize() { const dpr = Math.max(1, window.devicePixelRatio || 1); canvas.width = Math.floor(canvas.clientWidth * dpr); canvas.height = Math.floor(canvas.clientHeight * dpr); } resizeCanvasToDisplaySize(); window.addEventListener('resize', () => { resizeCanvasToDisplaySize(); }); context.configure({ device, format, alphaMode: 'opaque' }); // 頂点シェーダー(VSOut を定義) const vsCode = ` struct VSOut { @builtin(position) pos : vec4<f32>; @location(0) color : vec3<f32>; }; @vertex fn vs_main(@builtin(vertex_index) idx : u32) -> VSOut { var positions = array<vec2<f32>, 3>( vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5) ); var out : VSOut; out.pos = vec4<f32>(positions[idx], 0.0, 1.0); out.color = vec3<f32>(1.0, 0.2, 0.2); return out; } `; // フラグメント側では VSOut 型を直接参照せず、@location 指定で入力を宣言する const fsCode = ` @fragment fn fs_main(@location(0) color : vec3<f32>) -> @location(0) vec4<f32> { return vec4<f32>(color, 1.0); } `; const vsModule = device.createShaderModule({ code: vsCode }); const fsModule = device.createShaderModule({ code: fsCode }); // コンパイルログの明示的取得(サポートされていれば) await checkShaderCompilation(vsModule, 'vertex'); await checkShaderCompilation(fsModule, 'fragment'); // エラースコープを開いてパイプライン作成や初期コマンドを検査する例 device.pushErrorScope('validation'); const pipelineDescBase = { vertex: { module: vsModule, entryPoint: 'vs_main' }, fragment: { module: fsModule, entryPoint: 'fs_main', targets: [{ format }] }, primitive: { topology: 'triangle-list', cullMode: 'none' } }; let pipeline; try { // layout: 'auto' を試す。未対応実装があるため失敗したらフォールバックする pipeline = device.createRenderPipeline(Object.assign({ layout: 'auto' }, pipelineDescBase)); } catch (e) { console.warn('layout: "auto" によるパイプライン作成に失敗したため、明示的な PipelineLayout で再試行します:', e); const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [] }); pipeline = device.createRenderPipeline(Object.assign({ layout: pipelineLayout }, pipelineDescBase)); } const scopeError = await device.popErrorScope(); if (scopeError) { console.error('WebGPU validation error during initialization:', scopeError); } function frame() { const encoder = device.createCommandEncoder(); const view = context.getCurrentTexture().createView(); const passDesc = { colorAttachments: [{ view, clearValue: { r: 0.12, g: 0.12, b: 0.14, a: 1.0 }, loadOp: 'clear', storeOp: 'store' }] }; const pass = encoder.beginRenderPass(passDesc); pass.setPipeline(pipeline); pass.draw(3, 1, 0, 0); pass.end(); device.queue.submit([encoder.finish()]); requestAnimationFrame(frame); } requestAnimationFrame(frame); } init().catch(err => console.error(err)); |
ポイント(前回記事からの重要修正)
- フラグメントシェーダーは VSOut を外部モジュールから自動で共有できないため、フラグメント側で @location の入力変数を直接宣言するよう修正しました。
- device.pushErrorScope は同期呼び出し(await 不要)で、結果は device.popErrorScope() の Promise を await して得ます。
- createShaderModule 後に compilationInfo() を呼び出してコンパイルログを明示的に確認する手順を追加しました(ブラウザによるサポート差に対応)。
シェーダーモジュールのコンパイルログ(補足)
要点
- GPUShaderModule.compilationInfo() はブラウザ実装差があり、存在しないことがあります。存在する場合はエラー/警告をプログラム的に取得して処理できます。
利用方法(まとめ)
- module.compilationInfo() が存在するかチェックし、Promise を await して messages を列挙する。
- messages に error タイプが含まれていれば例外扱いにするか、処理を中断する。
- もし compilationInfo() 非対応なら DevTools のコンソールに依存する。最低でも createShaderModule 後にパイプライン作成時に例外が出る可能性があるため try/catch を用いる。
ハンズオン:簡単な Compute(ベクトル加算/結果検証)
目的と要点
- GPU 上で配列 A と B を足し、結果を CPU 側に読み戻して検証する流れを確認します。重要な点はバッファ作成時の usage フラグです。readback バッファは MAP_READ | COPY_DST、GPU 側にそのまま配置するバッファは STORAGE | COPY_SRC/COPY_DST 等を適切に付与してください。
WGSL(compute.wgsl)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct Params { size : u32; }; @group(0) @binding(3) var<uniform> params : Params; @group(0) @binding(0) var<storage, read> A : array<f32>; @group(0) @binding(1) var<storage, read> B : array<f32>; @group(0) @binding(2) var<storage, read_write> Out : array<f32>; @compute @workgroup_size(64) fn cs_main(@builtin(global_invocation_id) gid : vec3<u32>) { let i = gid.x; if (i >= params.size) { return; } Out[i] = A[i] + B[i]; } |
JS のポイント(フラグと典型的なハマりどころ)
- 入力バッファ(GPU で読み込むバッファ)は GPUBufferUsage.STORAGE と、必要に応じて COPY_SRC / COPY_DST を付与します(後で別バッファにコピーするなら COPY_SRC が必要)。
- 出力バッファを CPU で読み出す場合は、出力を COPY_SRC として用意し、readback 用バッファを MAP_READ | COPY_DST で作成して copyBufferToBuffer でコピーします。readback バッファに対して mapAsync(GPUMapMode.READ) を呼んで結果を取得します。
- 小さな入力データは queue.writeBuffer を使うと簡潔です。大きなデータは mappedAtCreation やステージングバッファを使うと効率的です。
読み戻しサンプル
|
1 2 3 4 5 6 |
// readbackBuffer は MAP_READ | COPY_DST で作成済み await readbackBuffer.mapAsync(GPUMapMode.READ); const copy = readbackBuffer.getMappedRange(); const result = new Float32Array(copy.slice(0)); // 必要ならコピーして使う readbackBuffer.unmap(); |
代表的なハマりどころ
- readback 用バッファを MAP_READ にし忘れると mapAsync 呼び出しが例外になります。
- コピー元バッファに COPY_SRC が無いと copyBufferToBuffer が失敗します。
- バッファのサイズはバイト単位で指定し、構造体や配列要素のサイズ・アラインメントを正しく計算してください(uniform/storage のオフセットは adapter.limits のアラインメント制約に従う必要があります)。
デバッグ・最適化・運用(フォールバックとデプロイ)
要点
- DevTools、SpectorJS、エラースコープ、device.lost などを組み合わせてデバッグし、バッファ再作成の削減やステージングの活用で最適化を図ってください。フラグは開発用に限定すること。
デバッグツール
- Chrome / Edge DevTools:コンソールに出るシェーダーコンパイルエラーや警告を確認。
- SpectorJS:フレームキャプチャでコマンドバッファやバインディングを可視化できる。
- device.pushErrorScope / device.popErrorScope:検証エラーを捕捉する。push は同期、pop は Promise。
最適化の実務的ポイント
- 可能な限りリソースは再利用する(毎フレームの create/destroy を避ける)。
- 大きな転送はステージングバッファで行う。小さいデータは queue.writeBuffer が簡便。
- uniform/storage バッファのオフセット調整は adapter.limits.minUniformBufferOffsetAlignment 等を確認。
- 過度に requiredFeatures を要求しない(最小限に留める)。
- 不要になった GPUBuffer/Texture は destroy が利用できる環境では適切に destroy する。
フォールバック戦略
- navigator.gpu がない場合は WebGL2、さらに無ければ CPU 実装へフォールバックする。
- ポリフィルは一部の API を補うが完全代替ではない(動作差・性能差に注意)。
- モバイル実機での大きな差異があるため必ず実機テストを行う。
参考情報(仕様・実装状況の確認先)
要点
- WebGPU 周りは仕様や実装が進化しています。主要な参照先を定期的に確認してください。
推奨参照先(実装状況や仕様の最新確認に有用)
-
GPU for the Web (GPUWeb) — 仕様・リポジトリ
https://gpuweb.github.io/gpuweb/ -
WGSL(シェーダー言語)仕様
https://gpuweb.github.io/gpuweb/wgsl/ -
MDN Web Docs — WebGPU(API ドキュメント)
https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API -
Can I Use — WebGPU サポート状況(ブラウザ互換性)
https://caniuse.com/webgpu -
Google / Chrome Developers の紹介記事や Codelabs(チュートリアル)
https://developer.chrome.com/docs/web-platform/webgpu/
https://codelabs.developers.google.com/codelabs/webgpu/ -
SpectorJS(レンダリングデバッグ)
https://github.com/BabylonJS/Spector.js
注意
- これら参照先で実装差分・フラグ情報・API 追加などが公開されるため、常に最新のドキュメントやブラウザ実装ノートを確認してください。
簡潔なまとめ(実務チェックリスト)
- 実行前に navigator.gpu の有無と adapter.features / adapter.limits を確認する。
- 環境は HTTPS か localhost で検証する。COOP/COEP の導入は必要性を検討のうえ慎重に行う。
- adapter.requestDevice では最小限の requiredFeatures を指定する。
- device.pushErrorScope は同期呼び出し、結果は await device.popErrorScope() で取得する。
- createShaderModule の後は可能なら compilationInfo() でエラー/警告を取得して処理する。存在しない実装もあるためフォールバック(DevTools の利用や try/catch)を用意する。
- layout: 'auto' は便利だが未対応実装があるため、失敗時のフォールバック(明示的な PipelineLayout 作成)を用意する。
- GPUBuffer の用途に応じた usage フラグを明示する(readback は MAP_READ | COPY_DST、GPU 書き込み用は STORAGE や COPY_SRC 等)。
- ブラウザフラグの使用は開発・検証に限定し、本番環境では利用しない。
- デバッグには DevTools と SpectorJS を活用し、device.lost とエラースコープのログを収集する運用を整える。
以上の点を踏まえれば、三角形描画や基本的な Compute の実装・検証がより確実になります。必要に応じてサンプルをプロジェクトに組み込み、実際のターゲットブラウザでの挙動確認を行ってください。