公開日: 2023 年 7 月 20 日、最終更新日: 2025 年 8 月 27 日
ウェブ デベロッパーにとって、WebGPU は GPU への統合された高速アクセスを提供するウェブ グラフィック API です。WebGPU は、最新のハードウェア機能を公開し、Direct3D 12、Metal、Vulkan と同様に、GPU でレンダリングと計算のオペレーションを実行できるようにします。
これは事実ですが、この話は不完全です。WebGPU は、Apple、Google、Intel、Mozilla、Microsoft などの大手企業を含む共同作業の成果です。その中で、WebGPU は Javascript API だけでなく、ウェブ以外のエコシステム全体でデベロッパーが利用できるクロス プラットフォームのグラフィック API にもなり得ると認識した人もいました。
主なユースケースを満たすため、Chrome 113 で JavaScript API が導入されました。ただし、それと並行して、もう 1 つの重要なプロジェクトである webgpu.h C API が開発されています。この C ヘッダー ファイルには、WebGPU で使用可能なすべてのプロシージャとデータ構造がリストされています。プラットフォームに依存しないハードウェア抽象化レイヤとして機能し、さまざまなプラットフォームで一貫したインターフェースを提供することで、プラットフォーム固有のアプリケーションを構築できます。
このドキュメントでは、ウェブと特定のプラットフォームの両方で実行される WebGPU を使用した小さな C++ アプリを作成する方法について説明します。ネタバレになりますが、コードベースを最小限に調整するだけで、ブラウザ ウィンドウとデスクトップ ウィンドウに表示される赤い三角形と同じものが表示されます。

仕組み
完成したアプリケーションを確認するには、WebGPU クロス プラットフォーム アプリのリポジトリをご覧ください。
このアプリは、1 つのコードベースからデスクトップ アプリとウェブアプリを構築するために WebGPU を使用する方法を示す、最小限の C++ の例です。内部的には、WebGPU の webgpu.h をプラットフォームに依存しないハードウェア抽象化レイヤとして使用し、webgpu_cpp.h という C++ ラッパーを介して使用します。
ウェブでは、アプリは emdawnwebgpu(Emscripten Dawn WebGPU)に対してビルドされます。これには、JavaScript API の上に webgpu.h を実装するバインディングがあります。macOS や Windows などの特定のプラットフォームでは、このプロジェクトは Chromium のクロス プラットフォーム WebGPU 実装である Dawn に対してビルドできます。webgpu.h の Rust 実装である wgpu-native も存在しますが、このドキュメントでは使用されていません。
始める
まず、標準的な方法でクロス プラットフォーム ビルドを処理するために、C++ コンパイラと CMake が必要です。専用フォルダ内に main.cpp
ソースファイルと CMakeLists.txt
ビルドファイルを作成します。
main.cpp
ファイルには、現時点では空の main()
関数が含まれている必要があります。
int main() {}
CMakeLists.txt
ファイルには、プロジェクトに関する基本情報が含まれています。最後の行は、実行可能ファイルの名前が「app」で、そのソースコードが main.cpp
であることを指定しています。
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
cmake -B build
を実行して「build/」サブフォルダにビルドファイルを作成し、cmake --build build
を実行してアプリを実際にビルドし、実行可能ファイルを生成します。
# Build the app with CMake.
$ cmake -B build && cmake --build build
# Run the app.
$ ./build/app
アプリは実行されますが、画面に描画する方法が必要なため、まだ出力はありません。
Get Dawn
三角形を描画するには、Chromium のクロスプラットフォーム WebGPU 実装である Dawn を利用できます。これには、画面に描画するための GLFW C++ ライブラリが含まれます。Dawn をダウンロードする方法の 1 つは、リポジトリに git サブモジュールとして追加することです。次のコマンドは、それを「dawn/」サブフォルダに取得します。
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
次に、次のように CMakeLists.txt
ファイルに追加します。
- CMake の
DAWN_FETCH_DEPENDENCIES
オプションは、Dawn のすべての依存関係を取得します。 - CMake の
DAWN_BUILD_MONOLITHIC_LIBRARY
オプションは、すべての Dawn コンポーネントを 1 つのライブラリにバンドルします。 dawn/
サブフォルダがターゲットに含まれます。- アプリは
webgpu_dawn
、webgpu_glfw
、glfw
の各ターゲットに依存するため、後でmain.cpp
ファイルで使用できます。
…
set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
ウィンドウを開く
Dawn が利用可能になったので、GLFW を使用して画面に描画します。webgpu_glfw
に含まれるこのライブラリを使用すると、ウィンドウ管理用のプラットフォームに依存しないコードを記述できます。
解像度 512x512 の「WebGPU window」という名前のウィンドウを開くには、main.cpp
ファイルを次のように更新します。ここで glfwWindowHint()
を使用して、特定のグラフィック API の初期化をリクエストしないようにしていることに注意してください。
#include <GLFW/glfw3.h>
const uint32_t kWidth = 512;
const uint32_t kHeight = 512;
void Start() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window =
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// TODO: Render a triangle using WebGPU.
}
}
int main() {
Start();
}
アプリを再ビルドして以前と同じように実行すると、空のウィンドウが表示されます。進歩していますね。

