react-native-webgpu 0.6.0 → 0.6.2

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 (47) hide show
  1. package/README.md +1 -15
  2. package/android/cpp/cpp-adapter.cpp +35 -13
  3. package/android/src/main/java/com/webgpu/WebGPUAHBView.java +2 -2
  4. package/android/src/main/java/com/webgpu/WebGPUAPI.java +7 -2
  5. package/android/src/main/java/com/webgpu/WebGPUSurfaceView.java +3 -1
  6. package/android/src/main/java/com/webgpu/WebGPUTextureView.java +11 -7
  7. package/android/src/main/java/com/webgpu/WebGPUView.java +10 -7
  8. package/android/src/main/java/com/webgpu/WebGPUViewManager.java +6 -0
  9. package/apple/MetalView.mm +33 -8
  10. package/cpp/jsi/NativeObject.h +77 -8
  11. package/cpp/jsi/RuntimeAwareCache.cpp +1 -1
  12. package/cpp/jsi/RuntimeAwareCache.h +25 -6
  13. package/cpp/rnwgpu/RNWebGPUManager.cpp +14 -1
  14. package/cpp/rnwgpu/RNWebGPUManager.h +9 -0
  15. package/cpp/rnwgpu/SurfaceRegistry.h +469 -131
  16. package/cpp/rnwgpu/api/GPUAdapter.cpp +10 -3
  17. package/cpp/rnwgpu/api/GPUAdapter.h +5 -4
  18. package/cpp/rnwgpu/api/GPUBuffer.cpp +12 -2
  19. package/cpp/rnwgpu/api/GPUBuffer.h +3 -2
  20. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +33 -19
  21. package/cpp/rnwgpu/api/GPUCanvasContext.h +3 -1
  22. package/cpp/rnwgpu/api/GPUDevice.cpp +32 -17
  23. package/cpp/rnwgpu/api/GPUDevice.h +9 -7
  24. package/cpp/rnwgpu/api/GPUQueue.cpp +6 -2
  25. package/cpp/rnwgpu/api/GPUQueue.h +3 -3
  26. package/cpp/rnwgpu/api/GPUShaderModule.cpp +7 -2
  27. package/cpp/rnwgpu/api/GPUShaderModule.h +3 -3
  28. package/cpp/rnwgpu/api/RNWebGPU.h +22 -1
  29. package/cpp/rnwgpu/async/RuntimeContext.h +9 -2
  30. package/lib/commonjs/Canvas.js +10 -0
  31. package/lib/commonjs/Canvas.js.map +1 -1
  32. package/lib/commonjs/WebPolyfillGPUModule.js +2 -0
  33. package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
  34. package/lib/module/Canvas.js +11 -1
  35. package/lib/module/Canvas.js.map +1 -1
  36. package/lib/module/WebPolyfillGPUModule.js +2 -0
  37. package/lib/module/WebPolyfillGPUModule.js.map +1 -1
  38. package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
  39. package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
  40. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  41. package/lib/typescript/src/index.d.ts +1 -0
  42. package/lib/typescript/src/index.d.ts.map +1 -1
  43. package/package.json +2 -6
  44. package/src/Canvas.tsx +11 -1
  45. package/src/WebPolyfillGPUModule.ts +2 -0
  46. package/src/index.tsx +3 -0
  47. package/android/src/main/java/com/webgpu/WebGPUBaseView.java +0 -61
package/README.md CHANGED
@@ -8,20 +8,6 @@ WebGPU for React Native, powered by Dawn.
8
8
 
9
9
  [Installation instructions](https://wcandillon.github.io/react-native-webgpu/docs/getting-started/installation)
10
10
 
11
- ## Expo config plugin
12
-
13
- The package ships with an Expo config plugin that disables Metal API validation in the iOS Xcode scheme, which is required to run WebGPU on the iOS Simulator (Metal API validation reports false positives for Dawn that do not occur on device). Because the plugin is part of the package, plugin and package versions are always aligned.
14
-
15
- ```json
16
- {
17
- "expo": {
18
- "plugins": ["react-native-webgpu"]
19
- }
20
- }
21
- ```
22
-
23
- See the [Expo guide](https://wcandillon.github.io/react-native-webgpu/docs/getting-started/expo#config-plugin) for details.
24
-
25
11
  ## Contributing
26
12
 
27
- For detailed information on library development, building, testing, and contributing guidelines, please see [CONTRIBUTING.md](packages/webgpu/CONTRIBUTING.md).
13
+ For detailed information on library development, building, testing, and contributing guidelines, please see [CONTRIBUTING.md](packages/webgpu/CONTRIBUTING.md).
@@ -25,8 +25,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUModule_initializeNative(
25
25
  auto jsCallInvoker{
26
26
  facebook::jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>{
27
27
  reinterpret_cast<facebook::react::CallInvokerHolder::javaobject>(
28
- jsCallInvokerHolder)} -> cthis()
29
- ->getCallInvoker()};
28
+ jsCallInvokerHolder)} -> cthis()->getCallInvoker()};
30
29
  auto platformContext =
