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.
- package/README.md +48 -43
- package/android/BUILD_VALIDATION.md +1 -1
- package/android/CMakeLists.txt +3 -2
- package/android/cpp/ExportHintSession.cpp +114 -0
- package/android/cpp/ExportHintSession.h +35 -0
- package/android/cpp/VideoEncoderHostObject.cpp +76 -101
- package/android/cpp/VideoEncoderHostObject.h +14 -9
- package/android/cpp/VideoFrame.cpp +3 -5
- package/android/cpp/cpp-adapter.cpp +7 -4
- package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +3 -9
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +8 -11
- package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +68 -28
- package/docs/FRAME_PIXEL_FORMAT.md +17 -2
- package/docs/MIGRATION.md +1 -1
- package/docs/UPSTREAM_NOTES.md +42 -2
- package/ios/RNSVColorSpace.h +8 -3
- package/lib/module/canvas.js +3 -5
- package/lib/module/canvas.js.map +1 -1
- package/lib/module/exportFrames.js +8 -10
- package/lib/module/exportFrames.js.map +1 -1
- package/lib/module/exportTarget.js +183 -40
- package/lib/module/exportTarget.js.map +1 -1
- package/lib/module/exportVideoComposition.js +78 -35
- package/lib/module/exportVideoComposition.js.map +1 -1
- package/lib/module/frameInterop.js +323 -82
- package/lib/module/frameInterop.js.map +1 -1
- package/lib/module/gpu.js +1 -3
- package/lib/module/gpu.js.map +1 -1
- package/lib/module/gpuDevice.js +12 -0
- package/lib/module/gpuDevice.js.map +1 -1
- package/lib/module/memory.js +7 -0
- package/lib/module/memory.js.map +1 -1
- package/lib/module/previewLifecycle.js +2 -1
- package/lib/module/previewLifecycle.js.map +1 -1
- package/lib/module/videoCompositionPlayer.js +65 -14
- package/lib/module/videoCompositionPlayer.js.map +1 -1
- package/lib/module/videoFrameImage.js +1 -1
- package/lib/module/yuvBlit.js +31 -24
- package/lib/module/yuvBlit.js.map +1 -1
- package/lib/typescript/src/canvas.d.ts.map +1 -1
- package/lib/typescript/src/exportFrames.d.ts +3 -2
- package/lib/typescript/src/exportFrames.d.ts.map +1 -1
- package/lib/typescript/src/exportTarget.d.ts +53 -14
- package/lib/typescript/src/exportTarget.d.ts.map +1 -1
- package/lib/typescript/src/exportVideoComposition.d.ts +2 -1
- package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -1
- package/lib/typescript/src/frameInterop.d.ts +16 -5
- package/lib/typescript/src/frameInterop.d.ts.map +1 -1
- package/lib/typescript/src/gpu.d.ts.map +1 -1
- package/lib/typescript/src/gpuDevice.d.ts +5 -1
- package/lib/typescript/src/gpuDevice.d.ts.map +1 -1
- package/lib/typescript/src/memory.d.ts +2 -0
- package/lib/typescript/src/memory.d.ts.map +1 -1
- package/lib/typescript/src/previewLifecycle.d.ts +2 -0
- package/lib/typescript/src/previewLifecycle.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +18 -12
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/videoCompositionPlayer.d.ts +4 -5
- package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -1
- package/lib/typescript/src/videoFrameImage.d.ts +1 -1
- package/lib/typescript/src/yuvBlit.d.ts +16 -14
- package/lib/typescript/src/yuvBlit.d.ts.map +1 -1
- package/package.json +6 -6
- package/patches/react-native-skia+3.10.0.patch +150 -0
- package/scripts/skia-canvas-patch.json +22 -258
- package/src/canvas.ts +3 -7
- package/src/exportFrames.ts +10 -11
- package/src/exportTarget.ts +217 -56
- package/src/exportVideoComposition.ts +130 -85
- package/src/frameInterop.ts +389 -96
- package/src/gpu.ts +1 -3
- package/src/gpuDevice.ts +26 -1
- package/src/memory.ts +6 -0
- package/src/previewLifecycle.ts +4 -1
- package/src/types.ts +18 -12
- package/src/videoCompositionPlayer.ts +76 -23
- package/src/videoFrameImage.ts +1 -1
- package/src/yuvBlit.ts +46 -31
- package/android/cpp/EncoderTargets.cpp +0 -226
- package/android/cpp/EncoderTargets.h +0 -70
- 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
|
-
/**
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
/**
|
|
22
|
-
* release. Call only after a checked flush(true) of its surface.
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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
|
-
/**
|
|
115
|
-
* failure, or null when this device can export through encoder
|
|
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":["
|
|
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
|
-
|
|
197
|
-
if (cleanup.safeToRelease && clean(() => state.surface?.dispose()))
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
|
455
|
-
//
|
|
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
|
-
|
|
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.
|