react-native-webgpu 0.13.1 → 0.14.1

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 (43) hide show
  1. package/android/CMakeLists.txt +1 -0
  2. package/android/cpp/AndroidPlatformContext.h +0 -9
  3. package/android/cpp/cpp-adapter.cpp +6 -4
  4. package/android/src/main/java/com/webgpu/WebGPUView.java +8 -0
  5. package/apple/ApplePlatformContext.h +0 -3
  6. package/apple/ApplePlatformContext.mm +0 -10
  7. package/apple/MetalView.mm +4 -3
  8. package/cpp/jsi/JSIConverter.h +6 -11
  9. package/cpp/jsi/NativeObject.h +130 -18
  10. package/cpp/rnwgpu/EnforceRange.h +68 -0
  11. package/cpp/rnwgpu/PlatformContext.h +0 -2
  12. package/cpp/rnwgpu/RNWebGPUManager.cpp +7 -95
  13. package/cpp/rnwgpu/RNWebGPUManager.h +4 -3
  14. package/cpp/rnwgpu/WebGPURuntime.cpp +91 -0
  15. package/cpp/rnwgpu/WebGPURuntime.h +36 -0
  16. package/cpp/rnwgpu/api/Convertors.h +10 -1
  17. package/cpp/rnwgpu/api/DestroyedError.h +26 -0
  18. package/cpp/rnwgpu/api/GPU.cpp +3 -57
  19. package/cpp/rnwgpu/api/GPUDevice.cpp +17 -9
  20. package/cpp/rnwgpu/api/GPUError.h +15 -0
  21. package/cpp/rnwgpu/api/GPUInternalError.h +2 -0
  22. package/cpp/rnwgpu/api/GPUOutOfMemoryError.h +2 -0
  23. package/cpp/rnwgpu/api/GPUQueue.cpp +7 -21
  24. package/cpp/rnwgpu/api/GPUSharedFence.cpp +39 -12
  25. package/cpp/rnwgpu/api/GPUSharedFence.h +21 -4
  26. package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +80 -4
  27. package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +29 -6
  28. package/cpp/rnwgpu/api/GPUTexture.h +6 -0
  29. package/cpp/rnwgpu/api/GPUValidationError.h +2 -0
  30. package/cpp/rnwgpu/api/descriptors/Unions.h +80 -0
  31. package/lib/commonjs/external/reanimated/registerWebGPUForReanimated.js +13 -3
  32. package/lib/commonjs/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
  33. package/lib/module/external/reanimated/registerWebGPUForReanimated.js +13 -3
  34. package/lib/module/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
  35. package/lib/typescript/lib/commonjs/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
  36. package/lib/typescript/lib/module/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
  37. package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts +12 -0
  38. package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
  39. package/lib/typescript/src/types.d.ts +2 -0
  40. package/lib/typescript/src/types.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/src/external/reanimated/registerWebGPUForReanimated.ts +29 -7
  43. package/src/types.ts +14 -1
@@ -54,6 +54,7 @@ add_library(${PACKAGE_NAME} SHARED
54
54
  ../cpp/rnwgpu/api/GPUComputePipeline.cpp
55
55
  ../cpp/rnwgpu/api/GPUCanvasContext.cpp
56
56
  ../cpp/rnwgpu/RNWebGPUManager.cpp
57
+ ../cpp/rnwgpu/WebGPURuntime.cpp
57
58
  ../cpp/jsi/Promise.cpp
58
59
  ../cpp/jsi/JSICache.cpp
59
60
  ../cpp/rnwgpu/async/RuntimeContext.cpp
@@ -131,15 +131,6 @@ public:
131
131
  }
132
132
  }
133
133
 