31
30
  std::make_shared<rnwgpu::AndroidPlatformContext>(globalBlobModule);
32
31
  manager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsCallInvoker,
@@ -37,35 +36,58 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceChanged(
37
36
  JNIEnv *env, jobject thiz, jobject surface, jint contextId, jfloat width,
38
37
  jfloat height) {
39
38
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
40
- registry.getSurfaceInfo(contextId)->resize(static_cast<int>(width),
41
- static_cast<int>(height));
39
+ if (auto info = registry.getSurfaceInfo(contextId)) {
40
+ info->resize(static_cast<int>(width), static_cast<int>(height));
41
+ }
42
42
  }
43
43
 
44
44
  extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
45
45
  JNIEnv *env, jobject thiz, jobject jSurface, jint contextId, jfloat width,
46
46
  jfloat height) {
47
+ if (manager == nullptr) {
48
+ return;
49
+ }
50
+ // ANativeWindow_fromSurface acquires a reference; SurfaceInfo releases it
51
+ // (via the releaser below) once it is done with the window.
47
52
  auto window = ANativeWindow_fromSurface(env, jSurface);
48
- // ANativeWindow_acquire(window);
53
+ if (window == nullptr) {
54
+ return;
55
+ }
49
56
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
50
57
  auto gpu = manager->_gpu;
51
58
  auto surface = manager->_platformContext->makeSurface(
52
59
  gpu, window, static_cast<int>(width), static_cast<int>(height));
53
- registry
54
- .getSurfaceInfoOrCreate(contextId, gpu, static_cast<int>(width),
55
- static_cast<int>(height))
56
- ->switchToOnscreen(window, surface);
60
+ // Find-or-create + attach runs atomically under the registry lock so a
61
+ // concurrent destroyContext cannot orphan this surface.
62
+ auto info = registry.attachSurface(
63
+ contextId, gpu, static_cast<int>(width), static_cast<int>(height), window,
64
+ surface, [](void *nativeSurface) {
65
+ ANativeWindow_release(static_cast<ANativeWindow *>(nativeSurface));
66
+ });
67
+ // The attach is adopted at the next frame boundary by the rendering thread;
68
+ // schedule a flush so contexts that are not currently rendering still pick
69
+ // it up (and present their last offscreen frame).
70
+ manager->flushPendingSurfaceTransition(info);
57
71
  }
58
72
 
59
73
  extern "C" JNIEXPORT void JNICALL
60
74
  Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
61
75
  jint contextId) {
62
76
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
63
- auto nativeSurface = registry.getSurfaceInfo(contextId)->switchToOffscreen();
64
- // ANativeWindow_release(reinterpret_cast<ANativeWindow *>(nativeSurface));
77
+ if (auto info = registry.getSurfaceInfo(contextId)) {
78
+ info->switchToOffscreen();
79
+ }
65
80
  }
66
81
 
