react-native-webgpu 0.5.14 → 0.5.15

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 (79) hide show
  1. package/README.md +19 -6
  2. package/android/CMakeLists.txt +2 -2
  3. package/cpp/jsi/NativeObject.h +39 -0
  4. package/cpp/rnwgpu/RNWebGPUManager.cpp +8 -0
  5. package/cpp/rnwgpu/SurfaceRegistry.h +33 -1
  6. package/cpp/rnwgpu/api/GPU.cpp +14 -11
  7. package/cpp/rnwgpu/api/GPU.h +6 -4
  8. package/cpp/rnwgpu/api/GPUAdapter.cpp +5 -8
  9. package/cpp/rnwgpu/api/GPUAdapter.h +3 -3
  10. package/cpp/rnwgpu/api/GPUBuffer.cpp +23 -24
  11. package/cpp/rnwgpu/api/GPUBuffer.h +3 -3
  12. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +13 -16
  13. package/cpp/rnwgpu/api/GPUCanvasContext.h +3 -0
  14. package/cpp/rnwgpu/api/GPUDevice.cpp +103 -19
  15. package/cpp/rnwgpu/api/GPUDevice.h +17 -3
  16. package/cpp/rnwgpu/api/GPUQueue.h +3 -3
  17. package/cpp/rnwgpu/api/GPUShaderModule.h +3 -3
  18. package/cpp/rnwgpu/api/GPUSharedFence.cpp +77 -0
  19. package/cpp/rnwgpu/api/GPUSharedFence.h +53 -0
  20. package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +60 -11
  21. package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +13 -9
  22. package/cpp/rnwgpu/api/descriptors/GPUSharedFenceDescriptor.h +58 -0
  23. package/cpp/rnwgpu/api/descriptors/GPUSharedFenceState.h +51 -0
  24. package/cpp/rnwgpu/async/AsyncTaskHandle.cpp +55 -23
  25. package/cpp/rnwgpu/async/AsyncTaskHandle.h +8 -5
  26. package/cpp/rnwgpu/async/RuntimeContext.cpp +193 -0
  27. package/cpp/rnwgpu/async/RuntimeContext.h +122 -0
  28. package/lib/commonjs/Canvas.js.map +1 -1
  29. package/lib/commonjs/Offscreen.js +1 -1
  30. package/lib/commonjs/WebPolyfillGPUModule.js +2 -0
  31. package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
  32. package/lib/commonjs/constants.js +40 -0
  33. package/lib/commonjs/constants.js.map +1 -0
  34. package/lib/commonjs/index.js +22 -0
  35. package/lib/commonjs/index.js.map +1 -1
  36. package/lib/commonjs/install.js +63 -0
  37. package/lib/commonjs/install.js.map +1 -0
  38. package/lib/module/Canvas.js.map +1 -1
  39. package/lib/module/Offscreen.js +1 -1
  40. package/lib/module/WebPolyfillGPUModule.js +2 -0
  41. package/lib/module/WebPolyfillGPUModule.js.map +1 -1
  42. package/lib/module/constants.js +34 -0
  43. package/lib/module/constants.js.map +1 -0
  44. package/lib/module/index.js +2 -0
  45. package/lib/module/index.js.map +1 -1
  46. package/lib/module/install.js +57 -0
  47. package/lib/module/install.js.map +1 -0
  48. package/lib/typescript/lib/commonjs/constants.d.ts +3 -0
  49. package/lib/typescript/lib/commonjs/constants.d.ts.map +1 -0
  50. package/lib/typescript/lib/commonjs/install.d.ts +35 -0
  51. package/lib/typescript/lib/commonjs/install.d.ts.map +1 -0
  52. package/lib/typescript/lib/module/constants.d.ts +6 -0
  53. package/lib/typescript/lib/module/constants.d.ts.map +1 -0
  54. package/lib/typescript/lib/module/index.d.ts +2 -0
  55. package/lib/typescript/lib/module/install.d.ts +2 -0
  56. package/lib/typescript/lib/module/install.d.ts.map +1 -0
  57. package/lib/typescript/src/Canvas.d.ts +9 -0
  58. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  59. package/lib/typescript/src/constants.d.ts +6 -0
  60. package/lib/typescript/src/constants.d.ts.map +1 -0
  61. package/lib/typescript/src/index.d.ts +5 -2
  62. package/lib/typescript/src/index.d.ts.map +1 -1
  63. package/lib/typescript/src/install.d.ts +34 -0
  64. package/lib/typescript/src/install.d.ts.map +1 -0
  65. package/lib/typescript/src/types.d.ts +34 -2
  66. package/lib/typescript/src/types.d.ts.map +1 -1
  67. package/package.json +1 -1
  68. package/src/Canvas.tsx +9 -0
  69. package/src/Offscreen.ts +1 -1
  70. package/src/WebPolyfillGPUModule.ts +3 -3
  71. package/src/constants.ts +37 -0
  72. package/src/index.tsx +13 -2
  73. package/src/install.ts +61 -0
  74. package/src/types.ts +69 -3
  75. package/cpp/rnwgpu/async/AsyncDispatcher.h +0 -28
  76. package/cpp/rnwgpu/async/AsyncRunner.cpp +0 -215
  77. package/cpp/rnwgpu/async/AsyncRunner.h +0 -53
  78. package/cpp/rnwgpu/async/JSIMicrotaskDispatcher.cpp +0 -23
  79. package/cpp/rnwgpu/async/JSIMicrotaskDispatcher.h +0 -22
