react-native-webgpu 0.15.2 → 0.16.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 (47) hide show
  1. package/android/CMakeLists.txt +5 -0
  2. package/android/cpp/cpp-adapter.cpp +109 -11
  3. package/android/src/main/java/com/webgpu/SurfaceFrameRateVote.java +97 -85
  4. package/android/src/main/java/com/webgpu/WebGPUView.java +13 -8
  5. package/apple/ApplePlatformContext.h +1 -0
  6. package/cpp/jsi/JSICache.cpp +2 -0
  7. package/cpp/jsi/NativeObject.h +36 -12
  8. package/cpp/rnwgpu/RNWebGPUManager.cpp +4 -0
  9. package/cpp/rnwgpu/SurfaceRegistry.h +4 -4
  10. package/cpp/rnwgpu/api/GPUDevice.cpp +96 -0
  11. package/cpp/rnwgpu/api/GPUDevice.h +22 -0
  12. package/cpp/rnwgpu/api/GPUError.h +1 -0
  13. package/cpp/rnwgpu/api/GPUExtent2D.h +56 -0
  14. package/cpp/rnwgpu/api/GPUExternalTexture.cpp +32 -408
  15. package/cpp/rnwgpu/api/GPUExternalTexture.h +12 -37
  16. package/cpp/rnwgpu/api/GPUInternalError.h +1 -0
  17. package/cpp/rnwgpu/api/GPUNativeBufferExternalTexture.h +60 -0
  18. package/cpp/rnwgpu/api/GPUNativeBufferTexture.h +52 -0
  19. package/cpp/rnwgpu/api/GPUNativeBufferWrapper.cpp +144 -0
  20. package/cpp/rnwgpu/api/GPUNativeBufferWrapper.h +142 -0
  21. package/cpp/rnwgpu/api/GPUOutOfMemoryError.h +1 -0
  22. package/cpp/rnwgpu/api/GPUQueue.h +1 -0
  23. package/cpp/rnwgpu/api/GPUSharedFence.h +1 -0
  24. package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +12 -44
  25. package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +1 -0
  26. package/cpp/rnwgpu/api/GPUUncapturedErrorEvent.h +1 -0
  27. package/cpp/rnwgpu/api/GPUValidationError.h +1 -0
  28. package/cpp/rnwgpu/api/NativeFrameImport.cpp +638 -0
  29. package/cpp/rnwgpu/api/NativeFrameImport.h +86 -0
  30. package/cpp/rnwgpu/api/VideoFrame.h +21 -7
  31. package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +153 -1
  32. package/cpp/rnwgpu/api/descriptors/GPUNativeBufferTextureDescriptor.h +55 -0
  33. package/cpp/rnwgpu/async/RuntimeContext.cpp +1 -0
  34. package/lib/commonjs/Canvas.js.map +1 -1
  35. package/lib/commonjs/index.js.map +1 -1
  36. package/lib/module/Canvas.js.map +1 -1
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/typescript/src/Canvas.d.ts +2 -31
  39. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  40. package/lib/typescript/src/index.d.ts +4 -2
  41. package/lib/typescript/src/index.d.ts.map +1 -1
  42. package/lib/typescript/src/types.d.ts +33 -0
  43. package/lib/typescript/src/types.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/Canvas.tsx +3 -36
  46. package/src/index.tsx +24 -0
  47. package/src/types.ts +97 -0
