Warning
Only Windows has been tested at this time. While the project does include support for other platforms, the code is untested, and it's unknown whether it will even compile.
Automatically generated opinionated idiomatic C++20 wrapper and utilities for WebGPU, adding features such as type-safety for enums and bitflags and interop with STL containers, while not introducing any runtime overhead.
This project also serves as a simple way to use wgpu-native with CMake.
Note that the actual webgpu.hpp
header file is not checked into this repository, as it is generated directly from
the spec when CMake configures the project.
This library turns the verbose C-style code from this:
WGPURenderPassColorAttachment colorAttachment;
colorAttachment.view = targetView;
colorAttachment.loadOp = WGPULoadOp_Clear;
colorAttachment.storeOp = WGPUStoreOp_Store;
colorAttachment.clearValue = { 0.0f, 0.0f, 0.0f, 1.0f };
WGPURenderPassDescriptor descriptor;
descriptor.colorAttachmentCount = 1;
descriptor.colorAttachments = &colorAttachment;
auto renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &descriptor);
wgpuRenderPassEncoderSetPipeline(renderPass, pipeline);
wgpuRenderPassEncoderDraw(renderPass, 3, 1, 0, 0);
wgpuRenderPassEncoderEnd(renderPass);
wgpuRenderPassEncoderRelease(renderPass);
into idiomatic C++-style code such as this:
wgpu::RenderPassColorAttachment colorAttachment {
.view = targetView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = { 0.0f, 0.0f, 0.0f, 1.0f },
};
wgpu::RenderPassDescriptor descriptor {
.colorAttachments = { 1, &colorAttachment },
};
auto renderPass = encoder.beginRenderPass(descriptor);
renderPass.setPipeline(pipeline);
renderPass.draw(3, 1, 0, 0);
renderPass.end();
renderPass.release();
For a more thorough example, see the project in the example
folder, which renders a basic triangle to the screen.
Important
Make sure you have some version of Python 3 installed on your system with the pyyaml
package installed.
Clone this repository into a subdirectory in your CMake project, and add it to your project:
add_subdirectory(webgpu-hpp)
target_link_libraries(MyProject PRIVATE webgpu-hpp)
target_copy_webgpu_binaries(MyProject)
You can also use FetchContent
:
include(FetchContent)
FetchContent_Declare(webgpu-hpp GIT_REPOSITORY https://github.com/chrrs/webgpu-hpp GIT_TAG main)
FetchContent_MakeAvailable(webgpu-hpp)
target_link_libraries(MyProject PRIVATE webgpu-hpp)
target_copy_webgpu_binaries(MyProject)
Note the call to the target_copy_webgpu_binaries
function. This copies the runtime binaries to the target folder at
build time, which are needed to run your application.
After this, you can include the header:
#include <webgpu/webgpu.hpp>
Tip
The code generator is designed to output human-readable code! If you're ever unsure about something, looking at the header might give you extra information.
All WebGPU-related functions, structs and objects are located in the wgpu
namespace. Their name is the same as their C
equivalent, without the wgpu
prefix.
For example, wgpuCreateInstance
becomes wgpu::createInstance
, and the type
WGPUDeviceDescriptor
becomes wgpu::DeviceDescriptor
.
Objects in WebGPU are reference-counted, and often have operations associated with them.
A common pattern you'll see is functions like wgpuInstanceRequestAdapter(instance, ...)
. These functions have been
converted to member functions. The previous example, for example, will become instance.requestAdapter(...)
.
This also extends to ownership management. Each object has two methods, which are called addRef
and release
. All
objects still have to be manually released as you would do in C.
Currently, webgpu-hpp does not support RAII handles, but this may change in the future.
auto renderPass = encoder.beginRenderPass(renderPassDescriptor);
renderPass.setPipeline(pipeline);
renderPass.draw(3, 1, 0, 0);
renderPass.end();
renderPass.release();
Structs in webgpu-hpp are a mostly 1-to-1 conversion from their C-equivalents. An important difference is that a lot of the structs have their defaults set as recommended by the spec, or zero otherwise. This means that in a lot of cases, if you don't care about a field, you can safely leave it out.
// Note that RequestAdapterOptions has a lot more fields that are left out.
wgpu::RequestAdapterOptions adapterOptions {
.powerPreference = wgpu::PowerPreference::HighPerformance,
};
Some structures in WebGPU support extension chains. In webgpu-hpp, any struct supporting this has a
ChainedStruct* next
member. Every extension has a ChainedStruct chain
member, which can be assigned to the next
pointer of the struct you wish to extend.
Note that for any extension, chain.sType
is set automatically!
// Extension: Shader source
wgpu::ShaderSourceWGSL shaderSource {
.code = MY_WGSL_SHADER_SOURCE,
};
// Extendable: Shader module
wgpu::ShaderModuleDescriptor shaderModuleDescriptor {
.next = &shaderSource.chain,
.label = "Triangle Shader",
};
Enums are represented by enum classes in order to achieve type-safety. They have no type prefix, but are scoped.
Some variants start with a number, which C++ disallows. In those cases, they are prefixed with _
(underscore). For
example, WGPUTextureViewDimension_2D
becomes wgpu::TextureViewDimension::_2D
.
wgpu::RenderPipelineDescriptor pipelineDescriptor {
.primitive = {
.topology = wgpu::PrimitiveTopology::TriangleList,
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = wgpu::FrontFace::CCW,
.cullMode = wgpu::CullMode::Back,
},
...
};
To achieve type-safety, bitflags are handled by the wgpu::Flags<>
wrapper.
The individual bits are defined as an enum class, identically to normal enums. Relevant operators, such as |
(Bitwise
OR), &
(Bitwise AND) and ^
(Bitwise XOR) have been overloaded to automatically create the wrapper class.
This class will function identically to normal bitflags, with the added benefit of not allowing mismatched flags.
wgpu::ColorTargetState colorTarget {
.writeMask = wgpu::ColorWriteMask::Red | wgpu::ColorWriteMask::Green
| wgpu::ColorWriteMask::Blue | wgpu::ColorWriteMask::Alpha,
...
};
Strings in WebGPU are not NUL-terminated, and have an additional length attached to them.
To make this easier, webgpu-hpp uses wgpu::StringView
when handling strings. This type defines implicit conversions
from a lot of commonly-used types such as const char*
, std::string_view
and std::string
. It can also be converted
back to std::string_view
and std::string
using static_cast
.
You can manually create a string view by using the StringView(size_t length, const char* data)
constructor.
// Creating string views:
wgpu::DeviceDescriptor deviceDescriptor {
.label = "my-device",
};
// Using string views:
auto deviceLostCallback = [](wgpu::Device const&, wgpu::DeviceLostReason, wgpu::StringView message, void*, void*) {
spdlog::error("device lost: {}", static_cast<std::string_view>(message));
};
Similarly to strings, arrays in WebGPU are a combination of their size, and a pointer to their first element.
The C header defines the count and pointer separately in all cases. webgpu-hpp, however, combines them into a single
wgpu::Array<>
. This type defines implicit conversions from a lot of commonly-used types such as std::array<>
,
std::vector<>
and std::span<>
.
No conversion is defined for raw pointers, as they have no size information attached. These can be used with the
Array(size_t count, T* data)
constructor.
wgpu::RenderPassDescriptor renderPassDescriptor {
.colorAttachments = { 1, &colorAttachment },
};
auto buffers = std::array { buffer1, buffer2, buffer3 };
m_queue.submit(buffers);
Callbacks are defined using their corresponding CallbackInfo struct, which defines the function pointer, callback mode and user data.
Every callback function takes two user data void*
arguments, which are directly passed from the CallbackInfo struct.
Some callbacks also take a callback mode, in which case a mode
member is present in the info struct. When not
specified, this is defaulted to wgpu::CallbackMode::AllowSpontaneous
.
Callbacks in webgpu-hpp are somewhat limited. Notably, lambda captures are not supported!
// Simple callback.
wgpu::DeviceLostCallbackInfo deviceLostCallbackInfo {
.callback = [](wgpu::Device const&, wgpu::DeviceLostReason, wgpu::StringView message, void*, void*) {
spdlog::error("device lost: {}", static_cast<std::string_view>(message));
},
};
// More complex callback using user data pointers.
bool success = false;
wgpu::RequestDeviceCallbackInfo deviceCallbackInfo {
.callback = [](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message, void* devicePtr, void* success) {
if (status == wgpu::RequestDeviceStatus::Success) {
*static_cast<wgpu::Device*>(devicePtr) = device;
*static_cast<bool*>(success) = true;
} else {
spdlog::error("failed to request device: {}", static_cast<std::string_view>(message));
*static_cast<bool*>(success) = false;
} },
.userdata1 = &device,
.userdata2 = &success,
};
Object handles in webgpu-hpp define implicit conversations from and to their C equivalents, allowing them to be used interchangeably with functions expecting C-style handles. Enums and structs do not offer this implicit conversion, but can often be cast, as their memory layout is the same.
The WebGPU standard and webgpu-native both do not provide an easy way to create a wgpu::Surface
from a GLFW window.
This header contains a single function wgpu::glfw3::createWindowSurface(wgpu::Instance, GLFWwindow*)
, which will
create the window surface for the given GLFW window.
As this header has some source files attached, you'll need to link it to your project:
target_link_libraries(MyProject PRIVATE glfw webgpu-hpp webgpu-glfw3-hpp)
For this to work, make sure that the webgpu-cpp CMake file is evaluated after GLFW! This is so webgpu-glfw3-hpp can link against GLFW.
Warning
webgpu-platform.hpp
is currently very incomplete!
This header defines some non-standard WebGPU functions that are found in webgpu-native.
- Huge thanks to the excellent Learn WebGPU for C++ series by Élie Michel, and the projects surrounding it for inspiring this project!
- The wgpu-native team for creating and documenting the spec files used to generate the header.