react-native-webgpu 0.15.0 → 0.15.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.
@@ -156,9 +156,58 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceChanged(
156
156
  }
157
157
  }
158
158
 
159
+ namespace {
160
+
161
+ // Weak handle on a Java view and one of its no-argument methods, for native
162
+ // 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
+ struct WeakViewCall {
168
+ jweak view = nullptr;
169
+ jmethodID method = nullptr;
170
+
171
+ WeakViewCall(JNIEnv *env, jobject thiz, const char *methodName) {
172
+ view = env->NewWeakGlobalRef(thiz);
173
+ jclass cls = env->GetObjectClass(thiz);
174
+ method = env->GetMethodID(cls, methodName, "()V");
175
+ env->DeleteLocalRef(cls);
176
+ }
177
+
178
+ ~WeakViewCall() {
179
+ // May run on any thread; ThreadScope attaches if needed.
180
+ facebook::jni::ThreadScope scope;
181
+ JNIEnv *env = facebook::jni::Environment::current();
182
+ if (env != nullptr && view != nullptr) {
183
+ env->DeleteWeakGlobalRef(view);
184
+ }
185
+ }
186
+
187
+ // Called on a native thread, outside the SurfaceInfo locks.
188
+ void operator()() const {
189
+ facebook::jni::ThreadScope scope;
190
+ JNIEnv *env = facebook::jni::Environment::current();
191
+ if (env == nullptr || method == nullptr) {
192
+ return;
193
+ }
194
+ jobject local = env->NewLocalRef(view);
195
+ if (local == nullptr) {
196
+ return; // the view was garbage collected
197
+ }
198
+ env->CallVoidMethod(local, method);
199
+ if (env->ExceptionCheck()) {
200
+ env->ExceptionClear();
201
+ }
202
+ env->DeleteLocalRef(local);
203
+ }
204
+ };
205
+
206
+ } // namespace
207
+
159
208
  extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
160
209
  JNIEnv *env, jobject thiz, jobject jSurface, jint contextId, jfloat width,
161
- jfloat height) {
210
+ jfloat height, jboolean reportPresents) {
162
211
  if (manager == nullptr) {
163
212
  return;
164
213
  }
@@ -179,6 +228,15 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
179
228
  surface, [](void *nativeSurface) {
180
229
  ANativeWindow_release(static_cast<ANativeWindow *>(nativeSurface));
181
230
  });
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)(); });
237
+ } else {
238
+ info->setFramePresentedCallback(nullptr);
239
+ }
182
240
  // The attach is adopted at the next frame boundary by the rendering thread;
183
241
  // schedule a flush so contexts that are not currently rendering still pick
184
242
  // it up (and present their last offscreen frame).
@@ -190,6 +248,7 @@ Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
190
248
  jint contextId) {
191
249
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
192
250
  if (auto info = registry.getSurfaceInfo(contextId)) {
251
+ info->setFramePresentedCallback(nullptr);
193
252
  info->switchToOffscreen();
194
253
  }
195
254
  }
@@ -202,6 +261,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
202
261
  // surface; see RNWebGPU::destroyContext for the ownership split.
203
262
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
204
263
  if (auto info = registry.getSurfaceInfo(contextId)) {
264
+ info->setFramePresentedCallback(nullptr);
205
265
  info->detachSurface();
206
266
  }
207
267
  registry.removeSurfaceInfo(contextId);
@@ -210,53 +270,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
210
270
  // --- WebGPUHardwareBufferView (AHB-pool presentation)
211
271
  // ---------------------------------
212
272
 
213
- namespace {
214
-
215
- // Weak handle on a WebGPUHardwareBufferView used by the native fence waiter to
216
- // wake the UI thread when a frame is ready. Holding it weakly means a view that
217
- // RN has dropped is not kept alive by a SurfaceInfo that outlives it (the
218
- // registry keeps the SurfaceInfo until onDropViewInstance).
219
- struct HardwareBufferViewWaker {
220
- jweak view = nullptr;
221
- jmethodID onFrameReady = nullptr;
222
-
223
- HardwareBufferViewWaker(JNIEnv *env, jobject thiz) {
224
- view = env->NewWeakGlobalRef(thiz);
225
- jclass cls = env->GetObjectClass(thiz);
226
- onFrameReady = env->GetMethodID(cls, "onNativeFrameReady", "()V");
227
- env->DeleteLocalRef(cls);
228
- }
229
-
230
- ~HardwareBufferViewWaker() {
231
- // May run on the waiter or JS thread; ThreadScope attaches if needed.
232
- facebook::jni::ThreadScope scope;
233
- JNIEnv *env = facebook::jni::Environment::current();
234
- if (env != nullptr && view != nullptr) {
235
- env->DeleteWeakGlobalRef(view);
236
- }
237
- }
238
-
239
- // Called on the waiter thread, outside the pool lock.
240
- void operator()() const {
241
- facebook::jni::ThreadScope scope;
242
- JNIEnv *env = facebook::jni::Environment::current();
243
- if (env == nullptr || onFrameReady == nullptr) {
244
- return;
245
- }
246
- jobject local = env->NewLocalRef(view);
247
- if (local == nullptr) {
248
- return; // the view was garbage collected
249
- }
250
- env->CallVoidMethod(local, onFrameReady);
251
- if (env->ExceptionCheck()) {
252
- env->ExceptionClear();
253
- }
254
- env->DeleteLocalRef(local);
255
- }
256
- };
257
-
258
- } // namespace
259
-
260
273
  // Turn on pool mode for this context. dpW/dpH is the canvas-client (dp) size;
