react-native-webgpu 0.8.1 → 0.8.3
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/apple/WebGPUView.mm +22 -0
- package/cpp/rnwgpu/api/GPUDevice.cpp +144 -54
- package/cpp/rnwgpu/api/GPUDevice.h +48 -12
- package/cpp/rnwgpu/api/GPUFeatures.h +114 -185
- package/cpp/rnwgpu/api/descriptors/Unions.h +13 -315
- package/cpp/rnwgpu/async/RuntimeContext.h +26 -17
- package/cpp/webgpu/webgpu.h +86 -44
- package/cpp/webgpu/webgpu_cpp.h +556 -283
- package/cpp/webgpu/webgpu_cpp_chained_struct.h +1 -1
- package/cpp/webgpu/webgpu_cpp_print.h +91 -79
- package/cpp/webgpu/webgpu_enum_class_bitmasks.h +2 -1
- package/libs/android/arm64-v8a/libwebgpu_dawn.so +0 -0
- package/libs/android/armeabi-v7a/libwebgpu_dawn.so +0 -0
- package/libs/android/x86/libwebgpu_dawn.so +0 -0
- package/libs/android/x86_64/libwebgpu_dawn.so +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/Info.plist +6 -6
- package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +0 -0
- package/package.json +3 -3
package/apple/WebGPUView.mm
CHANGED
|
@@ -10,12 +10,29 @@
|
|
|
10
10
|
|
|
11
11
|
using namespace facebook::react;
|
|
12
12
|
|
|
13
|
+
static const std::shared_ptr<const WebGPUViewProps> &defaultWebGPUViewProps() {
|
|
14
|
+
static const auto props = std::make_shared<const WebGPUViewProps>();
|
|
15
|
+
return props;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
@implementation WebGPUView
|
|
14
19
|
|
|
15
20
|
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
16
21
|
return concreteComponentDescriptorProvider<WebGPUViewComponentDescriptor>();
|
|
17
22
|
}
|
|
18
23
|
|
|
24
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
25
|
+
if (self = [super initWithFrame:frame]) {
|
|
26
|
+
/*
|
|
27
|
+
The base class initializes _props with a plain ViewProps instance;
|
|
28
|
+
updateProps: casts _props to WebGPUViewProps, so it must hold our
|
|
29
|
+
concrete props type from the start (RN 0.87+ asserts on this).
|
|
30
|
+
*/
|
|
31
|
+
_props = defaultWebGPUViewProps();
|
|
32
|
+
}
|
|
33
|
+
return self;
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
- (void)prepareForRecycle {
|
|
20
37
|
[super prepareForRecycle];
|
|
21
38
|
/*
|
|
@@ -24,6 +41,11 @@ using namespace facebook::react;
|
|
|
24
41
|
the last usage in the new context.
|
|
25
42
|
*/
|
|
26
43
|
self.contentView = nil;
|
|
44
|
+
/*
|
|
45
|
+
Reset to the default props (contextId == 0) so the next mount always
|
|
46
|
+
detects a contextId change and configures a fresh Metal layer.
|
|
47
|
+
*/
|
|
48
|
+
_props = defaultWebGPUViewProps();
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
- (MetalView *)getContentView {
|
|
@@ -19,9 +19,35 @@
|
|
|
19
19
|
|
|
20
20
|
namespace rnwgpu {
|
|
21
21
|
|
|
22
|
+
namespace {
|
|
23
|
+
|
|
24
|
+
// Hidden own properties used by device.lost. The promise cache lives on the
|
|
25
|
+
// device wrapper (the spec requires `lost` to return a stable promise) and
|
|
26
|
+
// the resolve function lives on the promise object itself, so both are traced
|
|
27
|
+
// by the GC as part of the device's JS object graph instead of being rooted
|
|
28
|
+
// from C++ (issue #445).
|
|
29
|
+
constexpr const char *kLostPromiseProp = "__rnwgpuLostPromise";
|
|
30
|
+
constexpr const char *kLostResolveProp = "__rnwgpuLostResolve";
|
|
31
|
+
|
|
32
|
+
void defineHiddenProperty(jsi::Runtime &runtime, const jsi::Object &target,
|
|
33
|
+
const char *name, const jsi::Value &value) {
|
|
34
|
+
auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
|
|
35
|
+
auto defineProperty =
|
|
36
|
+
objectCtor.getPropertyAsFunction(runtime, "defineProperty");
|
|
37
|
+
jsi::Object descriptor(runtime);
|
|
38
|
+
descriptor.setProperty(runtime, "value", value);
|
|
39
|
+
descriptor.setProperty(runtime, "enumerable", false);
|
|
40
|
+
descriptor.setProperty(runtime, "writable", false);
|
|
41
|
+
descriptor.setProperty(runtime, "configurable", false);
|
|
42
|
+
defineProperty.call(runtime, target,
|
|
43
|
+
jsi::String::createFromUtf8(runtime, name), descriptor);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} // namespace
|
|
47
|
+
|
|
22
48
|
void GPUDevice::notifyDeviceLost(wgpu::DeviceLostReason reason,
|
|
23
49
|
std::string message) {
|
|
24
|
-
std::
|
|
50
|
+
std::vector<PendingLostPromise> toResolve;
|
|
25
51
|
std::shared_ptr<GPUDeviceLostInfo> info;
|
|
26
52
|
{
|
|
27
53
|
std::lock_guard<std::mutex> lock(_lostMutex);
|
|
@@ -32,22 +58,49 @@ void GPUDevice::notifyDeviceLost(wgpu::DeviceLostReason reason,
|
|
|
32
58
|
_lostSettled = true;
|
|
33
59
|
_lostInfo = std::make_shared<GPUDeviceLostInfo>(reason, std::move(message));
|
|
34
60
|
info = _lostInfo;
|
|
61
|
+
toResolve = std::move(_lostPromises);
|
|
62
|
+
_lostPromises.clear();
|
|
63
|
+
}
|
|
35
64
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_lostResolve.reset();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
_lostHandle.reset();
|
|
65
|
+
if (toResolve.empty()) {
|
|
66
|
+
return;
|
|
42
67
|
}
|
|
43
68
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
69
|
+
// getLost() only registers promises when the device's context has a
|
|
70
|
+
// CallInvoker (main JS runtime), so a non-empty list implies an invoker.
|
|
71
|
+
auto invoker = _async ? _async->callInvoker() : nullptr;
|
|
72
|
+
if (!invoker) {
|
|
73
|
+
return;
|
|
50
74
|
}
|
|
75
|
+
|
|
76
|
+
// Settle on the owning runtime's JS thread. The promises are held weakly:
|
|
77
|
+
// if the device graph was collected in the meantime there is nothing to do,
|
|
78
|
+
// since nobody could observe the resolution anyway. The shared_ptr is only
|
|
79
|
+
// there because jsi::WeakObject is move-only and std::function requires a
|
|
80
|
+
// copyable closure; it also ensures the WeakObjects are destroyed on the JS
|
|
81
|
+
// thread.
|
|
82
|
+
auto pending =
|
|
83
|
+
std::make_shared<std::vector<PendingLostPromise>>(std::move(toResolve));
|
|
84
|
+
invoker->invokeAsync([pending, info]() {
|
|
85
|
+
for (auto &entry : *pending) {
|
|
86
|
+
auto &runtime = *entry.runtime;
|
|
87
|
+
auto locked = entry.promise.lock(runtime);
|
|
88
|
+
if (!locked.isObject()) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
auto promiseObj = locked.getObject(runtime);
|
|
92
|
+
auto resolveProp = promiseObj.getProperty(runtime, kLostResolveProp);
|
|
93
|
+
if (!resolveProp.isObject() ||
|
|
94
|
+
!resolveProp.getObject(runtime).isFunction(runtime)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
auto resolveFn = resolveProp.getObject(runtime).getFunction(runtime);
|
|
98
|
+
resolveFn.call(runtime,
|
|
99
|
+
JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(
|
|
100
|
+
runtime, info));
|
|
101
|
+
}
|
|
102
|
+
pending->clear();
|
|
103
|
+
});
|
|
51
104
|
}
|
|
52
105
|
|
|
53
106
|
void GPUDevice::forceLossForTesting() {
|
|
@@ -523,47 +576,84 @@ std::unordered_set<std::string> GPUDevice::getFeatures() {
|
|
|
523
576
|
return result;
|
|
524
577
|
}
|
|
525
578
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
579
|
+
jsi::Value GPUDevice::getLost(jsi::Runtime &runtime,
|
|
580
|
+
const jsi::Object &wrapper) {
|
|
581
|
+
// The promise is cached on the wrapper, not natively: a strong native
|
|
582
|
+
// reference would be a GC root, keeping the promise's .then reactions (and
|
|
583
|
+
// anything they capture, e.g. a whole three.js renderer) alive forever
|
|
584
|
+
// (issue #445).
|
|
585
|
+
auto cached = wrapper.getProperty(runtime, kLostPromiseProp);
|
|
586
|
+
if (cached.isObject()) {
|
|
587
|
+
return cached;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
auto promiseCtor = runtime.global().getPropertyAsObject(runtime, "Promise");
|
|
591
|
+
|
|
592
|
+
std::shared_ptr<GPUDeviceLostInfo> settledInfo;
|
|
593
|
+
{
|
|
594
|
+
std::lock_guard<std::mutex> lock(_lostMutex);
|
|
595
|
+
if (_lostSettled) {
|
|
596
|
+
settledInfo = _lostInfo;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
jsi::Value promiseValue;
|
|
601
|
+
if (settledInfo) {
|
|
602
|
+
auto info = JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(
|
|
603
|
+
runtime, settledInfo);
|
|
604
|
+
promiseValue = promiseCtor.getPropertyAsFunction(runtime, "resolve")
|
|
605
|
+
.callWithThis(runtime, promiseCtor, info);
|
|
606
|
+
} else {
|
|
607
|
+
// new Promise(executor): the executor runs synchronously inside the
|
|
608
|
+
// constructor call, capturing the resolve function.
|
|
609
|
+
auto capturedResolve = std::make_shared<jsi::Value>();
|
|
610
|
+
auto executor = jsi::Function::createFromHostFunction(
|
|
611
|
+
runtime, jsi::PropNameID::forUtf8(runtime, "lostExecutor"), 2,
|
|
612
|
+
[capturedResolve](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
|
|
613
|
+
const jsi::Value *args, size_t count) -> jsi::Value {
|
|
614
|
+
if (count > 0) {
|
|
615
|
+
*capturedResolve = jsi::Value(rt, args[0]);
|
|
616
|
+
}
|
|
617
|
+
return jsi::Value::undefined();
|
|
618
|
+
});
|
|
619
|
+
auto promiseObj = promiseCtor.asFunction(runtime)
|
|
620
|
+
.callAsConstructor(runtime, executor)
|
|
621
|
+
.getObject(runtime);
|
|
622
|
+
|
|
623
|
+
// Stash the resolve function on the promise itself so the GC traces it as
|
|
624
|
+
// part of the promise graph.
|
|
625
|
+
defineHiddenProperty(runtime, promiseObj, kLostResolveProp,
|
|
626
|
+
*capturedResolve);
|
|
627
|
+
|
|
628
|
+
// Register a WEAK reference so notifyDeviceLost() can settle the promise
|
|
629
|
+
// if it is still alive. Only wired for the device's own runtime when a
|
|
630
|
+
// CallInvoker exists (main JS runtime): spontaneous events are delivered
|
|
631
|
+
// through that invoker. A promise created on another runtime stays
|
|
632
|
+
// pending, matching the previous best-effort behavior.
|
|
633
|
+
bool settledMeanwhile = false;
|
|
634
|
+
if (_async && _async->callInvoker() && &runtime == &_async->runtime()) {
|
|
635
|
+
std::lock_guard<std::mutex> lock(_lostMutex);
|
|
636
|
+
if (_lostSettled) {
|
|
637
|
+
// The device was lost between the check above and now.
|
|
638
|
+
settledMeanwhile = true;
|
|
639
|
+
settledInfo = _lostInfo;
|
|
640
|
+
} else {
|
|
641
|
+
_lostPromises.push_back(
|
|
642
|
+
PendingLostPromise{&runtime, jsi::WeakObject(runtime, promiseObj)});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (settledMeanwhile) {
|
|
646
|
+
auto resolveFn =
|
|
647
|
+
promiseObj.getPropertyAsFunction(runtime, kLostResolveProp);
|
|
648
|
+
resolveFn.call(runtime,
|
|
649
|
+
JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(
|
|
650
|
+
runtime, settledInfo));
|
|
651
|
+
}
|
|
652
|
+
promiseValue = jsi::Value(runtime, promiseObj);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
defineHiddenProperty(runtime, wrapper, kLostPromiseProp, promiseValue);
|
|
656
|
+
return promiseValue;
|
|
567
657
|
}
|
|
568
658
|
void GPUDevice::addEventListener(std::string type, jsi::Function callback) {
|
|
569
659
|
auto funcPtr = std::make_shared<jsi::Function>(std::move(callback));
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
#include "GPURenderPipelineDescriptor.h"
|
|
46
46
|
#include "GPUSampler.h"
|
|
47
47
|
#include "GPUSamplerDescriptor.h"
|
|
48
|
+
#include "GPUShaderModule.h"
|
|
49
|
+
#include "GPUShaderModuleDescriptor.h"
|
|
48
50
|
#include "GPUSharedFenceDescriptor.h"
|
|
49
51
|
#include "GPUSharedTextureMemory.h"
|
|
50
52
|
#include "GPUSharedTextureMemoryDescriptor.h"
|
|
51
|
-
#include "GPUShaderModule.h"
|
|
52
|
-
#include "GPUShaderModuleDescriptor.h"
|
|
53
53
|
#include "GPUSupportedLimits.h"
|
|
54
54
|
#include "GPUTexture.h"
|
|
55
55
|
#include "GPUTextureDescriptor.h"
|
|
@@ -121,8 +121,8 @@ public:
|
|
|
121
121
|
std::shared_ptr<GPUExternalTextureDescriptor> descriptor);
|
|
122
122
|
std::shared_ptr<GPUSharedTextureMemory> importSharedTextureMemory(
|
|
123
123
|
std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor);
|
|
124
|
-
std::shared_ptr<GPUSharedFence>
|
|
125
|
-
|
|
124
|
+
std::shared_ptr<GPUSharedFence>
|
|
125
|
+
importSharedFence(std::shared_ptr<GPUSharedFenceDescriptor> descriptor);
|
|
126
126
|
std::shared_ptr<GPUBindGroupLayout> createBindGroupLayout(
|
|
127
127
|
std::shared_ptr<GPUBindGroupLayoutDescriptor> descriptor);
|
|
128
128
|
std::shared_ptr<GPUPipelineLayout>
|
|
@@ -153,7 +153,7 @@ public:
|
|
|
153
153
|
std::unordered_set<std::string> getFeatures();
|
|
154
154
|
std::shared_ptr<GPUSupportedLimits> getLimits();
|
|
155
155
|
std::shared_ptr<GPUQueue> getQueue();
|
|
156
|
-
|
|
156
|
+
jsi::Value getLost(jsi::Runtime &runtime, const jsi::Object &wrapper);
|
|
157
157
|
void notifyDeviceLost(wgpu::DeviceLostReason reason, std::string message);
|
|
158
158
|
void notifyUncapturedError(wgpu::ErrorType type, std::string message);
|
|
159
159
|
void forceLossForTesting();
|
|
@@ -211,7 +211,30 @@ public:
|
|
|
211
211
|
installGetter(runtime, prototype, "features", &GPUDevice::getFeatures);
|
|
212
212
|
installGetter(runtime, prototype, "limits", &GPUDevice::getLimits);
|
|
213
213
|
installGetter(runtime, prototype, "queue", &GPUDevice::getQueue);
|
|
214
|
-
|
|
214
|
+
// `lost` is installed manually: the getter needs the wrapper object
|
|
215
|
+
// itself, because the promise (and its resolve function) are cached as
|
|
216
|
+
// hidden properties on the JS side so the GC traces them as part of the
|
|
217
|
+
// device graph. Holding them strongly from C++ would root the promise's
|
|
218
|
+
// .then reactions forever (issue #445).
|
|
219
|
+
{
|
|
220
|
+
auto lostGetter = jsi::Function::createFromHostFunction(
|
|
221
|
+
runtime, jsi::PropNameID::forUtf8(runtime, "get_lost"), 0,
|
|
222
|
+
[](jsi::Runtime &rt, const jsi::Value &thisVal,
|
|
223
|
+
const jsi::Value * /*args*/, size_t /*count*/) -> jsi::Value {
|
|
224
|
+
auto native = GPUDevice::fromValue(rt, thisVal);
|
|
225
|
+
return native->getLost(rt, thisVal.getObject(rt));
|
|
226
|
+
});
|
|
227
|
+
auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
|
|
228
|
+
auto defineProperty =
|
|
229
|
+
objectCtor.getPropertyAsFunction(runtime, "defineProperty");
|
|
230
|
+
jsi::Object descriptor(runtime);
|
|
231
|
+
descriptor.setProperty(runtime, "get", lostGetter);
|
|
232
|
+
descriptor.setProperty(runtime, "enumerable", true);
|
|
233
|
+
descriptor.setProperty(runtime, "configurable", true);
|
|
234
|
+
defineProperty.call(runtime, prototype,
|
|
235
|
+
jsi::String::createFromUtf8(runtime, "lost"),
|
|
236
|
+
descriptor);
|
|
237
|
+
}
|
|
215
238
|
installGetterSetter(runtime, prototype, "label", &GPUDevice::getLabel,
|
|
216
239
|
&GPUDevice::setLabel);
|
|
217
240
|
installMethod(runtime, prototype, "forceLossForTesting",
|
|
@@ -262,15 +285,28 @@ private:
|
|
|
262
285
|
wgpu::Device _instance;
|
|
263
286
|
std::shared_ptr<async::RuntimeContext> _async;
|
|
264
287
|
std::string _label;
|
|
265
|
-
// Guards the device-lost state below.
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
288
|
+
// Guards the device-lost state below. getLost() runs on a JS thread, but
|
|
289
|
+
// Dawn's AllowSpontaneous device-lost callback (and device destruction) can
|
|
290
|
+
// fire notifyDeviceLost() from other threads, so the mutex keeps these
|
|
291
|
+
// fields safe.
|
|
269
292
|
std::mutex _lostMutex;
|
|
270
|
-
std::optional<async::AsyncTaskHandle> _lostHandle;
|
|
271
293
|
std::shared_ptr<GPUDeviceLostInfo> _lostInfo;
|
|
272
294
|
bool _lostSettled = false;
|
|
273
|
-
|
|
295
|
+
// Pending `lost` promises, held WEAKLY. A strong native reference would be
|
|
296
|
+
// a GC root: the promise's .then reactions (three.js captures its whole
|
|
297
|
+
// renderer there) could never be collected, pinning every GPU wrapper of
|
|
298
|
+
// the scene and its reported external memory forever (issue #445). The
|
|
299
|
+
// resolve function lives as a hidden property on the promise object itself,
|
|
300
|
+
// so if the promise is still alive when the device is lost we can settle
|
|
301
|
+
// it; if it was collected, nobody could have observed the resolution.
|
|
302
|
+
// Entries are only added for the device's own runtime when a CallInvoker is
|
|
303
|
+
// available (main JS runtime), matching the best-effort contract for
|
|
304
|
+
// spontaneous events.
|
|
305
|
+
struct PendingLostPromise {
|
|
306
|
+
jsi::Runtime *runtime;
|
|
307
|
+
jsi::WeakObject promise;
|
|
308
|
+
};
|
|
309
|
+
std::vector<PendingLostPromise> _lostPromises;
|
|
274
310
|
|
|
275
311
|
// Event listeners storage - keyed by event type
|
|
276
312
|
// Each entry contains a vector of shared_ptr to functions
|