@@ -6,6 +6,8 @@
6
6
  #include <utility>
7
7
  #include <vector>
8
8
 
9
+ #include <ReactCommon/CallInvoker.h>
10
+
9
11
  #include "Convertors.h"
10
12
  #include "JSIConverter.h"
11
13
 
@@ -19,23 +21,33 @@ namespace rnwgpu {
19
21
 
20
22
  void GPUDevice::notifyDeviceLost(wgpu::DeviceLostReason reason,
21
23
  std::string message) {
22
- if (_lostSettled) {
23
- return;
24
- }
24
+ std::optional<async::AsyncTaskHandle::ResolveFunction> resolveToCall;
25
+ std::shared_ptr<GPUDeviceLostInfo> info;
26
+ {
27
+ std::lock_guard<std::mutex> lock(_lostMutex);
28
+ if (_lostSettled) {
29
+ return;
30
+ }
25
31
 
26
- _lostSettled = true;
27
- _lostInfo = std::make_shared<GPUDeviceLostInfo>(reason, std::move(message));
32
+ _lostSettled = true;
33
+ _lostInfo = std::make_shared<GPUDeviceLostInfo>(reason, std::move(message));
34
+ info = _lostInfo;
35
+
36
+ if (_lostResolve.has_value()) {
37
+ resolveToCall = std::move(*_lostResolve);
38
+ _lostResolve.reset();
39
+ }
40
+
41
+ _lostHandle.reset();
42
+ }
28
43
 
29
- if (_lostResolve.has_value()) {
30
- auto resolve = std::move(*_lostResolve);
31
- _lostResolve.reset();
32
- resolve([info = _lostInfo](jsi::Runtime &runtime) mutable {
44
+ // Settle outside the lock: resolve() only enqueues onto the JS thread.
45
+ if (resolveToCall.has_value()) {
46
+ (*resolveToCall)([info](jsi::Runtime &runtime) mutable {
33
47
  return JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(runtime,
34
48
  info);
35
49
  });
36
50
  }
37
-
38
- _lostHandle.reset();
39
51
  }
40
52
 
41
53
  void GPUDevice::forceLossForTesting() {
@@ -286,6 +298,55 @@ std::shared_ptr<GPUSharedTextureMemory> GPUDevice::importSharedTextureMemory(
286
298
  std::move(label));
287
299
  }
288
300
 
301
+ std::shared_ptr<GPUSharedFence> GPUDevice::importSharedFence(
302
+ std::shared_ptr<GPUSharedFenceDescriptor> descriptor) {
303
+ if (!descriptor || descriptor->handle == nullptr) {
304
+ throw std::runtime_error("GPUDevice::importSharedFence(): handle must be a "
305
+ "non-null native handle");
306
+ }
307
+
308
+ wgpu::SharedFenceDescriptor desc{};
309
+ std::string label = descriptor->label.value_or("");
310
+ if (!label.empty()) {
311
+ desc.label = wgpu::StringView(label.c_str(), label.size());
312
+ }
313
+
314
+ // The chained platform descriptor must outlive the synchronous
315
+ // ImportSharedFence() below; declare them all and chain the matching one.
316
+ wgpu::SharedFenceMTLSharedEventDescriptor mtlDesc{};
317
+ wgpu::SharedFenceSyncFDDescriptor syncFdDesc{};
318
+ wgpu::SharedFenceVkSemaphoreOpaqueFDDescriptor vkFdDesc{};
319
+
320
+ const std::string &type = descriptor->type;
321
+ if (type == "mtl-shared-event") {
322
+ // handle is an id<MTLSharedEvent> pointer.
323
+ mtlDesc.sharedEvent = descriptor->handle;
324
+ desc.nextInChain = &mtlDesc;
325
+ } else if (type == "sync-fd") {
326
+ // handle is an OS file descriptor.
327
+ syncFdDesc.handle =
328
+ static_cast<int>(reinterpret_cast<uintptr_t>(descriptor->handle));
329
+ desc.nextInChain = &syncFdDesc;
330
+ } else if (type == "vk-semaphore-opaque-fd") {
331
+ vkFdDesc.handle =
332
+ static_cast<int>(reinterpret_cast<uintptr_t>(descriptor->handle));
333
+ desc.nextInChain = &vkFdDesc;
334
+ } else {
335
+ throw std::runtime_error(
336
+ "GPUDevice::importSharedFence(): unsupported fence type '" + type +
337
+ "' (expected 'mtl-shared-event', 'sync-fd' or "
338
+ "'vk-semaphore-opaque-fd')");
339
+ }
340
+
341
+ auto fence = _instance.ImportSharedFence(&desc);
342
+ if (fence == nullptr) {
343
+ throw std::runtime_error(
344
+ "GPUDevice::importSharedFence(): ImportSharedFence returned null - is "
345
+ "the matching 'shared-fence-*' feature enabled on the device?");
346
+ }
347
+ return std::make_shared<GPUSharedFence>(std::move(fence), std::move(label));
348
+ }
349
+
289
350
  async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
290
351
  std::shared_ptr<GPUComputePipelineDescriptor> descriptor) {
291
352
  wgpu::ComputePipelineDescriptor desc{};
@@ -318,9 +379,9 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
318
379
  runtime, pipelineHolder);
319
380
  });
320
381
  } else {
321
- std::string error =
322
- msg.length ? std::string(msg.data, msg.length)
323
- : "Failed to create compute pipeline";
382
+ std::string error = msg.length
383
+ ? std::string(msg.data, msg.length)
384
+ : "Failed to create compute pipeline";
324
385
  reject(std::move(error));
325
386
  }
