react-native-webgpu 0.10.5 → 0.11.1

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 (61) hide show
  1. package/android/cpp/AndroidPlatformContext.h +100 -9
  2. package/android/cpp/AndroidVideoPlayer.h +307 -0
  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 +78 -0
  6. package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +420 -0
  7. package/apple/ApplePlatformContext.h +14 -1
  8. package/apple/ApplePlatformContext.mm +176 -1
  9. package/apple/AppleVideoPlayer.mm +157 -47
  10. package/apple/WebGPUModule.mm +20 -1
  11. package/cpp/rnwgpu/PlatformContext.h +76 -1
  12. package/cpp/rnwgpu/RNWebGPUManager.cpp +7 -0
  13. package/cpp/rnwgpu/api/GPUDevice.cpp +2 -1
  14. package/cpp/rnwgpu/api/GPUDevice.h +6 -0
  15. package/cpp/rnwgpu/api/GPUQueue.cpp +261 -4
  16. package/cpp/rnwgpu/api/GPUQueue.h +29 -2
  17. package/cpp/rnwgpu/api/VideoFrameBlit.h +355 -0
  18. package/cpp/rnwgpu/api/VideoPlayer.h +50 -3
  19. package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageDestination.h +60 -0
  20. package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageSource.h +106 -0
  21. package/cpp/rnwgpu/api/descriptors/GPUImageCopyExternalImage.h +30 -3
  22. package/lib/commonjs/importDevice.js +2 -2
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/commonjs/main/index.js +45 -0
  25. package/lib/commonjs/main/index.js.map +1 -1
  26. package/lib/commonjs/main/index.web.js +11 -0
  27. package/lib/commonjs/main/index.web.js.map +1 -1
  28. package/lib/commonjs/video.js +58 -0
  29. package/lib/commonjs/video.js.map +1 -0
  30. package/lib/module/importDevice.js +2 -2
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/main/index.js +34 -0
  33. package/lib/module/main/index.js.map +1 -1
  34. package/lib/module/main/index.web.js +1 -0
  35. package/lib/module/main/index.web.js.map +1 -1
  36. package/lib/module/video.js +49 -0
  37. package/lib/module/video.js.map +1 -0
  38. package/lib/typescript/lib/commonjs/video.d.ts +30 -0
  39. package/lib/typescript/lib/commonjs/video.d.ts.map +1 -0
  40. package/lib/typescript/lib/module/main/index.d.ts +1 -0
  41. package/lib/typescript/lib/module/main/index.web.d.ts +1 -0
  42. package/lib/typescript/lib/module/video.d.ts +4 -0
  43. package/lib/typescript/lib/module/video.d.ts.map +1 -0
  44. package/lib/typescript/src/importDevice.d.ts +2 -2
  45. package/lib/typescript/src/index.d.ts +8 -3
  46. package/lib/typescript/src/index.d.ts.map +1 -1
  47. package/lib/typescript/src/main/index.d.ts +1 -0
  48. package/lib/typescript/src/main/index.d.ts.map +1 -1
  49. package/lib/typescript/src/main/index.web.d.ts +1 -0
  50. package/lib/typescript/src/main/index.web.d.ts.map +1 -1
  51. package/lib/typescript/src/types.d.ts +32 -0
  52. package/lib/typescript/src/types.d.ts.map +1 -1
  53. package/lib/typescript/src/video.d.ts +41 -0
  54. package/lib/typescript/src/video.d.ts.map +1 -0
  55. package/package.json +1 -1
  56. package/src/importDevice.ts +2 -2
  57. package/src/index.tsx +57 -3
  58. package/src/main/index.tsx +50 -0
  59. package/src/main/index.web.tsx +1 -0
  60. package/src/types.ts +89 -7
  61. package/src/video.ts +69 -0
