react-native-skia-webgpu-video 0.1.0-beta.3 → 0.1.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 (81) hide show
  1. package/README.md +48 -43
  2. package/android/BUILD_VALIDATION.md +1 -1
  3. package/android/CMakeLists.txt +3 -2
  4. package/android/cpp/ExportHintSession.cpp +114 -0
  5. package/android/cpp/ExportHintSession.h +35 -0
  6. package/android/cpp/VideoEncoderHostObject.cpp +76 -101
  7. package/android/cpp/VideoEncoderHostObject.h +14 -9
  8. package/android/cpp/VideoFrame.cpp +3 -5
  9. package/android/cpp/cpp-adapter.cpp +7 -4
  10. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +3 -9
  11. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +8 -11
  12. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +68 -28
  13. package/docs/FRAME_PIXEL_FORMAT.md +17 -2
  14. package/docs/MIGRATION.md +1 -1
  15. package/docs/UPSTREAM_NOTES.md +42 -2
  16. package/ios/RNSVColorSpace.h +8 -3
  17. package/lib/module/canvas.js +3 -5
  18. package/lib/module/canvas.js.map +1 -1
  19. package/lib/module/exportFrames.js +8 -10
  20. package/lib/module/exportFrames.js.map +1 -1
  21. package/lib/module/exportTarget.js +183 -40
  22. package/lib/module/exportTarget.js.map +1 -1
  23. package/lib/module/exportVideoComposition.js +78 -35
  24. package/lib/module/exportVideoComposition.js.map +1 -1
  25. package/lib/module/frameInterop.js +323 -82
  26. package/lib/module/frameInterop.js.map +1 -1
  27. package/lib/module/gpu.js +1 -3
  28. package/lib/module/gpu.js.map +1 -1
  29. package/lib/module/gpuDevice.js +12 -0
  30. package/lib/module/gpuDevice.js.map +1 -1
  31. package/lib/module/memory.js +7 -0
  32. package/lib/module/memory.js.map +1 -1
  33. package/lib/module/previewLifecycle.js +2 -1
  34. package/lib/module/previewLifecycle.js.map +1 -1
  35. package/lib/module/videoCompositionPlayer.js +65 -14
  36. package/lib/module/videoCompositionPlayer.js.map +1 -1
  37. package/lib/module/videoFrameImage.js +1 -1
  38. package/lib/module/yuvBlit.js +31 -24
  39. package/lib/module/yuvBlit.js.map +1 -1
  40. package/lib/typescript/src/canvas.d.ts.map +1 -1
  41. package/lib/typescript/src/exportFrames.d.ts +3 -2
  42. package/lib/typescript/src/exportFrames.d.ts.map +1 -1
  43. package/lib/typescript/src/exportTarget.d.ts +53 -14
  44. package/lib/typescript/src/exportTarget.d.ts.map +1 -1
  45. package/lib/typescript/src/exportVideoComposition.d.ts +2 -1
  46. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -1
  47. package/lib/typescript/src/frameInterop.d.ts +16 -5
  48. package/lib/typescript/src/frameInterop.d.ts.map +1 -1
  49. package/lib/typescript/src/gpu.d.ts.map +1 -1
  50. package/lib/typescript/src/gpuDevice.d.ts +5 -1
  51. package/lib/typescript/src/gpuDevice.d.ts.map +1 -1
  52. package/lib/typescript/src/memory.d.ts +2 -0
  53. package/lib/typescript/src/memory.d.ts.map +1 -1
  54. package/lib/typescript/src/previewLifecycle.d.ts +2 -0
  55. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -1
  56. package/lib/typescript/src/types.d.ts +18 -12
  57. package/lib/typescript/src/types.d.ts.map +1 -1
  58. package/lib/typescript/src/videoCompositionPlayer.d.ts +4 -5
  59. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -1
  60. package/lib/typescript/src/videoFrameImage.d.ts +1 -1
  61. package/lib/typescript/src/yuvBlit.d.ts +16 -14
  62. package/lib/typescript/src/yuvBlit.d.ts.map +1 -1
  63. package/package.json +6 -6
  64. package/patches/react-native-skia+3.10.0.patch +150 -0
  65. package/scripts/skia-canvas-patch.json +22 -258
  66. package/src/canvas.ts +3 -7
  67. package/src/exportFrames.ts +10 -11
  68. package/src/exportTarget.ts +217 -56
  69. package/src/exportVideoComposition.ts +130 -85
  70. package/src/frameInterop.ts +389 -96
  71. package/src/gpu.ts +1 -3
  72. package/src/gpuDevice.ts +26 -1
  73. package/src/memory.ts +6 -0
  74. package/src/previewLifecycle.ts +4 -1
  75. package/src/types.ts +18 -12
  76. package/src/videoCompositionPlayer.ts +76 -23
  77. package/src/videoFrameImage.ts +1 -1
  78. package/src/yuvBlit.ts +46 -31
  79. package/android/cpp/EncoderTargets.cpp +0 -226
  80. package/android/cpp/EncoderTargets.h +0 -70
  81. package/patches/react-native-skia+3.7.1.patch +0 -502