326
387
  });
@@ -360,9 +421,8 @@ async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
360
421
  runtime, pipelineHolder);
361
422
  });
362
423
  } else {
363
- std::string error =
364
- msg.length ? std::string(msg.data, msg.length)
365
- : "Failed to create render pipeline";
424
+ std::string error = msg.length ? std::string(msg.data, msg.length)
425
+ : "Failed to create render pipeline";
366
426
  reject(std::move(error));
367
427
  }
368
428
  });
@@ -449,6 +509,11 @@ std::unordered_set<std::string> GPUDevice::getFeatures() {
449
509
  }
450
510
 
451
511
  async::AsyncTaskHandle GPUDevice::getLost() {
512
+ // Held across the whole body: the postTask callback below runs synchronously
513
+ // on this (JS) thread and touches the same _lost* fields, so it must not
514
+ // re-lock. notifyDeviceLost() takes the same lock from its (possibly worker)
515
+ // thread.
516
+ std::lock_guard<std::mutex> lock(_lostMutex);
452
517
  if (_lostHandle.has_value()) {
453
518
  return *_lostHandle;
454
519
  }
@@ -463,7 +528,7 @@ async::AsyncTaskHandle GPUDevice::getLost() {
463
528
  runtime, info);
464
529
  });
465
530
  },
466
- false);
531
+ /*keepPumping=*/false);
467
532
  }
468
533
 
469
534
  auto handle = _async->postTask(
@@ -477,9 +542,10 @@ async::AsyncTaskHandle GPUDevice::getLost() {
477
542
  return;
478
543
  }
479
544
 
545
+ // Resolved later from notifyDeviceLost().
480
546
  _lostResolve = resolve;
481
547
  },
482
- false);
548
+ /*keepPumping=*/false);
483
549
 
484
550
  _lostHandle = handle;
485
551
  return handle;