GPU デバイスを取得する
JavaScript では、navigator.gpu
が GPU にアクセスするためのエントリポイントです。C++ では、同じ目的で使用される wgpu::Instance
変数を手動で作成する必要があります。便宜上、main.cpp
ファイルの先頭で instance
を宣言し、Init()
内で wgpu::CreateInstance()
を呼び出します。
#include <webgpu/webgpu_cpp.h>
…
wgpu::Instance instance;
…
void Init() {
static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
.requiredFeatures = &kTimedWaitAny};
instance = wgpu::CreateInstance(&instanceDesc);
}
int main() {
Init();
Start();
}
main.cpp
ファイルの先頭で 2 つの変数 wgpu::Adapter
と wgpu::Device
を宣言します。instance.RequestAdapter()
を呼び出してその結果コールバックを adapter
に割り当て、次に adapter.RequestDevice()
を呼び出してその結果コールバックを device
に割り当てるように Init()
関数を更新します。
#include <iostream>
#include <dawn/webgpu_cpp_print.h>
…
wgpu::Adapter adapter;
wgpu::Device device;
void Init() {
…
wgpu::Future f1 = instance.RequestAdapter(
nullptr, wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::RequestAdapterStatus status, wgpu::Adapter a,
wgpu::StringView message) {
if (status != wgpu::RequestAdapterStatus::Success) {
std::cout << "RequestAdapter: " << message << "\n";
exit(0);
}
adapter = std::move(a);
});
instance.WaitAny(f1, UINT64_MAX);
wgpu::DeviceDescriptor desc{};
desc.SetUncapturedErrorCallback([](const wgpu::Device&,
wgpu::ErrorType errorType,
wgpu::StringView message) {
std::cout << "Error: " << errorType << " - message: " << message << "\n";
});
wgpu::Future f2 = adapter.RequestDevice(
&desc, wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::RequestDeviceStatus status, wgpu::Device d,
wgpu::StringView message) {
if (status != wgpu::RequestDeviceStatus::Success) {
std::cout << "RequestDevice: " << message << "\n";
exit(0);
}
device = std::move(d);
});
instance.WaitAny(f2, UINT64_MAX);
}
三角形を描画する
スワップ チェーンは、ブラウザが処理するため、JavaScript API では公開されません。C++ では、手動で作成する必要があります。ここでも、便宜上、main.cpp
ファイルの先頭で wgpu::Surface
変数を宣言します。Start()
で GLFW ウィンドウを作成した直後に、便利な wgpu::glfw::CreateSurfaceForWindow()
関数を呼び出して wgpu::Surface
(HTML キャンバスに似ています)を作成し、InitGraphics()
の新しいヘルパー ConfigureSurface()
関数を呼び出して構成します。また、while ループで surface.Present()
を呼び出して次のテクスチャを表示する必要があります。まだレンダリングが行われていないため、目に見える効果はありません。
#include <webgpu/webgpu_glfw.h>
…
wgpu::Surface surface;
wgpu::TextureFormat format;
void ConfigureSurface() {
wgpu::SurfaceCapabilities capabilities;
surface.GetCapabilities(adapter, &capabilities);
format = capabilities.formats[0];
wgpu::SurfaceConfiguration config{.device = device,
.format = format,
.width = kWidth,
.height = kHeight,
.presentMode = wgpu::PresentMode::Fifo};
surface.Configure(&config);
}
void InitGraphics() {
ConfigureSurface();
}
void Render() {
// TODO: Render a triangle using WebGPU.
}
void Start() {
…
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
InitGraphics();
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
}
以下のコードを使用して、レンダリング パイプラインを作成します。アクセスしやすくするために、main.cpp
ファイルの先頭で wgpu::RenderPipeline
変数を宣言し、InitGraphics()
でヘルパー関数 CreateRenderPipeline()
を呼び出します。
wgpu::RenderPipeline pipeline; … const char shaderCode[] = R"( @vertex fn vertexMain(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f { const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1)); return vec4f(pos[i], 0, 1); } @fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(1, 0, 0, 1); } )"; void CreateRenderPipeline() { wgpu::ShaderSourceWGSL wgsl{{.code = shaderCode}}; wgpu::ShaderModuleDescriptor shaderModuleDescriptor{.nextInChain = &wgsl}; wgpu::ShaderModule shaderModule = device.CreateShaderModule(&shaderModuleDescriptor); wgpu::ColorTargetState colorTargetState{.format = format}; wgpu::FragmentState fragmentState{ .module = shaderModule, .targetCount = 1, .targets = &colorTargetState}; wgpu::RenderPipelineDescriptor descriptor{.vertex = {.module = shaderModule}, .fragment = &fragmentState}; pipeline = device.CreateRenderPipeline(&descriptor); } void InitGraphics() { … CreateRenderPipeline(); }
最後に、各フレームで呼び出される Render()
関数で、レンダリング コマンドを GPU に送信します。
void Render() {
wgpu::SurfaceTexture surfaceTexture;
surface.GetCurrentTexture(&surfaceTexture);
wgpu::RenderPassColorAttachment attachment{
.view = surfaceTexture.texture.CreateView(),
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store};
wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
.colorAttachments = &attachment};
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
pass.SetPipeline(pipeline);
pass.Draw(3);
pass.End();
wgpu::CommandBuffer commands = encoder.Finish();
device.GetQueue().Submit(1, &commands);
}
CMake でアプリを再ビルドして実行すると、待望の赤い三角形がウィンドウに表示されます。休憩しましょう。

