react-native-webgpu 0.10.4 → 0.11.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 (43) hide show
  1. package/Package.swift +24 -10
  2. package/android/cpp/AndroidPlatformContext.h +60 -3
  3. package/android/cpp/cpp-adapter.cpp +66 -6
  4. package/android/src/main/java/com/webgpu/ViewSnapshot.java +162 -0
  5. package/android/src/main/java/com/webgpu/WebGPUModule.java +68 -0
  6. package/apple/ApplePlatformContext.h +14 -1
  7. package/apple/ApplePlatformContext.mm +176 -1
  8. package/apple/WebGPUModule.mm +20 -1
  9. package/cpp/rnwgpu/PlatformContext.h +55 -0
  10. package/cpp/rnwgpu/RNWebGPUManager.cpp +7 -0
  11. package/cpp/rnwgpu/SurfaceRegistry.h +21 -2
  12. package/cpp/rnwgpu/api/GPU.cpp +1 -1
  13. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +14 -2
  14. package/cpp/rnwgpu/api/GPUCanvasContext.h +8 -0
  15. package/cpp/rnwgpu/api/GPUQueue.cpp +187 -0
  16. package/cpp/rnwgpu/api/GPUQueue.h +16 -0
  17. package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageDestination.h +60 -0
  18. package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageSource.h +106 -0
  19. package/lib/commonjs/WebPolyfillGPUModule.js +7 -0
  20. package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
  21. package/lib/commonjs/importDevice.js +3 -3
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/main/index.js +33 -0
  24. package/lib/commonjs/main/index.js.map +1 -1
  25. package/lib/module/WebPolyfillGPUModule.js +7 -0
  26. package/lib/module/WebPolyfillGPUModule.js.map +1 -1
  27. package/lib/module/importDevice.js +3 -3
  28. package/lib/module/index.js.map +1 -1
  29. package/lib/module/main/index.js +33 -0
  30. package/lib/module/main/index.js.map +1 -1
  31. package/lib/typescript/lib/commonjs/importDevice.d.ts +3 -3
  32. package/lib/typescript/src/importDevice.d.ts +3 -3
  33. package/lib/typescript/src/index.d.ts +5 -2
  34. package/lib/typescript/src/index.d.ts.map +1 -1
  35. package/lib/typescript/src/main/index.d.ts.map +1 -1
  36. package/lib/typescript/src/types.d.ts +13 -0
  37. package/lib/typescript/src/types.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/WebPolyfillGPUModule.ts +10 -2
  40. package/src/importDevice.ts +3 -3
  41. package/src/index.tsx +29 -0
  42. package/src/main/index.tsx +49 -0
  43. package/src/types.ts +29 -0