@@ -499,6 +565,24 @@ void GPUDevice::removeEventListener(std::string type, jsi::Function callback) {
499
565
 
500
566
  void GPUDevice::notifyUncapturedError(wgpu::ErrorType type,
501
567
  std::string message) {
568
+ // Dawn can surface an uncaptured error from any ProcessEvents pump (a worklet
569
+ // runtime sharing this instance may pump it on the wrong thread). Marshal to
570
+ // the owning runtime's JS thread via its CallInvoker before touching JSI. The
571
+ // invoker is wired only for the main JS runtime, so a device created on a
572
+ // worklet runtime does not deliver uncaptured errors to JS (best-effort; see
573
+ // README "Threading model").
574
+ auto invoker = _async ? _async->callInvoker() : nullptr;
575
+ if (!invoker) {
576
+ return;
577
+ }
578
+ auto self = shared_from_this();
579
+ invoker->invokeAsync([self, type, message = std::move(message)]() mutable {
580
+ self->deliverUncapturedError(type, std::move(message));
581
+ });
582
+ }
583
+
584
+ void GPUDevice::deliverUncapturedError(wgpu::ErrorType type,
585
+ std::string message) {
502
586
  auto it = _eventListeners.find("uncapturederror");
503
587
  if (it == _eventListeners.end() || it->second.empty()) {
504
588
  return;
@@ -15,8 +15,8 @@
15
15
 
16
16
  #include "NativeObject.h"
17
17
 
18
- #include "rnwgpu/async/AsyncRunner.h"
19
18
  #include "rnwgpu/async/AsyncTaskHandle.h"
19
+ #include "rnwgpu/async/RuntimeContext.h"
20
20
 
21
21
  #include "webgpu/webgpu_cpp.h"
22
22
 
@@ -45,6 +45,7 @@
45
45
  #include "GPURenderPipelineDescriptor.h"
46
46
  #include "GPUSampler.h"
47
47
  #include "GPUSamplerDescriptor.h"
48
+ #include "GPUSharedFenceDescriptor.h"
48
49
  #include "GPUSharedTextureMemory.h"
49
50
  #include "GPUSharedTextureMemoryDescriptor.h"
50
51
  #include "GPUShaderModule.h"
@@ -63,7 +64,7 @@ public:
63
64
  static constexpr const char *CLASS_NAME = "GPUDevice";
64
65
 
65
66
  explicit GPUDevice(wgpu::Device instance,
66
- std::shared_ptr<async::AsyncRunner> async,
67
+ std::shared_ptr<async::RuntimeContext> async,
67
68
  std::string label)
68
69
  : NativeObject(CLASS_NAME), _instance(instance), _async(async),
69
70
  _label(label) {}
@@ -120,6 +121,8 @@ public:
120
121
  std::shared_ptr<GPUExternalTextureDescriptor> descriptor);
121
122
  std::shared_ptr<GPUSharedTextureMemory> importSharedTextureMemory(
122
123
  std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor);
124
+ std::shared_ptr<GPUSharedFence> importSharedFence(
125
+ std::shared_ptr<GPUSharedFenceDescriptor> descriptor);
123
126
  std::shared_ptr<GPUBindGroupLayout> createBindGroupLayout(
124
127
  std::shared_ptr<GPUBindGroupLayoutDescriptor> descriptor);
125
128
  std::shared_ptr<GPUPipelineLayout>
@@ -175,6 +178,8 @@ public:
175
178
  &GPUDevice::importExternalTexture);
176
179
  installMethod(runtime, prototype, "importSharedTextureMemory",
177
180
  &GPUDevice::importSharedTextureMemory);
181
+ installMethod(runtime, prototype, "importSharedFence",
182
+ &GPUDevice::importSharedFence);
178
183
  installMethod(runtime, prototype, "createBindGroupLayout",
179
184
  &GPUDevice::createBindGroupLayout);
180
185
  installMethod(runtime, prototype, "createPipelineLayout",
@@ -248,9 +253,18 @@ public:
248
253
  private:
249
254
  friend class GPUAdapter;
250
255
 
256
+ // Runs the uncapturederror listeners on the creation runtime's JS thread.
257
+ // Invoked from notifyUncapturedError via the main CallInvoker.
258
+ void deliverUncapturedError(wgpu::ErrorType type, std::string message);
259
+
251
260
  wgpu::Device _instance;
252
- std::shared_ptr<async::AsyncRunner> _async;
261
+ std::shared_ptr<async::RuntimeContext> _async;
253
262
  std::string _label;
263
+ // Guards the device-lost state below. In the ProcessEvents model both
264
+ // notifyDeviceLost() (fired by Dawn during ProcessEvents) and getLost() run on
265
+ // the owning runtime's own thread, but device destruction can also trigger
266
+ // notifyDeviceLost() synchronously, so the mutex keeps these fields safe.
267
+ std::mutex _lostMutex;
254
268
  std::optional<async::AsyncTaskHandle> _lostHandle;
255
269
  std::shared_ptr<GPUDeviceLostInfo> _lostInfo;
256
270
  bool _lostSettled = false;
@@ -8,8 +8,8 @@
8
8
 
9
9
  #include "NativeObject.h"
10
10
 
11
- #include "rnwgpu/async/AsyncRunner.h"
12
11
  #include "rnwgpu/async/AsyncTaskHandle.h"
12
+ #include "rnwgpu/async/RuntimeContext.h"
13
13
 
14
14
  #include "webgpu/webgpu_cpp.h"
15
15
 
@@ -28,7 +28,7 @@ public:
28
28
  static constexpr const char *CLASS_NAME = "GPUQueue";
29
29
 
30
30
  explicit GPUQueue(wgpu::Queue instance,
31
- std::shared_ptr<async::AsyncRunner> async,
31
+ std::shared_ptr<async::RuntimeContext> async,
32
32
  std::string label)
33
33
  : NativeObject(CLASS_NAME), _instance(instance), _async(async),
34
34
  _label(label) {}
@@ -74,7 +74,7 @@ public:
74
74
 
75
75
  private:
76
76
  wgpu::Queue _instance;
77
- std::shared_ptr<async::AsyncRunner> _async;
77
+ std::shared_ptr<async::RuntimeContext> _async;
78
78
  std::string _label;
79
79
  };
80
80
 
@@ -7,8 +7,8 @@
7
7
 
8
8
  #include "NativeObject.h"
9
9
 
10
- #include "rnwgpu/async/AsyncRunner.h"
11
10
  #include "rnwgpu/async/AsyncTaskHandle.h"
11
+ #include "rnwgpu/async/RuntimeContext.h"
12
12
 
13
13
  #include "webgpu/webgpu_cpp.h"
14
14
 
@@ -23,7 +23,7 @@ public:
23
23
  static constexpr const char *CLASS_NAME = "GPUShaderModule";
24
24
 
25
25
  explicit GPUShaderModule(wgpu::ShaderModule instance,
26
- std::shared_ptr<async::AsyncRunner> async,
26
+ std::shared_ptr<async::RuntimeContext> async,
27
27
  std::string label)
28
28
  : NativeObject(CLASS_NAME), _instance(instance), _async(async),
29
29
  _label(label) {}
@@ -59,7 +59,7 @@ public:
59
59
 
60
60
  private:
61
61
  wgpu::ShaderModule _instance;
62
- std::shared_ptr<async::AsyncRunner> _async;
62
+ std::shared_ptr<async::RuntimeContext> _async;
63
63
  std::string _label;
64
64
  };
