react-native-webgpu 0.16.0 → 0.17.0

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 (71) hide show
  1. package/android/build.gradle +11 -1
  2. package/android/src/main/java/com/webgpu/BackingViewKind.java +38 -0
  3. package/android/src/main/java/com/webgpu/WebGPUSurfaceView.java +19 -2
  4. package/android/src/main/java/com/webgpu/WebGPUView.java +115 -63
  5. package/android/src/main/java/com/webgpu/WebGPUViewManager.java +13 -6
  6. package/cpp/jsi/NativeObject.h +84 -34
  7. package/cpp/rnwgpu/RNWebGPUManager.cpp +51 -48
  8. package/cpp/rnwgpu/RNWebGPUManager.h +10 -0
  9. package/cpp/rnwgpu/api/GPUFeatures.h +1 -0
  10. package/cpp/rnwgpu/api/GPUNativeBufferWrapper.h +1 -0
  11. package/cpp/rnwgpu/api/VideoFrame.h +2 -2
  12. package/lib/commonjs/Canvas.js +105 -8
  13. package/lib/commonjs/Canvas.js.map +1 -1
  14. package/lib/commonjs/WebGPUViewNativeComponent.js.map +1 -1
  15. package/lib/commonjs/WebGPUViewNativeComponent.web.js +3 -5
  16. package/lib/commonjs/WebGPUViewNativeComponent.web.js.map +1 -1
  17. package/lib/commonjs/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
  18. package/lib/commonjs/install.js +26 -7
  19. package/lib/commonjs/install.js.map +1 -1
  20. package/lib/commonjs/mock.js +0 -1
  21. package/lib/commonjs/mock.js.map +1 -1
  22. package/lib/module/Canvas.js +105 -8
  23. package/lib/module/Canvas.js.map +1 -1
  24. package/lib/module/WebGPUViewNativeComponent.js.map +1 -1
  25. package/lib/module/WebGPUViewNativeComponent.web.js +3 -5
  26. package/lib/module/WebGPUViewNativeComponent.web.js.map +1 -1
  27. package/lib/module/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
  28. package/lib/module/install.js +26 -7
  29. package/lib/module/install.js.map +1 -1
  30. package/lib/module/mock.js +0 -1
  31. package/lib/module/mock.js.map +1 -1
  32. package/lib/typescript/lib/commonjs/Canvas.d.ts +1 -2
  33. package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
  34. package/lib/typescript/lib/commonjs/WebGPUViewNativeComponent.web.d.ts.map +1 -1
  35. package/lib/typescript/lib/commonjs/install.d.ts +10 -7
  36. package/lib/typescript/lib/commonjs/install.d.ts.map +1 -1
  37. package/lib/typescript/lib/commonjs/mock.d.ts +1 -2
  38. package/lib/typescript/lib/commonjs/mock.d.ts.map +1 -1
  39. package/lib/typescript/lib/module/Canvas.d.ts +1 -2
  40. package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
  41. package/lib/typescript/lib/module/WebGPUViewNativeComponent.web.d.ts.map +1 -1
  42. package/lib/typescript/lib/module/install.d.ts.map +1 -1
  43. package/lib/typescript/lib/module/mock.d.ts +1 -2
  44. package/lib/typescript/lib/module/mock.d.ts.map +1 -1
  45. package/lib/typescript/src/Canvas.d.ts +62 -16
  46. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  47. package/lib/typescript/src/WebGPUViewNativeComponent.d.ts +3 -2
  48. package/lib/typescript/src/WebGPUViewNativeComponent.d.ts.map +1 -1
  49. package/lib/typescript/src/WebGPUViewNativeComponent.web.d.ts +3 -2
  50. package/lib/typescript/src/WebGPUViewNativeComponent.web.d.ts.map +1 -1
  51. package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts +1 -0
  52. package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
  53. package/lib/typescript/src/install.d.ts +10 -7
  54. package/lib/typescript/src/install.d.ts.map +1 -1
  55. package/lib/typescript/src/mock.d.ts +2 -2
  56. package/lib/typescript/src/mock.d.ts.map +1 -1
  57. package/lib/typescript/src/types.d.ts +1 -1
  58. package/package.json +8 -7
  59. package/plugin/build/index.js +2 -1
  60. package/plugin/build/setMinSdkVersion.d.ts +9 -0
  61. package/plugin/build/setMinSdkVersion.js +25 -0
  62. package/plugin/build/withMinSdkVersion.d.ts +11 -0
  63. package/plugin/build/withMinSdkVersion.js +20 -0
  64. package/src/Canvas.tsx +176 -33
  65. package/src/WebGPUViewNativeComponent.ts +6 -4
  66. package/src/WebGPUViewNativeComponent.web.ts +7 -5
  67. package/src/external/reanimated/registerWebGPUForReanimated.ts +4 -0
  68. package/src/index.tsx +3 -3
  69. package/src/install.ts +26 -7
  70. package/src/mock.ts +5 -7
  71. package/src/types.ts +1 -1