261
274
  // the native pool buffers are sized from the canvas drawing buffer lazily.
262
275
  extern "C" JNIEXPORT void JNICALL
@@ -267,7 +280,7 @@ Java_com_webgpu_WebGPUHardwareBufferView_nEnablePool(JNIEnv *env, jobject thiz,
267
280
  auto info = registry.getSurfaceInfoOrCreate(
268
281
  contextId, rnwgpu::WebGPURuntime::shared().instance(),
269
282
  static_cast<int>(dpW), static_cast<int>(dpH));
270
- auto waker = std::make_shared<HardwareBufferViewWaker>(env, thiz);
283
+ auto waker = std::make_shared<WeakViewCall>(env, thiz, "onNativeFrameReady");
271
284
  info->setPoolFrameReadyCallback([waker]() { (*waker)(); });
272
285
  info->enablePool(static_cast<int>(dpW), static_cast<int>(dpH));
273
286
  }
@@ -0,0 +1,184 @@
1
+ package com.webgpu;
2
+
3
+ import android.os.Build;
4
+ import android.os.Handler;
5
+ import android.os.Looper;
6
+ import android.util.Log;
7
+ import android.view.Display;
8
+ import android.view.Surface;
9
+ import android.view.View;
10
+
11
+ import androidx.annotation.Nullable;
12
+
13
+ /**
14
+ * Votes the display's refresh rate for a SurfaceView canvas while it animates.
15
+ *
16
+ * A SurfaceView is composited in a layer of its own, outside the window that
17
+ * votes for the app's views, so SurfaceFlinger picks the layer's refresh rate
18
+ * from its present history alone: a canvas drawing every frame on a display
19
+ * idling at 60 Hz looks like 60 Hz content and never gets more. Since Android
20
+ * 11 a layer can say what it wants with Surface.setFrameRate. Once the canvas
21
+ * presents frames back to back, this asks for the highest rate the display can
22
+ * switch to seamlessly, as the window does for its own views, and takes the
23
+ * vote back once the animation stops, whether the canvas stops drawing or
24
+ * drops back to a few redraws a second: a layer with a vote counts as active
25
+ * for SurfaceFlinger even when it no longer draws.
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.
30
+ */
31
+ final class SurfaceFrameRateVote {
32
+ private static final String TAG = "SurfaceFrameRateVote";
33
+
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;
47
+
48
+ private final View mView;
49
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
50
+ private final Runnable mIdleCheck = this::checkIdle;
51
+
52
+ @Nullable
53
+ private Surface mSurface;
54
+ 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;
61
+
62
+ /** The view the surface belongs to; its display decides the rate to ask for. */
63
+ SurfaceFrameRateVote(View view) {
64
+ mView = view;
65
+ }
66
+
67
+ /** The surface was created: it is voted on until detach(). Main thread. */
68
+ synchronized void attach(Surface surface) {
69
+ detach();
70
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
71
+ return;
72
+ }
73
+ mSurface = surface;
74
+ }
75
+
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();
88
+ }
89
+ }
90
+ if (mVoting && !mIdleCheckPending) {
91
+ scheduleIdleCheck(IDLE_AFTER_NANOS);
92
+ }
93
+ }
94
+
95
+ /** 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;
99
+ }
100
+ withdraw();
101
+ 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();
122
+ }
123
+ }
124
+
125
+ private void withdraw() {
126
+ if (!mVoting) {
127
+ return;
128
+ }
129
+ mVoting = false;
130
+ if (mSurface == null || !mSurface.isValid()) {
131
+ return;
132
+ }
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);
137
+ }
138
+ }
139
+
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) {
148
+ return;
149
+ }
150
+ long idleNanos = System.nanoTime() - mLastAnimatingNanos;
151
+ if (idleNanos >= IDLE_AFTER_NANOS) {
152
+ withdraw();
153
+ } else {
154
+ scheduleIdleCheck(IDLE_AFTER_NANOS - idleNanos);
155
+ }
156
+ }
157
+
158
+ // The highest rate a seamless switch from the current mode can reach, which
159
+ // is all the vote may ask for. Resolved when the vote is cast, so that it
160
+ // follows the display the view is on.
161
+ private float resolveMaxRefreshRate() {
162
+ Display display = mView.getDisplay();
163
+ if (display == null) {
164
+ return 0f;
165
+ }
166
+ Display.Mode current = display.getMode();
167
+ float maxRefreshRate = current.getRefreshRate();
168
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
169
+ for (float refreshRate : current.getAlternativeRefreshRates()) {
170
+ maxRefreshRate = Math.max(maxRefreshRate, refreshRate);
171
+ }
172
+ } else {
173
+ // Android 11 does not tell which modes are a seamless switch away: the
174
+ // ones at the current resolution usually are.
175
+ for (Display.Mode mode : display.getSupportedModes()) {
176
+ if (mode.getPhysicalWidth() == current.getPhysicalWidth()
177
+ && mode.getPhysicalHeight() == current.getPhysicalHeight()) {
178
+ maxRefreshRate = Math.max(maxRefreshRate, mode.getRefreshRate());
179
+ }
180
+ }
181
+ }
182
+ return maxRefreshRate;
183
+ }
184
+ }
@@ -24,6 +24,9 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
24
24
  private boolean mAppliedZOrderOnTop;
25
25
  private WebGPUModule mModule;
26
26
  private View mView = null;
27
+ // Only a SurfaceView votes: a TextureView or HardwareBufferView is
28
+ // composited into the window, which votes for it.
29
+ private final SurfaceFrameRateVote mFrameRateVote = new SurfaceFrameRateVote(this);
27
30
 
28
31
  WebGPUView(Context context) {
29
32
  super(context);
@@ -134,7 +137,11 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
134
137
  float density = getResources().getDisplayMetrics().density;
135
138
  float width = getWidth() / density;
136
139
  float height = getHeight() / density;
137
- onSurfaceCreate(surface, mContextId, width, height);
140
+ boolean votesFrameRate = mAppliedKind == KIND_SURFACE_VIEW;
141
+ if (votesFrameRate) {
142
+ mFrameRateVote.attach(surface);
143
+ }
144
+ onSurfaceCreate(surface, mContextId, width, height, votesFrameRate);
138
145
  }
139
146
 
140
147
  @Override
@@ -147,9 +154,20 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
147
154
 
148
155
  @Override
149
156
  public void surfaceOffscreen() {
157
+ // The surface is still valid here, so the vote can be taken back.
158
+ mFrameRateVote.detach();
150
159
  switchToOffscreenSurface(mContextId);
151
160
  }
152
161
 
162
+ /**
163
+ * Called from native (SurfaceInfo::presentFrame) on the thread that
164
+ * presented a frame to the surface, only for a SurfaceView.
165
+ */
166
+ @DoNotStrip
167
+ void onFramePresented() {
168
+ mFrameRateVote.onFramePresented();
169
+ }
170
+
153
171
  @Override
