native-dawn 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +90 -0
- package/LICENSE +21 -0
- package/NOTICE +9 -0
- package/README.md +254 -2
- package/bin/native-dawn.js +12 -0
- package/include/native_dawn/sdl2.h +88 -0
- package/include/native_dawn/window.h +102 -0
- package/index.d.ts +56 -0
- package/index.js +13 -0
- package/package.json +75 -4
- package/paths.d.ts +18 -0
- package/paths.js +22 -0
- package/scripts/build.mjs +187 -0
- package/scripts/check-release.mjs +13 -0
- package/scripts/common.mjs +47 -0
- package/scripts/fetch-dependencies.py +47 -0
- package/scripts/install.mjs +77 -0
- package/scripts/package-binary.mjs +14 -0
- package/scripts/test-android.mjs +43 -0
- package/scripts/test-c.mjs +29 -0
- package/scripts/test-package.mjs +34 -0
- package/scripts/test.mjs +11 -0
- package/src/node/extensions.cpp +285 -0
- package/src/node/extensions.h +6 -0
- package/upstream.json +4 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// Additions to Dawn's Node bindings: window surfaces and raw handles.
|
|
2
|
+
// The SDL handle layouts follow @kmamal/sdl; see NOTICE.
|
|
3
|
+
#include "extensions.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
#include "native_dawn/window.h"
|
|
11
|
+
#include "src/dawn/node/binding/Converter.h"
|
|
12
|
+
#include "src/dawn/node/binding/GPUDevice.h"
|
|
13
|
+
#include "src/dawn/node/binding/GPUTexture.h"
|
|
14
|
+
|
|
15
|
+
namespace native_dawn {
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
#define FAIL_VOID(env, message) { Napi::Error::New(env, message).ThrowAsJavaScriptException(); return; }
|
|
19
|
+
#define FAIL_VALUE(env, message, value) { Napi::Error::New(env, message).ThrowAsJavaScriptException(); return value; }
|
|
20
|
+
|
|
21
|
+
template <typename T> T Read(const Napi::Buffer<uint8_t>& buffer, size_t offset) {
|
|
22
|
+
T value;
|
|
23
|
+
std::memcpy(&value, buffer.Data() + offset, sizeof(T));
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
bool KindFromString(const std::string& name, NativeDawnWindowKind& kind) {
|
|
28
|
+
if (name == "xlib" || name == "x11") kind = NATIVE_DAWN_WINDOW_XLIB;
|
|
29
|
+
else if (name == "wayland") kind = NATIVE_DAWN_WINDOW_WAYLAND;
|
|
30
|
+
else if (name == "win32" || name == "windows") kind = NATIVE_DAWN_WINDOW_WIN32;
|
|
31
|
+
else if (name == "metal-layer" || name == "cocoa") kind = NATIVE_DAWN_WINDOW_METAL_LAYER;
|
|
32
|
+
else return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// @kmamal/sdl's `window.native.gpu` buffer. On Linux, SDL 0.11.x writes
|
|
37
|
+
// {display, window} for both X11 and Wayland without saying which, so the
|
|
38
|
+
// caller must name the video driver; a tagged {uint64 kind, display, window}
|
|
39
|
+
// layout is also accepted.
|
|
40
|
+
bool WindowFromSDLBuffer(Napi::Env env, Napi::Buffer<uint8_t> data, Napi::Value driver, NativeDawnWindow& window) {
|
|
41
|
+
window = {};
|
|
42
|
+
#if defined(_WIN32)
|
|
43
|
+
if (data.Length() != 2 * sizeof(void*)) FAIL_VALUE(env, "Expected an SDL Windows GPU handle", false);
|
|
44
|
+
window.kind = NATIVE_DAWN_WINDOW_WIN32;
|
|
45
|
+
window.handle = Read<void*>(data, 0);
|
|
46
|
+
window.display = Read<void*>(data, sizeof(void*));
|
|
47
|
+
#elif defined(__APPLE__)
|
|
48
|
+
if (data.Length() != sizeof(void*)) FAIL_VALUE(env, "Expected an SDL Metal GPU handle", false);
|
|
49
|
+
window.kind = NATIVE_DAWN_WINDOW_METAL_LAYER;
|
|
50
|
+
window.handle = Read<void*>(data, 0);
|
|
51
|
+
#else
|
|
52
|
+
size_t offset = 0;
|
|
53
|
+
if (data.Length() == sizeof(uint64_t) + 2 * sizeof(void*)) {
|
|
54
|
+
const uint64_t tag = Read<uint64_t>(data, 0);
|
|
55
|
+
if (tag != NATIVE_DAWN_WINDOW_XLIB && tag != NATIVE_DAWN_WINDOW_WAYLAND)
|
|
56
|
+
FAIL_VALUE(env, "Unsupported SDL window subsystem", false);
|
|
57
|
+
window.kind = static_cast<NativeDawnWindowKind>(tag);
|
|
58
|
+
offset = sizeof(uint64_t);
|
|
59
|
+
} else if (data.Length() == 2 * sizeof(void*)) {
|
|
60
|
+
if (!driver.IsString())
|
|
61
|
+
FAIL_VALUE(env, "This SDL handle does not say whether it is X11 or Wayland; pass the video driver (sdl.info.drivers.video.current)", false);
|
|
62
|
+
const std::string name = driver.As<Napi::String>();
|
|
63
|
+
if (name == "x11") window.kind = NATIVE_DAWN_WINDOW_XLIB;
|
|
64
|
+
else if (name == "wayland") window.kind = NATIVE_DAWN_WINDOW_WAYLAND;
|
|
65
|
+
else FAIL_VALUE(env, "SDL video driver '" + name + "' cannot present WebGPU; use x11 or wayland", false);
|
|
66
|
+
} else {
|
|
67
|
+
FAIL_VALUE(env, "Expected an SDL X11 or Wayland GPU handle", false);
|
|
68
|
+
}
|
|
69
|
+
window.display = Read<void*>(data, offset);
|
|
70
|
+
const uintptr_t second = Read<uintptr_t>(data, offset + sizeof(void*));
|
|
71
|
+
if (window.kind == NATIVE_DAWN_WINDOW_XLIB) window.xid = second;
|
|
72
|
+
else window.handle = reinterpret_cast<void*>(second);
|
|
73
|
+
#endif
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
bool Pointer(Napi::Env env, Napi::Object object, const char* name, uint64_t& out) {
|
|
78
|
+
const auto value = object.Get(name);
|
|
79
|
+
out = 0;
|
|
80
|
+
if (value.IsUndefined() || value.IsNull()) return true;
|
|
81
|
+
if (value.IsBigInt()) {
|
|
82
|
+
bool lossless = false;
|
|
83
|
+
out = value.As<Napi::BigInt>().Uint64Value(&lossless);
|
|
84
|
+
if (!lossless) FAIL_VALUE(env, std::string(name) + " does not fit in 64 bits", false);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (value.IsNumber()) {
|
|
88
|
+
const double number = value.As<Napi::Number>().DoubleValue();
|
|
89
|
+
if (number < 0 || number > 9007199254740991.0 || number != static_cast<double>(static_cast<uint64_t>(number)))
|
|
90
|
+
FAIL_VALUE(env, std::string(name) + " must be a non-negative integer or BigInt", false);
|
|
91
|
+
out = static_cast<uint64_t>(number);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
FAIL_VALUE(env, std::string(name) + " must be a BigInt or number", false);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// { kind, display, handle, xid } with pointers as BigInt, for window libraries
|
|
98
|
+
// other than SDL.
|
|
99
|
+
bool WindowFromObject(Napi::Env env, Napi::Object object, NativeDawnWindow& window) {
|
|
100
|
+
window = {};
|
|
101
|
+
const auto kind = object.Get("kind");
|
|
102
|
+
if (!kind.IsString() || !KindFromString(kind.As<Napi::String>(), window.kind))
|
|
103
|
+
FAIL_VALUE(env, "window.kind must be 'xlib', 'wayland', 'win32' or 'metal-layer'", false);
|
|
104
|
+
uint64_t display, handle;
|
|
105
|
+
if (!Pointer(env, object, "display", display) || !Pointer(env, object, "handle", handle) || !Pointer(env, object, "xid", window.xid))
|
|
106
|
+
return false;
|
|
107
|
+
window.display = reinterpret_cast<void*>(static_cast<uintptr_t>(display));
|
|
108
|
+
window.handle = reinterpret_cast<void*>(static_cast<uintptr_t>(handle));
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
bool TextureFormat(Napi::Env env, Napi::Value value, wgpu::TextureFormat& native) {
|
|
113
|
+
wgpu::interop::GPUTextureFormat js;
|
|
114
|
+
auto result = wgpu::interop::FromJS(env, value, js);
|
|
115
|
+
if (!result) FAIL_VALUE(env, result.error, false);
|
|
116
|
+
wgpu::binding::Converter convert(env);
|
|
117
|
+
return convert(native, js);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
wgpu::Device UnwrapDevice(Napi::Value value) {
|
|
121
|
+
if (!value.IsObject()) return nullptr;
|
|
122
|
+
auto* binding = wgpu::interop::GPUDevice::Unwrap(value.As<Napi::Object>());
|
|
123
|
+
if (!binding) return nullptr;
|
|
124
|
+
return static_cast<wgpu::binding::GPUDevice*>(binding)->GetNativeDawnDevice();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class Surface final : public Napi::ObjectWrap<Surface> {
|
|
128
|
+
wgpu::Device device_;
|
|
129
|
+
wgpu::Adapter adapter_;
|
|
130
|
+
wgpu::Surface surface_;
|
|
131
|
+
bool configured_ = false;
|
|
132
|
+
|
|
133
|
+
bool Check(Napi::Env env) {
|
|
134
|
+
if (!surface_) FAIL_VALUE(env, "Surface has been destroyed", false);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public:
|
|
139
|
+
static void Init(Napi::Env env, Napi::Object exports) {
|
|
140
|
+
exports.Set("NativeSurface", DefineClass(env, "NativeSurface", {
|
|
141
|
+
InstanceMethod("configure", &Surface::Configure),
|
|
142
|
+
InstanceMethod("getCurrentTexture", &Surface::GetCurrentTexture),
|
|
143
|
+
InstanceMethod("present", &Surface::Present),
|
|
144
|
+
InstanceMethod("unconfigure", &Surface::Unconfigure),
|
|
145
|
+
InstanceMethod("destroy", &Surface::Destroy),
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// new NativeSurface(device, sdlGpuBuffer, videoDriver?) or
|
|
150
|
+
// new NativeSurface(device, { kind, display, handle, xid })
|
|
151
|
+
explicit Surface(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Surface>(info) {
|
|
152
|
+
const auto env = info.Env();
|
|
153
|
+
device_ = UnwrapDevice(info[0]);
|
|
154
|
+
if (!device_) FAIL_VOID(env, "Expected a GPUDevice from native-dawn");
|
|
155
|
+
NativeDawnWindow window;
|
|
156
|
+
if (info[1].IsBuffer()) {
|
|
157
|
+
if (!WindowFromSDLBuffer(env, info[1].As<Napi::Buffer<uint8_t>>(), info[2], window)) return;
|
|
158
|
+
} else if (info[1].IsObject()) {
|
|
159
|
+
if (!WindowFromObject(env, info[1].As<Napi::Object>(), window)) return;
|
|
160
|
+
} else {
|
|
161
|
+
FAIL_VOID(env, "Expected an SDL GPU handle buffer or a { kind, display, handle, xid } window");
|
|
162
|
+
}
|
|
163
|
+
if (const char* error = nativeDawnWindowError(&window)) FAIL_VOID(env, error);
|
|
164
|
+
adapter_ = device_.GetAdapter();
|
|
165
|
+
wgpu::Instance instance = adapter_.GetInstance();
|
|
166
|
+
surface_ = wgpu::Surface::Acquire(nativeDawnCreateSurface(instance.Get(), &window));
|
|
167
|
+
wgpu::SurfaceCapabilities caps{};
|
|
168
|
+
if (!surface_ || surface_.GetCapabilities(adapter_, &caps) != wgpu::Status::Success || caps.formatCount == 0) {
|
|
169
|
+
surface_ = nullptr;
|
|
170
|
+
FAIL_VOID(env, "This GPU adapter cannot present to the window");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
~Surface() override {
|
|
174
|
+
if (surface_ && configured_) surface_.Unconfigure();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
void Configure(const Napi::CallbackInfo& info) {
|
|
178
|
+
auto env = info.Env();
|
|
179
|
+
if (!Check(env)) return;
|
|
180
|
+
if (!info[0].IsObject()) FAIL_VOID(env, "Expected a configuration object");
|
|
181
|
+
auto config = info[0].As<Napi::Object>();
|
|
182
|
+
wgpu::SurfaceConfiguration desc{};
|
|
183
|
+
desc.device = device_;
|
|
184
|
+
desc.width = config.Get("width").As<Napi::Number>().Uint32Value();
|
|
185
|
+
desc.height = config.Get("height").As<Napi::Number>().Uint32Value();
|
|
186
|
+
if (!TextureFormat(env, config.Get("format"), desc.format)) return;
|
|
187
|
+
desc.usage = static_cast<wgpu::TextureUsage>(config.Get("usage").As<Napi::Number>().Uint32Value());
|
|
188
|
+
const std::string alpha = config.Get("alphaMode").IsString() ? config.Get("alphaMode").As<Napi::String>() : std::string("opaque");
|
|
189
|
+
if (alpha == "opaque") desc.alphaMode = wgpu::CompositeAlphaMode::Opaque;
|
|
190
|
+
else if (alpha == "premultiplied") desc.alphaMode = wgpu::CompositeAlphaMode::Premultiplied;
|
|
191
|
+
else FAIL_VOID(env, "Invalid alphaMode");
|
|
192
|
+
const std::string present = config.Get("presentMode").IsString() ? config.Get("presentMode").As<Napi::String>() : std::string("fifo");
|
|
193
|
+
if (present == "fifo") desc.presentMode = wgpu::PresentMode::Fifo;
|
|
194
|
+
else if (present == "immediate") desc.presentMode = wgpu::PresentMode::Immediate;
|
|
195
|
+
else if (present == "mailbox") desc.presentMode = wgpu::PresentMode::Mailbox;
|
|
196
|
+
else if (present == "fifoRelaxed") desc.presentMode = wgpu::PresentMode::FifoRelaxed;
|
|
197
|
+
else FAIL_VOID(env, "Invalid presentMode");
|
|
198
|
+
wgpu::SurfaceCapabilities caps{};
|
|
199
|
+
if (surface_.GetCapabilities(adapter_, &caps) != wgpu::Status::Success)
|
|
200
|
+
FAIL_VOID(env, "Cannot query surface capabilities");
|
|
201
|
+
if (std::find(caps.formats, caps.formats + caps.formatCount, desc.format) == caps.formats + caps.formatCount)
|
|
202
|
+
FAIL_VOID(env, "Texture format is unsupported by this surface");
|
|
203
|
+
if (std::find(caps.presentModes, caps.presentModes + caps.presentModeCount, desc.presentMode) == caps.presentModes + caps.presentModeCount)
|
|
204
|
+
FAIL_VOID(env, "Present mode is unsupported by this surface");
|
|
205
|
+
if (std::find(caps.alphaModes, caps.alphaModes + caps.alphaModeCount, desc.alphaMode) == caps.alphaModes + caps.alphaModeCount)
|
|
206
|
+
FAIL_VOID(env, "Alpha mode is unsupported by this surface");
|
|
207
|
+
if ((desc.usage & caps.usages) != desc.usage)
|
|
208
|
+
FAIL_VOID(env, "Texture usage is unsupported by this surface");
|
|
209
|
+
std::vector<wgpu::TextureFormat> views;
|
|
210
|
+
const auto jsViews = config.Get("viewFormats");
|
|
211
|
+
if (jsViews.IsArray()) {
|
|
212
|
+
auto array = jsViews.As<Napi::Array>();
|
|
213
|
+
for (uint32_t i = 0; i < array.Length(); ++i) {
|
|
214
|
+
wgpu::TextureFormat format;
|
|
215
|
+
if (!TextureFormat(env, array.Get(i), format)) return;
|
|
216
|
+
views.push_back(format);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
desc.viewFormats = views.data();
|
|
220
|
+
desc.viewFormatCount = views.size();
|
|
221
|
+
surface_.Configure(&desc);
|
|
222
|
+
configured_ = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Napi::Value GetCurrentTexture(const Napi::CallbackInfo& info) {
|
|
226
|
+
const auto env = info.Env();
|
|
227
|
+
if (!Check(env)) return env.Undefined();
|
|
228
|
+
if (!configured_) FAIL_VALUE(env, "Surface is not configured", env.Undefined());
|
|
229
|
+
wgpu::SurfaceTexture texture{};
|
|
230
|
+
surface_.GetCurrentTexture(&texture);
|
|
231
|
+
if (texture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal &&
|
|
232
|
+
texture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal)
|
|
233
|
+
FAIL_VALUE(env, "Could not acquire a surface texture (status " + std::to_string(static_cast<int>(texture.status)) + "); reconfigure and retry", env.Undefined());
|
|
234
|
+
return wgpu::interop::GPUTexture::Create<wgpu::binding::GPUTexture>(env, device_, wgpu::TextureDescriptor{}, std::move(texture.texture));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
void Present(const Napi::CallbackInfo& info) {
|
|
238
|
+
if (!Check(info.Env())) return;
|
|
239
|
+
if (!configured_) FAIL_VOID(info.Env(), "Surface is not configured");
|
|
240
|
+
if (surface_.Present() != wgpu::Status::Success) FAIL_VOID(info.Env(), "Surface presentation failed");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
void Unconfigure(const Napi::CallbackInfo&) {
|
|
244
|
+
if (surface_ && configured_) surface_.Unconfigure();
|
|
245
|
+
configured_ = false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
void Destroy(const Napi::CallbackInfo& info) {
|
|
249
|
+
Unconfigure(info);
|
|
250
|
+
surface_ = nullptr;
|
|
251
|
+
adapter_ = nullptr;
|
|
252
|
+
device_ = nullptr;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// The WGPUDevice behind a GPUDevice, as a BigInt, for native code in the same
|
|
257
|
+
// process. It stays valid while the GPUDevice is alive; native code that keeps
|
|
258
|
+
// it longer must call wgpuDeviceAddRef.
|
|
259
|
+
Napi::Value DeviceHandle(const Napi::CallbackInfo& info) {
|
|
260
|
+
const auto env = info.Env();
|
|
261
|
+
wgpu::Device device = UnwrapDevice(info[0]);
|
|
262
|
+
if (!device) FAIL_VALUE(env, "Expected a GPUDevice from native-dawn", env.Undefined());
|
|
263
|
+
return Napi::BigInt::New(env, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(device.Get())));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Test hook: loses the device as a driver reset would, so tests can check how
|
|
267
|
+
// device.lost is delivered. Not part of the public API.
|
|
268
|
+
Napi::Value ForceDeviceLoss(const Napi::CallbackInfo& info) {
|
|
269
|
+
const auto env = info.Env();
|
|
270
|
+
if (!info[0].IsObject()) FAIL_VALUE(env, "Expected a GPUDevice from native-dawn", env.Undefined());
|
|
271
|
+
auto* binding = wgpu::interop::GPUDevice::Unwrap(info[0].As<Napi::Object>());
|
|
272
|
+
if (!binding) FAIL_VALUE(env, "Expected a GPUDevice from native-dawn", env.Undefined());
|
|
273
|
+
static_cast<wgpu::binding::GPUDevice*>(binding)->ForceLoss(wgpu::DeviceLostReason::Unknown, "forced by native-dawn test hook");
|
|
274
|
+
return env.Undefined();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
} // namespace
|
|
278
|
+
|
|
279
|
+
void Initialize(Napi::Env env, Napi::Object exports) {
|
|
280
|
+
Surface::Init(env, exports);
|
|
281
|
+
exports.Set("deviceHandle", Napi::Function::New(env, DeviceHandle, "deviceHandle"));
|
|
282
|
+
exports.Set("_forceDeviceLoss", Napi::Function::New(env, ForceDeviceLoss, "_forceDeviceLoss"));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
} // namespace native_dawn
|
package/upstream.json
ADDED