@@ -75,6 +75,16 @@ def getExtOrIntegerDefault(name) {
75
75
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Wgpu_" + name]).toInteger()
76
76
  }
77
77
 
78
+ // androidMinSdkVersion in package.json is the lowest API level the native code
79
+ // builds for: it calls the AHardwareBuffer API, from Android 8 (API 26). An
80
+ // app's lower minSdkVersion does not lower it, so such an app gets the
81
+ // manifest merger's error, with its suggested fix, instead of the compiler's.
82
+ def resolveMinSdkVersion() {
83
+ def floor = new groovy.json.JsonSlurper().parse(file("../package.json")).androidMinSdkVersion as Integer
84
+ def app = rootProject.ext.has("minSdkVersion") ? rootProject.ext.get("minSdkVersion").toString().toInteger() : floor
85
+ return Math.max(app, floor)
86
+ }
87
+
78
88
  def supportsNamespace() {
79
89
  def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
80
90
  def major = parsed[0].toInteger()
@@ -88,7 +98,7 @@ android {
88
98
  compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
89
99
 
90
100
  defaultConfig {
91
- minSdkVersion getExtOrIntegerDefault("minSdkVersion")
101
+ minSdkVersion resolveMinSdkVersion()
92
102
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
93
103
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
94
104
  externalNativeBuild {
@@ -0,0 +1,38 @@
1
+ package com.webgpu;
2
+
3
+ import androidx.annotation.Nullable;
4
+
5
+ /**
6
+ * The Android view a WebGPUView draws into, see WebGPUView.updateView(), and
7
+ * the rules the props resolve by. The JS side (Canvas.tsx) mirrors them to
8
+ * warn in development.
9
+ */
10
+ enum BackingViewKind {
11
+ SURFACE_VIEW,
12
+ TEXTURE_VIEW,
13
+ HARDWARE_BUFFER_VIEW;
14
+
15
+ /**
16
+ * The kind the androidBackingView prop names; a TextureView by default. A
17
+ * HardwareBufferView (a plain View drawing AHardwareBuffers inline) needs
18
+ * Android 10: below it, the request falls back to a TextureView, which
19
+ * composites the same way, and the JS side warns about it in development.
20
+ */
21
+ static BackingViewKind fromBackingView(@Nullable String backingView, boolean hardwareBufferSupported) {
22
+ if ("SurfaceView".equals(backingView)) {
23
+ return SURFACE_VIEW;
24
+ }
25
+ if ("HardwareBufferView".equals(backingView) && hardwareBufferSupported) {
26
+ return HARDWARE_BUFFER_VIEW;
27
+ }
28
+ return TEXTURE_VIEW;
29
+ }
30
+
31
+ /**
32
+ * Whether the canvas composites as opaque: what the prop asks for, except
33
+ * that a HardwareBufferView always composites with alpha.
34
+ */
35
+ static boolean resolveOpaque(BackingViewKind kind, boolean opaque) {
36
+ return opaque && kind != HARDWARE_BUFFER_VIEW;
37
+ }
38
+ }
@@ -13,10 +13,12 @@ public class WebGPUSurfaceView extends SurfaceView implements SurfaceHolder.Call
13
13
 
14
14
  WebGPUAPI mApi;
15
15
 
16
- public WebGPUSurfaceView(Context context, WebGPUAPI api, boolean zOrderOnTop, boolean opaque) {
16
+ public WebGPUSurfaceView(Context context, WebGPUAPI api, boolean zOrderOnTop,
17
+ boolean zOrderMediaOverlay, boolean opaque) {
17
18
  super(context);
18
19
  mApi = api;
19
- setZOrderOnTop(zOrderOnTop);
20
+ // Both are read when the surface is created.
21
+ setZOrder(zOrderOnTop, zOrderMediaOverlay);
20
22
  setOpaque(opaque);
21
23
  getHolder().addCallback(this);
22
24
  }
@@ -27,6 +29,21 @@ public class WebGPUSurfaceView extends SurfaceView implements SurfaceHolder.Call
27
29
  getHolder().setFormat(opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
28
30
  }
29
31
 
32
+ // The sublayer the surface is composited at, relative to the window: above
33
+ // it (on top), or below it, above the other SurfaceViews there (media
34
+ // overlay) or among them (the default). setZOrderOnTop(false) resets the
35
+ // request to the default, so it goes first. Before Android 11 the request
36
+ // is read when the surface is created; from Android 11 on, SurfaceView
37
+ // applies a changed request with the window's next frame (its pre-draw
38
+ // listener runs updateSurface()), which invalidate() schedules.
39
+ public void setZOrder(boolean onTop, boolean mediaOverlay) {
40
+ setZOrderOnTop(onTop);
41
+ if (!onTop) {
42
+ setZOrderMediaOverlay(mediaOverlay);
43
+ }
44
+ invalidate();
45
+ }
46
+
30
47
  @Override
31
48
  protected void onDetachedFromWindow() {
32
49
  super.onDetachedFromWindow();
@@ -2,28 +2,40 @@ package com.webgpu;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.os.Build;
5
+ import android.util.Log;
5
6
  import android.view.Surface;
6
7
  import android.view.View;
7
8
 
9
+ import androidx.annotation.Nullable;
10
+
8
11
  import com.facebook.proguard.annotations.DoNotStrip;
9
12
  import com.facebook.react.uimanager.ThemedReactContext;
10
13
  import com.facebook.react.views.view.ReactViewGroup;
11
14
 
15
+ /**
16
+ * The Android view behind <Canvas>. It is backed by a TextureView, a
17
+ * SurfaceView or a HardwareBufferView, named by the androidBackingView prop
18
+ * and created once, from the props of the first transaction, for the life of
19
+ * the view (see updateView()).
20
+ */
12
21
  public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
13
-
14
- // Backing view kinds, see updateView().
15
- private static final int KIND_SURFACE_VIEW = 0;
16
- private static final int KIND_TEXTURE_VIEW = 1;
17
- private static final int KIND_HARDWARE_BUFFER_VIEW = 2;
22
+ private static final String TAG = "WebGPUView";
18
23
 
19
24
  private int mContextId;
20
- private boolean mOpaque = true;
21
- private String mSurfaceType = "auto";
25
+ // Props. opaque is applied in place, and so are the z-order props where
26
+ // Android allows it (see takesZOrder); backingView is read once, when the
27
+ // backing view is created (see updateView()).
28
+ private BackingViewKind mBackingView = BackingViewKind.TEXTURE_VIEW;
29
+ private boolean mOpaque = false;
22
30
  private boolean mZOrderOnTop = false;
23
- private int mAppliedKind = -1;
24
- private boolean mAppliedZOrderOnTop;
31
+ private boolean mZOrderMediaOverlay = false;
25
32
  private WebGPUModule mModule;
33
+ // The backing view, created by the first prop transaction and kept, with
34
+ // the kind it was created with. The kind is null until then: it is what
35
+ // tells a mounted canvas apart.
26
36
  private View mView = null;
37
+ @Nullable
38
+ private BackingViewKind mKind = null;
27
39
  // Only a SurfaceView votes: a TextureView or HardwareBufferView is
28
40
  // composited into the window, which votes for it.
29
41
  private final SurfaceFrameRateVote mFrameRateVote = new SurfaceFrameRateVote(
@@ -51,74 +63,114 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
51
63
  mContextId = contextId;
52
64
  }
53
65
 
66
+ // The backing view is created from the props of the first transaction and
67
+ // kept (see updateView()). React calls a setter again only when its prop
68
+ // changed, so a call on a mounted canvas is a change: the setters below
69
+ // apply it in place where the view can take it, else they ignore it and
70
+ // say so (a canvas that needs another backing view is remounted, with a
71
+ // new key). The JS side warns about the same changes in development (see
72
+ // Canvas.tsx), mirroring these rules.
73
+
74
+ public void setBackingView(@Nullable String value) {
75
+ if (mKind != null) {
76
+ warnIgnoredOnceMounted("android.backingView");
77
+ return;
78
+ }
79
+ boolean hardwareBufferSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
80
+ mBackingView = BackingViewKind.fromBackingView(value, hardwareBufferSupported);
81
+ }
82
+
54
83
  public void setOpaque(boolean value) {
55
84
  mOpaque = value;
56
85
  }
57
86
 
58
- public void setSurfaceType(String value) {
59
- mSurfaceType = value;
87
+ public void setZOrderOnTop(boolean value) {
88
+ if (takesZOrder("android.zOrderOnTop")) {
89
+ mZOrderOnTop = value;
90
+ applyZOrder();
91
+ }
60
92
  }
61
93
 
62
- public void setZOrderOnTop(boolean value) {
63
- mZOrderOnTop = value;
64
- }
65
-
66
- // Resolve the backing view from the props. "auto" picks SurfaceView for an
67
- // opaque canvas and TextureView for a non-opaque one. WebGPUHardwareBufferView
68
- // (a plain View drawing AHardwareBuffers inline, API 29+) is opt-in via
69
- // surfaceType until it gets a runtime fallback to TextureView when the
70
- // AHardwareBuffer import fails on a device; below API 29 the request itself
71
- // falls back to TextureView.
72
- private int resolveKind() {
73
- if ("SurfaceView".equals(mSurfaceType)) {
74
- return KIND_SURFACE_VIEW;
94
+ public void setZOrderMediaOverlay(boolean value) {
95
+ if (takesZOrder("android.zOrderMediaOverlay")) {
96
+ mZOrderMediaOverlay = value;
97
+ applyZOrder();
98
+ }
99
+ }
100
+
101
+ // Whether a z-order prop is taken. Before the backing view exists it is
102
+ // read when the view is created. After that only a SurfaceView has a
103
+ // z-order of its own (elsewhere the prop never applied, and the change is
104
+ // moot), and SurfaceView moves a live surface since Android 11, for an app
105
+ // targeting it; before that, the surface reads it when it is created.
106
+ private boolean takesZOrder(String prop) {
107
+ if (mKind == null) {
108
+ return true;
109
+ }
110
+ if (mKind != BackingViewKind.SURFACE_VIEW) {
111
+ return false;
75
112
  }
76
- if ("TextureView".equals(mSurfaceType)) {
77
- return KIND_TEXTURE_VIEW;
113
+ boolean inPlace = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
114
+ && getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R;
115
+ if (!inPlace) {
116
+ warnIgnoredOnceMounted(prop);
78
117
  }
79
- if ("HardwareBufferView".equals(mSurfaceType)) {
80
- boolean hardwareBufferSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
81
- return hardwareBufferSupported ? KIND_HARDWARE_BUFFER_VIEW : KIND_TEXTURE_VIEW;
118
+ return inPlace;
119
+ }
120
+
121
+ private void applyZOrder() {
122
+ if (mKind == BackingViewKind.SURFACE_VIEW) {
123
+ ((WebGPUSurfaceView) mView).setZOrder(mZOrderOnTop, mZOrderMediaOverlay);
82
124
  }
83
- return mOpaque ? KIND_SURFACE_VIEW : KIND_TEXTURE_VIEW;
125
+ }
126
+
127
+ private void warnIgnoredOnceMounted(String prop) {
128
+ Log.w(TAG, "Ignoring a change of " + prop + " on a mounted canvas: the backing view"
129
+ + " is chosen when the canvas mounts. Remount the canvas (give it a new key)"
130
+ + " to change it.");
84
131
  }
85
132
 
86
133
  // Apply the complete prop transaction once, after contextId and all rendering
87
- // options have arrived. Only a change of backing view (or of zOrderOnTop,
88
- // which a SurfaceView must know before it attaches) replaces the child;
89
- // opacity is applied in place.
134
+ // options have arrived. The first one creates the backing view, which is
135
+ // then kept: a SurfaceView must know its z-order before it attaches (before
136
+ // Android 11), and keeping a canvas on screen while its backing view is
137
+ // replaced takes hold-and-retire logic specific to each Android release (see
138
+ // PR #563, and react-native-skia's #4189 and #4192), which this view does
139
+ // not carry. A canvas that needs another backing view is remounted instead,
140
+ // and is off screen for a frame or two until the new view draws its first
141
+ // frame. Later transactions apply the opacity in place (the setters decide
142
+ // what else is taken); WebGPUHardwareBufferView always composites with
143
+ // alpha, so there it is a no-op.
90
144
  public void updateView() {
91
- int kind = resolveKind();
92
- boolean zOrderOnTop = kind == KIND_SURFACE_VIEW && mZOrderOnTop;
93
- if (mView == null || kind != mAppliedKind || zOrderOnTop != mAppliedZOrderOnTop) {
94
- if (mView != null) {
95
- removeView(mView);
96
- }
97
- mAppliedKind = kind;
98
- mAppliedZOrderOnTop = zOrderOnTop;
99
- Context ctx = getContext();
100
- switch (kind) {
101
- case KIND_HARDWARE_BUFFER_VIEW:
102
- mView = new WebGPUHardwareBufferView(ctx, this);
103
- break;
104
- case KIND_TEXTURE_VIEW:
105
- mView = new WebGPUTextureView(ctx, this, mOpaque);
106
- break;
107
- default:
108
- mView = new WebGPUSurfaceView(ctx, this, zOrderOnTop, mOpaque);
109
- break;
145
+ if (mKind != null) {
146
+ boolean opaque = BackingViewKind.resolveOpaque(mKind, mOpaque);
147
+ if (mKind == BackingViewKind.TEXTURE_VIEW) {
148
+ ((WebGPUTextureView) mView).setOpaque(opaque);
149
+ } else if (mKind == BackingViewKind.SURFACE_VIEW) {
150
+ ((WebGPUSurfaceView) mView).setOpaque(opaque);
110
151
  }
111
- addView(mView);
112
- // ReactViewGroup.requestLayout() is a deliberate no-op, so addView outside
113
- // the layout pass never lays the child out; do it by hand or the new view
114
- // stays 0x0 and never gets a surface.
115
- layoutChild();
116
- } else if (kind == KIND_TEXTURE_VIEW) {
117
- ((WebGPUTextureView) mView).setOpaque(mOpaque);
118
- } else if (kind == KIND_SURFACE_VIEW) {
119
- ((WebGPUSurfaceView) mView).setOpaque(mOpaque);
152
+ return;
153
+ }
154
+ BackingViewKind kind = mBackingView;
155
+ mKind = kind;
156
+ boolean opaque = BackingViewKind.resolveOpaque(kind, mOpaque);
157
+ Context ctx = getContext();
158
+ switch (kind) {
159
+ case HARDWARE_BUFFER_VIEW:
160
+ mView = new WebGPUHardwareBufferView(ctx, this);
161
+ break;
162
+ case TEXTURE_VIEW:
163
+ mView = new WebGPUTextureView(ctx, this, opaque);
164
+ break;
165
+ default:
166
+ mView = new WebGPUSurfaceView(ctx, this, mZOrderOnTop, mZOrderMediaOverlay, opaque);
167
+ break;
120
168
  }
121
- // WebGPUHardwareBufferView always composites with alpha; opacity is a no-op.
169
+ addView(mView);
170
+ // ReactViewGroup.requestLayout() is a deliberate no-op, so addView outside
171
+ // the layout pass never lays the child out; do it by hand or the new view
172
+ // stays 0x0 and never gets a surface.
173
+ layoutChild();
122
174
  }
123
175
 
124
176
  private void layoutChild() {
@@ -138,7 +190,7 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
138
190
  float density = getResources().getDisplayMetrics().density;
139
191
  float width = getWidth() / density;
140
192
  float height = getHeight() / density;
141
- boolean votesFrameRate = mAppliedKind == KIND_SURFACE_VIEW;
193
+ boolean votesFrameRate = mKind == BackingViewKind.SURFACE_VIEW;
142
194
  if (votesFrameRate) {
143
195
  mFrameRateVote.attach(surface);
144
196
  }
@@ -1,6 +1,7 @@
1
1
  package com.webgpu;
2
2
 
3
3
  import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
4
5
 
5
6
  import com.facebook.react.module.annotations.ReactModule;
6
7
  import com.facebook.react.uimanager.ThemedReactContext;
@@ -30,15 +31,15 @@ public class WebGPUViewManager extends WebGPUViewManagerSpec<WebGPUView> {
30
31
  }
31
32
 
32
33
  @Override
33
- @ReactProp(name = "opaque", defaultBoolean = true)
34
- public void setOpaque(WebGPUView view, boolean value) {
35
- view.setOpaque(value);
34
+ @ReactProp(name = "androidBackingView")
35
+ public void setAndroidBackingView(WebGPUView view, @Nullable String value) {
36
+ view.setBackingView(value);
36
37
  }
37
38
 
38
39
  @Override
39
- @ReactProp(name = "androidSurfaceType")
40
- public void setAndroidSurfaceType(WebGPUView view, String value) {
41
- view.setSurfaceType(value);
40
+ @ReactProp(name = "androidOpaque")
41
+ public void setAndroidOpaque(WebGPUView view, boolean value) {
42
+ view.setOpaque(value);
42
43
  }
43
44
 
44
45
  @Override
@@ -47,6 +48,12 @@ public class WebGPUViewManager extends WebGPUViewManagerSpec<WebGPUView> {
47
48
  view.setZOrderOnTop(value);
48
49
  }
49
50
 
51
+ @Override
52
+ @ReactProp(name = "androidZOrderMediaOverlay")
53
+ public void setAndroidZOrderMediaOverlay(WebGPUView view, boolean value) {
54
+ view.setZOrderMediaOverlay(value);
55
+ }
56
+
50
57
  @Override
51
58
  protected void onAfterUpdateTransaction(@NonNull WebGPUView view) {
52
59
  super.onAfterUpdateTransaction(view);
@@ -179,6 +179,14 @@ private:
179
179
  * getters and Proxy traps of unrelated objects never run.
180
180
  * - box(value): wraps the object into a BoxedWebGPUObject, branded with its
181
181
  * class name, for unbox() on the target runtime.
182
+ * - installConstructors(): installs the constructor of every WebGPU class on
183
+ * the calling runtime, skipping the ones already there, so `instanceof`
184
+ * works against any class on that runtime, including classes no object of
185
+ * which was ever created there or crossed into it (unboxing installs only
186
+ * the class of the object that crosses). installWebGPU() calls it. The
187
+ * class list belongs to the API layer (RNWebGPUManager::installConstructors)
188
+ * and is handed in at construction: every API class includes this header,
189
+ * which therefore cannot include them back.
182
190
  *
183
191
  * One instance is installed on the main runtime as
184
192
  * globalThis.__webgpuWorkletHelpers and captured by the serializer's
@@ -189,8 +197,26 @@ private:
189
197
  */
190
198
  class WebGPUWorkletHelpers : public jsi::HostObject {
191
199
  public:
200
+ using InstallConstructors = void (*)(jsi::Runtime &);
201
+
202
+ explicit WebGPUWorkletHelpers(InstallConstructors installConstructors)
203
+ : _installConstructors(installConstructors) {}
204
+
192
205
  jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override {
193
206
  auto propName = name.utf8(runtime);
207
+ if (propName == "installConstructors") {
208
+ auto install = _installConstructors;
209
+ return jsi::Function::createFromHostFunction(
210
+ runtime, name, 0,
211
+ [install](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
212
+ const jsi::Value * /*args*/,
213
+ size_t /*count*/) -> jsi::Value {
214
+ if (install != nullptr) {
215
+ install(rt);
216
+ }
217
+ return jsi::Value::undefined();
218
+ });
219
+ }
194
220
  if (propName == "isNativeObject") {
195
221
  return jsi::Function::createFromHostFunction(
196
222
  runtime, name, 1,
@@ -230,11 +256,15 @@ public:
230
256
  std::vector<jsi::PropNameID>
231
257
  getPropertyNames(jsi::Runtime &runtime) override {
232
258
  std::vector<jsi::PropNameID> names;
233
- names.reserve(2);
259
+ names.reserve(3);
234
260
  names.push_back(jsi::PropNameID::forUtf8(runtime, "isNativeObject"));
235
261
  names.push_back(jsi::PropNameID::forUtf8(runtime, "box"));
262
+ names.push_back(jsi::PropNameID::forUtf8(runtime, "installConstructors"));
236
263
  return names;
237
264
  }
265
+
266
+ private:
267
+ InstallConstructors _installConstructors;
238
268
  };
239
269
 
240
270
  /**
@@ -298,6 +328,11 @@ public:
298
328
  return; // Already installed on this runtime
299
329
  }
300
330
 
331
+ // Every wrapper is made by create(), which installs the prototype first,
332
+ // so the class is registered before any of its objects can be boxed,
333
+ // whether or not its constructor is ever installed as a global.
334
+ registerReconstructor();
335
+
301
336
  // Create prototype object
302
337
  jsi::Object prototype(runtime);
303
338
 
@@ -378,43 +413,23 @@ public:
378
413
  * The constructor throws if called directly (these objects are only
379
414
  * created internally by the native code).
380
415
  *
381
- * Also registers this class with NativeObjectRegistry so that
382
- * BoxedWebGPUObject::unbox() can rebuild its objects on secondary runtimes.
416
+ * Idempotent per runtime: once the global exists there, later calls return
417
+ * without touching it, so the constructor object keeps its identity.
418
+ *
419
+ * Not needed for objects to cross runtimes: installPrototype() registers
420
+ * the class with NativeObjectRegistry.
383
421
  */
384
422
  static void installConstructor(jsi::Runtime &runtime) {
385
- // Register the reconstructor used by BoxedWebGPUObject::unbox() to
386
- // rebuild this object (prototype + native state) on another runtime.
387
- static std::once_flag registryFlag;
388
- std::call_once(registryFlag, []() {
389
- NativeObjectRegistry::getInstance().registerClass(
390
- Derived::CLASS_NAME,
391
- [](jsi::Runtime &rt,
392
- std::shared_ptr<jsi::NativeState> state) -> jsi::Value {
393
- auto instance = std::dynamic_pointer_cast<Derived>(state);
394
- if (instance == nullptr) {
395
- throw jsi::JSError(rt, "Invalid boxed native object state");
396
- }
397
- // The constructor makes `instanceof` work on this runtime too.
398
- if (!rt.global().hasProperty(rt, Derived::CLASS_NAME)) {
399
- Derived::installConstructor(rt);
400
- }
401
- // Unboxing hands back the wrapper cached for the target runtime,
402
- // creating it on first use. Keep the runtime the object was
403
- // originally created on: async native code (GPUDevice error/lost
404
- // events, GPUAdapter::requestDevice) delivers into the creation
405
- // runtime, and rebinding it to a worklet runtime would invoke
406
- // main-runtime jsi::Functions on the wrong runtime and thread.
407
- auto *originalRuntime = instance->getCreationRuntime();
408
- auto value = Derived::create(rt, instance);
409
- if (originalRuntime != nullptr) {
410
- instance->setCreationRuntime(originalRuntime);
411
- }
412
- return value;
413
- });
414
- });
415
-
416
423
  installPrototype(runtime);
417
424
 
425
+ // Once per runtime. installWebGPU() runs this at the top of every worklet
426
+ // invocation, and reinstalling would replace the global with a new
427
+ // function object each time (`const C = GPUBuffer; C === GPUBuffer`
428
+ // would be false).
429
+ if (runtime.global().hasProperty(runtime, Derived::CLASS_NAME)) {
430
+ return;
431
+ }
432
+
418
433
  auto *prototype = getCachedPrototype(runtime);
419
434
  if (prototype == nullptr) {
420
435
  return;
@@ -441,6 +456,41 @@ public:
441
456
  runtime.global().setProperty(runtime, Derived::CLASS_NAME, std::move(ctor));
442
457
  }
443
458
 
459
+ /**
460
+ * Registers the reconstructor BoxedWebGPUObject::unbox() uses to rebuild
461
+ * this class's objects (prototype + native state) on another runtime.
462
+ * Once per process: the registry outlives every runtime.
463
+ */
464
+ static void registerReconstructor() {
465
+ static std::once_flag registryFlag;
466
+ std::call_once(registryFlag, []() {
467
+ NativeObjectRegistry::getInstance().registerClass(
468
+ Derived::CLASS_NAME,
469
+ [](jsi::Runtime &rt,
470
+ std::shared_ptr<jsi::NativeState> state) -> jsi::Value {
471
+ auto instance = std::dynamic_pointer_cast<Derived>(state);
472
+ if (instance == nullptr) {
473
+ throw jsi::JSError(rt, "Invalid boxed native object state");
474
+ }
475
+ // The constructor makes `instanceof` work on this runtime too
476
+ // (a no-op once it is installed there).
477
+ Derived::installConstructor(rt);
478
+ // Unboxing hands back the wrapper cached for the target runtime,
479
+ // creating it on first use. Keep the runtime the object was
480
+ // originally created on: async native code (GPUDevice error/lost
481
+ // events, GPUAdapter::requestDevice) delivers into the creation
482
+ // runtime, and rebinding it to a worklet runtime would invoke
483
+ // main-runtime jsi::Functions on the wrong runtime and thread.
484
+ auto *originalRuntime = instance->getCreationRuntime();
485
+ auto value = Derived::create(rt, instance);
486
+ if (originalRuntime != nullptr) {
487
+ instance->setCreationRuntime(originalRuntime);
488
+ }
489
+ return value;
490
+ });
491
+ });
492
+ }
493
+
444
494
  /**
445
495
  * Returns the JS wrapper of `instance` on `runtime`, creating it on first
446
496
  * use.
@@ -41,6 +41,7 @@
41
41
  #include "GPUTextureView.h"
42
42
  #include "GPUUncapturedErrorEvent.h"
43
43
  #include "GPUValidationError.h"
44
+ #include "ImageBitmap.h"
44
45
  #include "VideoFrame.h"
45
46
  #include "VideoPlayer.h"
46
47
 
@@ -78,54 +79,11 @@ RNWebGPUManager::RNWebGPUManager(
78
79
  std::make_shared<RNWebGPU>(gpu, _platformContext, _jsCallInvoker);
79
80
  _gpu = gpu->get();
80
81
 
81
- // RNWebGPU needs its brand registered in NativeObjectRegistry so the boxing
82
- // path can install the prototype on worklet runtimes. installConstructor
83
- // does that registration but also sets globalThis.RNWebGPU = ctor, so we
84
- // call it FIRST and then overwrite the global with the actual instance.
85
- RNWebGPU::installConstructor(*_jsRuntime);
86
82
  _jsRuntime->global().setProperty(*_jsRuntime, "RNWebGPU",
87
83
  RNWebGPU::create(*_jsRuntime, rnWebGPU));
88
84
 
89
85
  // Install constructors for instanceof support
90
- GPU::installConstructor(*_jsRuntime);
91
- GPUAdapter::installConstructor(*_jsRuntime);
92
- GPUAdapterInfo::installConstructor(*_jsRuntime);
93
- GPUBindGroup::installConstructor(*_jsRuntime);
94
- GPUBindGroupLayout::installConstructor(*_jsRuntime);
95
- GPUBuffer::installConstructor(*_jsRuntime);
96
- GPUCanvasContext::installConstructor(*_jsRuntime);
97
- GPUCommandBuffer::installConstructor(*_jsRuntime);
98
- GPUCommandEncoder::installConstructor(*_jsRuntime);
99
- GPUCompilationInfo::installConstructor(*_jsRuntime);
100
- GPUCompilationMessage::installConstructor(*_jsRuntime);
101
- GPUComputePassEncoder::installConstructor(*_jsRuntime);
102
- GPUComputePipeline::installConstructor(*_jsRuntime);
103
- GPUDevice::installConstructor(*_jsRuntime);
104
- GPUDeviceLostInfo::installConstructor(*_jsRuntime);
105
- GPUError::installConstructor(*_jsRuntime);
106
- GPUExternalTexture::installConstructor(*_jsRuntime);
107
- GPUNativeBufferExternalTexture::installConstructor(*_jsRuntime);
108
- GPUNativeBufferTexture::installConstructor(*_jsRuntime);
109
- GPUInternalError::installConstructor(*_jsRuntime);
110
- GPUOutOfMemoryError::installConstructor(*_jsRuntime);
111
- GPUValidationError::installConstructor(*_jsRuntime);
112
- GPUUncapturedErrorEvent::installConstructor(*_jsRuntime);
113
- GPUPipelineLayout::installConstructor(*_jsRuntime);
114
- GPUQuerySet::installConstructor(*_jsRuntime);
115
- GPUQueue::installConstructor(*_jsRuntime);
116
- GPURenderBundle::installConstructor(*_jsRuntime);
117
- GPURenderBundleEncoder::installConstructor(*_jsRuntime);
118
- GPURenderPassEncoder::installConstructor(*_jsRuntime);
119
- GPURenderPipeline::installConstructor(*_jsRuntime);
120
- GPUSampler::installConstructor(*_jsRuntime);
121
- GPUSharedFence::installConstructor(*_jsRuntime);
122
- GPUSharedTextureMemory::installConstructor(*_jsRuntime);
123
- GPUShaderModule::installConstructor(*_jsRuntime);
124
- GPUSupportedLimits::installConstructor(*_jsRuntime);
125
- GPUTexture::installConstructor(*_jsRuntime);
126
- GPUTextureView::installConstructor(*_jsRuntime);
127
- VideoFrame::installConstructor(*_jsRuntime);
128
- VideoPlayer::installConstructor(*_jsRuntime);
86
+ installConstructors(*_jsRuntime);
129
87
 
130
88
  // Install constant objects as plain JS objects with own properties
131
89
  _jsRuntime->global().setProperty(*_jsRuntime, "GPUBufferUsage",
@@ -144,14 +102,59 @@ RNWebGPUManager::RNWebGPUManager(
144
102
  installWebGPUWorkletHelpers(*_jsRuntime);
145
103
  }
146
104
 
105
+ void RNWebGPUManager::installConstructors(jsi::Runtime &runtime) {
106
+ GPU::installConstructor(runtime);
107
+ GPUAdapter::installConstructor(runtime);
108
+ GPUAdapterInfo::installConstructor(runtime);
109
+ GPUBindGroup::installConstructor(runtime);
110
+ GPUBindGroupLayout::installConstructor(runtime);
111
+ GPUBuffer::installConstructor(runtime);
112
+ GPUCanvasContext::installConstructor(runtime);
113
+ GPUCommandBuffer::installConstructor(runtime);
114
+ GPUCommandEncoder::installConstructor(runtime);
115
+ GPUCompilationInfo::installConstructor(runtime);
116
+ GPUCompilationMessage::installConstructor(runtime);
117
+ GPUComputePassEncoder::installConstructor(runtime);
118
+ GPUComputePipeline::installConstructor(runtime);
119
+ GPUDevice::installConstructor(runtime);
120
+ GPUDeviceLostInfo::installConstructor(runtime);
121
+ GPUError::installConstructor(runtime);
122
+ GPUExternalTexture::installConstructor(runtime);
123
+ GPUNativeBufferExternalTexture::installConstructor(runtime);
124
+ GPUNativeBufferTexture::installConstructor(runtime);
125
+ GPUInternalError::installConstructor(runtime);
126
+ GPUOutOfMemoryError::installConstructor(runtime);
127
+ GPUValidationError::installConstructor(runtime);
128
+ GPUUncapturedErrorEvent::installConstructor(runtime);
129
+ GPUPipelineLayout::installConstructor(runtime);
130
+ GPUQuerySet::installConstructor(runtime);
131
+ GPUQueue::installConstructor(runtime);
132
+ GPURenderBundle::installConstructor(runtime);
133
+ GPURenderBundleEncoder::installConstructor(runtime);
134
+ GPURenderPassEncoder::installConstructor(runtime);
135
+ GPURenderPipeline::installConstructor(runtime);
136
+ GPUSampler::installConstructor(runtime);
137
+ GPUSharedFence::installConstructor(runtime);
138
+ GPUSharedTextureMemory::installConstructor(runtime);
139
+ GPUShaderModule::installConstructor(runtime);
140
+ GPUSupportedLimits::installConstructor(runtime);
141
+ GPUTexture::installConstructor(runtime);
142
+ GPUTextureView::installConstructor(runtime);
143
+ ImageBitmap::installConstructor(runtime);
144
+ VideoFrame::installConstructor(runtime);
145
+ VideoPlayer::installConstructor(runtime);
146
+ }
147
+
147
148
  void RNWebGPUManager::installWebGPUWorkletHelpers(jsi::Runtime &runtime) {
148
- // The serializer worklets capture this object by closure; worklets passes a
149
- // HostObject to every runtime by reference, so installing it here, on the
150
- // main runtime, is enough (see WebGPUWorkletHelpers).
149
+ // The serializer worklets and installWebGPU() capture this object by
150
+ // closure; worklets passes a HostObject to every runtime by reference, so
151
+ // installing it here, on the main runtime, is enough (see
152
+ // WebGPUWorkletHelpers).
151
153
  runtime.global().setProperty(
152
154
  runtime, "__webgpuWorkletHelpers",
153
155
  jsi::Object::createFromHostObject(
154
- runtime, std::make_shared<WebGPUWorkletHelpers>()));
156
+ runtime, std::make_shared<WebGPUWorkletHelpers>(
157
+ &RNWebGPUManager::installConstructors)));
155
158
  }
156
159
 
157
160
  void RNWebGPUManager::flushPendingSurfaceTransition(