@@ -1,16 +1,23 @@
1
1
  "use strict";
2
2
 
3
3
  /// <reference types="@webgpu/types" />
4
- import { Platform } from "react-native";
5
4
  import { BlendMode, Skia } from "react-native-skia";
6
5
  import { getVideoCanvas } from "./canvas.js";
7
- import { createNativeVideoGpuFrame, getVideoGpuDevice } from "./gpuDevice.js";
6
+ import { createNativeVideoGpuFrame, createNativeWindowGpuContext, encoderWindowTextureUsage, getVideoGpuDevice, nativeVideoTextureUsage } from "./gpuDevice.js";
7
+ import { frameByteSize, releaseVideoMemory, reserveVideoMemory } from "./memory.js";
8
8
 
9
- /** An encoder pool buffer that Skia draws into through WebGPU. */
9
+ /** A texture Skia draws an exported frame into through WebGPU: a lent
10
+ * encoder pool buffer on iOS, one of the encoder window's own textures on
11
+ * Android. */
10
12
 
11
- // Captured on the RN runtime. iOS lends CVPixelBuffers, imported through
12
- // their IOSurface; Android lends the AHardwareBuffer itself.
13
- const lendsPixelBuffers = Platform.OS === "ios";
13
+ /** Android: a canvas context presenting to the codec's input Surface. Skia
14
+ * draws each frame into one of two textures of our own, which a GPU copy then
15
+ * puts into the window's texture. The window's textures are the codec's
16
+ * input buffers: the copy waits on the GPU for the codec to hand one back,
17
+ * while the export goes on with the next frame, rather than the frame's
18
+ * checked wait. */
19
+
20
+ const windowSlots = 2;
14
21
  export const supportsEncoderTargets = encoder => {
15
22
  "worklet";
16
23
 
@@ -18,35 +25,20 @@ export const supportsEncoderTargets = encoder => {
18
25
  };
19
26
 
20
27
  // Declared before its callers: worklets capture their closure on creation.
21
- /** Ends GPU access; the lent buffer stays with the caller, to encode or
22
- * release. Call only after a checked flush(true) of its surface. With
23
- * `encoding`, Android keeps the access's sync-fds in `target.fences` for the
24
- * encoder, which owns and closes them: ending an AHB access submits a
25
- * release that flush(true) has not waited for. */
26
- export const closeEncoderTarget = (target, encoding = false) => {
28
+ /** iOS: ends GPU access; the lent buffer stays with the caller, to encode or
29
+ * release. Call only after a checked flush(true) of its surface. */
30
+ export const closeEncoderTarget = target => {
27
31
  "worklet";
28
32
 
29
- target.canvas?.dispose();
33
+ // Disposing the surface detaches its canvas.
30
34
  target.canvas = null;
31
35
  target.surface?.dispose();
32
36
  target.surface = null;
33
37
  if (target.accessing && target.memory && target.texture) {
34
38
  const ended = target.memory.endAccess(target.texture);
35
39
  target.accessing = false;
36
- for (const state of ended?.fences ?? []) {
37
- try {
38
- if (encoding && !lendsPixelBuffers) {
39
- const exported = state.fence.export();
40
- // Unreachable when negotiated: AHB interop requires the device's
41
- // only shared-fence feature to be sync-fd (see gpuDevice.ts).
42
- if (exported.type !== "sync-fd") throw new Error("GPU export needs sync-fd fences");
43
- target.fences.push(exported.handle);
44
- }
45
- } finally {
46
- // iOS: already waited for. The wrapper is released either way.
47
- state.fence.destroy();
48
- }
49
- }
40
+ // Already waited for. The wrappers are released either way.
41
+ for (const state of ended?.fences ?? []) state.fence.destroy();
50
42
  }
51
43
  target.accessing = false;
52
44
  target.texture?.destroy();
@@ -60,13 +52,12 @@ export const closeEncoderTarget = (target, encoding = false) => {
60
52
  target.frame = null;
61
53
  };
62
54
 
63
- /** Lends the next pool buffer and wraps it as a Skia surface. */
55
+ /** iOS: lends the next pool buffer and wraps it as a Skia surface. */
64
56
  export const acquireEncoderTarget = (encoder, width, height) => {
65
57
  "worklet";
66
58
 
67
59
  const target = {
68
60
  pointer: encoder.acquireFrameBuffer(),
69
- fences: [],
70
61
  frame: null,
71
62
  memory: null,
72
63
  texture: null,
@@ -75,24 +66,19 @@ export const acquireEncoderTarget = (encoder, width, height) => {
75
66
  canvas: null
76
67
  };
77
68
  try {
78
- if (lendsPixelBuffers) {
79
- target.frame = createNativeVideoGpuFrame(target.pointer);
80
- if (target.frame.width !== width || target.frame.height !== height || target.frame.pixelFormat !== "bgra8") {
81
- throw new Error("Encoder buffer dimensions or BGRA layout mismatch");
82
- }
69
+ target.frame = createNativeVideoGpuFrame(target.pointer);
70
+ if (target.frame.width !== width || target.frame.height !== height || target.frame.pixelFormat !== "bgra8") {
71
+ throw new Error("Encoder buffer dimensions or BGRA layout mismatch");
83
72
  }
84
73
  const device = getVideoGpuDevice();
85
74
  if (typeof device.importSharedTextureMemory !== "function") {
86
75
  throw new Error("WebGPU shared texture memory is unavailable");
87
76
  }
88
77
  target.memory = device.importSharedTextureMemory({
89
- handle: target.frame ? target.frame.handle : target.pointer,
78
+ handle: target.frame.handle,
90
79
  label: "video export frame"
91
80
  });
92
81
  target.texture = target.memory.createTexture();
93
- if (!lendsPixelBuffers && (target.texture.width !== width || target.texture.height !== height || target.texture.format !== "rgba8unorm")) {
94
- throw new Error("Encoder buffer dimensions or RGBA layout mismatch");
95
- }
96
82
  // Uninitialized: every frame starts with a full clear.
97
83
  target.memory.beginAccess(target.texture, false);
98
84
  target.accessing = true;
@@ -111,8 +97,9 @@ export const acquireEncoderTarget = (encoder, width, height) => {
111
97
  }
112
98
  };
113
99
 
114
- /** Clears one lent buffer on the GPU, then returns it unencoded. Returns the
115
- * failure, or null when this device can export through encoder targets. */
100
+ /** iOS: clears one lent buffer on the GPU, then returns it unencoded.
101
+ * Returns the failure, or null when this device can export through encoder
102
+ * targets. */
116
103
  export const probeEncoderTarget = (encoder, width, height) => {
117
104
  "worklet";
118
105
 
@@ -134,4 +121,160 @@ export const probeEncoderTarget = (encoder, width, height) => {
134
121
  encoder.releaseFrameBuffer();
135
122
  return failure;
136
123
  };
124
+
125
+ /** Android: the window's next texture, checked against the export size. */
126
+ const windowTexture = (window, width, height) => {
127
+ "worklet";
128
+
129
+ const texture = window.pending ?? window.context.getCurrentTexture();
130
+ window.pending = null;
131
+ if (texture.width !== width || texture.height !== height || texture.format !== "rgba8unorm") {
132
+ throw new Error("Encoder window dimensions or RGBA layout mismatch");
133
+ }
134
+ return texture;
135
+ };
136
+
137
+ /** Android: the slot the next frame draws into. Its previous copy completed:
138
+ * the checked wait the export makes before each frame's import covers the
139
+ * queue before it. */
140
+ export const acquireWindowTarget = window => {
141
+ "worklet";
142
+
143
+ return window.slots[window.next];
144
+ };
145
+
146
+ /** Android: copies the slot Skia drew into to the window's next texture and
147
+ * hands it to the encoder, with its time. Call once the slot's surface was
148
+ * flushed: the copy follows the drawing on the same queue. */
149
+ export const presentWindowTarget = (encoder, window, target, time) => {
150
+ "worklet";
151
+
152
+ const source = target.texture;
153
+ const destination = windowTexture(window, source.width, source.height);
154
+ const commands = window.device.createCommandEncoder({
155
+ label: "video export window copy"
156
+ });
157
+ commands.copyTextureToTexture({
158
+ texture: source
159
+ }, {
160
+ texture: destination
161
+ }, {
162
+ width: source.width,
163
+ height: source.height
164
+ });
165
+ window.device.queue.submit([commands.finish()]);
166
+ window.next = (window.next + 1) % window.slots.length;
167
+ // The encoder sets the frame's time on the window, then presents.
168
+ encoder.encodeFrame({
169
+ kind: "window",
170
+ present: () => window.context.present()
171
+ }, time);
172
+ };
173
+
174
+ /** Android: the swapchain gives the codec its buffers back at once, rather
175
+ * than once the context is collected. Call before disposing the encoder. */
176
+ export const closeEncoderWindow = window => {
177
+ "worklet";
178
+
179
+ window.pending = null;
180
+ if (!window.configured) return;
181
+ window.context.unconfigure();
182
+ window.configured = false;
183
+ };
184
+
185
+ /** Android: frees the window's own textures. Call once their last recording
186
+ * was submitted: a copy still in flight keeps its source alive. */
187
+ export const releaseEncoderWindow = window => {
188
+ "worklet";
189
+
190
+ while (window.slots.length) {
191
+ const slot = window.slots[window.slots.length - 1];
192
+ // Disposing the surface detaches its canvas.
193
+ slot.canvas = null;
194
+ slot.surface?.dispose();
195
+ slot.surface = null;
196
+ slot.texture?.destroy();
197
+ slot.texture = null;
198
+ window.slots.pop();
199
+ }
200
+ while (window.reservations.length) {
201
+ releaseVideoMemory(window.reservations[window.reservations.length - 1]);
202
+ window.reservations.pop();
203
+ }
204
+ };
205
+
206
+ /** Android: opens a canvas context on the encoder's input window with its
207
+ * slots, acquires the window's first texture and draws a clear into the
208
+ * first slot: nothing reaches the encoder. Returns the window, or the
209
+ * failure, after which the window is closed and the export can fall back to
210
+ * readback. */
211
+ export const probeEncoderWindow = (encoder, width, height) => {
212
+ "worklet";
213
+
214
+ let window = null;
215
+ let failure = null;
216
+ try {
217
+ if (typeof encoder.acquireInputWindow !== "function") throw new Error("Encoder cannot present through its input window");
218
+ const device = getVideoGpuDevice();
219
+ window = {
220
+ context: createNativeWindowGpuContext(encoder.acquireInputWindow(), width, height),
221
+ device,
222
+ configured: false,
223
+ slots: [],
224
+ next: 0,
225
+ reservations: [],
226
+ pending: null
227
+ };
228
+ // Presented in FIFO order, opaque: every frame reaches the codec.
229
+ window.context.configure({
230
+ device,
231
+ format: "rgba8unorm",
232
+ usage: encoderWindowTextureUsage
233
+ });
234
+ window.configured = true;
235
+ for (let i = 0; i < windowSlots; i++) {
236
+ window.reservations.push(reserveVideoMemory(frameByteSize(width, height), "export window slot"));
237
+ const slot = {
238
+ pointer: null,
239
+ frame: null,
240
+ memory: null,
241
+ texture: device.createTexture({
242
+ label: "video export window slot",
243
+ size: {
244
+ width,
245
+ height
246
+ },
247
+ format: "rgba8unorm",
248
+ usage: nativeVideoTextureUsage
249
+ }),
250
+ accessing: false,
251
+ surface: null,
252
+ canvas: null
253
+ };
254
+ window.slots.push(slot);
255
+ slot.surface = Skia.Surface.MakeFromGPUTexture(slot.texture);
256
+ if (!slot.surface) throw new Error("Cannot wrap the export frame");
257
+ slot.canvas = getVideoCanvas(slot.surface);
258
+ }
259
+ window.pending = windowTexture(window, width, height);
260
+ const target = acquireWindowTarget(window);
261
+ target.canvas.drawColor(Skia.Color("#00000000"), BlendMode.Clear);
262
+ target.surface.flush(true);
263
+ } catch (error) {
264
+ failure = error ?? new Error("GPU export probe failed");
265
+ }
266
+ if (failure === null) return {
267
+ window,
268
+ failure
269
+ };
270
+ if (window) {
271
+ // Not caught: a window still drawn by WebGPU cannot fall back to EGL.
272
+ closeEncoderWindow(window);
273
+ releaseEncoderWindow(window);
274
+ }
275
+ return {
276
+ window: null,
277
+ failure
278
+ };
279
+ };
137
280
  //# sourceMappingURL=exportTarget.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["Platform","BlendMode","Skia","getVideoCanvas","createNativeVideoGpuFrame","getVideoGpuDevice","lendsPixelBuffers","OS","supportsEncoderTargets","encoder","acquireFrameBuffer","releaseFrameBuffer","closeEncoderTarget","target","encoding","canvas","dispose","surface","accessing","memory","texture","ended","endAccess","state","fences","exported","fence","export","type","Error","push","handle","destroy","frame","release","acquireEncoderTarget","width","height","pointer","pixelFormat","device","importSharedTextureMemory","label","createTexture","format","beginAccess","Surface","MakeFromGPUTexture","error","probeEncoderTarget","failure","drawColor","Color","Clear","flush"],"sourceRoot":"../../src","sources":["exportTarget.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["BlendMode","Skia","getVideoCanvas","createNativeVideoGpuFrame","createNativeWindowGpuContext","encoderWindowTextureUsage","getVideoGpuDevice","nativeVideoTextureUsage","frameByteSize","releaseVideoMemory","reserveVideoMemory","windowSlots","supportsEncoderTargets","encoder","acquireFrameBuffer","releaseFrameBuffer","closeEncoderTarget","target","canvas","surface","dispose","accessing","memory","texture","ended","endAccess","state","fences","fence","destroy","frame","release","acquireEncoderTarget","width","height","pointer","pixelFormat","Error","device","importSharedTextureMemory","handle","label","createTexture","beginAccess","Surface","MakeFromGPUTexture","error","probeEncoderTarget","failure","drawColor","Color","Clear","flush","windowTexture","window","pending","context","getCurrentTexture","format","acquireWindowTarget","slots","next","presentWindowTarget","time","source","destination","commands","createCommandEncoder","copyTextureToTexture","queue","submit","finish","length","encodeFrame","kind","present","closeEncoderWindow","configured","unconfigure","releaseEncoderWindow","slot","pop","reservations","probeEncoderWindow","acquireInputWindow","configure","usage","i","push","size"],"sourceRoot":"../../src","sources":["exportTarget.ts"],"mappings":"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","ignoreList":[]}
@@ -9,7 +9,7 @@ import RNSkiaVideoModule from "./RNSkiaVideoModule.js";
9
9
  import { clearVideoFrameImages, ownVideoFrames, releaseVideoFrameImage } from "./frameInterop.js";
10
10
  import { frameByteSize, releaseVideoMemory, reserveVideoMemory } from "./memory.js";
11
11
  import { enqueueExport, exportAbortError } from "./exportQueue.js";
12
- import { acquireEncoderTarget, closeEncoderTarget, probeEncoderTarget, supportsEncoderTargets } from "./exportTarget.js";
12
+ import { acquireEncoderTarget, acquireWindowTarget, closeEncoderTarget, closeEncoderWindow, presentWindowTarget, probeEncoderTarget, probeEncoderWindow, releaseEncoderWindow, supportsEncoderTargets } from "./exportTarget.js";
13
13
  import { yieldToRuntime } from "./runtimeYield.js";
14
14
  import { createExportFrameImports, ownExportFrames, releaseExportFrames } from "./exportFrames.js";
15
15
 
@@ -19,10 +19,6 @@ import { createExportFrameImports, ownExportFrames, releaseExportFrames } from "
19
19
  /** Cumulative wall-clock milliseconds per export stage, measured on the
20
20
  * export runtime. Stages are sequential, so they add up to `totalMs`. */
21
21
 
22
- // Native Skia 3.4.4 accepts destination/stride in readPixels; its
23
- // published Canvas type currently omits these arguments. Use only a private,
24
- // full-length, zero-offset Uint8Array (the native implementation ignores offsets).
25
-
26
22
  let exportRuntime = null;
27
23
  const getExportRuntime = () => {
28
24
  if (!exportRuntime) exportRuntime = createWorkletRuntime({
@@ -142,6 +138,17 @@ const executeExport = args => new Promise((resolve, reject) => {
142
138
  if (clock.last >= 0) profile[stage] += now - clock.last;
143
139
  clock.last = now;
144
140
  };
141
+ /** Android window: the checked GPU wait the last frame left for later,
142
+ * so that the GPU converted and drew it while the CPU presented it and
143
+ * decoded the next one. An empty recording: Graphite still waits for
144
+ * every command submitted so far. */
145
+ const checkWindowFrame = () => {
146
+ "worklet";
147
+
148
+ if (!state.unchecked) return;
149
+ state.window.slots[state.window.next].surface.flush(true);
150
+ state.unchecked = false;
151
+ };
145
152
  const finish = async (error, aborted = false, failed = false) => {
146
153
  "worklet";
147
154
 
@@ -167,6 +174,7 @@ const executeExport = args => new Promise((resolve, reject) => {
167
174
  };
168
175
  // Submit any recording left by a drawing exception before an
169
176
  // adapter is allowed to destroy textures referenced by that canvas.
177
+ clean(checkWindowFrame);
170
178
  clean(() => state.surface?.flush(true));
171
179
  clean(() => state.target?.surface?.flush(true));
172
180
  if (cleanup.safeToRelease && state.pendingAfterDraw) {
@@ -183,6 +191,10 @@ const executeExport = args => new Promise((resolve, reject) => {
183
191
  }
184
192
  }
185
193
  if (clean(() => state.extractor?.dispose())) state.extractor = null;
194
+ // Android: the window's swapchain lets go of the codec's buffers
195
+ // before the encoder goes. Its own textures are freed further down.
196
+ const window = state.window;
197
+ if (window) clean(() => closeEncoderWindow(window));
186
198
  if (clean(() => state.encoder?.dispose())) state.encoder = null;
187
199
  if (cleanup.safeToRelease) {
188
200
  clean(() => state.surface?.flush(true));
@@ -193,8 +205,16 @@ const executeExport = args => new Promise((resolve, reject) => {
193
205
  if (imports && clean(() => releaseExportFrames(imports))) state.imports = null;
194
206
  clean(() => clearVideoFrameImages(true));
195
207
  }
196
- if (cleanup.safeToRelease && clean(() => state.canvas?.dispose())) state.canvas = null;
197
- if (cleanup.safeToRelease && clean(() => state.surface?.dispose())) state.surface = null;
208
+ // Disposing the surface detaches its canvas.
209
+ if (cleanup.safeToRelease && clean(() => state.surface?.dispose())) {
210
+ state.surface = null;
211
+ state.canvas = null;
212
+ }
213
+ // The target, if any, is one of the window's slots.
214
+ if (cleanup.safeToRelease && window && clean(() => releaseEncoderWindow(window))) {
215
+ state.window = null;
216
+ state.target = null;
217
+ }
198
218
  const target = state.target;
199
219
  if (cleanup.safeToRelease && target) {
200
220
  if (clean(() => closeEncoderTarget(target))) state.target = null;
@@ -213,7 +233,6 @@ const executeExport = args => new Promise((resolve, reject) => {
213
233
  }
214
234
  if (cleanup.safeToRelease) {
215
235
  state.frames = {};
216
- state.readback = null;
217
236
  delete storage.__rnskwgpuExport;
218
237
  } else {
219
238
  // One retained session at most; subsequent exports fail before
@@ -239,6 +258,8 @@ const executeExport = args => new Promise((resolve, reject) => {
239
258
  const owned = runWithVideoAutoreleasePool(() => {
240
259
  const decoded = state.extractor.decodeCompositionFrames(time);
241
260
  lap("decodeMs");
261
+ checkWindowFrame();
262
+ lap("gpuMs");
242
263
  const imports = state.imports;
243
264
  // In place, a producer's frame is released by its next import.
244
265
  const imported = imports ? ownExportFrames(imports, decoded, ownVideoFrames) : ownVideoFrames(decoded);
@@ -263,10 +284,14 @@ const executeExport = args => new Promise((resolve, reject) => {
263
284
  await finish(null, true);
264
285
  return false;
265
286
  }
287
+ // CPU readback path only: the frame's pixels, still on their way.
288
+ const readback = {
289
+ pixels: null
290
+ };
266
291
  const draw = () => {
267
292
  "worklet";
268
293
 
269
- if (state.gpu) state.target = acquireEncoderTarget(state.encoder, options.width, options.height);
294
+ if (state.gpu) state.target = state.window ? acquireWindowTarget(state.window) : acquireEncoderTarget(state.encoder, options.width, options.height);
270
295
  lap("targetMs");
271
296
  const target = state.target;
272
297
  const surface = target ? target.surface : state.surface;
@@ -289,37 +314,32 @@ const executeExport = args => new Promise((resolve, reject) => {
289
314
  height: options.height
290
315
  });
291
316
  lap("drawMs");
292
- surface.flush(true);
317
+ // Android window, no context to release: the next frame waits
318
+ // for this one (see checkWindowFrame).
319
+ const unchecked = !!state.window && !!target && !afterDrawFrame && !state.processor;
320
+ surface.flush(!unchecked);
321
+ state.unchecked = unchecked;
293
322
  drawn.drained = true;
294
323
  lap("gpuMs");
295
- if (target) {
296
- closeEncoderTarget(target, true);
324
+ if (target && state.window) {
325
+ state.target = null;
326
+ presentWindowTarget(state.encoder, state.window, target, time);
327
+ } else if (target) {
328
+ closeEncoderTarget(target);
297
329
  state.target = null;
298
330
  state.encoder.encodeFrame({
299
331
  kind: "native-buffer",
300
- nativeBuffer: target.pointer,
301
- fences: target.fences
332
+ nativeBuffer: target.pointer
302
333
  }, time);
303
334
  } else {
304
- const readbackCanvas = canvas;
305
- const pixels = readbackCanvas.readPixels(0, 0, {
335
+ // Skia 3.9 reads back asynchronously, into a new array: the
336
+ // frame is encoded once it lands, after this scope.
337
+ readback.pixels = canvas.readPixels(0, 0, {
306
338
  width: options.width,
307
339
  height: options.height,
308
340
  colorType: bgra ? ColorType.BGRA_8888 : ColorType.RGBA_8888,
309
341
  alphaType: AlphaType.Premul
310
- }, state.readback, options.width * 4);
311
- if (!(pixels instanceof Uint8Array) || pixels.byteLength !== bytes || pixels.byteOffset !== 0 || pixels.buffer !== state.readback.buffer) {
312
- throw new Error("Video export pixel readback failed");
313
- }
314
- // Fallback: consumed synchronously by the bounded native pool.
315
- const storage = pixels.byteOffset === 0 && pixels.buffer.byteLength === pixels.byteLength ? pixels.buffer : pixels.buffer.slice(pixels.byteOffset, pixels.byteOffset + pixels.byteLength);
316
- state.encoder.encodeFrame({
317
- kind: bgra ? "bgra" : "rgba",
318
- data: storage,
319
- width: options.width,
320
- height: options.height,
321
- bytesPerRow: options.width * 4
322
- }, time);
342
+ });
323
343
  }
324
344
  lap("encodeMs");
325
345
  } catch (error) {
@@ -357,6 +377,22 @@ const executeExport = args => new Promise((resolve, reject) => {
357
377
  if (drawn.failed) throw drawn.failure;
358
378
  };
359
379
  runWithVideoAutoreleasePool(draw);
380
+ if (readback.pixels) {
381
+ const pixels = await readback.pixels;
382
+ lap("gpuMs");
383
+ // isView, not instanceof: true of a typed array of any realm.
384
+ if (!ArrayBuffer.isView(pixels) || pixels.byteLength !== bytes) throw new Error("Video export pixel readback failed");
385
+ // Fallback: consumed synchronously by the bounded native pool.
386
+ const storage = pixels.byteOffset === 0 && pixels.buffer.byteLength === pixels.byteLength ? pixels.buffer : pixels.buffer.slice(pixels.byteOffset, pixels.byteOffset + pixels.byteLength);
387
+ runWithVideoAutoreleasePool(() => state.encoder.encodeFrame({
388
+ kind: bgra ? "bgra" : "rgba",
389
+ data: storage,
390
+ width: options.width,
391
+ height: options.height,
392
+ bytesPerRow: options.width * 4
393
+ }, time));
394
+ lap("encodeMs");
395
+ }
360
396
  state.index++;
361
397
  if (onProgress) scheduleOnRN(onProgress, {
362
398
  framesCompleted: state.index,
@@ -410,9 +446,9 @@ const executeExport = args => new Promise((resolve, reject) => {
410
446
  extractor: null,
411
447
  surface: null,
412
448
  canvas: null,
413
- readback: null,
414
449
  gpu: false,
415
450
  target: null,
451
+ window: null,
416
452
  imports: null,
417
453
  frames: {},
418
454
  processor: null,
@@ -435,8 +471,7 @@ const executeExport = args => new Promise((resolve, reject) => {
435
471
  // Render target + transient full-frame CPU readback. Native decoder
436
472
  // and encoder reservations are additional entries in the same budget.
437
473
  state.reservations.push(reserveVideoMemory(bytes, "export render target"));
438
- state.reservations.push(reserveVideoMemory(bytes * 2, "export reusable readback and transient raster"));
439
- state.readback = new Uint8Array(bytes);
474
+ state.reservations.push(reserveVideoMemory(bytes * 2, "export readback and transient raster"));
440
475
  state.hasRecorder = true;
441
476
  state.surface = Skia.Surface.MakeOffscreen(options.width, options.height);
442
477
  if (!state.surface) throw new Error("Cannot allocate export surface");
@@ -451,10 +486,18 @@ const executeExport = args => new Promise((resolve, reject) => {
451
486
  }, videoComposition);
452
487
  state.encoder.prepare();
453
488
  if (gpuExport) {
454
- // Skia draws straight into the encoder's buffers; the probe
455
- // keeps devices without IOSurface interop on the readback path.
489
+ // Skia draws straight into the encoder's buffers: lent pool
490
+ // buffers on iOS, the input window on Android. The probe keeps
491
+ // devices where that fails on the readback path.
456
492
  state.hasRecorder = true;
457
- const failure = supportsEncoderTargets(state.encoder) ? probeEncoderTarget(state.encoder, options.width, options.height) : new Error("Encoder cannot lend frame buffers");
493
+ let failure;
494
+ if (bgra) {
495
+ failure = supportsEncoderTargets(state.encoder) ? probeEncoderTarget(state.encoder, options.width, options.height) : new Error("Encoder cannot lend frame buffers");
496
+ } else {
497
+ const probe = probeEncoderWindow(state.encoder, options.width, options.height);
498
+ state.window = probe.window;
499
+ failure = probe.failure;
500
+ }
458
501
  state.gpu = failure === null;
459
502
  // Decoded frames are sampled in place too: on iOS through the
460
503
  // same IOSurface import, on Android once converted from YUV.