65
65
 
@@ -0,0 +1,77 @@
1
+ #include "GPUSharedFence.h"
2
+
3
+ #include <cstdint>
4
+ #include <string>
5
+
6
+ #if defined(__ANDROID__)
7
+ #include <fcntl.h>
8
+ #endif
9
+
10
+ namespace rnwgpu {
11
+
12
+ namespace {
13
+
14
+ // Kebab-case names matching the shared-fence-* feature strings (see Unions.h /
15
+ // GPUFeatures.h).
16
+ std::string sharedFenceTypeToString(wgpu::SharedFenceType type) {
17
+ switch (type) {
18
+ case wgpu::SharedFenceType::MTLSharedEvent:
19
+ return "mtl-shared-event";
20
+ case wgpu::SharedFenceType::SyncFD:
21
+ return "sync-fd";
22
+ case wgpu::SharedFenceType::VkSemaphoreOpaqueFD:
23
+ return "vk-semaphore-opaque-fd";
24
+ case wgpu::SharedFenceType::VkSemaphoreZirconHandle:
25
+ return "vk-semaphore-zircon-handle";
26
+ case wgpu::SharedFenceType::DXGISharedHandle:
27
+ return "dxgi-shared-handle";
28
+ case wgpu::SharedFenceType::EGLSync:
29
+ return "egl-sync";
30
+ default:
31
+ return "";
32
+ }
33
+ }
34
+
35
+ } // namespace
36
+
37
+ jsi::Value GPUSharedFence::exportInfo(jsi::Runtime &runtime, const jsi::Value &,
38
+ const jsi::Value *, size_t) {
39
+ wgpu::SharedFenceExportInfo info{};
40
+ uint64_t handle = 0;
41
+
42
+ #if defined(__APPLE__)
43
+ // Apple: the handle is an id<MTLSharedEvent> pointer.
44
+ wgpu::SharedFenceMTLSharedEventExportInfo mtlInfo{};
45
+ info.nextInChain = &mtlInfo;
46
+ _instance.ExportInfo(&info);
47
+ handle = reinterpret_cast<uint64_t>(mtlInfo.sharedEvent);
48
+ #elif defined(__ANDROID__)
49
+ // Android: the handle is an OS file descriptor (sync_fd). Dawn's ExportInfo returns a BORROWED fd — it is
50
+ // owned by the SharedFence and closed when the fence is destroyed. This exported handle is documented as
51
+ // caller-owned (the caller must close() it), so dup() it. Without the dup the same fd is closed twice —
52
+ // once by the caller and once by Dawn on fence destruction — tripping Android's fdsan (double-close abort).
53
+ wgpu::SharedFenceSyncFDExportInfo fdInfo{};
54
+ info.nextInChain = &fdInfo;
55
+ _instance.ExportInfo(&info);
56
+ int exportedFd =
57
+ fdInfo.handle >= 0 ? ::fcntl(fdInfo.handle, F_DUPFD_CLOEXEC, 0) : fdInfo.handle;
58
+ handle = static_cast<uint64_t>(static_cast<uint32_t>(exportedFd));
59
+ #else
60
+ // react-native-webgpu only targets Apple (Metal) and Android (Vulkan). On any
61
+ // other platform there is no native handle convention to expose, so fail loudly
62
+ // rather than handing back a meaningless handle of 0.
63
+ throw jsi::JSError(runtime,
64
+ "GPUSharedFence::export(): unsupported platform (only "
65
+ "Apple/Metal and Android/Vulkan are supported)");
66
+ #endif
67
+
68
+ jsi::Object result(runtime);
69
+ result.setProperty(
70
+ runtime, "type",
71
+ jsi::String::createFromUtf8(runtime, sharedFenceTypeToString(info.type)));
72
+ result.setProperty(runtime, "handle",
73
+ jsi::BigInt::fromUint64(runtime, handle));
74
+ return result;
75
+ }
76
+
77
+ } // namespace rnwgpu
@@ -0,0 +1,53 @@
1
+ #pragma once
2
+
3
+ #include <memory>
4
+ #include <string>
5
+
6
+ #include "NativeObject.h"
7
+
8
+ #include "webgpu/webgpu_cpp.h"
9
+
10
+ namespace rnwgpu {
11
+
12
+ namespace jsi = facebook::jsi;
13
+
14
+ // Wraps a wgpu::SharedFence: a native GPU sync primitive (id<MTLSharedEvent> on
15
+ // Apple, sync-fd / VkSemaphore on Android).
16
+ class GPUSharedFence : public NativeObject<GPUSharedFence> {
17
+ public:
18
+ static constexpr const char *CLASS_NAME = "GPUSharedFence";
19
+
20
+ explicit GPUSharedFence(wgpu::SharedFence instance, std::string label)
21
+ : NativeObject(CLASS_NAME), _instance(std::move(instance)),
22
+ _label(std::move(label)) {}
23
+
24
+ public:
25
+ std::string getBrand() { return CLASS_NAME; }
26
+
27
+ // export() -> { type, handle }: exposes the native handle (as a BigInt) so
28
+ // app code can wait on or signal the fence. The caller owns the returned
29
+ // handle (e.g. an exported sync-fd must be close()d).
30
+ jsi::Value exportInfo(jsi::Runtime &runtime, const jsi::Value &thisVal,
31
+ const jsi::Value *args, size_t count);
32
+
33
+ std::string getLabel() { return _label; }
34
+ void setLabel(const std::string &label) {
35
+ _label = label;
36
+ _instance.SetLabel(_label.c_str());
37
+ }
38
+
39
+ static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
40
+ installGetter(runtime, prototype, "__brand", &GPUSharedFence::getBrand);
41
+ installMethod(runtime, prototype, "export", &GPUSharedFence::exportInfo);
42
+ installGetterSetter(runtime, prototype, "label", &GPUSharedFence::getLabel,
43
+ &GPUSharedFence::setLabel);
44
+ }
45
+
46
+ inline wgpu::SharedFence get() { return _instance; }
47
+
48
+ private:
49
+ wgpu::SharedFence _instance;
50
+ std::string _label;
51
+ };
52
+
53
+ } // namespace rnwgpu
@@ -27,8 +27,9 @@ std::shared_ptr<GPUTexture> GPUSharedTextureMemory::createTexture(
27
27
  descriptor.value()->label.value_or(""));