@@ -26,10 +26,10 @@ export const importDevice = (pointer: bigint): GPUDevice => {
26
26
  * ownership of one reference: the returned texture releases it when destroyed.
27
27
  *
28
28
  * Pair with producers that hand out a +1 reference, such as
29
- * `Skia.Image.MakeNativeTextureFromImage()`:
29
+ * `Skia.Image.MakeGPUTextureFromImage()`:
30
30
  *
31
31
  * ```ts
32
- * const texture = adoptTexture(Skia.Image.MakeNativeTextureFromImage(image));
32
+ * const texture = adoptTexture(Skia.Image.MakeGPUTextureFromImage(image));
33
33
  * ```
34
34
  */
35
35
  export const adoptTexture = (pointer: bigint): GPUTexture => {
package/src/index.tsx CHANGED
@@ -1,10 +1,14 @@
1
1
  /// <reference types="@webgpu/types" />
2
2
  import type {
3
+ GPUDrawElementImageDestination,
4
+ GPUDrawElementImageSource,
3
5
  GPUSharedFence,
4
6
  GPUSharedFenceDescriptor,
5
7
  GPUDawnTogglesDescriptor,
8
+ GPUNativeExternalTextureDescriptor,
6
9
  GPUSharedTextureMemory,
7
10
  GPUSharedTextureMemoryDescriptor,
11
+ GPUCopyExternalNativeFrameSourceInfo,
8
12
  NativeCanvas,
9
13
  RNCanvasContext,
10
14
  VideoPlayer,
@@ -28,7 +32,12 @@ export type {
28
32
  GPUSharedTextureMemoryEndAccessState,
29
33
  NativeVideoPixelFormat,
30
34
  CreateVideoPlayerOptions,
35
+ GPUCopyExternalNativeFrameSourceInfo,
31
36
  GPUDawnTogglesDescriptor,
37
+ GPUNativeExternalTextureDescriptor,
38
+ GPUDrawElementImageDestination,
39
+ GPUDrawElementImageSource,
40
+ GPUDrawElementImageSourceView,
32
41
  } from "./types";
33
42
 
34
43
  declare global {
@@ -64,10 +73,11 @@ declare global {
64
73
  importDevice: (pointer: bigint) => GPUDevice;
65
74
  // Wrap an externally created WGPUTexture pointer into a GPUTexture,
66
75
  // taking ownership of one reference (pair with producers that return a
67
- // +1 pointer, e.g. Skia.Image.MakeNativeTextureFromImage()).
76
+ // +1 pointer, e.g. Skia.Image.MakeGPUTextureFromImage()).
68
77
  adoptTexture: (pointer: bigint) => GPUTexture;
78
+ // Prefer the createVideoPlayer() export, which takes an options object.
69
79
  createVideoPlayer: (
70
- path: string,
80
+ source: string,
71
81
  pixelFormat?: NativeVideoPixelFormat,
72
82
  ) => VideoPlayer;
73
83
  writeTestVideoFile: () => string;
@@ -75,18 +85,62 @@ declare global {
75
85
 
76
86
  interface GPUTexture {
77
87
  // Non-spec RN extension: raw WGPUTexture handle as a BigInt for
78
- // pointer-based interop (e.g. Skia.Image.MakeImageFromNativeTexture).
88
+ // pointer-based interop (Skia.Image.MakeImageFromGPUTexture reads it).
79
89
  // Borrowed: keep this GPUTexture alive while the pointer is in use.
80
90
  readonly nativePointer: bigint;
81
91
  }
82
92
 
83
93
  interface GPUDevice {
94
+ // Non-spec RN extension: the source of an external texture is a
95
+ // NativeVideoFrame (from a VideoPlayer, createVideoFrameFromNativeBuffer
96
+ // or createTestVideoFrame) instead of the web's HTMLVideoElement /
97
+ // VideoFrame. Overloads the spec signature, which stays available for
98
+ // the web implementation.
99
+ importExternalTexture(
100
+ descriptor: GPUNativeExternalTextureDescriptor,
101
+ ): GPUExternalTexture;
84
102
  importSharedTextureMemory(
85
103
  descriptor: GPUSharedTextureMemoryDescriptor,
86
104
  ): GPUSharedTextureMemory;
87
105
  importSharedFence(descriptor: GPUSharedFenceDescriptor): GPUSharedFence;
88
106
  }
89
107
 
108
+ // Non-spec RN extension modeled on the html-in-canvas proposal's
109
+ // GPUQueue.drawElementImageToTexture: rasterizes a native React Native view
110
+ // (the "element") and writes the pixels into `destination.texture`, which
111
+ // must be rgba8unorm or bgra8unorm (or their -srgb variants) with
112
+ // GPUTextureUsage.COPY_DST. Unlike the web API it returns a Promise: the
113
+ // view is rasterized on the platform UI thread, and the texture write is
114
+ // issued right before the promise resolves, so submit work that samples the
115
+ // texture after awaiting it. Snapshot the view with `collapsable={false}`
116
+ // so React Native does not optimize the host view away.
117
+ //
118
+ // What is captured is what the platform renders for the view hierarchy
119
+ // (iOS: UIView.drawViewHierarchyInRect; Android API 29+: HardwareRenderer
120
+ // over the views' display lists, older: View.draw into a software canvas).
121
+ // On Android, SurfaceView content (a WebGPU Canvas with the default
122
+ // "SurfaceView" surfaceType, video players) is not part of the hierarchy and
123
+ // comes out blank; use `android={{ surfaceType: "TextureView" }}` for a
124
+ // canvas that must be captured.
125
+ interface GPUQueue {
126
+ drawElementImageToTexture(
127
+ source: GPUDrawElementImageSource,
128
+ destination: GPUDrawElementImageDestination,
129
+ ): Promise<void>;
130
+ // Non-spec extension: a NativeVideoFrame (from createVideoPlayer,
131
+ // createVideoFrameFromNativeBuffer or createTestVideoFrame) as the copy
132
+ // source. The frame is rendered into the destination on the GPU through
133
+ // an external texture, which converts YUV to RGB and applies `rotation`
134
+ // and `mirrored`; the destination needs GPUTextureUsage.RENDER_ATTACHMENT
135
+ // and an rgba8unorm, bgra8unorm (or -srgb), rgba16float or rgb10a2unorm
136
+ // format. The frame can be released as soon as the call returns.
137
+ copyExternalImageToTexture(
138
+ source: GPUCopyExternalNativeFrameSourceInfo,
139
+ destination: GPUCopyExternalImageDestInfo,
140
+ copySize: GPUExtent3DStrict,
141
+ ): undefined;
142
+ }
143
+
90
144
  // Non-standard, Dawn-only. Lets callers set Dawn device-stage toggles at
91
145
  // device creation: adapter.requestDevice({ dawnToggles: { ... } }).
92
146
  interface GPUDeviceDescriptor {
@@ -1,6 +1,54 @@
1
+ import { findNodeHandle } from "react-native";
2
+
1
3
  import { registerWebGPUForReanimated } from "../external";
2
4
  import { provideGPUForInstall } from "../install";
3
5
  import WebGPUModule from "../NativeWebGPUModule";
6
+ import type { GPUDrawElementImageSourceView } from "../types";
7
+
8
+ // Resolve the `source` of drawElementImageToTexture to a React tag on the
9
+ // main JS runtime, where findNodeHandle is available: it handles class
10
+ // components and legacy host instances that the native fallback (a number or
11
+ // an instance carrying `__nativeTag`) does not.
12
+ const resolveElementSource = (source: GPUDrawElementImageSourceView) => {
13
+ if (typeof source === "number") {
14
+ return source;
15
+ }
16
+ const instance =
17
+ source !== null && typeof source === "object" && "current" in source
18
+ ? source.current
19
+ : source;
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ const tag = instance == null ? null : findNodeHandle(instance as any);
22
+ if (tag === null || tag === undefined) {
23
+ throw new Error(
24
+ "[WebGPU] drawElementImageToTexture: `source` does not resolve to a " +
25
+ "mounted native view (pass a host component ref rendered with " +
26
+ "collapsable={false})",
27
+ );
28
+ }
29
+ return tag;
30
+ };
31
+
32
+ const installDrawElementImageToTexture = () => {
33
+ if (typeof GPUQueue === "undefined") {
34
+ return;
35
+ }
36
+ const native = GPUQueue.prototype.drawElementImageToTexture;
37
+ if (typeof native !== "function") {
38
+ return;
39
+ }
40
+ GPUQueue.prototype.drawElementImageToTexture = function (
41
+ this: GPUQueue,
42
+ source,
43
+ destination,
44
+ ) {
45
+ return native.call(
46
+ this,
47
+ { ...source, source: resolveElementSource(source.source) },
48
+ destination,
49
+ );
50
+ };
51
+ };
4
52
 
5
53
  export * from "../Canvas";
6
54
  export * from "../Offscreen";
@@ -8,6 +56,7 @@ export * from "../WebGPUViewNativeComponent";
8
56
  export * from "../hooks";
9
57
  export * from "../GPUDeviceProvider";
10
58
  export * from "../importDevice";
59
+ export * from "../video";
11
60
  export * from "../formats";
12
61
 
13
62
  export { default as WebGPUModule } from "../NativeWebGPUModule";
@@ -37,6 +86,7 @@ if (typeof RNWebGPU !== "undefined") {
37
86
  }
38
87
  global.createImageBitmap =
39
88
  global.createImageBitmap ?? RNWebGPU.createImageBitmap.bind(RNWebGPU);
89
+ installDrawElementImageToTexture();
40
90
  // Let installWebGPU() put navigator.gpu on other runtimes (see install.ts).
41
91
  provideGPUForInstall(RNWebGPU.gpu);
42
92
  } else {
@@ -6,5 +6,6 @@ export * from "../WebGPUViewNativeComponent";
6
6
  export * from "../hooks";
7
7
  export * from "../GPUDeviceProvider";
8
8
  export * from "../formats";
9
+ export * from "../video";
9
10
 
10
11
  // We don't need to set all global properties on web, webgpu is already available globally
package/src/types.ts CHANGED
@@ -63,24 +63,106 @@ export interface NativeVideoFrame {
63
63
  release(): void;
64
64
  }
65
65
 
66
- // A handle to a decoded video stream. Poll copyLatestFrame() each render tick
67
- // to obtain the most recently decoded frame as an IOSurface/AHardwareBuffer
68
- // (returns null between frames so callers can skip the import work).
66
+ // GPUExternalTextureDescriptor with a NativeVideoFrame as the source: what
67
+ // GPUDevice.importExternalTexture() takes on React Native, where there is no
68
+ // HTMLVideoElement or WebCodecs VideoFrame. The other members (label,
69
+ // colorSpace, and the rotation / mirrored extensions) are the same.
70
+ export interface GPUNativeExternalTextureDescriptor extends Omit<
71
+ GPUExternalTextureDescriptor,
72
+ "source"
73
+ > {
74
+ source: NativeVideoFrame;
75
+ }
76
+
77
+ // A decoded video, modeled on HTMLMediaElement: play(), pause(), paused,
78
+ // currentTime (in seconds, assign it to seek), duration, volume and loop work
79
+ // as on the web. Frames are pulled instead of displayed: poll
80
+ // copyLatestFrame() on every render tick to obtain the most recently decoded
81
+ // frame as an IOSurface/AHardwareBuffer-backed NativeVideoFrame (null between
82
+ // frames, so callers can skip the import work).
69
83
  export interface VideoPlayer {
70
84
  copyLatestFrame(): NativeVideoFrame | null;
71
85
  play(): void;
72
86
  pause(): void;
87
+ // Stops playback and releases the decoder. Frames already copied stay
88
+ // valid until they are released.
73
89
  release(): void;
90
+ readonly paused: boolean;
91
+ // The playback position in seconds. Assign it to seek, also while paused:
92
+ // the next copyLatestFrame() then returns the frame at the new position.
93
+ currentTime: number;
94
+ // The length in seconds, 0 until the metadata has loaded.
95
+ readonly duration: number;
96
+ // 0 to 1.
97
+ volume: number;
98
+ // Restart from the beginning at the end of the stream. Unlike on the web,
99
+ // it defaults to true.
100
+ loop: boolean;
101
+ // The coded size of the frames in pixels, 0 until the metadata has loaded.
102
+ readonly videoWidth: number;
103
+ readonly videoHeight: number;
104
+ // The clockwise rotation to apply when displaying the frames: a video
105
+ // recorded in portrait stores landscape frames with a rotation. Pass it as
106
+ // the `rotation` of importExternalTexture().
107
+ readonly rotation: 0 | 90 | 180 | 270;
108
+ // The nominal frame rate of the video track, 0 if unknown.
109
+ readonly frameRate: number;
110
+ }
111
+
112
+ // The source info of GPUQueue.copyExternalImageToTexture() for a native
113
+ // frame (a React Native extension of GPUCopyExternalImageSourceInfo). The
114
+ // frame is rendered into the destination through an external texture, which
115
+ // converts YUV to RGB and applies `rotation` and `mirrored` (the extension of
116
+ // importExternalTexture), so the destination needs
117
+ // GPUTextureUsage.RENDER_ATTACHMENT. `origin` and the copy size are in the
118
+ // upright frame, after rotation.
119
+ export interface GPUCopyExternalNativeFrameSourceInfo {
120
+ source: NativeVideoFrame;
121
+ origin?: GPUOrigin2D;
122
+ flipY?: boolean;
123
+ rotation?: 0 | 90 | 180 | 270;
124
+ mirrored?: boolean;
74
125
  }
75
126
 
76
127
  export interface CreateVideoPlayerOptions {
77
- // 'bgra8' (default): emit a single-plane BGRA surface, suitable for
78
- // SharedTextureMemory and a regular sampled GPUTexture.
79
- // 'nv12': emit biplanar Y + CbCr surfaces, suitable for
80
- // GPUDevice.importExternalTexture.
128
+ // The layout of the frames on Apple platforms:
129
+ // 'bgra8' (default): single-plane BGRA surfaces, for importSharedTextureMemory
130
+ // and a regular sampled GPUTexture.
131
+ // 'nv12': biplanar Y + CbCr surfaces, for GPUDevice.importExternalTexture.
132
+ // On Android the decoder always writes its native YUV layout (reported as
133
+ // 'nv12'), which only importExternalTexture samples; the option is ignored.
81
134
  pixelFormat?: NativeVideoPixelFormat;
82
135
  }
83
136
 
137
+ // A React Native view to draw with GPUQueue.drawElementImageToTexture: a host
138
+ // component ref (`useRef` object), the host instance itself (`ref.current`),
139
+ // or the numeric tag returned by findNodeHandle. On worklet runtimes (no
140
+ // findNodeHandle) pass the instance or the tag.
141
+ export type GPUDrawElementImageSourceView =
142
+ number | { current: unknown } | React.Component | object;
143
+
144
+ // The source argument of drawElementImageToTexture, modeled on the
145
+ // html-in-canvas proposal's GPUDrawElementImageSource. The optional rectangle
146
+ // selects the part of the view to draw, in the view's own coordinate space
147
+ // and in density-independent points (React Native layout units); it defaults
148
+ // to the whole view.
149
+ export interface GPUDrawElementImageSource {
150
+ source: GPUDrawElementImageSourceView;
151
+ sourceX?: number;
152
+ sourceY?: number;
153
+ sourceWidth?: number;
154
+ sourceHeight?: number;
155
+ }
156
+
157
+ // The destination argument of drawElementImageToTexture:
158
+ // GPUCopyExternalImageDestInfo plus the pixel size of the region the view is
159
+ // drawn into. `size` defaults to the source rectangle's natural pixel size
160
+ // (points times the device pixel ratio); pass it explicitly to scale the view
161
+ // to the destination region.
162
+ export interface GPUDrawElementImageDestination extends GPUCopyExternalImageDestInfo {
163
+ size?: GPUExtent3DStrict;
164
+ }
165
+
84
166
  export interface GPUSharedTextureMemoryDescriptor {
85
167
  // Raw native handle (IOSurfaceRef on Apple, AHardwareBuffer* on Android),
86
168
  // encoded as a BigInt. The caller is responsible for keeping the underlying
package/src/video.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type {
2
+ CreateVideoPlayerOptions,
3
+ NativeVideoFrame,
4
+ VideoPlayer,
5
+ } from "./types";
6
+
7
+ const native = (name: string) => {
8
+ if (typeof RNWebGPU === "undefined") {
9
+ throw new Error(
10
+ `react-native-webgpu is not installed natively; ${name} is unavailable`,
11
+ );
12
+ }
13
+ return RNWebGPU;
14
+ };
15
+
16
+ /**
17
+ * Decodes a video into native frames the GPU samples without any copy.
18
+ *
19
+ * `source` is a URL (`http`, `https`, `file`) or a file path. Playback is
20
+ * controlled like an HTMLMediaElement (`play()`, `pause()`, `currentTime`,
21
+ * `loop`, `volume`); frames are pulled with `copyLatestFrame()` on every
22
+ * render tick and sampled through `importExternalTexture()` or
23
+ * `importSharedTextureMemory()`:
24
+ *
25
+ * ```ts
26
+ * const player = createVideoPlayer(url);
27
+ * player.play();
28
+ * const render = () => {
29
+ * const frame = player.copyLatestFrame();
30
+ * if (frame) {
31
+ * // import, draw, then release once the frame is no longer sampled
32
+ * }
33
+ * requestAnimationFrame(render);
34
+ * };
35
+ * ```
36
+ *
37
+ * On Apple platforms the frames are BGRA by default, or NV12 with
38
+ * `{ pixelFormat: "nv12" }`. On Android they are always in the decoder's
39
+ * native YUV layout, for `importExternalTexture()`.
40
+ */
41
+ export const createVideoPlayer = (
42
+ source: string,
43
+ options?: CreateVideoPlayerOptions,
44
+ ): VideoPlayer =>
45
+ native("createVideoPlayer").createVideoPlayer(source, options?.pixelFormat);
46
+
47
+ /**
48
+ * Wraps a native buffer produced by another library (a `CVPixelBufferRef` on
49
+ * Apple platforms, an `AHardwareBuffer*` on Android, for instance
50
+ * VisionCamera's `frame.getNativeBuffer().pointer`) into a `NativeVideoFrame`
51
+ * that keeps it alive until released.
52
+ */
53
+ export const createVideoFrameFromNativeBuffer = (
54
+ pointer: bigint,
55
+ ): NativeVideoFrame =>
56
+ native("createVideoFrameFromNativeBuffer").createVideoFrameFromNativeBuffer(
57
+ pointer,
58
+ );
59
+
60
+ /**
61
+ * Allocates a native frame of the given size filled with a test pattern (a
62
+ * red/green gradient with diagonal stripes in the blue channel), for examples
63
+ * and tests that need a frame without a camera or a video.
64
+ */
65
+ export const createTestVideoFrame = (
66
+ width: number,
67
+ height: number,
68
+ ): NativeVideoFrame =>
69
+ native("createTestVideoFrame").createTestVideoFrame(width, height);