67
- extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceDestroy(
82
+ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
68
83
  JNIEnv *env, jobject thiz, jint contextId) {
84
+ // The view dies with its Canvas (contextIds are never reused), so view
85
+ // teardown retires the registry entry. The JS-side cleanup
86
+ // (RNWebGPU.destroyContext) only handles entries that never had a native
87
+ // surface; see RNWebGPU::destroyContext for the ownership split.
69
88
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
89
+ if (auto info = registry.getSurfaceInfo(contextId)) {
90
+ info->detachSurface();
91
+ }
70
92
  registry.removeSurfaceInfo(contextId);
71
- }
93
+ }
@@ -254,9 +254,9 @@ public class WebGPUAHBView extends View implements ImageReader.OnImageAvailableL
254
254
  protected void onDetachedFromWindow() {
255
255
  super.onDetachedFromWindow();
256
256
 
257
- // Notify WebGPU that surface is being destroyed
257
+ // Detach: rendering falls back to the offscreen texture until reattach.
258
258
  if (mSurfaceCreated) {
259
- mApi.surfaceDestroyed();
259
+ mApi.surfaceOffscreen();
260
260
  mSurfaceCreated = false;
261
261
  }
262
262
 
@@ -4,6 +4,13 @@ import android.view.Surface;
4
4
 
5
5
  import com.facebook.proguard.annotations.DoNotStrip;
6
6
 
7
+ /**
8
+ * Surface lifecycle events a WebGPU child view reports. The registry entry
9
+ * itself is owned by the JS Canvas component (created lazily, removed via
10
+ * RNWebGPU.destroyContext on unmount); views only attach and detach surfaces.
11
+ * A detached context keeps rendering into an offscreen texture whose content
12
+ * is blitted onto the next attached surface.
13
+ */
7
14
  public interface WebGPUAPI {
8
15
 
9
16
  void surfaceCreated(
@@ -14,7 +21,5 @@ public interface WebGPUAPI {
14
21
  Surface surface
15
22
  );
16
23
 
17
- void surfaceDestroyed();
18
-
19
24
  void surfaceOffscreen();
20
25
  }
@@ -21,7 +21,9 @@ public class WebGPUSurfaceView extends SurfaceView implements SurfaceHolder.Call
21
21
  @Override
22
22
  protected void onDetachedFromWindow() {
23
23
  super.onDetachedFromWindow();
24
- mApi.surfaceDestroyed();
24
+ // surfaceDestroyed() normally fires during detach as well; going offscreen
25
+ // is idempotent, so this is just a safety net for paths where it does not.
26
+ mApi.surfaceOffscreen();
25
27
  }
26
28
 
27
29
  @Override
@@ -7,12 +7,11 @@ import android.view.Surface;
7
7
  import android.view.TextureView;
8
8
  import androidx.annotation.NonNull;
9
9
 
10
- import org.w3c.dom.Text;
11
-
12
10
  @SuppressLint("ViewConstructor")
13
11
  public class WebGPUTextureView extends TextureView implements TextureView.SurfaceTextureListener {
14
12
 
15
13
  WebGPUAPI mApi;
14
+ private Surface mSurface;
16
15
 
17
16
  public WebGPUTextureView(Context context, WebGPUAPI api) {
18
17
  super(context);
@@ -23,19 +22,24 @@ public class WebGPUTextureView extends TextureView implements TextureView.Surfac
23
22
 
24
23
  @Override
25
24
  public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
26
- Surface surface = new Surface(surfaceTexture);
27
- mApi.surfaceCreated(surface);
25
+ mSurface = new Surface(surfaceTexture);
26
+ mApi.surfaceCreated(mSurface);
28
27
  }
29
28
 
30
29
  @Override
31
30
  public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
32
- Surface surface = new Surface(surfaceTexture);
33
- mApi.surfaceChanged(surface);
31
+ mApi.surfaceChanged(mSurface);
34
32
  }
35
33
 
36
34
  @Override
37
35
  public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
38
- mApi.surfaceDestroyed();
36
+ // Detach first (synchronous through JNI) so the native side has dropped
37
+ // its window reference before we release ours.
38
+ mApi.surfaceOffscreen();
39
+ if (mSurface != null) {
40
+ mSurface.release();
41
+ mSurface = null;
42
+ }
39
43
  return true;
40
44
  }
41
45
 
@@ -73,16 +73,19 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
73
73
  onSurfaceChanged(surface, mContextId, width, height);
74
74
  }
75
75
 
76
- @Override
77
- public void surfaceDestroyed() {
78
- onSurfaceDestroy(mContextId);
79
- }
80
-
81
76
  @Override
82
77
  public void surfaceOffscreen() {
83
78
  switchToOffscreenSurface(mContextId);
84
79
  }
85
80
 
81
+ /**
82
+ * Called from WebGPUViewManager.onDropViewInstance when React removes this
83
+ * view: the view dies with its Canvas, so it retires the registry entry.
84
+ */
85
+ public void destroy() {
86
+ onViewDestroyed(mContextId);
87
+ }
88
+
86
89
  @DoNotStrip