@@ -46,6 +46,8 @@ add_library(${PACKAGE_NAME} SHARED
46
46
  ../cpp/rnwgpu/api/GPUSharedFence.cpp
47
47
  ../cpp/rnwgpu/api/GPUSharedTextureMemory.cpp
48
48
  ../cpp/rnwgpu/api/GPUExternalTexture.cpp
49
+ ../cpp/rnwgpu/api/GPUNativeBufferWrapper.cpp
50
+ ../cpp/rnwgpu/api/NativeFrameImport.cpp
49
51
  ../cpp/rnwgpu/api/GPURenderBundleEncoder.cpp
50
52
  ../cpp/rnwgpu/api/GPURenderPassEncoder.cpp
51
53
  ../cpp/rnwgpu/api/GPURenderPipeline.cpp
@@ -96,6 +98,8 @@ if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
96
98
  log
97
99
  jnigraphics
98
100
  android
101
+ # sync_merge, for the one fence endAccess() returns
102
+ sync
99
103
  fbjni::fbjni
100
104
  ReactAndroid::jsi
101
105
  ReactAndroid::reactnative
@@ -108,6 +112,7 @@ else()
108
112
  log
109
113
  jnigraphics
110
114
  android
115
+ sync
111
116
  fbjni::fbjni
112
117
  ReactAndroid::jsi
113
118
  ReactAndroid::reactnativejni
@@ -1,7 +1,11 @@
1
1
  #include <unistd.h>
2
2
 
3
+ #include <atomic>
4
+ #include <chrono>
5
+ #include <cstdint>
3
6
  #include <cstring>
4
7
  #include <memory>
8
+ #include <mutex>
5
9
  #include <string>
6
10
  #include <unordered_map>
7
11
  #include <utility>
@@ -160,10 +164,10 @@ namespace {
160
164
 
161
165
  // Weak handle on a Java view and one of its no-argument methods, for native
162
166
  // threads to call into it: the fence waiter wakes a WebGPUHardwareBufferView
163
- // when a frame is ready, the presenting thread reports each frame a
164
- // SurfaceView shows. Holding the view weakly means a view that RN has dropped
165
- // is not kept alive by a SurfaceInfo that outlives it (the registry keeps the
166
- // SurfaceInfo until onDropViewInstance).
167
+ // when a frame is ready, the presenting thread tells a SurfaceView it is
168
+ // animating (AnimationReporter). Holding the view weakly means a view that RN
169
+ // has dropped is not kept alive by a SurfaceInfo that outlives it (the
170
+ // registry keeps the SurfaceInfo until onDropViewInstance).
167
171
  struct WeakViewCall {
168
172
  jweak view = nullptr;
169
173
  jmethodID method = nullptr;
@@ -203,11 +207,84 @@ struct WeakViewCall {
203
207
  }
204
208
  };
205
209
 
210
+ // Tells a SurfaceView it starts animating, for its refresh rate vote
211
+ // (WebGPUView.onAnimating), from the frames presented to its surface. A call
212
+ // into Java on the render path costs a JNI transition and attaches a presenting
213
+ // thread the JVM doesn't know for the length of the call, so there is one call
214
+ // per animation: the first frame presented right after its predecessor calls,
215
+ // the frames after it only record the time. The view polls that time from the
216
+ // main thread (releaseIfIdle) and, once the animation has stopped, takes its
217
+ // vote back and re-arms the call for the next animation. Lock-free, since it
218
+ // runs on every present.
219
+ class AnimationReporter {
220
+ public:
221
+ AnimationReporter(JNIEnv *env, jobject view)
222
+ : _onAnimating(env, view, "onAnimating") {}
223
+
224
+ void onFramePresented() {
225
+ const int64_t now = nowNanos();
226
+ const int64_t previous = _lastPresent.exchange(now);
227
+ if (now - previous > kAnimatingIntervalNanos) {
228
+ return;
229
+ }
230
+ _lastAnimating.store(now);
231
+ if (!_reported.exchange(true)) {
232
+ _onAnimating();
233
+ }
234
+ }
235
+
236
+ // Main thread. Returns how much longer the animation must stay stopped
237
+ // before it counts as idle, or 0 once it has: the next animating frame then
238
+ // calls into Java again.
239
+ int64_t releaseIfIdle(int64_t idleAfterNanos) {
240
+ const int64_t idle = nowNanos() - _lastAnimating.load();
241
+ if (idle < idleAfterNanos) {
242
+ return idleAfterNanos - idle;
243
+ }
244
+ _reported.store(false);
245
+ return 0;
246
+ }
247
+
248
+ private:
249
+ // Presents this close together are an animation (faster than 40 fps).
250
+ static constexpr int64_t kAnimatingIntervalNanos = 25'000'000;
251
+
252
+ static int64_t nowNanos() {
253
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
254
+ std::chrono::steady_clock::now().time_since_epoch())
255
+ .count();
256
+ }
257
+
258
+ WeakViewCall _onAnimating;
259
+ // INT64_MIN / 2 rather than a sentinel: the first present is never "right
260
+ // after" it, and the subtractions above don't overflow.
261
+ std::atomic<int64_t> _lastPresent{INT64_MIN / 2};
262
+ std::atomic<int64_t> _lastAnimating{INT64_MIN / 2};
263
+ // Java has been told about the current animation.
264
+ std::atomic<bool> _reported{false};
265
+ };
266
+
267
+ // The reporters by contextId, for WebGPUView.releaseFrameRateVoteIfIdle. The
268
+ // surface callbacks and the idle check all run on the main thread; the lock
269
+ // only guards against a caller that doesn't.
270
+ std::mutex animationReportersMutex;
271
+ std::unordered_map<int, std::shared_ptr<AnimationReporter>> animationReporters;
272
+
273
+ void setAnimationReporter(int contextId,
274
+ std::shared_ptr<AnimationReporter> reporter) {
275
+ std::lock_guard<std::mutex> lock(animationReportersMutex);
276
+ if (reporter) {
277
+ animationReporters[contextId] = std::move(reporter);
278
+ } else {
279
+ animationReporters.erase(contextId);
280
+ }
281
+ }
282
+
206
283
  } // namespace