package/Package.swift CHANGED
@@ -30,14 +30,29 @@ let sibling = { (relative: String) -> String in
30
30
  // after changing either package's Dawn version reset the package caches
31
31
  // (File > Packages > Reset Package Caches) if this does not re-run.
32
32
  let dawnReleaseTag = "dawn-chrome-m154"
33
- let skiaCandidates = [
34
- sibling("../@shopify/react-native-skia"), // consumer: sibling in node_modules
35
- sibling("../../node_modules/@shopify/react-native-skia"), // this monorepo
36
- ]
37
- for skia in skiaCandidates {
38
- // Only Graphite builds of react-native-skia write this marker (Ganesh
39
- // builds do not link Dawn and impose no constraint).
40
- guard let data = FileManager.default.contents(atPath: "\(skia)/libs/.dawn-version"),
33
+ // Only Graphite builds of react-native-skia carry this marker (Ganesh builds
34
+ // do not link Dawn and impose no constraint). From v3 the Skia binaries ship
35
+ // in react-native-skia-graphite-apple-ios, which records the tag at
36
+ // libs/.dawn-version; react-native-skia's podspec copies it into its own
37
+ // libs/ at `pod install`, which never runs here. The v2 Graphite previews
38
+ // (e.g. @shopify/react-native-skia@2.12.0-next.1) keep it in their own libs/.
39
+ // The graphite packages are normally hoisted next to this package, but may
40
+ // be nested under react-native-skia when the package manager cannot hoist
41
+ // them. Every marker found is checked.
42
+ let skiaDawnMarkers = [
43
+ "react-native-skia-graphite-apple-ios/libs/.dawn-version",
44
+ "react-native-skia-graphite-apple-macos/libs/.dawn-version",
45
+ "react-native-skia/node_modules/react-native-skia-graphite-apple-ios/libs/.dawn-version",
46
+ "react-native-skia/node_modules/react-native-skia-graphite-apple-macos/libs/.dawn-version",
47
+ "@shopify/react-native-skia/libs/.dawn-version",
48
+ ].flatMap { marker in
49
+ [
50
+ sibling("../\(marker)"), // consumer: sibling in node_modules
51
+ sibling("../../node_modules/\(marker)"), // this monorepo
52
+ ]
53
+ }
54
+ for marker in skiaDawnMarkers {
55
+ guard let data = FileManager.default.contents(atPath: marker),
41
56
  let skiaDawn = String(data: data, encoding: .utf8)?
42
57
  .trimmingCharacters(in: .whitespacesAndNewlines)
43
58
  else { continue }
@@ -45,11 +60,10 @@ for skia in skiaCandidates {
45
60
  fatalError(
46
61
  """
47
62
  react-native-webgpu: Dawn version mismatch. This package links \(dawnReleaseTag) \
48
- but the @shopify/react-native-skia Graphite build at \(skia) links \(skiaDawn). \
63
+ but the react-native-skia Graphite binaries (\(marker)) link \(skiaDawn). \
49
64
  Align the two packages so the app contains exactly one Dawn.
50
65
  """)
51
66
  }
52
- break
53
67
  }
54
68
 
55
69
  // Unlike a native Xcode target, SwiftPM doesn't generate an implicit
@@ -21,9 +21,19 @@ namespace rnwgpu {
21
21
  namespace jsi = facebook::jsi;
22
22
  namespace jni = facebook::jni;
23
23
 
24
+ // The completion callbacks of one snapshotView request, heap-allocated and
25
+ // carried through Java as an opaque pointer (WebGPUModule.snapshotView ->
26
+ // onViewSnapshot), which hands them back exactly once.
27
+ struct ViewSnapshotCallbacks {
28
+ std::function<void(ImageData)> onSuccess;
29
+ std::function<void(std::string)> onError;
30
+ };
31
+
24
32
  class AndroidPlatformContext : public PlatformContext {
25
33
  private:
26
34
  jobject _blobModule;
35
+ // Global ref to the com.webgpu.WebGPUModule instance (view snapshots).
36
+ jobject _module;
27
37
 
28
38
  std::vector<uint8_t> resolveBlob(JNIEnv *env, const std::string &blobId,
29
39
  double offset, double size) {
@@ -63,14 +73,61 @@ private:
63
73
  }
64
74
 
65
75
  public:
66
- explicit AndroidPlatformContext(jobject blobModule)
67
- : _blobModule(blobModule) {}
76
+ // Both arguments are global refs; the context releases them.
77
+ AndroidPlatformContext(jobject blobModule, jobject module)
78
+ : _blobModule(blobModule), _module(module) {}
68
79
  ~AndroidPlatformContext() {
80
+ JNIEnv *env = facebook::jni::Environment::current();
69
81
  if (_blobModule) {
70
- JNIEnv *env = facebook::jni::Environment::current();
71
82
  env->DeleteGlobalRef(_blobModule);
72
83
  _blobModule = nullptr;
73
84
  }
85
+ if (_module) {
86
+ env->DeleteGlobalRef(_module);
87
+ _module = nullptr;
88
+ }
89
+ }
90
+
91
+ void snapshotView(const ViewSnapshotRequest &request,
92
+ std::function<void(ImageData)> onSuccess,
93
+ std::function<void(std::string)> onError) override {
94
+ if (!_module) {
95
+ onError("drawElementImageToTexture: the WebGPU module is gone");
96
+ return;
97
+ }
98
+ jni::Environment::ensureCurrentThreadIsAttached();
99
+ JNIEnv *env = facebook::jni::Environment::current();
100
+ if (!env) {
101
+ onError("drawElementImageToTexture: couldn't get the JNI environment");
102
+ return;
103
+ }
104
+ jclass moduleClass = env->GetObjectClass(_module);
105
+ jmethodID method =
106
+ env->GetMethodID(moduleClass, "snapshotView", "(IDDDDIIJ)V");
107
+ env->DeleteLocalRef(moduleClass);
108
+ if (!method) {
109
+ env->ExceptionClear();
110
+ onError("drawElementImageToTexture: WebGPUModule.snapshotView not found");
111
+ return;
112
+ }
113
+ // Ownership passes to Java; Java_com_webgpu_WebGPUModule_onViewSnapshot
114
+ // (cpp-adapter.cpp) reclaims it.
115
+ auto *callbacks = new ViewSnapshotCallbacks{std::move(onSuccess),
116
+ std::move(onError)};
117
+ env->CallVoidMethod(_module, method, static_cast<jint>(request.viewTag),
118
+ static_cast<jdouble>(request.sourceX),
119
+ static_cast<jdouble>(request.sourceY),
120
+ static_cast<jdouble>(request.sourceWidth),
121
+ static_cast<jdouble>(request.sourceHeight),
122
+ static_cast<jint>(request.width),
123
+ static_cast<jint>(request.height),
124
+ reinterpret_cast<jlong>(callbacks));
125
+ if (env->ExceptionCheck()) {
126
+ env->ExceptionClear();
127
+ auto fail = std::move(callbacks->onError);
128
+ delete callbacks;
129
+ fail("drawElementImageToTexture: WebGPUModule.snapshotView threw");
130
+ }
74
131
  }
75
132
 
76
133
  wgpu::Surface makeSurface(wgpu::Instance instance, void *window, int width,
@@ -1,11 +1,15 @@
1
+ #include <cstring>
1
2
  #include <memory>
3
+ #include <string>
2
4
  #include <unordered_map>
5
+ #include <utility>
3
6
 
4
7
  #include <fbjni/fbjni.h>
5
8
  #include <jni.h>
6
9
  #include <jsi/jsi.h>
7
10
 
8
11
  #include <ReactCommon/CallInvokerHolder.h>
12
+ #include <android/bitmap.h>
9
13
  #include <android/hardware_buffer_jni.h>
10
14
  #include <android/native_window_jni.h>
11
15
  #include <webgpu/webgpu_cpp.h>
@@ -19,21 +23,77 @@
19
23
  std::shared_ptr<rnwgpu::RNWebGPUManager> manager;
20
24
 
21
25
  extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUModule_initializeNative(
22
- JNIEnv *env, jobject /* this */, jlong jsRuntime,
23
- jobject jsCallInvokerHolder, jobject blobModule) {
26
+ JNIEnv *env, jobject thiz, jlong jsRuntime, jobject jsCallInvokerHolder,
27
+ jobject blobModule) {
24
28
  auto runtime = reinterpret_cast<facebook::jsi::Runtime *>(jsRuntime);
25
29
  jobject globalBlobModule = env->NewGlobalRef(blobModule);
30
+ jobject globalModule = env->NewGlobalRef(thiz);
26
31
  auto jsCallInvoker{
27
32
  facebook::jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>{
28
33
  reinterpret_cast<facebook::react::CallInvokerHolder::javaobject>(
29
- jsCallInvokerHolder)} -> cthis()
30
- ->getCallInvoker()};
31
- auto platformContext =
32
- std::make_shared<rnwgpu::AndroidPlatformContext>(globalBlobModule);
34
+ jsCallInvokerHolder)} -> cthis()->getCallInvoker()};
35
+ auto platformContext = std::make_shared<rnwgpu::AndroidPlatformContext>(
36
+ globalBlobModule, globalModule);
33
37
  manager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsCallInvoker,
34
38
  platformContext);
35
39
  }
36
40
 
41
+ // Completion of WebGPUModule.snapshotView (UI thread). Reclaims the callbacks
42
+ // handed to Java and delivers either the bitmap's pixels or the error.
43
+ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUModule_onViewSnapshot(
44
+ JNIEnv *env, jobject /* this */, jlong callbackPointer, jobject bitmap,
45
+ jstring error) {
46
+ std::unique_ptr<rnwgpu::ViewSnapshotCallbacks> callbacks(
47
+ reinterpret_cast<rnwgpu::ViewSnapshotCallbacks *>(callbackPointer));
48
+ if (!callbacks) {
49
+ return;
50
+ }
51
+ if (error != nullptr) {
52
+ const char *chars = env->GetStringUTFChars(error, nullptr);
53
+ std::string message(chars ? chars : "drawElementImageToTexture failed");
54
+ if (chars) {
55
+ env->ReleaseStringUTFChars(error, chars);
56
+ }
57
+ callbacks->onError(std::move(message));
58
+ return;
59
+ }
60
+ if (bitmap == nullptr) {
61
+ callbacks->onError("drawElementImageToTexture: no bitmap was produced");
62
+ return;
63
+ }
64
+ AndroidBitmapInfo info;
65
+ if (AndroidBitmap_getInfo(env, bitmap, &info) !=
66
+ ANDROID_BITMAP_RESULT_SUCCESS ||
67
+ info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
68
+ callbacks->onError(
69
+ "drawElementImageToTexture: unexpected snapshot bitmap format");
70
+ return;
71
+ }
72
+ void *pixels = nullptr;
73
+ if (AndroidBitmap_lockPixels(env, bitmap, &pixels) !=
74
+ ANDROID_BITMAP_RESULT_SUCCESS ||
75
+ pixels == nullptr) {
76
+ callbacks->onError(
77
+ "drawElementImageToTexture: couldn't lock the snapshot bitmap");
78
+ return;
79
+ }
80
+ rnwgpu::ImageData image;
81
+ image.width = info.width;
82
+ image.height = info.height;
83
+ image.format = wgpu::TextureFormat::RGBA8Unorm;
84
+ // Bitmap.Config.ARGB_8888 stores premultiplied RGBA bytes.
85
+ image.premultiplied = true;
86
+ const size_t rowBytes = static_cast<size_t>(info.width) * 4;
87
+ image.data.resize(rowBytes * info.height);
88
+ const auto *src = static_cast<const uint8_t *>(pixels);
89
+ for (uint32_t row = 0; row < info.height; ++row) {
90
+ std::memcpy(image.data.data() + row * rowBytes, src + row * info.stride,
91
+ rowBytes);
92
+ }
93
+ AndroidBitmap_unlockPixels(env, bitmap);
94
+ callbacks->onSuccess(std::move(image));
95
+ }
96
+
37
97
  extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceChanged(
38
98
  JNIEnv *env, jobject thiz, jobject surface, jint contextId, jfloat width,
39
99
  jfloat height) {
@@ -0,0 +1,162 @@
1
+ package com.webgpu;
2
+
3
+ import android.graphics.Bitmap;
4
+ import android.graphics.Canvas;
5
+ import android.graphics.ColorSpace;
6
+ import android.graphics.HardwareRenderer;
7
+ import android.graphics.PixelFormat;
8
+ import android.graphics.RecordingCanvas;
9
+ import android.graphics.RenderNode;
10
+ import android.hardware.HardwareBuffer;
11
+ import android.media.Image;
12
+ import android.media.ImageReader;
13
+ import android.os.Build;
14
+ import android.util.Log;
15
+ import android.view.View;
16
+
17
+ import androidx.annotation.NonNull;
18
+ import androidx.annotation.RequiresApi;
19
+
20
+ /**
21
+ * Rasterizes a native view for GPUQueue.drawElementImageToTexture. Must be
22
+ * called on the UI thread.
23
+ *
24
+ * On API 29+ the view is rendered through {@link HardwareRenderer}: the
25
+ * recording reuses the children's existing display lists, so the result is
26
+ * exactly what the window's own renderer draws (shadows, hardware layers,
27
+ * TextureView content). Older devices, or a failed hardware render, fall back
28
+ * to drawing the view into a software canvas, which misses anything that is
29
+ * only drawn hardware-accelerated. SurfaceView content is composed outside
30
+ * the view hierarchy and is captured by neither path.
31
+ */
32
+ final class ViewSnapshot {
33
+ private static final String TAG = "WebGPUViewSnapshot";
34
+
35
+ private ViewSnapshot() {}
36
+
37
+ /**
38
+ * @param sourceX/Y/Width/Height the source rectangle in the view's own
39
+ * coordinate space, in pixels (already scaled by the display density)
40
+ * @param width/height the output size in pixels
41
+ * @return a software ARGB_8888 (premultiplied) bitmap of exactly
42
+ * width x height pixels
43
+ */
44
+ @NonNull
45
+ static Bitmap snapshot(
46
+ @NonNull View view,
47
+ float sourceX,
48
+ float sourceY,
49
+ float sourceWidth,
50
+ float sourceHeight,
51
+ int width,
52
+ int height) {
53
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && view.isHardwareAccelerated()) {
54
+ try {
55
+ return snapshotHardware(view, sourceX, sourceY, sourceWidth, sourceHeight, width, height);
56
+ } catch (Throwable t) {
57
+ Log.w(TAG, "HardwareRenderer snapshot failed, falling back to software rendering", t);
58
+ }
59
+ }
60
+ return snapshotSoftware(view, sourceX, sourceY, sourceWidth, sourceHeight, width, height);
61
+ }
62
+
63
+ @NonNull
64
+ private static Bitmap snapshotSoftware(
65
+ @NonNull View view,
66
+ float sourceX,
67
+ float sourceY,
68
+ float sourceWidth,
69
+ float sourceHeight,
70
+ int width,
71
+ int height) {
72
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
73
+ Canvas canvas = new Canvas(bitmap);
74
+ canvas.scale(width / sourceWidth, height / sourceHeight);
75
+ canvas.translate(-sourceX, -sourceY);
76
+ view.draw(canvas);
77
+ return bitmap;
78
+ }
79
+
80
+ @RequiresApi(Build.VERSION_CODES.Q)
81
+ @NonNull
82
+ private static Bitmap snapshotHardware(
83
+ @NonNull View view,
84
+ float sourceX,
85
+ float sourceY,
86
+ float sourceWidth,
87
+ float sourceHeight,
88
+ int width,
89
+ int height) {
90
+ // The renderer draws into an ImageReader whose buffers are GPU-sampleable
91
+ // (a later zero-copy path can import them directly); for now the frame is
92
+ // read back into a software bitmap.
93
+ ImageReader reader =
94
+ ImageReader.newInstance(
95
+ width,
96
+ height,
97
+ PixelFormat.RGBA_8888,
98
+ 1,
99
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
100
+ HardwareRenderer renderer = new HardwareRenderer();
101
+ Image image = null;
102
+ try {
103
+ renderer.setSurface(reader.getSurface());
104
+ // Match the framework's default light so elevation shadows come out
105
+ // like on screen (ThreadedRenderer reads these from the theme; the
106
+ // Material defaults are used here).
107
+ float density = view.getResources().getDisplayMetrics().density;
108
+ renderer.setLightSourceGeometry(
109
+ width / 2f, -400f * density, 500f * density, 800f * density);
110
+ renderer.setLightSourceAlpha(0.039f, 0.19f);
111
+
112
+ RenderNode root = new RenderNode("rnwgpu-view-snapshot");
113
+ root.setPosition(0, 0, width, height);
114
+ RecordingCanvas canvas = root.beginRecording(width, height);
115
+ try {
116
+ canvas.scale(width / sourceWidth, height / sourceHeight);
117
+ canvas.translate(-sourceX, -sourceY);
118
+ view.draw(canvas);
119
+ } finally {
120
+ root.endRecording();
121
+ }
122
+ renderer.setContentRoot(root);
123
+
124
+ HardwareRenderer.FrameRenderRequest request = renderer.createRenderRequest();
125
+ request.setWaitForPresent(true);
126
+ int status = request.syncAndDraw();
127
+ int failed =
128
+ HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND
129
+ | HardwareRenderer.SYNC_CONTEXT_IS_STOPPED
130
+ | HardwareRenderer.SYNC_FRAME_DROPPED;
131
+ if ((status & failed) != 0) {
132
+ throw new IllegalStateException("HardwareRenderer.syncAndDraw failed with status " + status);
133
+ }
134
+ image = reader.acquireNextImage();
135
+ if (image == null) {
136
+ throw new IllegalStateException("HardwareRenderer produced no image");
137
+ }
138
+ HardwareBuffer buffer = image.getHardwareBuffer();
139
+ if (buffer == null) {
140
+ throw new IllegalStateException("HardwareRenderer image has no HardwareBuffer");
141
+ }
142
+ Bitmap hardware =
143
+ Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
144
+ buffer.close();
145
+ if (hardware == null) {
146
+ throw new IllegalStateException("Bitmap.wrapHardwareBuffer failed");
147
+ }
148
+ Bitmap software = hardware.copy(Bitmap.Config.ARGB_8888, false);
149
+ hardware.recycle();
150
+ if (software == null) {
151
+ throw new IllegalStateException("could not read back the hardware bitmap");
152
+ }
153
+ return software;
154
+ } finally {
155
+ if (image != null) {
156
+ image.close();
157
+ }
158
+ renderer.destroy();
159
+ reader.close();
160
+ }
161
+ }
162
+ }
@@ -1,6 +1,8 @@
1
1
  package com.webgpu;
2
2
 
3
+ import android.graphics.Bitmap;
3
4
  import android.util.Log;
5
+ import android.view.View;
4
6
 
5
7
  import androidx.annotation.OptIn;
6
8
 
@@ -11,7 +13,10 @@ import com.facebook.proguard.annotations.DoNotStrip;
11
13
  import com.facebook.react.bridge.ReactApplicationContext;
12
14
  import com.facebook.react.bridge.JavaScriptContextHolder;
13
15
  import com.facebook.react.bridge.ReactMethod;
16
+ import com.facebook.react.bridge.UIManager;
17
+ import com.facebook.react.bridge.UiThreadUtil;
14
18
  import com.facebook.react.common.annotations.FrameworkAPI;
19
+ import com.facebook.react.uimanager.UIManagerHelper;
15
20
  import com.facebook.react.module.annotations.ReactModule;
16
21
  import com.facebook.react.modules.blob.BlobModule;
17
22
  import com.facebook.react.modules.blob.BlobProvider;
@@ -47,4 +52,67 @@ public class WebGPUModule extends NativeWebGPUModuleSpec {
47
52
  @OptIn(markerClass = FrameworkAPI.class)
48
53
  @DoNotStrip
49
54
  private native void initializeNative(long jsRuntime, CallInvokerHolderImpl jsInvoker, BlobModule blobModule);
55
+
56
+ /**
57
+ * Called from C++ (AndroidPlatformContext::snapshotView) on an arbitrary
58
+ * thread for GPUQueue.drawElementImageToTexture. Hops to the UI thread,
59
+ * resolves the view for {@code tag}, rasterizes the requested rectangle and
60
+ * always reports back through {@link #onViewSnapshot}, with either a bitmap
61
+ * or an error message. {@code callback} is an opaque native pointer that
62
+ * must be handed back exactly once.
63
+ *
64
+ * The source rectangle is in density-independent points (React Native
65
+ * layout units) and is converted to pixels here; a non-positive
66
+ * sourceWidth/sourceHeight means the whole view. A zero width/height means
67
+ * the natural pixel size of the source rectangle.
68
+ */
69
+ @DoNotStrip
70
+ void snapshotView(
71
+ final int tag,
72
+ final double sourceX,
73
+ final double sourceY,
74
+ final double sourceWidth,
75
+ final double sourceHeight,
76
+ final int width,
77
+ final int height,
78
+ final long callback) {
79
+ UiThreadUtil.runOnUiThread(
80
+ () -> {
81
+ Bitmap bitmap = null;
82
+ String error = null;
83
+ try {
84
+ ReactApplicationContext context = getReactApplicationContext();
85
+ UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(context, tag);
86
+ View view = uiManager == null ? null : uiManager.resolveView(tag);
87
+ if (view == null) {
88
+ throw new IllegalArgumentException(
89
+ "drawElementImageToTexture: no native view found for tag "
90
+ + tag
91
+ + " (is the view mounted, and rendered with collapsable={false}?)");
92
+ }
93
+ if (view.getWidth() <= 0 || view.getHeight() <= 0) {
94
+ throw new IllegalStateException(
95
+ "drawElementImageToTexture: the view has no size yet");
96
+ }
97
+ float density = view.getResources().getDisplayMetrics().density;
98
+ float sx = (float) (sourceX * density);
99
+ float sy = (float) (sourceY * density);
100
+ float sw = sourceWidth > 0 ? (float) (sourceWidth * density) : view.getWidth();
101
+ float sh = sourceHeight > 0 ? (float) (sourceHeight * density) : view.getHeight();
102
+ int w = width > 0 ? width : Math.round(sw);
103
+ int h = height > 0 ? height : Math.round(sh);
104
+ if (w <= 0 || h <= 0) {
105
+ throw new IllegalArgumentException(
106
+ "drawElementImageToTexture: the source rectangle is empty");
107
+ }
108
+ bitmap = ViewSnapshot.snapshot(view, sx, sy, sw, sh, w, h);
109
+ } catch (Throwable t) {
110
+ error = t.getMessage() != null ? t.getMessage() : t.toString();
111
+ }
112
+ onViewSnapshot(callback, bitmap, error);
113
+ });
114
+ }
115
+
116
+ @DoNotStrip
117
+ private native void onViewSnapshot(long callback, Bitmap bitmap, String error);
50
118
  }
@@ -1,15 +1,25 @@
1
1
  #pragma once
2
2
 
3
3
  #include "PlatformContext.h"
4
+ #include <functional>
4
5
  #include <string>
5
6
 
6
7
  namespace rnwgpu {
7
8
 
8
9
  class ApplePlatformContext : public PlatformContext {
9
10
  public:
10
- ApplePlatformContext();
11
+ // Resolves a React tag to its native view (an unretained
12
+ // UIView* / NSView* bridged to void*, or nullptr). Called on the main thread
13
+ // only. Provided by WebGPUModule from React Native's view registry.
14
+ using ViewLookup = std::function<void *(int)>;
15
+
16
+ explicit ApplePlatformContext(ViewLookup viewLookup = nullptr);
11
17
  ~ApplePlatformContext() = default;
12
18
 
19
+ void snapshotView(const ViewSnapshotRequest &request,
20
+ std::function<void(ImageData)> onSuccess,
21
+ std::function<void(std::string)> onError) override;
22
+
13
23
  wgpu::Surface makeSurface(wgpu::Instance instance, void *surface, int width,
14
24
  int height) override;
15
25
 
@@ -39,6 +49,9 @@ public:
39
49
  std::string writeTestVideoFile() override;
40
50
 
41
51
  VideoFrameHandle wrapNativeBuffer(void *pointer) override;
52
+
53
+ private:
54
+ ViewLookup _viewLookup;
42
55
  };
43
56
 
44
57
  } // namespace rnwgpu