87
90
  private native void onSurfaceCreate(
88
91
  Surface surface,
@@ -100,9 +103,9 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
100
103
  );
101
104
 
102
105
  @DoNotStrip
103
- private native void onSurfaceDestroy(int contextId);
106
+ private native void switchToOffscreenSurface(int contextId);
104
107
 
105
108
  @DoNotStrip
106
- private native void switchToOffscreenSurface(int contextId);
109
+ private native void onViewDestroyed(int contextId);
107
110
 
108
111
  }
@@ -24,6 +24,12 @@ public class WebGPUViewManager extends WebGPUViewManagerSpec<WebGPUView> {
24
24
  return new WebGPUView(context);
25
25
  }
26
26
 
27
+ @Override
28
+ public void onDropViewInstance(@NonNull WebGPUView view) {
29
+ super.onDropViewInstance(view);
30
+ view.destroy();
31
+ }
32
+
27
33
  @Override
28
34
  @ReactProp(name = "transparent")
29
35
  public void setTransparent(WebGPUView view, boolean value) {
@@ -23,27 +23,52 @@
23
23
  - (void)configure {
24
24
  auto size = self.frame.size;
25
25
  std::shared_ptr<rnwgpu::RNWebGPUManager> manager = [WebGPUModule getManager];
26
- void *nativeSurface = (__bridge void *)self.layer;
26
+ if (manager == nullptr) {
27
+ return;
28
+ }
29
+ // Retain the layer for as long as SurfaceInfo holds the pointer: the
30
+ // latched attach (and the flush lambda that adopts it) can outlive this
31
+ // view, e.g. across a dev reload where the registry is cleared before
32
+ // dealloc runs. Balanced by the releaser below.
33
+ void *nativeSurface = (void *)CFBridgingRetain(self.layer);
27
34
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
28
35
  auto gpu = manager->_gpu;
29
36
  auto surface = manager->_platformContext->makeSurface(
30
37
  gpu, nativeSurface, size.width, size.height);
31
- registry
32
- .getSurfaceInfoOrCreate([_contextId intValue], gpu, size.width,
33
- size.height)
34
- ->switchToOnscreen(nativeSurface, surface);
38
+ // Find-or-create + attach runs atomically under the registry lock so a
39
+ // concurrent destroyContext cannot orphan this surface.
40
+ auto info = registry.attachSurface(
41
+ [_contextId intValue], gpu, size.width, size.height, nativeSurface,
42
+ surface, [](void *layer) {
43
+ // The releaser can run on the rendering thread; CALayer teardown
44
+ // belongs on the main thread.
45
+ dispatch_async(dispatch_get_main_queue(), ^{
46
+ CFBridgingRelease(layer);
47
+ });
48
+ });
49
+ // The attach is adopted at the next frame boundary by the rendering thread;
50
+ // schedule a flush so contexts that are not currently rendering still pick
51
+ // it up (and present their last offscreen frame).
52
+ manager->flushPendingSurfaceTransition(info);
35
53
  }
36
54
 
37
55
  - (void)update {
38
56
  auto size = self.frame.size;
39
57
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
40
- registry.getSurfaceInfo([_contextId intValue])
41
- ->resize(size.width, size.height);
58
+ if (auto info = registry.getSurfaceInfo([_contextId intValue])) {
59
+ info->resize(size.width, size.height);
60
+ }
42
61
  }
43
62
 
44
63
  - (void)dealloc {
64
+ // The view dies with its Canvas (contextIds are never reused), so view
65
+ // teardown retires the registry entry. The JS-side cleanup
66
+ // (RNWebGPU.destroyContext) only handles entries that never had a native
67
+ // surface; see RNWebGPU::destroyContext for the ownership split.
45
68
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
46
- // Remove the surface info from the registry
69
+ if (auto info = registry.getSurfaceInfo([_contextId intValue])) {
70
+ info->detachSurface();
71
+ }
47
72
  registry.removeSurfaceInfo([_contextId intValue]);
48
73
  }
49
74
 
@@ -9,6 +9,7 @@
9
9
  #include <memory>
10
10
  #include <mutex>
11
11
  #include <optional>
12
+ #include <stdexcept>
12
13
  #include <string>
13
14
  #include <type_traits>
14
15
  #include <unordered_map>
@@ -48,6 +49,12 @@ public:
48
49
 
49
50
  void registerInstaller(const std::string &brand, InstallerFunc installer) {
50
51
  std::lock_guard<std::mutex> lock(_mutex);
52
+ if (_installers.count(brand) != 0) {
53
+ // The brand is the key unbox() uses to rebuild objects on worklet
54
+ // runtimes - a duplicate would let one class hijack another's unboxing.
55
+ throw std::runtime_error("Duplicate native object brand registered: " +
56
+ brand);
57
+ }
51
58
  _installers[brand] = std::move(installer);
52
59
  }
53
60
 
@@ -235,6 +242,10 @@ public:
235
242
  * Each NativeObject<Derived> type has its own static cache.
236
243
  * Uses StaticRuntimeAwareCache to properly handle runtime lifecycle
237
244
  * and hot reload (where the main runtime is destroyed and recreated).
245
+ *
246
+ * Callers must hold getPrototypeCacheMutex(): the cache is reached
247
+ * concurrently from the main JS thread (create()) and from worklet
248
+ * runtime threads (BoxedWebGPUObject::unbox() -> installPrototype()).
238
249
  */
239
250
  static RuntimeAwareCache<PrototypeCacheEntry> &
240
251
  getPrototypeCache(jsi::Runtime &runtime) {
@@ -242,11 +253,22 @@ public:
242
253
  return cache.get(runtime);
243
254
  }
244
255
 
256
+ /**
257
+ * Per-class mutex guarding getPrototypeCache() and prototype
258
+ * installation. Serializes the StaticRuntimeAwareCache pointer swap
259
+ * (hot reload) and the per-runtime cache lookups.
260
+ */
261
+ static std::mutex &getPrototypeCacheMutex() {
262
+ static std::mutex mutex;
263
+ return mutex;
264
+ }
265
+
245
266
  /**
246
267
  * Ensure the prototype is installed for this runtime.
247
268
  * Called automatically by create(), but can be called manually.
248
269
  */
249
270
  static void installPrototype(jsi::Runtime &runtime) {
271
+ std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
250
272
  auto &entry = getPrototypeCache(runtime).get(runtime);
251
273
  if (entry.prototype.has_value()) {
252
274
  return; // Already installed
@@ -278,6 +300,49 @@ public:
278
300
  defineProperty.call(runtime, prototype, toStringTag, descriptor);
279
301
  }
280
302
 
303
+ // Install a generic toJSON so JSON.stringify works: data properties live
304
+ // as getters on this shared prototype, and JSON.stringify only serializes
305
+ // own enumerable properties (so it would otherwise produce {}).
306
+ auto toJSON = jsi::Function::createFromHostFunction(
307
+ runtime, jsi::PropNameID::forUtf8(runtime, "toJSON"), 0,
308
+ [](jsi::Runtime &rt, const jsi::Value &thisVal,
309
+ const jsi::Value * /*args*/, size_t /*count*/) -> jsi::Value {
310
+ auto thisObj = thisVal.getObject(rt);
311
+ auto objectCtor = rt.global().getPropertyAsObject(rt, "Object");
312
+ auto getPrototypeOf =
313
+ objectCtor.getPropertyAsFunction(rt, "getPrototypeOf");
314
+ auto proto = getPrototypeOf.call(rt, thisObj);
315
+ jsi::Object result(rt);
316
+ if (!proto.isObject()) {
317
+ return std::move(result);
318
+ }
319
+ auto getOwnPropertyNames =
320
+ objectCtor.getPropertyAsFunction(rt, "getOwnPropertyNames");
321
+ auto names =
322
+ getOwnPropertyNames.call(rt, proto).getObject(rt).getArray(rt);
323
+ size_t length = names.size(rt);
324
+ for (size_t i = 0; i < length; i++) {
325
+ auto nameValue = names.getValueAtIndex(rt, i);
326
+ if (!nameValue.isString()) {
327
+ continue;
328
+ }
329
+ auto name = nameValue.getString(rt).utf8(rt);
330
+ if (name == "constructor" || name == "toJSON") {
331
+ continue;
332
+ }
333
+ // Read off `this` so prototype getters evaluate against the
334
+ // object's native state. Getters that throw keep throwing.
335
+ auto value = thisObj.getProperty(rt, name.c_str());
336
+ if (value.isObject() && value.getObject(rt).isFunction(rt)) {
337
+ // Skip methods - JSON.stringify would drop them anyway
338
+ continue;
339
+ }
340
+ result.setProperty(rt, name.c_str(), value);
341
+ }
342
+ return std::move(result);
343
+ });
344
+ prototype.setProperty(runtime, "toJSON", toJSON);
345
+
281
346
  // Cache the prototype
282
347
  entry.prototype = std::move(prototype);
283
348
  }
@@ -303,6 +368,7 @@ public:
303
368
 
304
369
  installPrototype(runtime);
305
370
 
371
+ std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
306
372
  auto &entry = getPrototypeCache(runtime).get(runtime);
307
373
  if (!entry.prototype.has_value()) {
308
374
  return;
@@ -346,14 +412,17 @@ public:
346
412
  obj.setNativeState(runtime, instance);
347
413
 
348
414
  // Set prototype
349
- auto &entry = getPrototypeCache(runtime).get(runtime);
350
- if (entry.prototype.has_value()) {
351
- // Use Object.setPrototypeOf to set the prototype
352
- auto objectCtor =
353
- runtime.global().getPropertyAsObject(runtime, "Object");
354
- auto setPrototypeOf =
355
- objectCtor.getPropertyAsFunction(runtime, "setPrototypeOf");
356
- setPrototypeOf.call(runtime, obj, *entry.prototype);
415
+ {
416
+ std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
417
+ auto &entry = getPrototypeCache(runtime).get(runtime);
418
+ if (entry.prototype.has_value()) {
419
+ // Use Object.setPrototypeOf to set the prototype
420
+ auto objectCtor =
421
+ runtime.global().getPropertyAsObject(runtime, "Object");
422
+ auto setPrototypeOf =
423
+ objectCtor.getPropertyAsFunction(runtime, "setPrototypeOf");
424
+ setPrototypeOf.call(runtime, obj, *entry.prototype);
425
+ }
357
426
  }
358
427
 
359
428
  // Set memory pressure hint for GC
@@ -2,6 +2,6 @@
2
2
 
3
3
  namespace rnwgpu {
4
4
 
5
- jsi::Runtime *BaseRuntimeAwareCache::_mainRuntime = nullptr;
5
+ std::atomic<jsi::Runtime *> BaseRuntimeAwareCache::_mainRuntime{nullptr};
6
6
 
7
7
  } // namespace rnwgpu
@@ -2,8 +2,10 @@
2
2
 
3
3
  #include <jsi/jsi.h>
4
4
 
5
+ #include <atomic>
5
6
  #include <cassert>
6
7
  #include <memory>
8
+ #include <mutex>
7
9
  #include <unordered_map>
8
10
  #include <utility>
9
11
 
@@ -15,17 +17,22 @@ namespace jsi = facebook::jsi;
15
17
 
16
18
  class BaseRuntimeAwareCache {
17
19
  public:
18
- static void setMainJsRuntime(jsi::Runtime *rt) { _mainRuntime = rt; }
20
+ static void setMainJsRuntime(jsi::Runtime *rt) {
21
+ // Atomic: hot reload rewrites this from the JS thread while worklet
22
+ // runtime threads read it concurrently.
23
+ _mainRuntime.store(rt, std::memory_order_release);
24
+ }
19
25
  static jsi::Runtime *getMainJsRuntime() {
20
- assert(_mainRuntime != nullptr &&
26
+ auto *rt = _mainRuntime.load(std::memory_order_acquire);
27
+ assert(rt != nullptr &&
21
28
  "Expected main Javascript runtime to be set in the "
22
29
  "BaseRuntimeAwareCache class.");
23
30
 
24
- return _mainRuntime;
31
+ return rt;
25
32
  }
26
33
 
27
34
  private:
28
- static jsi::Runtime *_mainRuntime;
35
+ static std::atomic<jsi::Runtime *> _mainRuntime;
29
36
  };
30
37
 
31
38
  /**
@@ -56,7 +63,10 @@ class RuntimeAwareCache : public BaseRuntimeAwareCache,
56
63
  public:
57
64
  void onRuntimeDestroyed(jsi::Runtime *rt) override {
58
65
  if (getMainJsRuntime() != rt) {
59
- // We are removing a secondary runtime
66
+ // We are removing a secondary runtime. This is invoked by
67
+ // RuntimeLifecycleMonitor on the destroyed runtime's thread, which may
68
+ // run concurrently with get() on another runtime's thread.
69
+ std::lock_guard<std::mutex> lock(_secondaryCachesMutex);
60
70
  _secondaryRuntimeCaches.erase(rt);
61
71
  }
62
72
  }
@@ -75,6 +85,14 @@ public:
75
85
  if (getMainJsRuntime() == &rt) {
76
86
  return _primaryCache;
77
87
  } else {
88
+ // Guard the secondary map: it can be mutated concurrently by get()
89
+ // on a worklet runtime's thread and by onRuntimeDestroyed() when
90
+ // another secondary runtime is torn down. The main-runtime path above
91
+ // stays lock-free. References into the map remain valid after the
92
+ // lock is released (unordered_map never invalidates references on
93
+ // insert/erase of other keys, and this runtime's entry can only be
94
+ // erased from this runtime's own thread).
95
+ std::lock_guard<std::mutex> lock(_secondaryCachesMutex);
78
96
  if (_secondaryRuntimeCaches.count(&rt) == 0) {
79
97
  // we only add listener when the secondary runtime is used, this assumes
80
98
  // that the secondary runtime is terminated first. This lets us avoid
@@ -88,11 +106,12 @@ public:
88
106
  T cache;
89
107
  _secondaryRuntimeCaches.emplace(&rt, std::move(cache));
90
108
  }
109
+ return _secondaryRuntimeCaches.at(&rt);
91
110
  }
92
- return _secondaryRuntimeCaches.at(&rt);
93
111
  }
94
112
 
95
113
  private:
114
+ std::mutex _secondaryCachesMutex;
96
115
  std::unordered_map<void *, T> _secondaryRuntimeCaches;
97
116
  T _primaryCache;
98
117
  };
@@ -31,9 +31,9 @@
31
31
  #include "GPURenderPassEncoder.h"
32
32
  #include "GPURenderPipeline.h"
33
33
  #include "GPUSampler.h"
34
+ #include "GPUShaderModule.h"
34
35
  #include "GPUSharedFence.h"
35
36
  #include "GPUSharedTextureMemory.h"
36
- #include "GPUShaderModule.h"
37
37
  #include "GPUSupportedLimits.h"
38
38
  #include "GPUTexture.h"
39
39
  #include "GPUTextureView.h"
@@ -237,7 +237,20 @@ void RNWebGPUManager::installWebGPUWorkletHelpers(jsi::Runtime &runtime) {
237
237
  runtime.global().setProperty(runtime, "__webgpuBox", std::move(boxFunc));
238
238
  }
239
239
 
240
+ void RNWebGPUManager::flushPendingSurfaceTransition(
241
+ std::shared_ptr<SurfaceInfo> info) {
242
+ if (info == nullptr || _jsCallInvoker == nullptr) {
243
+ return;
244
+ }
245
+ _jsCallInvoker->invokeAsync(
246
+ [info = std::move(info)] { info->applyPendingAttach(); });
247
+ }
248
+
240
249
  RNWebGPUManager::~RNWebGPUManager() {
250
+ // Drop all canvas registry entries: after a reload the JS side restarts its
251
+ // contextId counter, and stale entries would alias new canvases onto dead
252
+ // surfaces.
253
+ SurfaceRegistry::getInstance().clear();
241
254
  _jsRuntime = nullptr;
242
255
  _jsCallInvoker = nullptr;
243
256
  }
@@ -34,6 +34,15 @@ public:
34
34
  */
35
35
  static void installWebGPUWorkletHelpers(jsi::Runtime &runtime);
36
36
 
37
+ /**
38
+ * Applies a surface attach latched by the platform UI thread (see
39
+ * SurfaceInfo::applyPendingAttach) from the JS thread. Surface attaches are
40
+ * normally adopted at the next frame boundary by whichever thread renders;
41
+ * this flush covers contexts that are not actively rendering (static
42
+ * content), so the last offscreen frame still makes it on screen.
43
+ */
44
+ void flushPendingSurfaceTransition(std::shared_ptr<SurfaceInfo> info);
45
+
37
46
  private:
38
47
  jsi::Runtime *_jsRuntime;
39
48
  std::shared_ptr<facebook::react::CallInvoker> _jsCallInvoker;