Skip to content

Commit d39c06e

Browse files
committed
Add simple code.
1 parent b1c7a3a commit d39c06e

File tree

4 files changed

+233
-0
lines changed

4 files changed

+233
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[submodule "dawn"]
2+
path = dawn
3+
url = https://dawn.googlesource.com/dawn
4+
branch = chromium/7204 # Chrome 138
5+
shallow = true
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
cmake_minimum_required(VERSION 3.13) # CMake version check
2+
project(app) # Create project "app"
3+
set(CMAKE_CXX_STANDARD 20) # Enable C++20 standard
4+
5+
add_executable(app "main.cpp")
6+
7+
set(DAWN_FETCH_DEPENDENCIES ON)
8+
add_subdirectory("dawn" EXCLUDE_FROM_ALL)
9+
10+
if(EMSCRIPTEN)
11+
set_target_properties(app PROPERTIES SUFFIX ".html")
12+
target_link_libraries(app PRIVATE emdawnwebgpu_cpp webgpu_glfw)
13+
target_link_options(app PRIVATE "-sASYNCIFY=1" "-sUSE_GLFW=3")
14+
else()
15+
target_link_libraries(app PRIVATE dawn::webgpu_dawn glfw webgpu_glfw)
16+
endif()
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
![image](https://github.com/beaufortfrancois/webgpu-cross-platform-app/assets/634478/81579516-7390-4198-bb18-68e7f4cb34c3)
2+
3+
# WebGPU cross-platform app with CMake/Emscripten
4+
5+
This app is a <em>minimalistic</em> C++ example that shows how to use [WebGPU](https://gpuweb.github.io/gpuweb/) to build desktop and web apps from a single codebase. Under the hood, it uses WebGPU's [webgpu.h](https://github.com/webgpu-native/webgpu-headers/blob/main/webgpu.h) as a platform-agnostic hardware abstraction layer through a C++ wrapper called [webgpu_cpp.h](https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/include/webgpu/webgpu_cpp.h).
6+
7+
On the web, the app is built against [emdawnwebgpu](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/emdawnwebgpu/) (Emscripten Dawn WebGPU), which has bindings implementing webgpu.h on top of the JavaScript API. It uses [Emscripten](https://emscripten.org/), a tool for compiling C/C++ programs to WebAssembly. On specific platforms such as macOS or Windows, this project can be built against [Dawn](https://dawn.googlesource.com/dawn/), Chromium's cross-platform WebGPU implementation.
8+
9+
<p align="center" width="100%">
10+
:warning: The webgpu.h and webgpu_cpp.h APIs are not yet stabilized. :warning:
11+
</p>
12+
13+
## Setup
14+
15+
```sh
16+
# Clone repository and initialize submodules.
17+
git clone https://github.com/beaufortfrancois/webgpu-cross-platform-app.git
18+
cd webgpu-cross-platform-app/
19+
git submodule update --init
20+
```
21+
22+
## Requirements
23+
24+
<i>Instructions are for macOS; they will need to be adapted to work on Linux and Windows.</i>
25+
26+
```sh
27+
# Make sure CMake and Emscripten are installed.
28+
brew install cmake emscripten
29+
```
30+
31+
## Specific platform build
32+
33+
```sh
34+
# Build the app with CMake.
35+
cmake -B build && cmake --build build -j4
36+
37+
# Run the app.
38+
./build/app
39+
```
40+
41+
## Web build
42+
43+
```sh
44+
# Build the app with Emscripten.
45+
emcmake cmake -B build-web && cmake --build build-web -j4
46+
47+
# Run a server.
48+
npx http-server
49+
```
50+
51+
```sh
52+
# Open the web app.
53+
open http://127.0.0.1:8080/build-web/app.html
54+
```
55+
56+
### Debugging WebAssembly
57+
58+
When building the app, compile it with DWARF debug information included thanks to `emcmake cmake -DCMAKE_BUILD_TYPE=Debug -B build-web`. And make sure to install the [C/C++ DevTools Support (DWARF) Chrome extension](https://goo.gle/wasm-debugging-extension) to enable WebAssembly debugging in DevTools.
59+
60+
<img width="1112" alt="image" src="https://github.com/beaufortfrancois/webgpu-cross-platform-app/assets/634478/e82f2494-6b1a-4534-b9e3-0c04caeca96d">
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include <iostream>
2+
3+
#include <GLFW/glfw3.h>
4+
#if defined(__EMSCRIPTEN__)
5+
#include <emscripten/emscripten.h>
6+
#endif
7+
#include <dawn/webgpu_cpp_print.h>
8+
#include <webgpu/webgpu_cpp.h>
9+
#include <webgpu/webgpu_glfw.h>
10+
11+
wgpu::Instance instance;
12+
wgpu::Adapter adapter;
13+
wgpu::Device device;
14+
wgpu::RenderPipeline pipeline;
15+
16+
wgpu::Surface surface;
17+
wgpu::TextureFormat format;
18+
const uint32_t kWidth = 512;
19+
const uint32_t kHeight = 512;
20+
21+
void ConfigureSurface() {
22+
wgpu::SurfaceCapabilities capabilities;
23+
surface.GetCapabilities(adapter, &capabilities);
24+
format = capabilities.formats[0];
25+
26+
wgpu::SurfaceConfiguration config{.device = device,
27+
.format = format,
28+
.width = kWidth,
29+
.height = kHeight,
30+
.presentMode = wgpu::PresentMode::Fifo};
31+
surface.Configure(&config);
32+
}
33+
34+
void Init() {
35+
wgpu::InstanceDescriptor instanceDesc{
36+
.capabilities = {.timedWaitAnyEnable = true}};
37+
instance = wgpu::CreateInstance(&instanceDesc);
38+
39+
wgpu::Future f1 = instance.RequestAdapter(
40+
nullptr, wgpu::CallbackMode::WaitAnyOnly,
41+
[](wgpu::RequestAdapterStatus status, wgpu::Adapter a,
42+
wgpu::StringView message) {
43+
if (status != wgpu::RequestAdapterStatus::Success) {
44+
std::cout << "RequestAdapter: " << message << "\n";
45+
exit(0);
46+
}
47+
adapter = std::move(a);
48+
});
49+
instance.WaitAny(f1, UINT64_MAX);
50+
51+
wgpu::DeviceDescriptor desc{};
52+
desc.SetUncapturedErrorCallback([](const wgpu::Device&,
53+
wgpu::ErrorType errorType,
54+
wgpu::StringView message) {
55+
std::cout << "Error: " << errorType << " - message: " << message << "\n";
56+
});
57+
58+
wgpu::Future f2 = adapter.RequestDevice(
59+
&desc, wgpu::CallbackMode::WaitAnyOnly,
60+
[](wgpu::RequestDeviceStatus status, wgpu::Device d,
61+
wgpu::StringView message) {
62+
if (status != wgpu::RequestDeviceStatus::Success) {
63+
std::cout << "RequestDevice: " << message << "\n";
64+
exit(0);
65+
}
66+
device = std::move(d);
67+
});
68+
instance.WaitAny(f2, UINT64_MAX);
69+
}
70+
71+
const char shaderCode[] = R"(
72+
@vertex fn vertexMain(@builtin(vertex_index) i : u32) ->
73+
@builtin(position) vec4f {
74+
const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1));
75+
return vec4f(pos[i], 0, 1);
76+
}
77+
@fragment fn fragmentMain() -> @location(0) vec4f {
78+
return vec4f(1, 0, 0, 1);
79+
}
80+
)";
81+
82+
void CreateRenderPipeline() {
83+
wgpu::ShaderSourceWGSL wgsl{{.code = shaderCode}};
84+
85+
wgpu::ShaderModuleDescriptor shaderModuleDescriptor{.nextInChain = &wgsl};
86+
wgpu::ShaderModule shaderModule =
87+
device.CreateShaderModule(&shaderModuleDescriptor);
88+
89+
wgpu::ColorTargetState colorTargetState{.format = format};
90+
91+
wgpu::FragmentState fragmentState{
92+
.module = shaderModule, .targetCount = 1, .targets = &colorTargetState};
93+
94+
wgpu::RenderPipelineDescriptor descriptor{.vertex = {.module = shaderModule},
95+
.fragment = &fragmentState};
96+
pipeline = device.CreateRenderPipeline(&descriptor);
97+
}
98+
99+
void Render() {
100+
wgpu::SurfaceTexture surfaceTexture;
101+
surface.GetCurrentTexture(&surfaceTexture);
102+
103+
wgpu::RenderPassColorAttachment attachment{
104+
.view = surfaceTexture.texture.CreateView(),
105+
.loadOp = wgpu::LoadOp::Clear,
106+
.storeOp = wgpu::StoreOp::Store};
107+
108+
wgpu::RenderPassDescriptor renderpass{.colorAttachmentCount = 1,
109+
.colorAttachments = &attachment};
110+
111+
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
112+
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
113+
pass.SetPipeline(pipeline);
114+
pass.Draw(3);
115+
pass.End();
116+
wgpu::CommandBuffer commands = encoder.Finish();
117+
device.GetQueue().Submit(1, &commands);
118+
}
119+
120+
void InitGraphics() {
121+
ConfigureSurface();
122+
CreateRenderPipeline();
123+
}
124+
125+
void Start() {
126+
if (!glfwInit()) {
127+
return;
128+
}
129+
130+
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
131+
GLFWwindow* window =
132+
glfwCreateWindow(kWidth, kHeight, "WebGPU window", nullptr, nullptr);
133+
surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
134+
135+
InitGraphics();
136+
137+
#if defined(__EMSCRIPTEN__)
138+
emscripten_set_main_loop(Render, 0, false);
139+
#else
140+
while (!glfwWindowShouldClose(window)) {
141+
glfwPollEvents();
142+
Render();
143+
surface.Present();
144+
instance.ProcessEvents();
145+
}
146+
#endif
147+
}
148+
149+
int main() {
150+
Init();
151+
Start();
152+
}

0 commit comments

Comments
 (0)