28
28
  }
29
29
 
30
- bool GPUSharedTextureMemory::beginAccess(std::shared_ptr<GPUTexture> texture,
31
- bool initialized) {
30
+ void GPUSharedTextureMemory::beginAccess(
31
+ std::shared_ptr<GPUTexture> texture, bool initialized,
32
+ std::optional<std::vector<std::shared_ptr<GPUSharedFenceState>>> fences) {
32
33
  if (!texture) {
33
34
  throw std::runtime_error(
34
35
  "GPUSharedTextureMemory::beginAccess(): texture is null");
@@ -36,9 +37,28 @@ bool GPUSharedTextureMemory::beginAccess(std::shared_ptr<GPUTexture> texture,
36
37
  wgpu::SharedTextureMemoryBeginAccessDescriptor desc{};
37
38
  desc.initialized = initialized;
38
39
  desc.concurrentRead = false;
39
- desc.fenceCount = 0;
40
- desc.fences = nullptr;
41
- desc.signaledValues = nullptr;
40
+
41
+ // Built in lockstep so fenceCount covers both arrays, and kept in locals so
42
+ // the raw pointers outlive the synchronous BeginAccess() below.
43
+ std::vector<wgpu::SharedFence> rawFences;
44
+ std::vector<uint64_t> values;
45
+ if (fences.has_value()) {
46
+ for (const auto &state : *fences) {
47
+ if (state && state->fence) {
48
+ rawFences.push_back(state->fence->get());
49
+ values.push_back(state->signaledValue);
50
+ }
51
+ }
52
+ }
53
+ if (!rawFences.empty()) {
54
+ desc.fenceCount = rawFences.size();
55
+ desc.fences = rawFences.data();
56
+ desc.signaledValues = values.data();
57
+ } else {
58
+ desc.fenceCount = 0;
59
+ desc.fences = nullptr;
60
+ desc.signaledValues = nullptr;
61
+ }
42
62
 
43
63
  #if defined(__ANDROID__)
44
64
  // Dawn's Vulkan backend (AHardwareBuffer) validates that the begin-access
@@ -55,14 +75,21 @@ bool GPUSharedTextureMemory::beginAccess(std::shared_ptr<GPUTexture> texture,
55
75
  #endif
56
76
 
57
77
  auto status = _instance.BeginAccess(texture->get(), &desc);
58
- return static_cast<bool>(status);
78
+ if (!status) {
79
+ throw std::runtime_error("GPUSharedTextureMemory::beginAccess() failed");
80
+ }
59
81
  }
60
82
 
61
- bool GPUSharedTextureMemory::endAccess(std::shared_ptr<GPUTexture> texture) {
62
- if (!texture) {
63
- throw std::runtime_error(
64
- "GPUSharedTextureMemory::endAccess(): texture is null");
83
+ jsi::Value GPUSharedTextureMemory::endAccess(jsi::Runtime &runtime,
84
+ const jsi::Value &,
85
+ const jsi::Value *args,
86
+ size_t count) {
87
+ if (count < 1 || !args[0].isObject()) {
88
+ throw jsi::JSError(
89
+ runtime, "GPUSharedTextureMemory::endAccess(): expected (texture)");
65
90
  }
91
+ auto texture = GPUTexture::fromValue(runtime, args[0]);
92
+
66
93
  wgpu::SharedTextureMemoryEndAccessState state{};
67
94
 
68
95
  #if defined(__ANDROID__)
@@ -74,7 +101,29 @@ bool GPUSharedTextureMemory::endAccess(std::shared_ptr<GPUTexture> texture) {
74
101
  #endif
75
102
 
76
103
  auto status = _instance.EndAccess(texture->get(), &state);
77
- return static_cast<bool>(status);
104
+ if (!status) {
105
+ throw jsi::JSError(runtime, "GPUSharedTextureMemory::endAccess() failed");
106
+ }
107
+
108
+ // Copy each wgpu::SharedFence (ref-counted) into its own GPUSharedFence
109
+ // wrapper before `state` is destroyed.
110
+ jsi::Array fences(runtime, state.fenceCount);
111
+ for (size_t i = 0; i < state.fenceCount; i++) {
112
+ wgpu::SharedFence fence = state.fences[i];
113
+ auto wrapper = std::make_shared<GPUSharedFence>(std::move(fence), "");
114
+ jsi::Object entry(runtime);
115
+ entry.setProperty(runtime, "fence",
116
+ GPUSharedFence::create(runtime, std::move(wrapper)));
117
+ entry.setProperty(runtime, "signaledValue",
118
+ jsi::BigInt::fromUint64(runtime, state.signaledValues[i]));
119
+ fences.setValueAtIndex(runtime, i, std::move(entry));
120
+ }
121
+
122
+ jsi::Object result(runtime);
123
+ result.setProperty(runtime, "initialized",
124
+ jsi::Value(static_cast<bool>(state.initialized)));
125
+ result.setProperty(runtime, "fences", std::move(fences));
126
+ return result;
78
127
  }
79
128
 
80
129
  } // namespace rnwgpu
@@ -1,13 +1,17 @@
1
1
  #pragma once
2
2
 
3
+ #include <cstdint>
3
4
  #include <memory>
4
5
  #include <optional>
5
6
  #include <string>
7
+ #include <vector>
6
8
 
7
9
  #include "NativeObject.h"
8
10
 
9
11
  #include "webgpu/webgpu_cpp.h"
10
12
 
13
+ #include "GPUSharedFence.h"
14
+ #include "GPUSharedFenceState.h"
11
15
  #include "GPUTexture.h"
12
16
  #include "GPUTextureDescriptor.h"
13
17
 
@@ -30,16 +34,16 @@ public:
30
34
  std::shared_ptr<GPUTexture>
31
35
  createTexture(std::optional<std::shared_ptr<GPUTextureDescriptor>> descriptor);
32
36
 
33
- // Returns true on success. Marks the shared memory as initialized so the
34
- // texture's content is preserved (or not). Callers that want fence-based
35
- // synchronization should pass fences via beginAccess descriptor (not yet
36
- // exposed - we currently take the implicit/no-fence path that matches the
37
- // most common RN use cases: still images, single-producer video frames).
38
- bool beginAccess(std::shared_ptr<GPUTexture> texture, bool initialized);
37
+ // Optional `fences` are wait fences: Dawn waits for each to reach its
38
+ // signaledValue before writing the surface. Throws on failure.
39
+ void beginAccess(
40
+ std::shared_ptr<GPUTexture> texture, bool initialized,
41
+ std::optional<std::vector<std::shared_ptr<GPUSharedFenceState>>> fences);
39
42
 
40
- // Returns true on success. Drops any fences produced by end-access (we do
41
- // not yet surface them to JS).
42
- bool endAccess(std::shared_ptr<GPUTexture> texture);
43
+ // endAccess(texture) -> { initialized, fences: { fence, signaledValue }[] }
44
+ // Surfaces the fences Dawn produced for the access. Throws on failure.
45
+ jsi::Value endAccess(jsi::Runtime &runtime, const jsi::Value &thisVal,
46
+ const jsi::Value *args, size_t count);
43
47
 
44
48
  std::string getLabel() { return _label; }
45
49
  void setLabel(const std::string &label) {
@@ -0,0 +1,58 @@
1
+ #pragma once
2
+
3
+ #include <memory>
4
+ #include <optional>
5
+ #include <string>
6
+
7
+ #include "webgpu/webgpu_cpp.h"
8
+
9
+ #include "JSIConverter.h"
10
+
11
+ namespace jsi = facebook::jsi;
12
+
13
+ namespace rnwgpu {
14
+
15
+ // Descriptor for GPUDevice.importSharedFence. `handle` is the native handle
16
+ // (an id<MTLSharedEvent> pointer on Apple, an OS file descriptor on Android),
17
+ // passed from JS as a BigInt.
18
+ struct GPUSharedFenceDescriptor {
19
+ std::string type;
20
+ void *handle = nullptr;
21
+ std::optional<std::string> label;
22
+ };
23
+
24
+ } // namespace rnwgpu
25
+
26
+ namespace rnwgpu {
27
+
28
+ template <>
29
+ struct JSIConverter<std::shared_ptr<rnwgpu::GPUSharedFenceDescriptor>> {
30
+ static std::shared_ptr<rnwgpu::GPUSharedFenceDescriptor>
31
+ fromJSI(jsi::Runtime &runtime, const jsi::Value &arg, bool outOfBounds) {
32
+ auto result = std::make_unique<rnwgpu::GPUSharedFenceDescriptor>();
33
+ if (!outOfBounds && arg.isObject()) {
34
+ auto value = arg.getObject(runtime);
35
+ if (value.hasProperty(runtime, "type")) {
36
+ result->type =
37
+ value.getProperty(runtime, "type").asString(runtime).utf8(runtime);
38
+ }
39
+ if (value.hasProperty(runtime, "handle")) {
40
+ auto prop = value.getProperty(runtime, "handle");
41
+ result->handle = JSIConverter<void *>::fromJSI(runtime, prop, false);
42
+ }
43
+ if (value.hasProperty(runtime, "label")) {
44
+ auto prop = value.getProperty(runtime, "label");
45
+ result->label = JSIConverter<std::optional<std::string>>::fromJSI(
46
+ runtime, prop, false);
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+ static jsi::Value
52
+ toJSI(jsi::Runtime & /*runtime*/,
53
+ std::shared_ptr<rnwgpu::GPUSharedFenceDescriptor> /*arg*/) {
54
+ throw std::runtime_error("Invalid GPUSharedFenceDescriptor::toJSI()");
55
+ }
56
+ };
57
+
58
+ } // namespace rnwgpu