207
284
 
208
285
  extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
209
286
  JNIEnv *env, jobject thiz, jobject jSurface, jint contextId, jfloat width,
210
- jfloat height, jboolean reportPresents) {
287
+ jfloat height, jboolean reportAnimation) {
211
288
  if (manager == nullptr) {
212
289
  return;
213
290
  }
@@ -228,13 +305,15 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
228
305
  surface, [](void *nativeSurface) {
229
306
  ANativeWindow_release(static_cast<ANativeWindow *>(nativeSurface));
230
307
  });
231
- // A SurfaceView asks to hear about each presented frame, for its refresh
232
- // rate vote (WebGPUView.onFramePresented).
233
- if (reportPresents) {
234
- auto reporter =
235
- std::make_shared<WeakViewCall>(env, thiz, "onFramePresented");
236
- info->setFramePresentedCallback([reporter]() { (*reporter)(); });
308
+ // A SurfaceView asks to hear when it animates, for its refresh rate vote
309
+ // (WebGPUView.onAnimating).
310
+ if (reportAnimation) {
311
+ auto reporter = std::make_shared<AnimationReporter>(env, thiz);
312
+ setAnimationReporter(contextId, reporter);
313
+ info->setFramePresentedCallback(
314
+ [reporter]() { reporter->onFramePresented(); });
237
315
  } else {
316
+ setAnimationReporter(contextId, nullptr);
238
317
  info->setFramePresentedCallback(nullptr);
239
318
  }
240
319
  // The attach is adopted at the next frame boundary by the rendering thread;
@@ -246,6 +325,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
246
325
  extern "C" JNIEXPORT void JNICALL
247
326
  Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
248
327
  jint contextId) {
328
+ setAnimationReporter(contextId, nullptr);
249
329
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
250
330
  if (auto info = registry.getSurfaceInfo(contextId)) {
251
331
  info->setFramePresentedCallback(nullptr);
@@ -253,12 +333,30 @@ Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
253
333
  }
254
334
  }
255
335
 
336
+ // See AnimationReporter::releaseIfIdle; 0 when the surface reports nothing.
337
+ extern "C" JNIEXPORT jlong JNICALL
338
+ Java_com_webgpu_WebGPUView_releaseFrameRateVoteIfIdle(JNIEnv *env, jobject thiz,
339
+ jint contextId,
340
+ jlong idleAfterNanos) {
341
+ std::shared_ptr<AnimationReporter> reporter;
342
+ {
343
+ std::lock_guard<std::mutex> lock(animationReportersMutex);
344
+ auto it = animationReporters.find(contextId);
345
+ if (it == animationReporters.end()) {
346
+ return 0;
347
+ }
348
+ reporter = it->second;
349
+ }
350
+ return static_cast<jlong>(reporter->releaseIfIdle(idleAfterNanos));
351
+ }
352
+
256
353
  extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