154
172
  public int getContextId() {
155
173
  return mContextId;
@@ -160,15 +178,19 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
160
178
  * view: the view dies with its Canvas, so it retires the registry entry.
161
179
  */
162
180
  public void destroy() {
181
+ mFrameRateVote.detach();
163
182
  onViewDestroyed(mContextId);
164
183
  }
165
184
 
185
+ // reportPresents asks native to call onFramePresented() for each frame
186
+ // presented to this surface.
166
187
  @DoNotStrip
167
188
  private native void onSurfaceCreate(
168
189
  Surface surface,
169
190
  int contextId,
170
191
  float width,
171
- float height
192
+ float height,
193
+ boolean reportPresents
172
194
  );
173
195
 
174
196
  @DoNotStrip
@@ -42,159 +42,279 @@ ApplePlatformContext::ApplePlatformContext(ViewLookup viewLookup)
42
42
 
43
43
  namespace {
44
44
 
45
- // Rasterize `request` on the main thread into tightly packed, premultiplied
46
- // BGRA8 pixels. Throws std::runtime_error with a user-facing message.
47
- ImageData snapshotViewOnMainThread(const ApplePlatformContext::ViewLookup &lookup,
48
- const ViewSnapshotRequest &request) {
45
+ // A view that UIKit declined to render (it is not in a window). The pixel
46
+ // path would fail the same way, so there is no point falling back to it.
47
+ struct ViewNotRenderable : std::runtime_error {
48
+ using std::runtime_error::runtime_error;
49
+ };
50
+
51
+ // The view, the output size and the rectangle to draw it in, resolved from a
52
+ // request on the main thread. Throws std::runtime_error with a user-facing
53
+ // message.
54
+ struct ResolvedSnapshot {
55
+ RNWGPlatformView *view = nil;
56
+ CGRect bounds = CGRectZero;
57
+ // The source rectangle in points (view space).
58
+ double sourceX = 0;
59
+ double sourceY = 0;
60
+ double sourceWidth = 0;
61
+ double sourceHeight = 0;
62
+ // The output size in pixels.
63
+ uint32_t width = 0;
64
+ uint32_t height = 0;
65
+ CGFloat scaleX = 1;
66
+ CGFloat scaleY = 1;
67
+ // The view's bounds, scaled and offset so that the source rectangle covers
68
+ // the whole output, in pixels and with UIKit's top-left origin.
69
+ CGRect drawRect = CGRectZero;
70
+ };
71
+
72
+ ResolvedSnapshot resolveSnapshot(const ApplePlatformContext::ViewLookup &lookup,
73
+ const ViewSnapshotRequest &request) {
49
74
  if (!lookup) {
50
75
  throw std::runtime_error(
51
76
  "drawElementImageToTexture: no view registry is available");
52
77
  }
53
- RNWGPlatformView *view = (__bridge RNWGPlatformView *)lookup(request.viewTag);
54
- if (view == nil) {
78
+ ResolvedSnapshot snapshot;
79
+ snapshot.view = (__bridge RNWGPlatformView *)lookup(request.viewTag);
80
+ if (snapshot.view == nil) {
55
81
  throw std::runtime_error(
56
82
  "drawElementImageToTexture: no native view found for tag " +
57
83
  std::to_string(request.viewTag) +
58
84
  " (is the view mounted, and rendered with collapsable={false}?)");
59
85
  }
60
- const CGRect bounds = view.bounds;
86
+ snapshot.bounds = snapshot.view.bounds;
87
+ const CGRect bounds = snapshot.bounds;
61
88
  if (bounds.size.width <= 0 || bounds.size.height <= 0) {
62
89
  throw std::runtime_error(
63
90
  "drawElementImageToTexture: the view has no size yet");
64
91
  }
65
92
 
66
93
  // Source rectangle in points (view space); the whole view by default.
67
- const double sourceX = request.sourceX;
68
- const double sourceY = request.sourceY;
69
- const double sourceWidth =
94
+ snapshot.sourceX = request.sourceX;
95
+ snapshot.sourceY = request.sourceY;
96
+ snapshot.sourceWidth =
70
97
  request.sourceWidth > 0 ? request.sourceWidth : bounds.size.width;
71
- const double sourceHeight =
98
+ snapshot.sourceHeight =
72
99
  request.sourceHeight > 0 ? request.sourceHeight : bounds.size.height;
73
100
 
74
101
  // Natural pixel size: points times the screen scale the view is shown at.
75
102
  #if !TARGET_OS_OSX
76
- CGFloat scale = view.window.screen.scale;
103
+ CGFloat scale = snapshot.view.window.screen.scale;
77
104
  if (scale <= 0) {
78
105
  scale = UIScreen.mainScreen.scale;
79
106
  }
80
107
  #else
81
- CGFloat scale = view.window.backingScaleFactor;
108
+ CGFloat scale = snapshot.view.window.backingScaleFactor;
82
109
  if (scale <= 0) {
83
110
  scale = NSScreen.mainScreen.backingScaleFactor;
84
111
  }
85
112
  #endif
86
- const uint32_t width =
113
+ snapshot.width =
87
114
  request.width > 0
88
115
  ? request.width
89
- : static_cast<uint32_t>(std::llround(sourceWidth * scale));
90
- const uint32_t height =
116
+ : static_cast<uint32_t>(std::llround(snapshot.sourceWidth * scale));
117
+ snapshot.height =
91
118
  request.height > 0
92
119
  ? request.height
93
- : static_cast<uint32_t>(std::llround(sourceHeight * scale));
94
- if (width == 0 || height == 0) {
120
+ : static_cast<uint32_t>(std::llround(snapshot.sourceHeight * scale));
121
+ if (snapshot.width == 0 || snapshot.height == 0) {
95
122
  throw std::runtime_error(
96
123
  "drawElementImageToTexture: the source rectangle is empty");
97
124
  }
98
125
 
99
126
  // The view's bounds are drawn scaled so that the source rectangle covers
100
127
  // the whole output.
101
- const CGFloat scaleX = static_cast<CGFloat>(width) / sourceWidth;
102
- const CGFloat scaleY = static_cast<CGFloat>(height) / sourceHeight;
103
- const CGRect drawRect =
104
- CGRectMake(-sourceX * scaleX, -sourceY * scaleY,
105
- bounds.size.width * scaleX, bounds.size.height * scaleY);
128
+ snapshot.scaleX = static_cast<CGFloat>(snapshot.width) / snapshot.sourceWidth;
129
+ snapshot.scaleY =
130
+ static_cast<CGFloat>(snapshot.height) / snapshot.sourceHeight;
131
+ snapshot.drawRect = CGRectMake(-snapshot.sourceX * snapshot.scaleX,
132
+ -snapshot.sourceY * snapshot.scaleY,
133
+ bounds.size.width * snapshot.scaleX,
134
+ bounds.size.height * snapshot.scaleY);
135
+ return snapshot;
136
+ }
137
+
138
+ // A premultiplied BGRA8 Core Graphics context over `pixels`, the layout both
139
+ // snapshot paths produce. Returns NULL when Core Graphics rejects it.
140
+ CGContextRef createBgraContext(void *pixels, uint32_t width, uint32_t height,
141
+ size_t bytesPerRow) {
142
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
143
+ CGContextRef context = CGBitmapContextCreate(
144
+ pixels, width, height, 8, bytesPerRow, colorSpace,
145
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
146
+ CGColorSpaceRelease(colorSpace);
147
+ return context;
148
+ }
106
149
 
107
- CGImageRef cgImage = NULL;
108
150
  #if !TARGET_OS_OSX
109
- // drawViewHierarchyInRect renders what is on screen (including Metal /
110
- // CAMetalLayer content), which is what "the element as displayed" means.
111
- // afterScreenUpdates:YES flushes pending layout and layer changes first, so
112
- // a snapshot taken right after a state change reflects that change.
113
- UIGraphicsImageRendererFormat *format =
114
- [UIGraphicsImageRendererFormat defaultFormat];
115
- format.scale = 1.0;
116
- format.opaque = NO;
117
- format.preferredRange = UIGraphicsImageRendererFormatRangeStandard;
118
- UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
119
- initWithSize:CGSizeMake(width, height)
120
- format:format];
121
- // drawViewHierarchyInRect returns NO and draws nothing when the view is not
122
- // in a window (e.g. a screen pushed under another one). The image would
123
- // then be fully transparent, so treat it as a failure instead.
124
- __block BOOL drawn = NO;
125
- UIImage *image = [renderer
126
- imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
127
- drawn = [view drawViewHierarchyInRect:drawRect afterScreenUpdates:YES];
128
- }];
151
+ // Draws the view hierarchy through `context`, a bitmap context of exactly
152
+ // `snapshot.width` x `snapshot.height` pixels (Core Graphics orientation; the
153
+ // flip to UIKit's top-left origin is applied here).
154
+ //
155
+ // drawViewHierarchyInRect renders what is on screen (including Metal /
156
+ // CAMetalLayer content), which is what "the element as displayed" means.
157
+ // afterScreenUpdates:YES flushes pending layout and layer changes first, so a
158
+ // snapshot taken right after a state change reflects that change; NO draws
159
+ // the last committed frame and skips that round trip to the render server.
160
+ //
161
+ // drawViewHierarchyInRect returns NO and draws nothing when the view is not
162
+ // in a window (e.g. a screen pushed under another one). The output would then
163
+ // be fully transparent, so that is reported as a failure instead.
164
+ void drawViewHierarchy(const ResolvedSnapshot &snapshot, CGContextRef context,
165
+ bool afterScreenUpdates) {
166
+ CGContextSaveGState(context);
167
+ CGContextTranslateCTM(context, 0, snapshot.height);
168
+ CGContextScaleCTM(context, 1, -1);
169
+ UIGraphicsPushContext(context);
170
+ const BOOL drawn =
171
+ [snapshot.view drawViewHierarchyInRect:snapshot.drawRect
172
+ afterScreenUpdates:afterScreenUpdates ? YES : NO];
173
+ UIGraphicsPopContext();
174
+ CGContextRestoreGState(context);
129
175
  if (!drawn) {
130
- throw std::runtime_error(
176
+ throw ViewNotRenderable(
131
177
  "drawElementImageToTexture: the view could not be rendered (is it "
132
178
  "attached to a window?)");
133
179
  }
134
- cgImage = image.CGImage;
135
- if (cgImage != NULL) {
136
- CGImageRetain(cgImage);
137
- }
138
- #else
139
- NSBitmapImageRep *rep = [view bitmapImageRepForCachingDisplayInRect:bounds];
140
- if (rep != nil) {
141
- [view cacheDisplayInRect:bounds toBitmapImageRep:rep];
142
- cgImage = rep.CGImage;
143
- if (cgImage != NULL) {
144
- CGImageRetain(cgImage);
180
+ }
181
+
182
+ // Releases a pixel buffer when it goes out of scope.
183
+ struct PixelBufferRelease {
184
+ CVPixelBufferRef buffer;
185
+ ~PixelBufferRelease() {
186
+ if (buffer != NULL) {
187
+ CFRelease(buffer);
145
188
  }
146
189
  }
147
- #endif
148
- if (cgImage == NULL) {
190
+ };
191
+
192
+ // The zero-copy path: renders the view into an IOSurface-backed BGRA pixel
193
+ // buffer, which the caller imports as shared texture memory and draws into
194
+ // the destination on the GPU (VideoFrameBlit, which also converts the
195
+ // premultiplied pixels to straight alpha when asked). UIKit rasterizes
196
+ // straight into the surface's memory through a Core Graphics context, so the
197
+ // pixels are never copied or converted on the CPU. Throws on an allocation
198
+ // failure, which the caller answers with the pixel path.
199
+ VideoFrameHandle renderViewIntoSurface(const ResolvedSnapshot &snapshot,
200
+ bool afterScreenUpdates) {
201
+ NSDictionary *attributes = @{
202
+ (NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
203
+ (NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
204
+ };
205
+ CVPixelBufferRef pixelBuffer = NULL;
206
+ const CVReturn created =
207
+ CVPixelBufferCreate(kCFAllocatorDefault, snapshot.width, snapshot.height,
208
+ kCVPixelFormatType_32BGRA,
209
+ (__bridge CFDictionaryRef)attributes, &pixelBuffer);
210
+ if (created != kCVReturnSuccess || pixelBuffer == NULL) {
149
211
  throw std::runtime_error(
150
- "drawElementImageToTexture: the view could not be rendered");
212
+ "drawElementImageToTexture: could not allocate an IOSurface for the "
213
+ "view (CVPixelBufferCreate returned " +
214
+ std::to_string(created) + ")");
151
215
  }
216
+ // wrapCVPixelBuffer retains its own reference for the frame.
217
+ PixelBufferRelease release{pixelBuffer};
152
218
 
153
- // Normalize into premultiplied BGRA8 with no row padding, whatever the
154
- // renderer produced.
219
+ if (CVPixelBufferLockBaseAddress(pixelBuffer, 0) != kCVReturnSuccess) {
220
+ throw std::runtime_error(
221
+ "drawElementImageToTexture: could not lock the IOSurface");
222
+ }
223
+ CGContextRef context = createBgraContext(
224
+ CVPixelBufferGetBaseAddress(pixelBuffer), snapshot.width, snapshot.height,
225
+ CVPixelBufferGetBytesPerRow(pixelBuffer));
226
+ if (context == NULL) {
227
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
228
+ throw std::runtime_error(
229
+ "drawElementImageToTexture: could not draw into the IOSurface");
230
+ }
231
+ try {
232
+ // A fresh surface holds whatever was in that memory, and the view may be
233
+ // transparent.
234
+ CGContextClearRect(context,
235
+ CGRectMake(0, 0, snapshot.width, snapshot.height));
236
+ drawViewHierarchy(snapshot, context, afterScreenUpdates);
237
+ } catch (...) {
238
+ CGContextRelease(context);
239
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
240
+ throw;
241
+ }
242
+ CGContextRelease(context);
243
+ // Unlocking publishes the CPU writes to the GPU.
244
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
245
+ return wrapCVPixelBuffer(pixelBuffer);
246
+ }
247
+ #endif
248
+
249
+ // The pixel path: rasterizes the view into tightly packed, premultiplied
250
+ // BGRA8 pixels that the caller uploads with writeTexture.
251
+ ImageData renderViewIntoPixels(const ResolvedSnapshot &snapshot,
252
+ bool afterScreenUpdates) {
155
253
  ImageData result;
156
- result.width = width;
157
- result.height = height;
254
+ result.width = snapshot.width;
255
+ result.height = snapshot.height;
158
256
  result.format = wgpu::TextureFormat::BGRA8Unorm;
159
257
  result.premultiplied = true;
160
- result.data.assign(static_cast<size_t>(width) * height * 4, 0);
161
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
162
- CGContextRef context = CGBitmapContextCreate(
163
- result.data.data(), width, height, 8, static_cast<size_t>(width) * 4,
164
- colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
165
- CGColorSpaceRelease(colorSpace);
258
+ // Zeroed: the view may be transparent.
259
+ result.data.assign(static_cast<size_t>(snapshot.width) * snapshot.height * 4,
260
+ 0);
261
+ CGContextRef context =
262
+ createBgraContext(result.data.data(), snapshot.width, snapshot.height,
263
+ static_cast<size_t>(snapshot.width) * 4);
166
264
  if (context == NULL) {
167
- CGImageRelease(cgImage);
168
265
  throw std::runtime_error(
169
266
  "drawElementImageToTexture: could not allocate the pixel buffer");
170
267
  }
171
268
  #if !TARGET_OS_OSX
172
- // The renderer already produced exactly width x height pixels.
173
- const CGRect imageRect = CGRectMake(0, 0, width, height);
269
+ // UIKit draws straight into the vector; no intermediate image.
270
+ try {
271
+ drawViewHierarchy(snapshot, context, afterScreenUpdates);
272
+ } catch (...) {
273
+ CGContextRelease(context);
274
+ throw;
275
+ }
174
276
  #else
277
+ (void)afterScreenUpdates;
278
+ CGImageRef cgImage = NULL;
279
+ NSBitmapImageRep *rep =
280
+ [snapshot.view bitmapImageRepForCachingDisplayInRect:snapshot.bounds];
281
+ if (rep != nil) {
282
+ [snapshot.view cacheDisplayInRect:snapshot.bounds toBitmapImageRep:rep];
283
+ cgImage = rep.CGImage;
284
+ if (cgImage != NULL) {
285
+ CGImageRetain(cgImage);
286
+ }
287
+ }
288
+ if (cgImage == NULL) {
289
+ CGContextRelease(context);
290
+ throw std::runtime_error(
291
+ "drawElementImageToTexture: the view could not be rendered");
292
+ }
175
293
  // The cached display covers the whole view at the backing scale; draw it so
176
294
  // that the source rectangle fills the output. CoreGraphics' origin is the
177
295
  // bottom-left corner, hence the flipped vertical offset.
178
- const CGRect imageRect = CGRectMake(
179
- -sourceX * scaleX,
180
- -(bounds.size.height - sourceY - sourceHeight) * scaleY,
181
- bounds.size.width * scaleX, bounds.size.height * scaleY);
182
- #endif
296
+ const CGRect imageRect =
297
+ CGRectMake(-snapshot.sourceX * snapshot.scaleX,
298
+ -(snapshot.bounds.size.height - snapshot.sourceY -
299
+ snapshot.sourceHeight) *
300
+ snapshot.scaleY,
301
+ snapshot.bounds.size.width * snapshot.scaleX,
302
+ snapshot.bounds.size.height * snapshot.scaleY);
183
303
  CGContextSetBlendMode(context, kCGBlendModeCopy);
184
304
  CGContextDrawImage(context, imageRect, cgImage);
185
- CGContextRelease(context);
186
305
  CGImageRelease(cgImage);
306
+ #endif
307
+ CGContextRelease(context);
187
308
  return result;
188
309
  }
189
310
 
190
311
  } // namespace
191
312
 
192
- // Always delivers pixels: the view is drawn into a CPU bitmap, so there is no
193
- // GPU frame to hand over (onFrame is unused).
313
+ // On iOS a caller that can draw a GPU frame (allowFrame) gets the view
314
+ // rendered into an IOSurface; everything else, and macOS, gets pixels.
194
315
  void ApplePlatformContext::snapshotView(
195
- const ViewSnapshotRequest &request,
196
- std::function<void(ImageData)> onPixels,
197
- std::function<void(VideoFrameHandle)> /* onFrame */,
316
+ const ViewSnapshotRequest &request, std::function<void(ImageData)> onPixels,
317
+ std::function<void(VideoFrameHandle)> onFrame,
198
318
  std::function<void(std::string)> onError) {
199
319
  // Copies: the block outlives this call.
200
320
  ViewLookup lookup = _viewLookup;
@@ -202,7 +322,29 @@ void ApplePlatformContext::snapshotView(
202
322
  dispatch_async(dispatch_get_main_queue(), ^{
203
323
  @autoreleasepool {
204
324
  try {
205
- onPixels(snapshotViewOnMainThread(lookup, snapshotRequest));
325
+ const ResolvedSnapshot snapshot =
326
+ resolveSnapshot(lookup, snapshotRequest);
327
+ #if !TARGET_OS_OSX
328
+ if (snapshotRequest.allowFrame) {
329
+ try {
330
+ onFrame(renderViewIntoSurface(snapshot,
331
+ snapshotRequest.afterScreenUpdates));
332
+ return;
333
+ } catch (const ViewNotRenderable &) {
334
+ throw;
335
+ } catch (const std::exception &error) {
336
+ // An IOSurface could not be set up; the pixel path draws the
337
+ // same view.
338
+ NSLog(@"[WebGPU] drawElementImageToTexture: %s; reading the "
339
+ @"pixels back instead",
340
+ error.what());
341
+ }
342
+ }
343
+ #else
344
+ (void)onFrame;
345
+ #endif
346
+ onPixels(
347
+ renderViewIntoPixels(snapshot, snapshotRequest.afterScreenUpdates));
206
348
  } catch (const std::exception &error) {
207
349
  onError(error.what());
208
350
  } catch (...) {
@@ -325,12 +467,10 @@ void ApplePlatformContext::createImageBitmapFromDataAsync(
325
467
  });
326
468
  }
327
469
 
328
- VideoFrameHandle
329
- ApplePlatformContext::loadVideoFrame(const std::string &path) {
470
+ VideoFrameHandle ApplePlatformContext::loadVideoFrame(const std::string &path) {
330
471
  NSString *nsPath = [NSString stringWithUTF8String:path.c_str()];
331
- NSURL *url = [nsPath hasPrefix:@"file://"]
332
- ? [NSURL URLWithString:nsPath]
333
- : [NSURL fileURLWithPath:nsPath];
472
+ NSURL *url = [nsPath hasPrefix:@"file://"] ? [NSURL URLWithString:nsPath]
473
+ : [NSURL fileURLWithPath:nsPath];
334
474
  AVURLAsset *asset = [AVURLAsset assetWithURL:url];
335
475
 
336
476
  NSArray<AVAssetTrack *> *videoTracks =
@@ -350,8 +490,7 @@ ApplePlatformContext::loadVideoFrame(const std::string &path) {
350
490
  }
351
491
 
352
492
  NSDictionary *outputSettings = @{
353
- (NSString *)kCVPixelBufferPixelFormatTypeKey :
354
- @(kCVPixelFormatType_32BGRA),
493
+ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
355
494
  (NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
356
495
  (NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
357
496
  };
@@ -405,7 +544,7 @@ ApplePlatformContext::loadVideoFrame(const std::string &path) {
405
544
 
406
545
  std::unique_ptr<IVideoPlayer>
407
546
  ApplePlatformContext::createVideoPlayer(const std::string &path,
408
- VideoPixelFormat format) {
547
+ VideoPixelFormat format) {
409
548
  return createAppleVideoPlayer(path, format);
410
549
  }
411
550
 
@@ -148,6 +148,12 @@ struct ViewSnapshotRequest {
148
148
  // destination itself. A platform that renders the view into a GPU buffer
149
149
  // then hands that buffer over instead of reading the pixels back.
150
150
  bool allowFrame = false;
151
+ // iOS: whether to flush pending layout and layer changes before drawing
152
+ // (drawViewHierarchyInRect's afterScreenUpdates), so a snapshot taken right
153
+ // after a state change reflects it; false draws the last committed frame
154
+ // without that round trip. Android renders the views' current display
155
+ // lists either way.
156
+ bool afterScreenUpdates = true;
151
157
  };
152
158
 
153
159
  class PlatformContext {
@@ -548,16 +548,27 @@ public:
548
548
  if (device) {
549
549
  dawn::native::metal::WaitForCommandsToBeScheduled(device.Get());
550
550
  }
551
+ #endif
552
+ #if defined(__ANDROID__)
553
+ std::function<void()> framePresented;
551
554
  #endif
552
555
  {
553
556
  std::unique_lock<std::shared_mutex> lock(_mutex);
554
557
  if (_surface && _acquiredFromSurface) {
555
558
  _surface.Present();
559
+ #if defined(__ANDROID__)
560
+ framePresented = _framePresented;
561
+ #endif
556
562
  }
557
563
  _acquiredFromSurface = false;
558
564
  _frameInFlight = false;
559
565
  _frameEpoch++;
560
566
  }
567
+ #if defined(__ANDROID__)
568
+ if (framePresented) {
569
+ framePresented();
570
+ }
571
+ #endif
561
572
  applyPendingAttach();
562
573
  }
563
574
 
@@ -691,6 +702,16 @@ public:
691
702
  std::lock_guard<std::mutex> poolLock(_poolMutex);
692
703
  _poolFrameReady = std::move(cb);
693
704
  }
705
+
706
+ // Invoked from the presenting thread (never under _mutex) after each frame
707
+ // presentFrame() shows on the attached surface; the blit of the last
708
+ // offscreen frame onto a newly attached surface does not count. The view
709
+ // uses it to vote the display's refresh rate while the canvas animates
710
+ // (see SurfaceFrameRateVote.java). Pass nullptr to unregister.
711
+ void setFramePresentedCallback(std::function<void()> cb) {
712
+ std::unique_lock<std::shared_mutex> lock(_mutex);
713
+ _framePresented = std::move(cb);
714
+ }
694
715
  #endif
695
716
 
696
717
  private:
@@ -1239,6 +1260,9 @@ private:
1239
1260
  static constexpr int kPoolSize =
1240
1261
  5; // 2 held + 1 rendering + 1 waiting + 1 ready
1241
1262
 
1263
+ // See setFramePresentedCallback(); read under _mutex, called outside it.
1264
+ std::function<void()> _framePresented;
1265
+
1242
1266
  std::atomic<bool> _poolMode{false};
1243
1267
  std::mutex _poolMutex;
1244
1268
  std::condition_variable _freeCv; // a slot returned to Free
@@ -357,12 +357,15 @@ async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
357
357
  "a positive width and height");
358
358
  }
359
359
 
360
+ request.afterScreenUpdates = source->afterScreenUpdates.value_or(true);
361
+
360
362
  // With RENDER_ATTACHMENT as well, a platform that renders the view into a
361
- // GPU buffer (Android 13 and above, through HardwareRenderer) hands that
362
- // buffer over instead of reading the pixels back, and the view is drawn into
363
- // the destination on the GPU (VideoFrameBlit), which writes the color aspect
364
- // of the first layer. An omitted aspect means "all" (dst.aspect itself stays
365
- // at Dawn's Undefined default, which WriteTexture reads the same way).
363
+ // GPU buffer (Android 13 and above through HardwareRenderer, iOS through an
364
+ // IOSurface) hands that buffer over instead of reading the pixels back, and
365
+ // the view is drawn into the destination on the GPU (VideoFrameBlit), which
366
+ // writes the color aspect of the first layer. An omitted aspect means "all"
367
+ // (dst.aspect itself stays at Dawn's Undefined default, which WriteTexture
368
+ // reads the same way).
366
369
  request.allowFrame =
367
370
  (texture.GetUsage() & wgpu::TextureUsage::RenderAttachment) != 0 &&
368
371
  info->aspect.value_or(wgpu::TextureAspect::All) ==
@@ -22,6 +22,9 @@ struct GPUDrawElementImageSource {
22
22
  std::optional<double> sourceY; // float
23
23
  std::optional<double> sourceWidth; // float
24
24
  std::optional<double> sourceHeight; // float
25
+ // iOS: pass false to draw the last committed frame instead of waiting for
26
+ // pending layout and layer changes (default true). Ignored on Android.
27
+ std::optional<bool> afterScreenUpdates;
25
28
  };
26
29
 
27
30
  } // namespace rnwgpu
@@ -93,6 +96,10 @@ struct JSIConverter<std::shared_ptr<rnwgpu::GPUDrawElementImageSource>> {
93
96
  result->sourceHeight = JSIConverter<std::optional<double>>::fromJSI(
94
97
  runtime, obj.getProperty(runtime, "sourceHeight"), false);
95
98
  }
99
+ if (obj.hasProperty(runtime, "afterScreenUpdates")) {
100
+ result->afterScreenUpdates = JSIConverter<std::optional<bool>>::fromJSI(
101
+ runtime, obj.getProperty(runtime, "afterScreenUpdates"), false);
102
+ }
96
103
  return result;
97
104
  }
98
105
 
@@ -73,6 +73,7 @@ export interface GPUDrawElementImageSource {
73
73
  sourceY?: number;
74
74
  sourceWidth?: number;
75
75
  sourceHeight?: number;
76
+ afterScreenUpdates?: boolean;
76
77
  }
77
78
  export interface GPUDrawElementImageDestination extends GPUCopyExternalImageDestInfo {
78
79
  size?: GPUExtent3DStrict;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG,MAAM,CAAC;AAE7B,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IAGrB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IACjE,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAClC,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG;IAC/C;;;;;;;;OAQG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,UAAU,CAAC,WAAW,EAAE,QAAQ,GAAG,eAAe,GAAG,IAAI,CAAC;IAC1D,gBAAgB,EAAE,MAAM,YAAY,CAAC;CACtC;AAKD,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,MAAM,CAAC;AAkBtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD,MAAM,WAAW,kCAAmC,SAAQ,IAAI,CAC9D,4BAA4B,EAC5B,QAAQ,CACT;IACC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAQD,MAAM,WAAW,WAAW;IAC1B,eAAe,IAAI,gBAAgB,GAAG,IAAI,CAAC;IAC3C,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IAGd,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAGzB,WAAW,EAAE,MAAM,CAAC;IAEpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,MAAM,EAAE,MAAM,CAAC;IAGf,IAAI,EAAE,OAAO,CAAC;IAEd,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAI7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;IAEtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AASD,MAAM,WAAW,oCAAoC;IACnD,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IAOvC,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC;AAMD,MAAM,MAAM,6BAA6B,GACvC,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;AAO3D,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,6BAA6B,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAMtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAOD,MAAM,WAAW,8BAA+B,SAAQ,4BAA4B;IAClF,IAAI,CAAC,EAAE,iBAAiB,CAAC;CAC1B;AAED,MAAM,WAAW,gCAAgC;IAM/C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,MAAM,MAAM,kBAAkB,GAC5B,kBAAkB,GAAG,SAAS,GAAG,wBAAwB,CAAC;AAE5D,MAAM,WAAW,wBAAwB;IAGvC,IAAI,EAAE,kBAAkB,CAAC;IAGzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,kBAAkB,CAAC;IAIzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IAGd,MAAM,IAAI,wBAAwB,CAAC;IAInC,OAAO,IAAI,IAAI,CAAC;CACjB;AAID,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,MAAM,WAAW,oCAAoC;IACnD,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAQD,MAAM,WAAW,wBAAwB;IACvC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAMD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,UAAU,CAAC,EAAE,oBAAoB,GAAG,UAAU,CAAC;IAM7D,WAAW,CACT,OAAO,EAAE,UAAU,EACnB,WAAW,EAAE,OAAO,EACpB,MAAM,CAAC,EAAE,mBAAmB,EAAE,GAC7B,IAAI,CAAC;IAGR,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,oCAAoC,CAAC;IAMrE,OAAO,IAAI,IAAI,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-webgpu",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "React Native WebGPU",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
package/src/types.ts CHANGED
@@ -152,6 +152,12 @@ export interface GPUDrawElementImageSource {
152
152
  sourceY?: number;
153
153
  sourceWidth?: number;
154
154
  sourceHeight?: number;
155
+ // iOS only (default true): flush pending layout and layer changes before
156
+ // drawing, so a snapshot taken right after a state change reflects it. Pass
157
+ // false to draw the last committed frame instead, which skips a round trip
158
+ // to the render server; the choice for repeated captures. Android renders
159
+ // the views' current display lists either way.
160
+ afterScreenUpdates?: boolean;
155
161
  }
156
162
 
157
163
  // The destination argument of drawElementImageToTexture: