發布日期:2023 年 7 月 20 日,上次更新日期:2025 年 6 月 17 日
對網路開發人員而言,WebGPU 是一種網路圖形 API,可提供統一且快速的 GPU 存取方式。WebGPU 會公開新式硬體功能,並允許在 GPU 上進行算繪和運算作業,類似於 Direct3D 12、Metal 和 Vulkan。
雖然這項說法正確,但故事並未完整呈現。WebGPU 是 Apple、Google、Intel、Mozilla 和 Microsoft 等主要公司共同合作的成果。其中,有些人意識到 WebGPU 不只是 JavaScript API,而是跨平台圖形 API,可供網頁以外的各個生態系統開發人員使用。
為滿足主要用途,我們在 Chrome 113 中推出了 JavaScript API。不過,我們也同時開發了另一個重要專案:webgpu.h C API。這個 C 標頭檔案會列出 WebGPU 的所有可用程序和資料結構。它可做為跨平台硬體抽象層,讓您在不同平台上提供一致的介面,藉此建構特定平台的應用程式。
在本文中,您將學習如何使用 WebGPU 編寫小型 C++ 應用程式,以便在網路和特定平台上執行。劇透警告:您會看到瀏覽器視窗和電腦視窗中顯示的相同紅色三角形,只需對程式碼庫進行少許調整即可。

運作方式
如要查看完成的應用程式,請前往 WebGPU 跨平台應用程式存放區。
這個應用程式是簡化的 C++ 範例,說明如何使用 WebGPU 從單一程式碼集建構電腦和網頁應用程式。實際上,它會透過名為 webgpu_cpp.h 的 C++ 包裝函式,使用 WebGPU 的 webgpu.h 做為平台無關的硬體抽象層。
在網頁上,應用程式會針對 emdawnwebgpu (Emscripten Dawn WebGPU) 建構,這個應用程式會在 JavaScript API 上實作 webgpu.h 繫結。在 macOS 或 Windows 等特定平台上,這個專案可以根據 Dawn 建構,這是 Chromium 的跨平台 WebGPU 實作。值得一提的是,wgpu-native 也是 webgpu.h 的 Rust 實作項目,但本文並未使用。
開始使用
首先,您需要 C++ 編譯器和 CMake,以標準方式處理跨平台建構作業。在專用資料夾中,建立 main.cpp
來源檔案和 CMakeLists.txt
建構檔案。
main.cpp
檔案目前應包含空白的 main()
函式。
int main() {}
CMakeLists.txt
檔案包含專案的基本資訊。最後一行指定可執行檔的名稱為「app」,其原始碼為 main.cpp
。
cmake_minimum_required(VERSION 3.13) # 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
應用程式會執行,但尚未產生任何輸出內容,因為您需要在畫面上繪製內容。
取得 Dawn
如要繪製三角形,您可以利用 Dawn,這是 Chromium 的跨平台 WebGPU 實作。這包括用於繪製螢幕畫面的 GLFW C++ 程式庫。下載 Dawn 的方式之一,是將其新增為 git 子模組至您的存放區。下列指令會在「dawn/」子資料夾中擷取該檔案。
$ git init
$ git submodule add https://dawn.googlesource.com/dawn
接著,請按照下列方式附加至 CMakeLists.txt
檔案:
- CMake
DAWN_FETCH_DEPENDENCIES
選項會擷取所有 Dawn 依附元件。 - 目標中包含
dawn/
子資料夾。 - 您的應用程式會依賴
dawn::webgpu_dawn
、glfw
和webgpu_glfw
目標,以便您稍後在main.cpp
檔案中使用這些目標。
…
set(DAWN_FETCH_DEPENDENCIES ON)
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
開啟視窗
有了 Dawn,您就可以使用 GLFW 在畫面上繪製內容。webgpu_glfw
中包含的這個程式庫可讓您編寫不受平台限制的視窗管理程式碼。
如要開啟名為「WebGPU window」的視窗,解析度為 512x512,請更新 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() {
wgpu::InstanceDescriptor instanceDesc{
.capabilities = {.timedWaitAnyEnable = true}};
instance = wgpu::CreateInstance(&instanceDesc);
}
int main() {
Init();
Start();
}
在 main.cpp
檔案頂端宣告兩個變數 wgpu::Adapter
和 wgpu::Device
。更新 Init()
函式以呼叫 instance.RequestAdapter()
,並將其結果回呼指派給 adapter
,然後呼叫 adapter.RequestDevice()
,並將其結果回呼指派給 device
。
#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()
函式進行設定。您還需要呼叫 surface.Present()
,才能在 while 迴圈中顯示下一個紋理。由於尚未進行轉譯,因此不會有任何可見的影響。
#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。這項工具使用 Emscripten,這是用於將 C/C++ 程式編譯為 WebAssembly 的工具。
更新 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
應用程式連結選項會指示 Emscripten 使用 GLFW 3 API 內建的 JavaScript 實作項目。
cmake_minimum_required(VERSION 3.13) # 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)
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 dawn::webgpu_dawn glfw webgpu_glfw)
endif()
更新程式碼
請呼叫 emscripten_set_main_loop(Render)
,而不是使用 while 迴圈,以確保 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 應用程式,請參閱「Learn WebGPU for C++ documentation」和「Dawn Native WebGPU Examples」。
如果您對 Rust 感興趣,也可以探索以 WebGPU 為基礎的 wgpu 圖形程式庫。請參閱他們的 hello-triangle 示範。
致謝
本文經 Corentin Wallez、Kai Ninomiya 和 Rachel Andrew 審查。
相片來源:Marc-Olivier Jodoin 在 Unsplash 網站上提供。