react-native-webgpu 0.8.2 → 0.8.4

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.
Files changed (41) hide show
  1. package/apple/WebGPUView.mm +22 -0
  2. package/cpp/rnwgpu/api/GPUDevice.cpp +144 -54
  3. package/cpp/rnwgpu/api/GPUDevice.h +48 -12
  4. package/cpp/rnwgpu/async/RuntimeContext.h +26 -17
  5. package/lib/commonjs/index.js +11 -10
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/install.js +64 -17
  8. package/lib/commonjs/install.js.map +1 -1
  9. package/lib/commonjs/main/index.js +3 -0
  10. package/lib/commonjs/main/index.js.map +1 -1
  11. package/lib/commonjs/mock.js +0 -12
  12. package/lib/commonjs/mock.js.map +1 -1
  13. package/lib/module/index.js +1 -2
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/install.js +61 -16
  16. package/lib/module/install.js.map +1 -1
  17. package/lib/module/main/index.js +3 -0
  18. package/lib/module/main/index.js.map +1 -1
  19. package/lib/module/mock.js +0 -1
  20. package/lib/module/mock.js.map +1 -1
  21. package/lib/typescript/lib/commonjs/index.d.ts +1 -0
  22. package/lib/typescript/lib/commonjs/install.d.ts +3 -30
  23. package/lib/typescript/lib/commonjs/install.d.ts.map +1 -1
  24. package/lib/typescript/lib/commonjs/mock.d.ts.map +1 -1
  25. package/lib/typescript/lib/module/index.d.ts +1 -1
  26. package/lib/typescript/lib/module/install.d.ts +1 -0
  27. package/lib/typescript/lib/module/install.d.ts.map +1 -1
  28. package/lib/typescript/lib/module/mock.d.ts +0 -1
  29. package/lib/typescript/lib/module/mock.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +1 -1
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/install.d.ts +33 -16
  33. package/lib/typescript/src/install.d.ts.map +1 -1
  34. package/lib/typescript/src/main/index.d.ts.map +1 -1
  35. package/lib/typescript/src/mock.d.ts +0 -1
  36. package/lib/typescript/src/mock.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/index.tsx +1 -1
  39. package/src/install.ts +56 -16
  40. package/src/main/index.tsx +3 -0
  41. package/src/mock.ts +0 -1
@@ -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::optional<async::AsyncTaskHandle::ResolveFunction> resolveToCall;
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
- if (_lostResolve.has_value()) {
37
- resolveToCall = std::move(*_lostResolve);
38
- _lostResolve.reset();
39
- }
40
-
41
- _lostHandle.reset();
65
+ if (toResolve.empty()) {
66
+ return;
42
67
  }
43
68
 
