Skip to content

Commit 55e93f2

Browse files
committed
[WebGPU] Add device abstraction via wgpu-native
WebGPUDevice wraps wgpu-native (Metal/Vulkan) behind a uniform C++ interface. Includes a setup script that downloads prebuilt wgpu-native binaries.
1 parent 0c155b0 commit 55e93f2

3 files changed

Lines changed: 269 additions & 0 deletions

File tree

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/backends/webgpu/runtime/WebGPUDevice.h>
10+
11+
#include <cstdio>
12+
#include <cstdlib>
13+
#include <stdexcept>
14+
15+
namespace executorch {
16+
namespace backends {
17+
namespace webgpu {
18+
19+
namespace {
20+
21+
struct AdapterResult {
22+
WGPUAdapter adapter = nullptr;
23+
bool done = false;
24+
};
25+
26+
struct DeviceResult {
27+
WGPUDevice device = nullptr;
28+
bool done = false;
29+
};
30+
31+
void on_adapter_request(
32+
WGPURequestAdapterStatus status,
33+
WGPUAdapter adapter,
34+
WGPUStringView message,
35+
void* userdata1,
36+
void* /*userdata2*/) {
37+
auto* result = static_cast<AdapterResult*>(userdata1);
38+
if (status == WGPURequestAdapterStatus_Success) {
39+
result->adapter = adapter;
40+
} else {
41+
fprintf(
42+
stderr,
43+
"WebGPU adapter request failed (status %d): %.*s\n",
44+
static_cast<int>(status),
45+
static_cast<int>(message.length),
46+
message.data);
47+
}
48+
result->done = true;
49+
}
50+
51+
void on_device_request(
52+
WGPURequestDeviceStatus status,
53+
WGPUDevice device,
54+
WGPUStringView message,
55+
void* userdata1,
56+
void* /*userdata2*/) {
57+
auto* result = static_cast<DeviceResult*>(userdata1);
58+
if (status == WGPURequestDeviceStatus_Success) {
59+
result->device = device;
60+
} else {
61+
fprintf(
62+
stderr,
63+
"WebGPU device request failed (status %d): %.*s\n",
64+
static_cast<int>(status),
65+
static_cast<int>(message.length),
66+
message.data);
67+
}
68+
result->done = true;
69+
}
70+
71+
void on_device_error(
72+
WGPUDevice const* /*device*/,
73+
WGPUErrorType type,
74+
WGPUStringView message,
75+
void* /*userdata1*/,
76+
void* /*userdata2*/) {
77+
fprintf(
78+
stderr,
79+
"WebGPU device error (type %d): %.*s\n",
80+
static_cast<int>(type),
81+
static_cast<int>(message.length),
82+
message.data);
83+
}
84+
85+
} // namespace
86+
87+
WebGPUContext create_webgpu_context() {
88+
WebGPUContext ctx;
89+
90+
ctx.instance = wgpuCreateInstance(nullptr);
91+
if (!ctx.instance) {
92+
throw std::runtime_error("Failed to create WebGPU instance");
93+
}
94+
95+
// Request adapter using AllowSpontaneous mode (fires during
96+
// wgpuInstanceProcessEvents or any other API call).
97+
AdapterResult adapter_result;
98+
WGPURequestAdapterCallbackInfo adapter_cb = {};
99+
adapter_cb.mode = WGPUCallbackMode_AllowSpontaneous;
100+
adapter_cb.callback = on_adapter_request;
101+
adapter_cb.userdata1 = &adapter_result;
102+
103+
wgpuInstanceRequestAdapter(ctx.instance, nullptr, adapter_cb);
104+
while (!adapter_result.done) {
105+
wgpuInstanceProcessEvents(ctx.instance);
106+
}
107+
108+
if (!adapter_result.adapter) {
109+
wgpuInstanceRelease(ctx.instance);
110+
ctx.instance = nullptr;
111+
throw std::runtime_error(
112+
"Failed to get WebGPU adapter. "
113+
"Ensure a GPU with Vulkan (Linux) or Metal (macOS) is available.");
114+
}
115+
ctx.adapter = adapter_result.adapter;
116+
117+
// Request device
118+
DeviceResult device_result;
119+
WGPURequestDeviceCallbackInfo device_cb = {};
120+
device_cb.mode = WGPUCallbackMode_AllowSpontaneous;
121+
device_cb.callback = on_device_request;
122+
device_cb.userdata1 = &device_result;
123+
124+
WGPUDeviceDescriptor device_desc = {};
125+
device_desc.uncapturedErrorCallbackInfo.callback = on_device_error;
126+
127+
wgpuAdapterRequestDevice(ctx.adapter, &device_desc, device_cb);
128+
while (!device_result.done) {
129+
wgpuInstanceProcessEvents(ctx.instance);
130+
}
131+
132+
if (!device_result.device) {
133+
wgpuAdapterRelease(ctx.adapter);
134+
wgpuInstanceRelease(ctx.instance);
135+
ctx.adapter = nullptr;
136+
ctx.instance = nullptr;
137+
throw std::runtime_error("Failed to get WebGPU device");
138+
}
139+
ctx.device = device_result.device;
140+
ctx.queue = wgpuDeviceGetQueue(ctx.device);
141+
142+
return ctx;
143+
}
144+
145+
namespace {
146+
WebGPUContext* g_default_context = nullptr;
147+
} // namespace
148+
149+
void set_default_webgpu_context(WebGPUContext* ctx) {
150+
g_default_context = ctx;
151+
}
152+
153+
WebGPUContext* get_default_webgpu_context() {
154+
return g_default_context;
155+
}
156+
157+
void destroy_webgpu_context(WebGPUContext& ctx) {
158+
if (ctx.queue) {
159+
wgpuQueueRelease(ctx.queue);
160+
ctx.queue = nullptr;
161+
}
162+
if (ctx.device) {
163+
wgpuDeviceRelease(ctx.device);
164+
ctx.device = nullptr;
165+
}
166+
if (ctx.adapter) {
167+
wgpuAdapterRelease(ctx.adapter);
168+
ctx.adapter = nullptr;
169+
}
170+
if (ctx.instance) {
171+
wgpuInstanceRelease(ctx.instance);
172+
ctx.instance = nullptr;
173+
}
174+
}
175+
176+
} // namespace webgpu
177+
} // namespace backends
178+
} // namespace executorch
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#pragma once
10+
11+
#include <webgpu/webgpu.h>
12+
13+
namespace executorch {
14+
namespace backends {
15+
namespace webgpu {
16+
17+
struct WebGPUContext {
18+
WGPUInstance instance = nullptr;
19+
WGPUAdapter adapter = nullptr;
20+
WGPUDevice device = nullptr;
21+
WGPUQueue queue = nullptr;
22+
};
23+
24+
WebGPUContext create_webgpu_context();
25+
void destroy_webgpu_context(WebGPUContext& ctx);
26+
27+
// Global context used by WebGPUGraph::build() when no device is pre-set.
28+
void set_default_webgpu_context(WebGPUContext* ctx);
29+
WebGPUContext* get_default_webgpu_context();
30+
31+
} // namespace webgpu
32+
} // namespace backends
33+
} // namespace executorch
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/bin/bash
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
# All rights reserved.
4+
#
5+
# This source code is licensed under the BSD-style license found in the
6+
# LICENSE file in the root directory of this source tree.
7+
8+
# Download prebuilt wgpu-native binaries for native (non-browser) WebGPU testing.
9+
# Usage: bash backends/webgpu/scripts/setup-wgpu-native.sh
10+
11+
set -euo pipefail
12+
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
WGPU_DIR="${SCRIPT_DIR}/../third-party/wgpu-native"
15+
16+
WGPU_VERSION="v27.0.4.0"
17+
WGPU_BASE_URL="https://github.com/gfx-rs/wgpu-native/releases/download/${WGPU_VERSION}"
18+
19+
if [[ -f "${WGPU_DIR}/lib/libwgpu_native.a" ]]; then
20+
echo "wgpu-native already installed at ${WGPU_DIR}"
21+
exit 0
22+
fi
23+
24+
OS="$(uname -s)"
25+
ARCH="$(uname -m)"
26+
27+
case "${OS}" in
28+
Darwin) PLATFORM="macos" ;;
29+
Linux) PLATFORM="linux" ;;
30+
*)
31+
echo "Unsupported OS: ${OS}"
32+
exit 1
33+
;;
34+
esac
35+
36+
case "${ARCH}" in
37+
x86_64) WGPU_ARCH="x86_64" ;;
38+
aarch64|arm64) WGPU_ARCH="aarch64" ;;
39+
*)
40+
echo "Unsupported architecture: ${ARCH}"
41+
exit 1
42+
;;
43+
esac
44+
45+
ZIP_NAME="wgpu-${PLATFORM}-${WGPU_ARCH}-release.zip"
46+
URL="${WGPU_BASE_URL}/${ZIP_NAME}"
47+
48+
echo "Downloading wgpu-native ${WGPU_VERSION} for ${PLATFORM}-${WGPU_ARCH}..."
49+
TMPDIR_DL="$(mktemp -d)"
50+
trap "rm -rf ${TMPDIR_DL}" EXIT
51+
52+
curl -sL "${URL}" -o "${TMPDIR_DL}/${ZIP_NAME}"
53+
54+
mkdir -p "${WGPU_DIR}"
55+
unzip -qo "${TMPDIR_DL}/${ZIP_NAME}" -d "${WGPU_DIR}"
56+
57+
echo "Installed wgpu-native to ${WGPU_DIR}"
58+
ls -la "${WGPU_DIR}/lib/"

0 commit comments

Comments
 (0)