Опубликовано: 20 июля 2023 г., Последнее обновление: 22 октября 2025 г.
Для веб-разработчиков WebGPU — это API веб-графики, предоставляющий унифицированный и быстрый доступ к графическим процессорам. WebGPU использует современные аппаратные возможности и позволяет выполнять рендеринг и вычисления на графическом процессоре, аналогично Direct3D 12, Metal и Vulkan.
Хотя это и правда, эта история неполная. WebGPU — результат совместных усилий таких крупных компаний, как Apple, Google, Intel, Mozilla и Microsoft. Некоторые из них осознали , что WebGPU может быть не просто API Javascript, а кроссплатформенным графическим API для разработчиков из разных экосистем, помимо веба.
Для реализации основного сценария использования в Chrome 113 был представлен JavaScript API. Однако параллельно с ним был разработан ещё один важный проект: C API webgpu.h . Этот заголовочный файл C содержит список всех доступных процедур и структур данных WebGPU. Он служит платформенно-независимым уровнем аппаратной абстракции, позволяя создавать платформенно-зависимые приложения, предоставляя единый интерфейс для разных платформ.
В этом документе вы узнаете, как написать небольшое приложение на C++ с использованием WebGPU, работающее как в веб-браузере, так и на определённых платформах. Спойлер: вы получите тот же красный треугольник, что и в окне браузера, и в окне рабочего стола, внеся минимальные изменения в кодовую базу.

Как это работает?
Чтобы увидеть готовое приложение, посетите репозиторий кроссплатформенных приложений WebGPU .
Приложение представляет собой минималистичный пример на C++, демонстрирующий использование WebGPU для создания десктопных и веб-приложений на основе единой кодовой базы. В основе приложения лежит файл WebGPU webgpu.h , который служит платформенно-независимым слоем аппаратной абстракции через C++-обёртку webgpu_cpp.h .
В веб-версии приложение построено на основе emdawnwebgpu (Emscripten Dawn WebGPU), который имеет привязки, реализующие webgpu.h поверх JavaScript API. На некоторых платформах, таких как macOS или Windows, этот проект может быть собран с использованием Dawn , кроссплатформенной реализации WebGPU в Chromium. Стоит упомянуть, что 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.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 Приложение работает, но пока нет вывода данных, поскольку вам нужен способ рисовать что-либо на экране.
Получить рассвет
Для рисования треугольника вы можете воспользоваться Dawn — кроссплатформенной реализацией WebGPU в Chromium. Она включает в себя библиотеку GLFW C++ для рисования на экране. Один из способов загрузить Dawn — добавить её как подмодуль git в свой репозиторий. Следующие команды скачивают её в подпапку "dawn/".
$ git init $ git submodule add https://github.com/google/dawn.git Затем добавьте в файл CMakeLists.txt следующее:
- Параметр CMake
DAWN_FETCH_DEPENDENCIESизвлекает все зависимости Dawn. - Подпапка
dawn/включена в целевой объект. - Ваше приложение будет зависеть от целей
webgpu_dawn,webgpu_glfwиglfw, поэтому вы сможете позже использовать их в файлеmain.cpp.
… set(DAWN_FETCH_DEPENDENCIES ON) add_subdirectory("dawn" EXCLUDE_FROM_ALL) target_link_libraries(app PRIVATE webgpu_dawn webgpu_glfw 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 — это точка входа для доступа к графическому процессору. В C++ необходимо вручную создать переменную wgpu::Instance , которая используется для той же цели. Для удобства объявите instance в начале файла main.cpp и вызовите wgpu::CreateInstance() внутри Init() .
#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(); } Объявите две переменные wgpu::Adapter и wgpu::Device в начале файла main.cpp . Обновите функцию 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++ вам нужно создать её вручную. И снова, для удобства, объявите переменную wgpu::Surface в начале файла main.cpp . Сразу после создания окна GLFW в Start() вызовите удобную функцию wgpu::glfw::CreateSurfaceForWindow() для создания wgpu::Surface (аналогичного HTML-холсту) и настройте его, вызвав новую вспомогательную функцию ConfigureSurface() в InitGraphics() . Вам также нужно вызвать 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}; 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(); } } Сейчас самое время создать конвейер рендеринга с помощью кода ниже. Для удобства доступа объявите переменную wgpu::RenderPipeline в начале файла main.cpp и вызовите вспомогательную функцию CreateRenderPipeline() в InitGraphics() .
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() вызываемой в каждом кадре.
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), которое реализует привязки webgpu.h поверх JavaScript API. Оно использует Emscripten — инструмент для компиляции программ на C/C++ в WebAssembly.
Обновить настройки CMake
После установки Emscripten обновите файл сборки CMakeLists.txt следующим образом. Выделенный код — единственное, что нужно изменить.
-
set_target_propertiesиспользуется для автоматического добавления расширения «html» к целевому файлу. Другими словами, вы создадите файл «app.html». - Библиотека целевой ссылки
emdawnwebgpu_cppобеспечивает поддержку WebGPU в Emscripten. Без неё ваш файлmain.cppне сможет получить доступ к файлуwebgpu/webgpu_cpp.h. - Параметр ссылки приложения
ASYNCIFY=1позволяет синхронному коду C++ взаимодействовать с асинхронным JavaScript. - Параметр ссылки приложения
USE_GLFW=3указывает Emscripten использовать встроенную реализацию JavaScript API GLFW 3.
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) 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 
Что дальше?
Заглядывая в будущее, можно ожидать первоначальной поддержки Dawn на Android и iOS.
В то же время отправляйте сообщения об ошибках WebGPU для Emscripten и Dawn с предложениями и вопросами.
Ресурсы
Не стесняйтесь изучить исходный код этого приложения.
Если вы хотите глубже погрузиться в создание собственных 3D-приложений на языке C++ с нуля с помощью WebGPU, ознакомьтесь с документацией Learn WebGPU for C++ и примерами Dawn Native WebGPU .
Если вам интересен Rust, вы также можете изучить графическую библиотеку wgpu , основанную на WebGPU. Взгляните на её демонстрацию hello-triangle .
Благодарности
Эту статью рецензировали Корентин Валлез , Кай Ниномия и Рэйчел Эндрю .