134
- wgpu::Surface makeSurface(wgpu::Instance instance, void *window, int width,
135
- int height) override {
136
- wgpu::SurfaceSourceAndroidNativeWindow androidSurfaceDesc;
137
- androidSurfaceDesc.window = reinterpret_cast<ANativeWindow *>(window);
138
- wgpu::SurfaceDescriptor surfaceDescriptor;
139
- surfaceDescriptor.nextInChain = &androidSurfaceDesc;
140
- return instance.CreateSurface(&surfaceDescriptor);
141
- }
142
-
143
134
  ImageData createImageBitmap(std::string blobId, double offset,
144
135
  double size) override {
145
136
  jni::Environment::ensureCurrentThreadIsAttached();
@@ -17,6 +17,7 @@
17
17
  #include "AndroidPlatformContext.h"
18
18
  #include "GPUCanvasContext.h"
19
19
  #include "RNWebGPUManager.h"
20
+ #include "WebGPURuntime.h"
20
21
 
21
22
  #define LOG_TAG "WebGPUModule"
22
23
 
@@ -116,9 +117,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
116
117
  return;
117
118
  }
118
119
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
119
- auto gpu = manager->_gpu;
120
- auto surface = manager->_platformContext->makeSurface(
121
- gpu, window, static_cast<int>(width), static_cast<int>(height));
120
+ auto &runtime = rnwgpu::WebGPURuntime::shared();
121
+ auto gpu = runtime.instance();
122
+ auto surface = runtime.makeSurface(window);
122
123
  // Find-or-create + attach runs atomically under the registry lock so a
123
124
  // concurrent destroyContext cannot orphan this surface.
124
125
  auto info = registry.attachSurface(
@@ -212,7 +213,8 @@ Java_com_webgpu_WebGPUHardwareBufferView_nEnablePool(JNIEnv *env, jobject thiz,
212
213
  jint dpH) {
213
214
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
214
215
  auto info = registry.getSurfaceInfoOrCreate(
215
- contextId, manager->_gpu, static_cast<int>(dpW), static_cast<int>(dpH));
216
+ contextId, rnwgpu::WebGPURuntime::shared().instance(),
217
+ static_cast<int>(dpW), static_cast<int>(dpH));
216
218
  auto waker = std::make_shared<HardwareBufferViewWaker>(env, thiz);
217
219
  info->setPoolFrameReadyCallback([waker]() { (*waker)(); });
218
220
  info->enablePool(static_cast<int>(dpW), static_cast<int>(dpH));
@@ -27,6 +27,14 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
27
27
 
28
28
  WebGPUView(Context context) {
29
29
  super(context);
30
+ // ReactViewGroup disables clipChildren (RN overflow is handled with canvas
31
+ // clipping instead). Android copies the parent's clipChildren into each
32
+ // child's RenderNode.clipToBounds, and HWUI treats a damaged node without
33
+ // it as unbounded damage (RenderNode::damageSelf). The TextureView and
34
+ // HardwareBufferView children invalidate on every frame, so without this
35
+ // every non-opaque canvas frame repaints the whole window. Our only child
36
+ // exactly fills this view, so clipping it changes nothing visually.
37
+ setClipChildren(true);
30
38
  }
31
39
 
32
40
  public void setContextId(int contextId) {
@@ -20,9 +20,6 @@ public:
20
20
  std::function<void(ImageData)> onSuccess,
21
21
  std::function<void(std::string)> onError) override;
22
22
 
23
- wgpu::Surface makeSurface(wgpu::Instance instance, void *surface, int width,
24
- int height) override;
25
-
26
23
  ImageData createImageBitmap(std::string blobId, double offset,
27
24
  double size) override;
28
25
 
@@ -209,16 +209,6 @@ void ApplePlatformContext::snapshotView(
209
209
  });
210
210
  }
211
211
 
212
- wgpu::Surface ApplePlatformContext::makeSurface(wgpu::Instance instance,
213
- void *surface, int width,
214
- int height) {
215
- wgpu::SurfaceSourceMetalLayer metalSurfaceDesc;
216
- metalSurfaceDesc.layer = surface;
217
- wgpu::SurfaceDescriptor surfaceDescriptor;
218
- surfaceDescriptor.nextInChain = &metalSurfaceDesc;
219
- return instance.CreateSurface(&surfaceDescriptor);
220
- }
221
-
222
212
  static std::span<const uint8_t> nsDataToSpan(NSData *data) {
223
213
  return {static_cast<const uint8_t *>(data.bytes), data.length};
224
214
  }
@@ -1,4 +1,5 @@
1
1
  #import "MetalView.h"