257
354
  JNIEnv *env, jobject thiz, jint contextId) {
258
355
  // The view dies with its Canvas (contextIds are never reused), so view
259
356
  // teardown retires the registry entry. The JS-side cleanup
260
357
  // (RNWebGPU.destroyContext) only handles entries that never had a native
261
358
  // surface; see RNWebGPU::destroyContext for the ownership split.
359
+ setAnimationReporter(contextId, nullptr);
262
360
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
263
361
  if (auto info = registry.getSurfaceInfo(contextId)) {
264
362
  info->setFramePresentedCallback(nullptr);
@@ -24,134 +24,146 @@ import androidx.annotation.Nullable;
24
24
  * drops back to a few redraws a second: a layer with a vote counts as active
25
25
  * for SurfaceFlinger even when it no longer draws.
26
26
  *
27
- * attach() and detach() run on the main thread with the surface callbacks;
28
- * onFramePresented() runs on whichever thread presents (the JS thread, the
29
- * Reanimated UI runtime or a worklet runtime), so the state is locked.
27
+ * Native code tells onAnimating() once per animation, on the thread that
28
+ * presents (the JS thread, the Reanimated UI runtime or a worklet runtime),
29
+ * and the vote is cast right there, with no hop to a main thread that may be
30
+ * busy. The rate is resolved ahead of time on the main thread, where the
31
+ * view's display can be read. The idle check runs on the main thread and asks
32
+ * native code how long ago the canvas last animated; once it has stopped, it
33
+ * takes the vote back and native code calls onAnimating() again at the next
34
+ * animation. The lock only guards the state: the binder calls
35
+ * (Surface.setFrameRate) are made outside it.
30
36
  */
31
37
  final class SurfaceFrameRateVote {
32
38
  private static final String TAG = "SurfaceFrameRateVote";
33
39
 
34
- // Presents this close together are an animation (faster than 40 fps). The
35
- // threshold is absolute rather than relative to the current refresh rate,
36
- // so that a canvas that only manages 60 fps at 120 Hz keeps its vote rather
37
- // than oscillating between the two rates. A canvas that redraws a few times
38
- // a second never votes.
39
- private static final long ANIMATING_INTERVAL_NANOS = 25_000_000L;
40
-
41
- // The vote is taken back once the canvas has not animated (no two presents
42
- // within ANIMATING_INTERVAL_NANOS) for this long. Measured from the last
43
- // back-to-back present rather than the last present, so that a canvas that
44
- // slows down to a few redraws a second lets the vote lapse like one that
45
- // stops drawing.
46
- private static final long IDLE_AFTER_NANOS = 500_000_000L;
40
+ // The vote is taken back once the canvas has not animated for this long,
41
+ // whether it stops drawing or drops back to a few redraws a second. Native
42
+ // code decides what animating means (presents faster than 40 fps, see
43
+ // AnimationReporter in cpp-adapter.cpp). The threshold is absolute rather
44
+ // than relative to the current refresh rate, so that a canvas that only
45
+ // manages 60 fps at 120 Hz keeps its vote rather than oscillating between
46
+ // the two rates. A canvas that redraws a few times a second never votes.
47
+ static final long IDLE_AFTER_NANOS = 500_000_000L;
48
+
49
+ /** Native side of the vote, see AnimationReporter in cpp-adapter.cpp. */
50
+ interface AnimationSource {
51
+ /**
52
+ * How much longer the canvas must stay still before it counts as idle,
53
+ * or 0 once it does, in which case the next animation calls
54
+ * onAnimating() again. Main thread.
55
+ */
56
+ long releaseIfIdle(long idleAfterNanos);
57
+ }
47
58
 
48
59
  private final View mView;
60
+ private final AnimationSource mSource;
49
61
  private final Handler mHandler = new Handler(Looper.getMainLooper());
50
62
  private final Runnable mIdleCheck = this::checkIdle;
51
63
 
52
64
  @Nullable
53
65
  private Surface mSurface;
54
66
  private boolean mVoting = false;
55
- private boolean mIdleCheckPending = false;
56
- // 0 before the first present on the surface.
57
- private long mLastPresentNanos = 0;
58
- // The last present that followed its predecessor within
59
- // ANIMATING_INTERVAL_NANOS; 0 until the first.
60
- private long mLastAnimatingNanos = 0;
67
+ // The rate to ask for, resolved on the main thread; 0 when unknown, in
68
+ // which case animations don't vote until the next idle check resolves it.
69
+ private float mRefreshRate = 0f;
61
70
 
62
71
  /** The view the surface belongs to; its display decides the rate to ask for. */
63
- SurfaceFrameRateVote(View view) {
72
+ SurfaceFrameRateVote(View view, AnimationSource source) {
64
73
  mView = view;
74
+ mSource = source;
65
75
  }
66
76
 
67
77
  /** The surface was created: it is voted on until detach(). Main thread. */
68
- synchronized void attach(Surface surface) {
78
+ void attach(Surface surface) {
69
79
  detach();
70
80
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
71
81
  return;
72
82
  }
73
- mSurface = surface;
83
+ float refreshRate = resolveMaxRefreshRate();
84
+ synchronized (this) {
85
+ mSurface = surface;
86
+ mRefreshRate = refreshRate;
87
+ }
74
88
  }
75
89
 
76
- /** A frame was presented on the surface. Any thread. */
77
- synchronized void onFramePresented() {
78
- if (mSurface == null) {
79
- return;
80
- }
81
- long now = System.nanoTime();
82
- boolean animating = mLastPresentNanos != 0 && now - mLastPresentNanos <= ANIMATING_INTERVAL_NANOS;
83
- mLastPresentNanos = now;
84
- if (animating) {
85
- mLastAnimatingNanos = now;
86
- if (!mVoting) {
87
- vote();
90
+ /**
91
+ * The canvas started animating (presenting frames back to back). Called
92
+ * once per animation, on the presenting thread.
93
+ */
94
+ void onAnimating() {
95
+ Surface surface;
96
+ float refreshRate;
97
+ synchronized (this) {
98
+ if (mSurface == null) {
99
+ return;
88
100
  }
101
+ surface = mSurface;
102
+ refreshRate = mRefreshRate;
103
+ mVoting = refreshRate > 0;
89
104
  }
90
- if (mVoting && !mIdleCheckPending) {
91
- scheduleIdleCheck(IDLE_AFTER_NANOS);
105
+ // Whether it votes or not, the idle check re-arms native code once the
106
+ // animation stops.
107
+ mHandler.postDelayed(mIdleCheck, IDLE_AFTER_NANOS / 1_000_000);
108
+ if (refreshRate > 0) {
109
+ // This form only switches when the display can do so seamlessly, like
110
+ // the vote the window casts for its own views: a canvas animation is
111
+ // not worth a blank screen. Surface.setFrameRate is thread-safe.
112
+ setFrameRate(surface, refreshRate);
92
113
  }
93
114
  }
94
115
 
95
116
  /** The surface is going away (it is still valid here) or the view left its window. Main thread. */
96
- synchronized void detach() {
97
- if (mSurface == null) {
98
- return;
117
+ void detach() {
118
+ Surface surface;
119
+ boolean voting;
120
+ synchronized (this) {
121
+ surface = mSurface;
122
+ voting = mVoting;
123
+ mSurface = null;
124
+ mVoting = false;
99
125
  }
100
- withdraw();
101
126
  mHandler.removeCallbacks(mIdleCheck);
102
- mIdleCheckPending = false;
103
- mLastPresentNanos = 0;
104
- mLastAnimatingNanos = 0;
105
- mSurface = null;
106
- }
107
-
108
- private void vote() {
109
- float refreshRate = resolveMaxRefreshRate();
110
- if (refreshRate <= 0 || !mSurface.isValid()) {
111
- return;
112
- }
113
- try {
114
- // This form only switches when the display can do so seamlessly, like
115
- // the vote the window casts for its own views: a canvas animation is
116
- // not worth a blank screen. Surface.setFrameRate is thread-safe.
117
- mSurface.setFrameRate(refreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
118
- mVoting = true;
119
- } catch (RuntimeException e) {
120
- Log.w(TAG, "Could not vote the refresh rate, leaving it to the display", e);
121
- detach();
127
+ // A vote cast concurrently on the presenting thread may land after this;
128
+ // it dies with the surface, which is going away.
129
+ if (surface != null && voting) {
130
+ setFrameRate(surface, 0);
122
131
  }
123
132
  }
124
133
 
125
- private void withdraw() {
126
- if (!mVoting) {
134
+ private void checkIdle() {
135
+ long remainingNanos = mSource.releaseIfIdle(IDLE_AFTER_NANOS);
136
+ if (remainingNanos > 0) {
137
+ mHandler.postDelayed(mIdleCheck, (remainingNanos + 999_999) / 1_000_000);
127
138
  return;
128
139
  }
129
- mVoting = false;
130
- if (mSurface == null || !mSurface.isValid()) {
131
- return;
140
+ // Native code is re-armed: the next onAnimating() can only come after
141
+ // this point, so it doesn't race with the withdrawal below.
142
+ float refreshRate = resolveMaxRefreshRate();
143
+ Surface surface;
144
+ boolean voting;
145
+ synchronized (this) {
146
+ surface = mSurface;
147
+ voting = mVoting;
148
+ mVoting = false;
149
+ if (surface != null) {
150
+ // Follows the display the view is on, for the next animation.
151
+ mRefreshRate = refreshRate;
152
+ }
132
153
  }
133
- try {
134
- mSurface.setFrameRate(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
135
- } catch (RuntimeException e) {
136
- Log.w(TAG, "Could not withdraw the refresh rate vote", e);
154
+ if (surface != null && voting) {
155
+ setFrameRate(surface, 0);
137
156
  }
138
157
  }
139
158
 
140
- private void scheduleIdleCheck(long delayNanos) {
141
- mIdleCheckPending = true;
142
- mHandler.postDelayed(mIdleCheck, (delayNanos + 999_999) / 1_000_000);
143
- }
144
-
145
- private synchronized void checkIdle() {
146
- mIdleCheckPending = false;
147
- if (!mVoting) {
159
+ private static void setFrameRate(Surface surface, float refreshRate) {
160
+ if (!surface.isValid()) {
148
161
  return;
149
162
  }
150
- long idleNanos = System.nanoTime() - mLastAnimatingNanos;
151
- if (idleNanos >= IDLE_AFTER_NANOS) {
152
- withdraw();
153
- } else {
154
- scheduleIdleCheck(IDLE_AFTER_NANOS - idleNanos);
163
+ try {
164
+ surface.setFrameRate(refreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
165
+ } catch (RuntimeException e) {
166
+ Log.w(TAG, "Could not set the surface's frame rate, leaving it to the display", e);
155
167
  }
156
168
  }
157
169
 
@@ -26,7 +26,8 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
26
26
  private View mView = null;
27
27
  // Only a SurfaceView votes: a TextureView or HardwareBufferView is
28
28
  // composited into the window, which votes for it.
29
- private final SurfaceFrameRateVote mFrameRateVote = new SurfaceFrameRateVote(this);
29
+ private final SurfaceFrameRateVote mFrameRateVote = new SurfaceFrameRateVote(
30
+ this, idleAfterNanos -> releaseFrameRateVoteIfIdle(mContextId, idleAfterNanos));
30
31
 
31
32
  WebGPUView(Context context) {
32
33
  super(context);
@@ -160,12 +161,12 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
160
161
  }
161
162
 
162
163
  /**
163
- * Called from native (SurfaceInfo::presentFrame) on the thread that
164
- * presented a frame to the surface, only for a SurfaceView.
164
+ * Called from native (AnimationReporter in cpp-adapter.cpp) on the thread
165
+ * that presents to the surface, once per animation, only for a SurfaceView.
165
166
  */
166
167
  @DoNotStrip
167
- void onFramePresented() {
168
- mFrameRateVote.onFramePresented();
168
+ void onAnimating() {
169
+ mFrameRateVote.onAnimating();
169
170
  }
170
171
 
171
172
  @Override
@@ -182,15 +183,15 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
182
183
  onViewDestroyed(mContextId);
183
184
  }
184
185
 
185
- // reportPresents asks native to call onFramePresented() for each frame
186
- // presented to this surface.
186
+ // reportAnimation asks native to call onAnimating() when frames start being
187
+ // presented to this surface back to back.
187
188
  @DoNotStrip
188
189
  private native void onSurfaceCreate(
189
190
  Surface surface,
190
191
  int contextId,
191
192
  float width,
192
193
  float height,
193
- boolean reportPresents
194
+ boolean reportAnimation
194
195
  );
195
196
 
196
197
  @DoNotStrip
@@ -207,4 +208,8 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
207
208
  @DoNotStrip
208
209
  private native void onViewDestroyed(int contextId);
209
210
 
211
+ // See SurfaceFrameRateVote.AnimationSource.
212
+ @DoNotStrip
213
+ private native long releaseFrameRateVoteIfIdle(int contextId, long idleAfterNanos);
214
+
210
215
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "PlatformContext.h"
4
4
  #include <functional>
5
+ #include <memory>
5
6
  #include <string>
6
7
 
7
8
  namespace rnwgpu {
@@ -2,8 +2,10 @@
2
2
 
3
3
  #include <algorithm>
4
4
  #include <iterator>
5
+ #include <memory>
5
6
  #include <stdexcept>
6
7
  #include <string>
8
+ #include <unordered_map>
7
9
  #include <utility>
8
10
 
9
11
  namespace rnwgpu {
@@ -14,6 +14,7 @@
14
14
  #include <typeindex>
15
15
  #include <unordered_map>
16
16
  #include <utility>
17
+ #include <vector>
17
18
 
18
19
  #include "EnforceRange.h"
19
20
  #include "JSICache.h"
@@ -640,19 +641,26 @@ protected:
640
641
  rt, std::move(result));
641
642
  }
642
643
  });
644
+ defineGetter(runtime, prototype, name, getterFunc);
645
+ }
643
646
 
644
- // Use Object.defineProperty to create a proper getter
645
- auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
646
- auto defineProperty =
647
- objectCtor.getPropertyAsFunction(runtime, "defineProperty");
648
-
649
- jsi::Object descriptor(runtime);
650
- descriptor.setProperty(runtime, "get", getterFunc);
651
- descriptor.setProperty(runtime, "enumerable", true);
652
- descriptor.setProperty(runtime, "configurable", true);
653
-
654
- defineProperty.call(runtime, prototype,
655
- jsi::String::createFromUtf8(runtime, name), descriptor);
647
+ /**
648
+ * Install a getter whose native implementation builds the JS value itself,
649
+ * from the calling jsi::Runtime: for values no JSIConverter produces (null
650
+ * or a BigInt, say).
651
+ */
652
+ static void installGetter(jsi::Runtime &runtime, jsi::Object &prototype,
653
+ const char *name,
654
+ jsi::Value (Derived::*getter)(jsi::Runtime &)) {
655
+ auto getterFunc = jsi::Function::createFromHostFunction(
656
+ runtime, jsi::PropNameID::forUtf8(runtime, std::string("get_") + name),
657
+ 0,
658
+ [getter](jsi::Runtime &rt, const jsi::Value &thisVal,
659
+ const jsi::Value *args, size_t count) -> jsi::Value {
660
+ auto native = Derived::fromValue(rt, thisVal);
661
+ return (native.get()->*getter)(rt);
662
+ });
663
+ defineGetter(runtime, prototype, name, getterFunc);
656
664
  }
657
665
 
658
666
  /**
@@ -757,6 +765,22 @@ protected:
757
765
  }
758
766
 
759
767
  private:
768
+ // Use Object.defineProperty to create a proper getter
769
+ static void defineGetter(jsi::Runtime &runtime, jsi::Object &prototype,
770
+ const char *name, const jsi::Function &getterFunc) {
771
+ auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
772
+ auto defineProperty =
773
+ objectCtor.getPropertyAsFunction(runtime, "defineProperty");
774
+
775
+ jsi::Object descriptor(runtime);
776
+ descriptor.setProperty(runtime, "get", getterFunc);
777
+ descriptor.setProperty(runtime, "enumerable", true);
778
+ descriptor.setProperty(runtime, "configurable", true);
779
+
780
+ defineProperty.call(runtime, prototype,
781
+ jsi::String::createFromUtf8(runtime, name), descriptor);
782
+ }
783
+
760
784
  // Helper to call a method that takes the calling jsi::Runtime as its first
761
785
  // parameter, with JSI argument conversion for the rest and JSI conversion of
762
786
  // the result.
@@ -22,6 +22,8 @@
22
22
  #include "GPUError.h"
23
23
  #include "GPUExternalTexture.h"
24
24
  #include "GPUInternalError.h"
25
+ #include "GPUNativeBufferExternalTexture.h"
26
+ #include "GPUNativeBufferTexture.h"
25
27
  #include "GPUOutOfMemoryError.h"
26
28
  #include "GPUPipelineLayout.h"
27
29
  #include "GPUQuerySet.h"
@@ -102,6 +104,8 @@ RNWebGPUManager::RNWebGPUManager(
102
104
  GPUDeviceLostInfo::installConstructor(*_jsRuntime);
103
105
  GPUError::installConstructor(*_jsRuntime);
104
106
  GPUExternalTexture::installConstructor(*_jsRuntime);
107
+ GPUNativeBufferExternalTexture::installConstructor(*_jsRuntime);
108
+ GPUNativeBufferTexture::installConstructor(*_jsRuntime);
105
109
  GPUInternalError::installConstructor(*_jsRuntime);
106
110
  GPUOutOfMemoryError::installConstructor(*_jsRuntime);
107
111
  GPUValidationError::installConstructor(*_jsRuntime);
@@ -59,7 +59,7 @@ struct Size {
59
59
  using NativeSurfaceReleaser = std::function<void(void *)>;
60
60
 
61
61
  #if defined(__ANDROID__)
62
- // --- AHB-pool presentation mode (WebGPUHardwareBufferView)
62
+ // --- AHB-pool presentation mode (WebGPUNativeBufferView)
63
63
  // -----------------------------
64
64
  //
65
65
  // A third presentation backend (besides the on-screen wgpu::Surface swapchain
@@ -71,7 +71,7 @@ using NativeSurfaceReleaser = std::function<void(void *)>;
71
71
  // like the swapchain, so the canvas texture always matches the app's other
72
72
  // attachments (e.g. its depth texture).
73
73
 
74
- #define RNWGPU_LOG_TAG "WebGPUHardwareBufferView"
74
+ #define RNWGPU_LOG_TAG "WebGPUNativeBufferView"
75
75
  // Failure / anomaly reporting only (never per-frame).
76
76
  #define RNWGPU_POOL_WARN(...) \
77
77
  __android_log_print(ANDROID_LOG_WARN, RNWGPU_LOG_TAG, __VA_ARGS__)
@@ -435,7 +435,7 @@ public:
435
435
  bool hasNativeSurface() {
436
436
  std::shared_lock<std::shared_mutex> lock(_mutex);
437
437
  #if defined(__ANDROID__)
438
- // A WebGPUHardwareBufferView in pool mode owns presentation the same way a
438
+ // A WebGPUNativeBufferView in pool mode owns presentation the same way a
439
439
  // surface-backed view does, and retires the entry on its own teardown.
440
440
  if (_poolMode.load()) {
441
441
  return true;
@@ -725,7 +725,7 @@ private:
725
725
  {
726
726
  std::unique_lock<std::shared_mutex> lock(_mutex);
727
727
  #if defined(__ANDROID__)
728
- // Leaving pool mode (the WebGPUHardwareBufferView detached): drop the
728
+ // Leaving pool mode (the WebGPUNativeBufferView detached): drop the
729
729
  // pool. A configured context keeps rendering into the offscreen
730
730
  // fallback, exactly like the surface path.
731
731
  if (_poolMode.load()) {