44
- // Settle outside the lock: resolve() only enqueues onto the JS thread.
45
- if (resolveToCall.has_value()) {
46
- (*resolveToCall)([info](jsi::Runtime &runtime) mutable {
47
- return JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(runtime,
48
- info);
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
- async::AsyncTaskHandle GPUDevice::getLost() {
527
- // Held across the whole body: the postTask callback below runs synchronously
528
- // on this (JS) thread and touches the same _lost* fields, so it must not
529
- // re-lock. notifyDeviceLost() takes the same lock from its (possibly worker)
530
- // thread.
531
- std::lock_guard<std::mutex> lock(_lostMutex);
532
- if (_lostHandle.has_value()) {
533
- return *_lostHandle;
534
- }
535
-
536
- if (_lostSettled && _lostInfo) {
537
- return _async->postTask(
538
- [info = _lostInfo](
539
- const async::AsyncTaskHandle::ResolveFunction &resolve,
540
- const async::AsyncTaskHandle::RejectFunction & /*reject*/) {
541
- resolve([info](jsi::Runtime &runtime) mutable {
542
- return JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(
543
- runtime, info);
544
- });
545
- },
546
- /*keepPumping=*/false);
547
- }
548
-
549
- auto handle = _async->postTask(
550
- [this](const async::AsyncTaskHandle::ResolveFunction &resolve,
551
- const async::AsyncTaskHandle::RejectFunction & /*reject*/) {
552
- if (_lostSettled && _lostInfo) {
553
- resolve([info = _lostInfo](jsi::Runtime &runtime) mutable {
554
- return JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(
555
- runtime, info);
556
- });
557
- return;
558
- }
559
-
560
- // Resolved later from notifyDeviceLost().
561
- _lostResolve = resolve;
562
- },
563
- /*keepPumping=*/false);
564
-
565
- _lostHandle = handle;
566
- return handle;
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> importSharedFence(
125
- std::shared_ptr<GPUSharedFenceDescriptor> descriptor);
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
- async::AsyncTaskHandle getLost();
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
- installGetter(runtime, prototype, "lost", &GPUDevice::getLost);
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. In the ProcessEvents model both
266
- // notifyDeviceLost() (fired by Dawn during ProcessEvents) and getLost() run on
267
- // the owning runtime's own thread, but device destruction can also trigger
268
- // notifyDeviceLost() synchronously, so the mutex keeps these fields safe.
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
- std::optional<async::AsyncTaskHandle::ResolveFunction> _lostResolve;
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
@@ -36,14 +36,18 @@ namespace rnwgpu::async {
36
36
  * The pump only runs while at least one "pumping" task is outstanding, so it
37
37
  * costs nothing when idle and stops cleanly.
38
38
  *
39
- * Spontaneous events (keepPumping = false): events that may fire at any time,
40
- * independent of any request/response op (today only GPUDevice::getLost, whose
41
- * Dawn callback is registered AllowSpontaneous). These are NOT driven by the
42
- * pump. Instead their settle is marshalled onto the owning runtime's JS thread
43
- * via that runtime's CallInvoker, which is wired only for the MAIN JS runtime
44
- * (callInvoker()). A device created on a worklet runtime has no invoker, so its
45
- * device.lost is best-effort and may never fire. See the README "Threading
46
- * model" section.
39
+ * Spontaneous events: events that may fire at any time, independent of any
40
+ * request/response op (today only device.lost, whose Dawn callback is
41
+ * registered AllowSpontaneous). These are NOT driven by the pump and do NOT go
42
+ * through postTask: a task that may never settle must not hold the JS promise
43
+ * strongly from C++, or the promise (and everything reachable from its .then
44
+ * reactions) becomes a permanent GC root (issue #445). Instead GPUDevice keeps
45
+ * a jsi::WeakObject to the promise and settles it on the owning runtime's JS
46
+ * thread via that runtime's CallInvoker, which is wired only for the MAIN JS
47
+ * runtime (callInvoker()). A device created on a worklet runtime has no
48
+ * invoker, so its device.lost is best-effort and may never fire. See the
49
+ * README "Threading model" section. The keepPumping=false path in postTask is
50
+ * currently unused; it remains for tasks that settle without Dawn's pump.
47
51
  *
48
52
  * Shared-instance safety (mailbox): multiple runtimes may share one
49
53
  * wgpu::Instance. ProcessEvents() drains the whole instance queue and fires
@@ -78,9 +82,10 @@ public:
78
82
  static std::shared_ptr<RuntimeContext> getOrCreate(jsi::Runtime &runtime,
79
83
  wgpu::Instance instance);
80
84
 
81
- // Register the main JS runtime and its CallInvoker. The RuntimeContext created
82
- // for this runtime gets the invoker (callInvoker() returns it); every other
83
- // runtime's context returns null. Called once from RNWebGPUManager on install.
85
+ // Register the main JS runtime and its CallInvoker. The RuntimeContext
86
+ // created for this runtime gets the invoker (callInvoker() returns it); every
87
+ // other runtime's context returns null. Called once from RNWebGPUManager on
88
+ // install.
84
89
  static void
85
90
  registerMainRuntime(jsi::Runtime *runtime,
86
91
  std::shared_ptr<facebook::react::CallInvoker> invoker);
@@ -95,17 +100,21 @@ public:
95
100
  // The wgpu::Instance bound to this runtime.
96
101
  wgpu::Instance instance() const { return _instance; }
97
102
 
103
+ // The runtime this context belongs to. Safe for pointer-identity checks from
104
+ // any thread; only touch JSI through it on the runtime's own thread.
105
+ jsi::Runtime &runtime() const { return _runtime; }
106
+
98
107
  AsyncTaskHandle postTask(const TaskCallback &callback,
99
108
  bool keepPumping = true);
100
109
 
101
- // Deposit a settle-action to run on THIS context's runtime thread. Thread-safe
102
- // (callable from any thread, e.g. another runtime that pumped ProcessEvents).
103
- // The job must not touch JSI until it runs (it runs during drainMailbox on the
104
- // owning thread).
110
+ // Deposit a settle-action to run on THIS context's runtime thread.
111
+ // Thread-safe (callable from any thread, e.g. another runtime that pumped
112
+ // ProcessEvents). The job must not touch JSI until it runs (it runs during
113
+ // drainMailbox on the owning thread).
105
114
  void postSettle(std::function<void()> job);
106
115
 
107
- // Invoked by a drained settle-action when its task settles. Runs on the owning
108
- // runtime's thread.
116
+ // Invoked by a drained settle-action when its task settles. Runs on the
117
+ // owning runtime's thread.
109
118
  void onTaskSettled(bool keepPumping);
110
119
 
111
120
  private:
@@ -3,9 +3,19 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ var _exportNames = {
7
+ installWebGPU: true
8
+ };
9
+ Object.defineProperty(exports, "installWebGPU", {
10
+ enumerable: true,
11
+ get: function () {
12
+ return _install.installWebGPU;
13
+ }
14
+ });
6
15
  var _main = require("./main");
7
16
  Object.keys(_main).forEach(function (key) {
8
17
  if (key === "default" || key === "__esModule") return;
18
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
9
19
  if (key in exports && exports[key] === _main[key]) return;
10
20
  Object.defineProperty(exports, key, {
11
21
  enumerable: true,
@@ -17,6 +27,7 @@ Object.keys(_main).forEach(function (key) {
17
27
  var _constants = require("./constants");
18
28
  Object.keys(_constants).forEach(function (key) {
19
29
  if (key === "default" || key === "__esModule") return;
30
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
20
31
  if (key in exports && exports[key] === _constants[key]) return;
21
32
  Object.defineProperty(exports, key, {
22
33
  enumerable: true,
@@ -26,14 +37,4 @@ Object.keys(_constants).forEach(function (key) {
26
37
  });
27
38
  });
28
39
  var _install = require("./install");
29
- Object.keys(_install).forEach(function (key) {
30
- if (key === "default" || key === "__esModule") return;
31
- if (key in exports && exports[key] === _install[key]) return;
32
- Object.defineProperty(exports, key, {
33
- enumerable: true,
34
- get: function () {
35
- return _install[key];
36
- }
37
- });
38
- });
39
40
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_main","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_constants","_install"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;AAcA,IAAAA,KAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,KAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,KAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,KAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,UAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,UAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,UAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,UAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,QAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,QAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,QAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,QAAA,CAAAN,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
1
+ {"version":3,"names":["_main","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_constants","_install"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;;;;AAcA,IAAAA,KAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,KAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAC,YAAA,EAAAJ,GAAA;EAAA,IAAAA,GAAA,IAAAK,OAAA,IAAAA,OAAA,CAAAL,GAAA,MAAAL,KAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAS,cAAA,CAAAD,OAAA,EAAAL,GAAA;IAAAO,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAb,KAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAS,UAAA,GAAAb,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAW,UAAA,EAAAV,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAC,YAAA,EAAAJ,GAAA;EAAA,IAAAA,GAAA,IAAAK,OAAA,IAAAA,OAAA,CAAAL,GAAA,MAAAS,UAAA,CAAAT,GAAA;EAAAH,MAAA,CAAAS,cAAA,CAAAD,OAAA,EAAAL,GAAA;IAAAO,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,UAAA,CAAAT,GAAA;IAAA;EAAA;AAAA;AACA,IAAAU,QAAA,GAAAd,OAAA","ignoreList":[]}
@@ -3,8 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.installWebGPU = void 0;
6
+ exports.provideGPUForInstall = exports.installWebGPU = void 0;
7
7
  var _constants = require("./constants");
8
+ /// <reference types="@webgpu/types" />
9
+
8
10
  // Globals that this function installs on the calling runtime. These are the
9
11
  // native-derived flag constants re-exported from `./constants` (a single source
10
12
  // of truth, matching the native `wgpu::*Usage` enums), so they are safe to set
@@ -17,38 +19,69 @@ const constants = {
17
19
  GPUMapMode: _constants.GPUMapMode
18
20
  };
19
21
 
22
+ // The GPU instance destined for `navigator.gpu` on other runtimes, wrapped in
23
+ // a holder object. It cannot be read at module evaluation: this module can be
24
+ // evaluated before the native install has populated `RNWebGPU.gpu`, so
25
+ // `main/index.tsx` fills the holder right after installing. The holder itself
26
+ // is captured into the `installWebGPU` worklet closure; when a worklet is
27
+ // serialized (always after startup, so after the holder is filled), the
28
+ // Worklets custom serializer boxes the GPU object inside it, and unboxing on
29
+ // the target runtime installs the GPU prototype there.
30
+ const holder = {};
31
+
32
+ /**
33
+ * @internal Called once by `main/index.tsx` after the native install, so
34
+ * `installWebGPU()` can put `navigator.gpu` on other runtimes.
35
+ */
36
+ const provideGPUForInstall = gpu => {
37
+ holder.gpu = gpu;
38
+ };
39
+
20
40
  /**
21
41
  * Install WebGPU on the runtime that calls it.
22
42
  *
23
- * The native module installs the WebGPU flag constants (`GPUBufferUsage`,
24
- * `GPUTextureUsage`, `GPUShaderStage`, `GPUColorWrite`, `GPUMapMode`) as globals
25
- * on the main JS runtime, but worklet runtimes (Reanimated UI, dedicated worklet
26
- * runtimes, Vision Camera frame processors) start without them, so referencing
27
- * the bare global inside a worklet yields `undefined`.
43
+ * The native module sets up WebGPU on the main JS runtime, but worklet
44
+ * runtimes (Reanimated UI, dedicated worklet runtimes, Vision Camera frame
45
+ * processors) start without it: `navigator.gpu` and the flag constants
46
+ * (`GPUBufferUsage`, `GPUTextureUsage`, `GPUShaderStage`, `GPUColorWrite`,
47
+ * `GPUMapMode`) are all `undefined` there.
28
48
  *
29
- * Call `installWebGPU()` once at the top of a worklet to make those globals
30
- * available there, instead of importing each constant by hand:
49
+ * Call `installWebGPU()` once at the top of a worklet to make them available:
31
50
  *
32
51
  * ```tsx
33
52
  * import { installWebGPU } from "react-native-webgpu";
34
53
  *
35
- * const work = (device: GPUDevice) => {
54
+ * const work = () => {
36
55
  * "worklet";
37
56
  * installWebGPU();
38
- * device.createBuffer({
39
- * usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
57
+ * globalThis.navigator.gpu.requestAdapter().then((adapter) => {
58
+ * // ...
40
59
  * });
41
60
  * };
42
61
  * ```
43
62
  *
44
- * The constants are captured into the worklet by closure (the same way a shader
45
- * string is), so they work on every runtime. Calling it on a runtime that
46
- * already has the globals (e.g. the main JS runtime) is a safe no-op.
63
+ * Known limitations:
64
+ *
65
+ * - Read `navigator` through `globalThis.navigator`: the Worklets Babel
66
+ * plugin does not treat a bare `navigator` as a known global, so it would
67
+ * capture the main runtime's `navigator` object by closure instead of
68
+ * reading the one this function installed. Fixed upstream in
69
+ * react-native-reanimated#10364; the prefix becomes unnecessary on
70
+ * react-native-worklets versions that include it.
71
+ * - Spontaneous events are main-runtime only: on a device created on a
72
+ * worklet runtime, `device.lost` never settles (unless the device is
73
+ * already lost when read) and `uncapturederror` listeners never fire.
74
+ * Observe those on a device created on the main JS thread.
47
75
  *
48
- * This is the explicit entry point for runtime setup; for now it only installs
49
- * the flag constants, but it is the place where other per-runtime WebGPU setup
50
- * (e.g. `navigator.gpu`) can be wired in later.
76
+ * Everything is captured into the worklet by closure: the constants like a
77
+ * shader string would be, and the GPU object through the Worklets custom
78
+ * serializer, which installs the native prototypes on the target runtime when
79
+ * it crosses. Promises returned by `requestAdapter`/`requestDevice` settle on
80
+ * the calling runtime (each runtime gets its own async pump). Calling it on a
81
+ * runtime that already has the globals (e.g. the main JS runtime) is a safe
82
+ * no-op.
51
83
  */
84
+ exports.provideGPUForInstall = provideGPUForInstall;
52
85
  const installWebGPU = () => {
53
86
  "worklet";
54
87
 
@@ -58,6 +91,20 @@ const installWebGPU = () => {
58
91
  g[key] = value;
59
92
  }
60
93
  }
94
+ const {
95
+ gpu
96
+ } = holder;
97
+ if (gpu !== undefined) {
98
+ const nav = g.navigator;
99
+ if (nav === undefined) {
100
+ g.navigator = {
101
+ gpu,
102
+ userAgent: "react-native"
103
+ };
104
+ } else if (nav.gpu === undefined) {
105
+ nav.gpu = gpu;
106
+ }
107
+ }
61
108
  };
62
109
  exports.installWebGPU = installWebGPU;
63
110
  //# sourceMappingURL=install.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_constants","require","constants","GPUBufferUsage","GPUTextureUsage","GPUShaderStage","GPUColorWrite","GPUMapMode","installWebGPU","g","globalThis","key","value","Object","entries","undefined","exports"],"sourceRoot":"../../src","sources":["install.ts"],"mappings":";;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AAQA;AACA;AACA;AACA;AACA,MAAMC,SAAS,GAAG;EAChBC,cAAc,EAAdA,yBAAc;EACdC,eAAe,EAAfA,0BAAe;EACfC,cAAc,EAAdA,yBAAc;EACdC,aAAa,EAAbA,wBAAa;EACbC,UAAU,EAAVA;AACF,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMC,aAAa,GAAGA,CAAA,KAAM;EACjC,SAAS;;EACT,MAAMC,CAAC,GAAGC,UAAgD;EAC1D,KAAK,MAAM,CAACC,GAAG,EAAEC,KAAK,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACZ,SAAS,CAAC,EAAE;IACpD,IAAIO,CAAC,CAACE,GAAG,CAAC,KAAKI,SAAS,EAAE;MACxBN,CAAC,CAACE,GAAG,CAAC,GAAGC,KAAK;IAChB;EACF;AACF,CAAC;AAACI,OAAA,CAAAR,aAAA,GAAAA,aAAA","ignoreList":[]}
1
+ {"version":3,"names":["_constants","require","constants","GPUBufferUsage","GPUTextureUsage","GPUShaderStage","GPUColorWrite","GPUMapMode","holder","provideGPUForInstall","gpu","exports","installWebGPU","g","globalThis","key","value","Object","entries","undefined","nav","navigator","userAgent"],"sourceRoot":"../../src","sources":["install.ts"],"mappings":";;;;;;AACA,IAAAA,UAAA,GAAAC,OAAA;AADA;;AASA;AACA;AACA;AACA;AACA,MAAMC,SAAS,GAAG;EAChBC,cAAc,EAAdA,yBAAc;EACdC,eAAe,EAAfA,0BAAe;EACfC,cAAc,EAAdA,yBAAc;EACdC,aAAa,EAAbA,wBAAa;EACbC,UAAU,EAAVA;AACF,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,MAAqB,GAAG,CAAC,CAAC;;AAEhC;AACA;AACA;AACA;AACO,MAAMC,oBAAoB,GAAIC,GAAQ,IAAK;EAChDF,MAAM,CAACE,GAAG,GAAGA,GAAG;AAClB,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3CAC,OAAA,CAAAF,oBAAA,GAAAA,oBAAA;AA4CO,MAAMG,aAAa,GAAGA,CAAA,KAAM;EACjC,SAAS;;EACT,MAAMC,CAAC,GAAGC,UAAgD;EAC1D,KAAK,MAAM,CAACC,GAAG,EAAEC,KAAK,CAAC,IAAIC,MAAM,CAACC,OAAO,CAAChB,SAAS,CAAC,EAAE;IACpD,IAAIW,CAAC,CAACE,GAAG,CAAC,KAAKI,SAAS,EAAE;MACxBN,CAAC,CAACE,GAAG,CAAC,GAAGC,KAAK;IAChB;EACF;EACA,MAAM;IAAEN;EAAI,CAAC,GAAGF,MAAM;EACtB,IAAIE,GAAG,KAAKS,SAAS,EAAE;IACrB,MAAMC,GAAG,GAAGP,CAAC,CAACQ,SAA0D;IACxE,IAAID,GAAG,KAAKD,SAAS,EAAE;MACrBN,CAAC,CAACQ,SAAS,GAAG;QAAEX,GAAG;QAAEY,SAAS,EAAE;MAAe,CAAC;IAClD,CAAC,MAAM,IAAIF,GAAG,CAACV,GAAG,KAAKS,SAAS,EAAE;MAChCC,GAAG,CAACV,GAAG,GAAGA,GAAG;IACf;EACF;AACF,CAAC;AAACC,OAAA,CAAAC,aAAA,GAAAA,aAAA","ignoreList":[]}
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "WebGPUModule", {
13
13
  }
14
14
  });
15
15
  var _external = require("../external");
16
+ var _install = require("../install");
16
17
  var _NativeWebGPUModule = _interopRequireDefault(require("../NativeWebGPUModule"));
17
18
  var _Canvas = require("../Canvas");
18
19
  Object.keys(_Canvas).forEach(function (key) {
@@ -121,6 +122,8 @@ if (typeof RNWebGPU !== "undefined") {
121
122
  }
122
123
  }
123
124
  global.createImageBitmap = global.createImageBitmap ?? RNWebGPU.createImageBitmap.bind(RNWebGPU);
125
+ // Let installWebGPU() put navigator.gpu on other runtimes (see install.ts).
126
+ (0, _install.provideGPUForInstall)(RNWebGPU.gpu);
124
127
  } else {
125
128
  console.warn(`[react-native-webgpu] install() returned ${_installOk} but RNWebGPU global is not available`);
126
129
  }