2
+ #import "WebGPURuntime.h"
2
3
  #import "webgpu/webgpu_cpp.h"
3
4
 
4
5
  @implementation MetalView {
@@ -32,9 +33,9 @@
32
33
  // dealloc runs. Balanced by the releaser below.
33
34
  void *nativeSurface = (void *)CFBridgingRetain(self.layer);
34
35
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
35
- auto gpu = manager->_gpu;
36
- auto surface = manager->_platformContext->makeSurface(
37
- gpu, nativeSurface, size.width, size.height);
36
+ auto &runtime = rnwgpu::WebGPURuntime::shared();
37
+ auto gpu = runtime.instance();
38
+ auto surface = runtime.makeSurface(nativeSurface);
38
39
  // Find-or-create + attach runs atomically under the registry lock so a
39
40
  // concurrent destroyContext cannot orphan this surface.
40
41
  auto info = registry.attachSurface(
@@ -13,6 +13,7 @@
13
13
 
14
14
  #include "WGPULogger.h"
15
15
 
16
+ #include "EnforceRange.h"
16
17
  #include "EnumMapper.h"
17
18
  #include "Promise.h"
18
19
 
@@ -49,7 +50,7 @@ private:
49
50
  // int <> number
50
51
  template <> struct JSIConverter<int> {
51
52
  static int fromJSI(jsi::Runtime&, const jsi::Value& arg, bool outOfBound) {
52
- return static_cast<int>(arg.asNumber());
53
+ return enforceRange<int>(arg.asNumber());
53
54
  }
54
55
  static jsi::Value toJSI(jsi::Runtime&, int arg) {
55
56
  return jsi::Value(arg);
@@ -89,8 +90,7 @@ template <typename T>
89
90
  struct JSIConverter<T, std::enable_if_t<!std::is_same_v<T, uint64_t> && std::is_same_v<T, size_t>>> {
90
91
  static size_t fromJSI(jsi::Runtime& runtime, const jsi::Value& arg, bool outOfBound) {
91
92
  if (arg.isNumber()) {
92
- double value = arg.asNumber();
93
- return static_cast<size_t>(value);
93
+ return enforceRange<size_t>(arg.asNumber());
94
94
  } else {
95
95
  return arg.asBigInt(runtime).asInt64(runtime);
96
96
  }
@@ -103,9 +103,8 @@ struct JSIConverter<T, std::enable_if_t<!std::is_same_v<T, uint64_t> && std::is_
103
103
 
104
104
  // uint32_t <> double
105
105
  template <> struct JSIConverter<uint32_t> {
106
- static double fromJSI(jsi::Runtime& runtime, const jsi::Value& arg, bool outOfBound) {
107
- double value = arg.asNumber();
108
- return static_cast<uint32_t>(value);
106
+ static uint32_t fromJSI(jsi::Runtime& runtime, const jsi::Value& arg, bool outOfBound) {
107
+ return enforceRange<uint32_t>(arg.asNumber());
109
108
  }
110
109
  static jsi::Value toJSI(jsi::Runtime& runtime, uint32_t arg) {
111
110
  return jsi::Value(static_cast<double>(arg));
@@ -116,11 +115,7 @@ template <> struct JSIConverter<uint32_t> {
116
115
  template <> struct JSIConverter<uint64_t> {
117
116
  static uint64_t fromJSI(jsi::Runtime& runtime, const jsi::Value& arg, bool outOfBound) {
118
117
  if (arg.isNumber()) {
119
- double value = arg.asNumber();
120
- if (value < 0 || value > MAX_SAFE_INTEGER) {
121
- throw jsi::JSError(runtime, "Number out of range for uint64_t");
122
- }
123
- return static_cast<uint64_t>(value);
118
+ return enforceRange<uint64_t>(arg.asNumber());
124
119
  } else {
125
120
  return arg.asBigInt(runtime).getUint64(runtime);
126
121
  }
@@ -15,6 +15,7 @@
15
15
  #include <unordered_map>
16
16
  #include <utility>
17
17
 
18
+ #include "EnforceRange.h"
18
19
  #include "JSICache.h"
19
20
  #include "WGPULogger.h"
20
21
 
@@ -33,6 +34,22 @@ namespace jsi = facebook::jsi;
33
34
  // Forward declaration
34
35
  template <typename Derived> class NativeObject;
35
36
 
37
+ /**
38
+ * Non-template base of every NativeObject. It is what lets code that does not
39
+ * know the concrete class (the worklet serializer helper below) tell a WebGPU
40
+ * object from any other object carrying jsi::NativeState, and read its brand.
41
+ */
42
+ class NativeObjectBase : public jsi::NativeState {
43
+ public:
44
+ /** The class brand (Derived::CLASS_NAME), e.g. "GPUBuffer". */
45
+ const char *getClassName() const { return _name; }
46
+
47
+ protected:
48
+ explicit NativeObjectBase(const char *name) : _name(name) {}
49
+
50
+ const char *_name;
51
+ };
52
+
36
53
  /**
37
54
  * Registry mapping a NativeObject class brand (CLASS_NAME) to a reconstructor
38
55
  * that rebuilds a fully functional JS object (prototype + native state) on
@@ -92,8 +109,9 @@ private:
92
109
  * - The NativeState from the original object
93
110
  * - The brand name for prototype reconstruction
94
111
  *
95
- * Usage pattern with registerCustomSerializable:
96
- * - pack(): Call WebGPU.box(obj) to create a BoxedWebGPUObject (HostObject)
112
+ * Usage pattern with registerCustomSerializable (see
113
+ * src/external/reanimated/registerWebGPUForReanimated.ts):
114
+ * - pack(): WebGPUWorkletHelpers::box(obj) creates a BoxedWebGPUObject
97
115
  * - The HostObject is serialized by Worklets and transferred to UI runtime
98
116
  * - unpack(): Call boxed.unbox() to get the object's wrapper on that runtime
99
117
  * (cached per runtime, see NativeObject::create), prototype included
@@ -149,6 +167,75 @@ private:
149
167
  std::string _brand;
150
168
  };
151
169
 
170
+ /**
171
+ * The native half of the worklet serializer registered in
172
+ * src/external/reanimated/registerWebGPUForReanimated.ts:
173
+ *
174
+ * - isNativeObject(value): true only for objects whose NativeState is a
175
+ * NativeObjectBase. Other native-state objects (Skia objects, worklets' own
176
+ * serializable refs) and objects that merely inherit from a WebGPU prototype
177
+ * (Object.create(device)) are rejected, and no JS property is ever read, so
178
+ * getters and Proxy traps of unrelated objects never run.
179
+ * - box(value): wraps the object into a BoxedWebGPUObject, branded with its
180
+ * class name, for unbox() on the target runtime.
181
+ *
182
+ * One instance is installed on the main runtime as
183
+ * globalThis.__webgpuWorkletHelpers and captured by the serializer's
184
+ * determine/pack worklets. Worklets hands a HostObject to every runtime by
185
+ * reference, so the helper is available wherever a WebGPU object gets captured
186
+ * (Reanimated UI runtime, createWorkletRuntime, Vision Camera frame
187
+ * processors) without installing anything on those runtimes.
188
+ */
189
+ class WebGPUWorkletHelpers : public jsi::HostObject {
190
+ public:
191
+ jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override {
192
+ auto propName = name.utf8(runtime);
193
+ if (propName == "isNativeObject") {
194
+ return jsi::Function::createFromHostFunction(
195
+ runtime, name, 1,
196
+ [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
197
+ const jsi::Value *args, size_t count) -> jsi::Value {
198
+ return jsi::Value(
199
+ count > 0 && args[0].isObject() &&
200
+ args[0].getObject(rt).hasNativeState<NativeObjectBase>(rt));
201
+ });
202
+ }
203
+ if (propName == "box") {
204
+ return jsi::Function::createFromHostFunction(
205
+ runtime, name, 1,
206
+ [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
207
+ const jsi::Value *args, size_t count) -> jsi::Value {
208
+ if (count < 1 || !args[0].isObject()) {
209
+ throw jsi::JSError(rt, "box() expects a WebGPU object");
210
+ }
211
+ auto obj = args[0].getObject(rt);
212
+ if (!obj.hasNativeState<NativeObjectBase>(rt)) {
213
+ throw jsi::JSError(rt, "box() expects a WebGPU object");
214
+ }
215
+ auto state = obj.getNativeState<NativeObjectBase>(rt);
216
+ return jsi::Object::createFromHostObject(
217
+ rt, std::make_shared<BoxedWebGPUObject>(state,
218
+ state->getClassName()));
219
+ });
220
+ }
221
+ return jsi::Value::undefined();
222
+ }
223
+
224
+ void set(jsi::Runtime &runtime, const jsi::PropNameID & /*name*/,
225
+ const jsi::Value & /*value*/) override {
226
+ throw jsi::JSError(runtime, "__webgpuWorkletHelpers is read-only");
227
+ }
228
+
229
+ std::vector<jsi::PropNameID>
230
+ getPropertyNames(jsi::Runtime &runtime) override {
231
+ std::vector<jsi::PropNameID> names;
232
+ names.reserve(2);
233
+ names.push_back(jsi::PropNameID::forUtf8(runtime, "isNativeObject"));
234
+ names.push_back(jsi::PropNameID::forUtf8(runtime, "box"));
235
+ return names;
236
+ }
237
+ };
238
+
152
239
  /**
153
240
  * Base class for native objects using the NativeState pattern.
154
241
  *
@@ -178,7 +265,7 @@ private:
178
265
  * ```
179
266
  */
180
267
  template <typename Derived>
181
- class NativeObject : public jsi::NativeState,
268
+ class NativeObject : public NativeObjectBase,
182
269
  public std::enable_shared_from_this<Derived> {
183
270
  public:
184
271
  // Marker type for SFINAE detection in JSIConverter
@@ -450,7 +537,7 @@ public:
450
537
  jsi::Runtime *getCreationRuntime() const { return _creationRuntime; }
451
538
 
452
539
  protected:
453
- explicit NativeObject(const char *name) : _name(name) {
540
+ explicit NativeObject(const char *name) : NativeObjectBase(name) {
454
541
  #if DEBUG && RNF_ENABLE_LOGS
455
542
  Logger::logToConsole("NativeObject", "(MEMORY) Creating %s... ✅", _name);
456
543
  #endif
@@ -462,13 +549,30 @@ protected:
462
549
  #endif
463
550
  }
464
551
 
465
- const char *_name;
466
552
  jsi::Runtime *_creationRuntime = nullptr;
467
553
 
468
554
  // ============================================================
469
555
  // Helper methods for definePrototype() implementations
470
556
  // ============================================================
471
557
 
558
+ /**
559
+ * Runs a host function body, turning a RangeTypeError (a JS number outside
560
+ * its WebIDL [EnforceRange] integer range, see EnforceRange.h) into a JS
561
+ * TypeError, as browsers throw for the same input. Everything else
562
+ * propagates unchanged.
563
+ */
564
+ template <typename Body>
565
+ static jsi::Value withWebIDLConversions(jsi::Runtime &runtime, Body &&body) {
566
+ try {
567
+ return body();
568
+ } catch (const RangeTypeError &error) {
569
+ auto typeError = runtime.global().getPropertyAsFunction(runtime, "TypeError");
570
+ throw jsi::JSError(runtime,
571
+ typeError.callAsConstructor(
572
+ runtime, jsi::String::createFromUtf8(runtime, error.what())));
573
+ }
574
+ }
575
+
472
576
  /**
473
577
  * Install a method on the prototype.
474
578
  */
@@ -481,8 +585,10 @@ protected:
481
585
  [method](jsi::Runtime &rt, const jsi::Value &thisVal,
482
586
  const jsi::Value *args, size_t count) -> jsi::Value {
483
587
  auto native = Derived::fromValue(rt, thisVal);
484
- return callMethod(native.get(), method, rt, args,
485
- std::index_sequence_for<Args...>{}, count);
588
+ return withWebIDLConversions(rt, [&] {
589
+ return callMethod(native.get(), method, rt, args,
590
+ std::index_sequence_for<Args...>{}, count);
591
+ });
486
592
  });
487
593
  prototype.setProperty(runtime, name, func);
488
594
  }
@@ -503,9 +609,11 @@ protected:
503
609
  [method](jsi::Runtime &rt, const jsi::Value &thisVal,
504
610
  const jsi::Value *args, size_t count) -> jsi::Value {
505
611
  auto native = Derived::fromValue(rt, thisVal);
506
- return callMethodWithRuntime(native.get(), method, rt, args,
507
- std::index_sequence_for<Args...>{},
508
- count);
612
+ return withWebIDLConversions(rt, [&] {
613
+ return callMethodWithRuntime(native.get(), method, rt, args,
614
+ std::index_sequence_for<Args...>{},
615
+ count);
616
+ });
509
617
  });
510
618
  prototype.setProperty(runtime, name, func);
511
619
  }
@@ -563,10 +671,12 @@ protected:
563
671
  throw jsi::JSError(rt, "Setter requires a value argument");
564
672
  }
565
673
  auto native = Derived::fromValue(rt, thisVal);
566
- auto value =
567
- rnwgpu::JSIConverter<std::decay_t<ValueType>>::fromJSI(rt, args[0], false);
568
- (native.get()->*setter)(std::move(value));
569
- return jsi::Value::undefined();
674
+ return withWebIDLConversions(rt, [&] {
675
+ auto value =
676
+ rnwgpu::JSIConverter<std::decay_t<ValueType>>::fromJSI(rt, args[0], false);
677
+ (native.get()->*setter)(std::move(value));
678
+ return jsi::Value::undefined();
679
+ });
570
680
  });
571
681
 
572
682
  // Use Object.defineProperty to create a proper setter
@@ -624,10 +734,12 @@ protected:
624
734
  throw jsi::JSError(rt, "Setter requires a value argument");
625
735
  }
626
736
  auto native = Derived::fromValue(rt, thisVal);
627
- auto value =
628
- rnwgpu::JSIConverter<std::decay_t<ValueType>>::fromJSI(rt, args[0], false);
629
- (native.get()->*setter)(std::move(value));
630
- return jsi::Value::undefined();
737
+ return withWebIDLConversions(rt, [&] {
738
+ auto value =
739
+ rnwgpu::JSIConverter<std::decay_t<ValueType>>::fromJSI(rt, args[0], false);
740
+ (native.get()->*setter)(std::move(value));
741
+ return jsi::Value::undefined();
742
+ });
631
743
  });
632
744
 
633
745
  auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
@@ -0,0 +1,68 @@
1
+ #pragma once
2
+
3
+ #include <algorithm>
4
+ #include <cmath>
5
+ #include <cstdint>
6
+ #include <limits>
7
+ #include <stdexcept>
8
+ #include <string>
9
+ #include <type_traits>
10
+
11
+ namespace rnwgpu {
12
+
13
+ // Thrown when a JS number cannot be converted to a WebIDL [EnforceRange]
14
+ // integer. The host function wrappers in NativeObject rethrow it as a JS
15
+ // TypeError, which is what browsers throw for the same input.
16
+ class RangeTypeError : public std::invalid_argument {
17
+ public:
18
+ using std::invalid_argument::invalid_argument;
19
+ };
20
+
21
+ namespace detail {
22
+
23
+ // The WebIDL name of the integer type, for the same message Chrome produces.
24
+ template <typename T> constexpr const char *webIDLIntegerName() {
25
+ if constexpr (sizeof(T) == 1) {
26
+ return std::is_signed_v<T> ? "byte" : "octet";
27
+ } else if constexpr (sizeof(T) == 2) {
28
+ return std::is_signed_v<T> ? "short" : "unsigned short";
29
+ } else if constexpr (sizeof(T) == 4) {
30
+ return std::is_signed_v<T> ? "long" : "unsigned long";
31
+ } else {
32
+ return std::is_signed_v<T> ? "long long" : "unsigned long long";
33
+ }
34
+ }
35
+
36
+ } // namespace detail
37
+
38
+ // WebIDL [EnforceRange] conversion of a JS number to an integer
39
+ // (https://webidl.spec.whatwg.org/#abstract-opdef-converttoint): non-finite
40
+ // values and values outside the type's range are rejected, fractions are
41
+ // truncated toward zero. 64-bit types are limited to the integers a double
42
+ // holds exactly, [-(2^53 - 1), 2^53 - 1], as the spec requires.
43
+ //
44
+ // WebGPU marks nearly every integer it takes [EnforceRange] (GPUSize32,
45
+ // GPUIndex32, GPUSize64, GPUIntegerCoordinate, GPUSignedOffset32,
46
+ // GPUStencilValue, GPUSampleMask, GPUDepthBias). The flag constants
47
+ // (GPUBufferUsageFlags and friends) are plain `unsigned long` and are not
48
+ // converted here.
49
+ template <typename T> T enforceRange(double value) {
50
+ static_assert(std::is_integral_v<T> && !std::is_same_v<T, bool>,
51
+ "enforceRange converts to integer types only");
52
+ constexpr double kMaxSafeInteger = 9007199254740991.0;
53
+ constexpr double kLower = std::max(
54
+ static_cast<double>(std::numeric_limits<T>::min()), -kMaxSafeInteger);
55
+ constexpr double kUpper = std::min(
56
+ static_cast<double>(std::numeric_limits<T>::max()), kMaxSafeInteger);
57
+ if (!std::isfinite(value)) {
58
+ throw RangeTypeError("The provided double value is non-finite.");
59
+ }
60
+ const double integer = std::trunc(value);
61
+ if (integer < kLower || integer > kUpper) {
62
+ throw RangeTypeError(std::string("Value is outside the '") +
63
+ detail::webIDLIntegerName<T>() + "' value range.");
64
+ }
65
+ return static_cast<T>(integer);
66
+ }
67
+
68
+ } // namespace rnwgpu
@@ -142,8 +142,6 @@ public:
142
142
  std::function<void(ImageData)> onSuccess,
143
143
  std::function<void(std::string)> onError) = 0;
144
144
 
145
- virtual wgpu::Surface makeSurface(wgpu::Instance instance, void *surface,
146
- int width, int height) = 0;
147
145
  virtual ImageData createImageBitmap(std::string blobId, double offset,
148
146
  double size) = 0;
149
147
 
@@ -141,101 +141,13 @@ RNWebGPUManager::RNWebGPUManager(
141
141
  }
142
142
 
143
143
  void RNWebGPUManager::installWebGPUWorkletHelpers(jsi::Runtime &runtime) {
144
- // __webgpuIsWebGPUObject - checks if a value is a WebGPU NativeObject
145
- auto isWebGPUObjectFunc = jsi::Function::createFromHostFunction(
146
- runtime, jsi::PropNameID::forUtf8(runtime, "__webgpuIsWebGPUObject"), 1,
147
- [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
148
- const jsi::Value *args, size_t count) -> jsi::Value {
149
- if (count < 1 || !args[0].isObject()) {
150
- return jsi::Value(false);
151
- }
152
- auto obj = args[0].getObject(rt);
153
-
154
- // Check if it has native state
155
- if (!obj.hasNativeState(rt)) {
156
- return jsi::Value(false);
157
- }
158
-
159
- // Check if it has Symbol.toStringTag on its prototype (WebGPU objects
160
- // do)
161
- auto objectCtor = rt.global().getPropertyAsObject(rt, "Object");
162
- auto getPrototypeOf =
163
- objectCtor.getPropertyAsFunction(rt, "getPrototypeOf");
164
- auto proto = getPrototypeOf.call(rt, obj);
165
-
166
- if (!proto.isObject()) {
167
- return jsi::Value(false);
168
- }
169
-
170
- auto protoObj = proto.getObject(rt);
171
- auto symbolCtor = rt.global().getPropertyAsObject(rt, "Symbol");
172
- auto toStringTag = symbolCtor.getProperty(rt, "toStringTag");
173
- if (toStringTag.isUndefined()) {
174
- return jsi::Value(false);
175
- }
176
-
177
- auto getOwnPropertyDescriptor =
178
- objectCtor.getPropertyAsFunction(rt, "getOwnPropertyDescriptor");
179
- auto desc = getOwnPropertyDescriptor.call(rt, protoObj, toStringTag);
180
- return jsi::Value(desc.isObject());
181
- });
182
- runtime.global().setProperty(runtime, "__webgpuIsWebGPUObject",
183
- std::move(isWebGPUObjectFunc));
184
-
185
- // __webgpuBox - boxes a WebGPU object for Worklets serialization
186
- auto boxFunc = jsi::Function::createFromHostFunction(
187
- runtime, jsi::PropNameID::forUtf8(runtime, "__webgpuBox"), 1,
188
- [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
189
- const jsi::Value *args, size_t count) -> jsi::Value {
190
- if (count < 1 || !args[0].isObject()) {
191
- throw jsi::JSError(rt,
192
- "__webgpuBox() requires a WebGPU object argument");
193
- }
194
-
195
- auto obj = args[0].getObject(rt);
196
-
197
- // Check if it has native state
198
- if (!obj.hasNativeState(rt)) {
199
- throw jsi::JSError(
200
- rt, "Object has no native state - not a WebGPU object");
201
- }
202
-
203
- // Get the brand name from Symbol.toStringTag on the prototype
204
- auto objectCtor = rt.global().getPropertyAsObject(rt, "Object");
205
- auto getPrototypeOf =
206
- objectCtor.getPropertyAsFunction(rt, "getPrototypeOf");
207
- auto proto = getPrototypeOf.call(rt, obj);
208
-
209
- std::string brand;
210
- if (proto.isObject()) {
211
- auto protoObj = proto.getObject(rt);
212
- auto symbolCtor = rt.global().getPropertyAsObject(rt, "Symbol");
213
- auto toStringTag = symbolCtor.getProperty(rt, "toStringTag");
214
- if (!toStringTag.isUndefined()) {
215
- auto getOwnPropertyDescriptor = objectCtor.getPropertyAsFunction(
216
- rt, "getOwnPropertyDescriptor");
217
- auto desc =
218
- getOwnPropertyDescriptor.call(rt, protoObj, toStringTag);
219
- if (desc.isObject()) {
220
- auto descObj = desc.getObject(rt);
221
- auto value = descObj.getProperty(rt, "value");
222
- if (value.isString()) {
223
- brand = value.getString(rt).utf8(rt);
224
- }
225
- }
226
- }
227
- }
228
-
229
- if (brand.empty()) {
230
- throw jsi::JSError(rt, "Cannot determine WebGPU object type - no "
231
- "Symbol.toStringTag found");
232
- }
233
-
234
- auto nativeState = obj.getNativeState(rt);
235
- auto boxed = std::make_shared<BoxedWebGPUObject>(nativeState, brand);
236
- return jsi::Object::createFromHostObject(rt, boxed);
237
- });
238
- runtime.global().setProperty(runtime, "__webgpuBox", std::move(boxFunc));
144
+ // The serializer worklets capture this object by closure; worklets passes a
145
+ // HostObject to every runtime by reference, so installing it here, on the
146
+ // main runtime, is enough (see WebGPUWorkletHelpers).
147
+ runtime.global().setProperty(
148
+ runtime, "__webgpuWorkletHelpers",
149
+ jsi::Object::createFromHostObject(
150
+ runtime, std::make_shared<WebGPUWorkletHelpers>()));
239
151
  }
240
152
 
241
153
  void RNWebGPUManager::flushPendingSurfaceTransition(
@@ -28,9 +28,10 @@ public:
28
28
  ~RNWebGPUManager();
29
29
 
30
30
  /**
31
- * Install global helper functions for Worklets serialization.
32
- * This installs __webgpuIsWebGPUObject and __webgpuBox on the global object.
33
- * Can be called on any runtime (main JS, UI, or custom worklet runtimes).
31
+ * Installs globalThis.__webgpuWorkletHelpers, the HostObject the worklet
32
+ * serializer (registerWebGPUForReanimated) captures to detect and box
33
+ * WebGPU objects. Only needed on the main runtime: the serializer worklets
34
+ * carry it to every other runtime by reference. See WebGPUWorkletHelpers.
34
35
  */
35
36
  static void installWebGPUWorkletHelpers(jsi::Runtime &runtime);
36
37