WebAssembly にコンパイルする
既存のコードベースを調整して、ブラウザ ウィンドウにこの赤い三角形を描画するために必要な最小限の変更を見てみましょう。このアプリも emdawnwebgpu(Emscripten Dawn WebGPU)に対してビルドされています。これには、JavaScript API の上に webgpu.h を実装するバインディングがあります。これは、C/C++ プログラムを WebAssembly にコンパイルするツールである Emscripten を使用します。
CMake の設定を更新する
Emscripten をインストールしたら、次のように CMakeLists.txt
ビルドファイルを更新します。ハイライト表示されたコードのみを変更する必要があります。
set_target_properties
は、ターゲット ファイルに「html」ファイル拡張子を自動的に追加するために使用されます。つまり、「app.html」ファイルが生成されます。emdawnwebgpu_cpp
ターゲット リンク ライブラリにより、Emscripten で WebGPU のサポートが有効になります。この設定がないと、main.cpp
ファイルはwebgpu/webgpu_cpp.h
ファイルにアクセスできません。ASYNCIFY=1
アプリリンク オプションを使用すると、同期 C++ コードが非同期 JavaScript とやり取りできます。USE_GLFW=3
アプリリンク オプションは、GLFW 3 API の組み込み JavaScript 実装を使用するように Emscripten に指示します。
cmake_minimum_required(VERSION 3.22) # CMake version check
project(app) # Create project "app"
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
add_executable(app "main.cpp")
set(DAWN_FETCH_DEPENDENCIES ON)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
if(EMSCRIPTEN)
set_target_properties(app PROPERTIES SUFFIX ".html")
target_link_libraries(app PRIVATE emdawnwebgpu_cpp webgpu_glfw)
target_link_options(app PRIVATE "-sASYNCIFY=1" "-sUSE_GLFW=3")
else()
target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw glfw)
endif()
コードを更新する
while ループを使用する代わりに、emscripten_set_main_loop(Render)
を呼び出して、Render()
関数がブラウザとモニターに適切に同期する適切なスムーズなレートで呼び出されるようにします。
#include <iostream>
#include <GLFW/glfw3.h>
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <webgpu/webgpu_glfw.h>
void Start() {
…
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop(Render, 0, false);
#else
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
Render();
surface.Present();
instance.ProcessEvents();
}
#endif
}
Emscripten でアプリをビルドする
Emscripten でアプリをビルドするために必要な変更は、cmake
コマンドの前に 魔法の emcmake
シェル スクリプトを追加することだけです。今回は、build-web
サブフォルダにアプリを生成し、HTTP サーバーを起動します。最後に、ブラウザを開いて build-web/app.html
にアクセスします。
# Build the app with Emscripten.
$ emcmake cmake -B build-web && cmake --build build-web
# Start a HTTP server.
$ npx http-server

次のステップ
今後の予定は次のとおりです。
- webgpu.h と webgpu_cpp.h API の安定性を改善しました。
- Android と iOS 向けの Dawn の初期サポート。
それまでの間、ご提案やご質問については、Emscripten の WebGPU の問題と Dawn の問題としてご報告ください。
リソース
このアプリのソースコードをご覧ください。
WebGPU を使用して C++ でネイティブ 3D アプリケーションをゼロから作成する方法について詳しくは、C++ 向け WebGPU のドキュメントと Dawn ネイティブ WebGPU の例をご覧ください。
Rust に興味がある場合は、WebGPU に基づく wgpu グラフィック ライブラリもご覧ください。hello-triangle デモをご覧ください。
謝辞
この記事は、Corentin Wallez、Kai Ninomiya、Rachel Andrew によってレビューされました。
写真: Marc-Olivier Jodoin(出典: Unsplash)。