react-native-skia-webgpu-video 0.1.0-beta.4 → 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 +21 -23
- package/android/BUILD_VALIDATION.md +1 -1
- package/docs/FRAME_PIXEL_FORMAT.md +16 -1
- package/docs/UPSTREAM_NOTES.md +28 -1
- package/ios/RNSVColorSpace.h +8 -3
- package/lib/module/exportFrames.js +2 -2
- package/lib/module/exportFrames.js.map +1 -1
- package/lib/module/exportVideoComposition.js +25 -23
- package/lib/module/exportVideoComposition.js.map +1 -1
- package/lib/module/frameInterop.js +314 -70
- package/lib/module/frameInterop.js.map +1 -1
- package/lib/module/gpu.js +1 -1
- package/lib/module/gpu.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 -0
- 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 +3 -3
- 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/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 +8 -7
- 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/package.json +6 -6
- package/patches/{react-native-skia+3.7.4.patch → react-native-skia+3.10.0.patch} +7 -3
- package/scripts/skia-canvas-patch.json +6 -6
- package/src/exportFrames.ts +2 -2
- package/src/exportVideoComposition.ts +43 -59
- package/src/frameInterop.ts +380 -85
- package/src/gpu.ts +1 -1
- package/src/memory.ts +6 -0
- package/src/previewLifecycle.ts +4 -0
- package/src/types.ts +8 -7
- package/src/videoCompositionPlayer.ts +76 -23
- package/src/videoFrameImage.ts +1 -1
- package/src/yuvBlit.ts +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
--- a/node_modules/react-native-skia/cpp/rnskia/RNDawnContext.h
|
|
2
2
|
+++ b/node_modules/react-native-skia/cpp/rnskia/RNDawnContext.h
|
|
3
|
-
@@ -
|
|
3
|
+
@@ -492,6 +492,44 @@
|
|
4
4
|
}
|
|
5
5
|
current->context->submit(syncToCpu);
|
|
6
6
|
return inserted;
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
+ throw std::runtime_error(
|
|
18
18
|
+ "Checked GPU submissions require a Graphite recorder");
|
|
19
19
|
+ }
|
|
20
|
+
+ // As in submitRecording: the snap compiles the pipelines the recording
|
|
21
|
+
+ // needs, inline, GPU work the pipeline warmup stays behind (see
|
|
22
|
+
+ // RNSkGpuActivity), as is the run.
|
|
23
|
+
+ RNSkGpuActivity::Scope activity;
|
|
20
24
|
+ auto recording = recorder->snap();
|
|
21
25
|
+ if (recording == nullptr) {
|
|
22
26
|
+ throw std::runtime_error("Cannot create checked Graphite recording");
|
|
@@ -118,7 +122,7 @@
|
|
|
118
122
|
} // namespace RNSkia
|
|
119
123
|
--- a/node_modules/react-native-skia/cpp/api/JsiSkApi.h
|
|
120
124
|
+++ b/node_modules/react-native-skia/cpp/api/JsiSkApi.h
|
|
121
|
-
@@ -
|
|
125
|
+
@@ -167,6 +167,16 @@
|
|
122
126
|
}
|
|
123
127
|
JSI_HOST_FUNCTION(Color) {
|
|
124
128
|
return JsiSkColor::createCtor()(runtime, thisValue, arguments, count);
|
|
@@ -135,7 +139,7 @@
|
|
|
135
139
|
}
|
|
136
140
|
JSI_HOST_FUNCTION(getNativeDevice) {
|
|
137
141
|
// Raw WGPUDevice pointer of the Graphite device, as a BigInt. Consumed by
|
|
138
|
-
@@ -
|
|
142
|
+
@@ -267,6 +277,8 @@
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.
|
|
2
|
+
"version": "3.10.0",
|
|
3
3
|
"files": {
|
|
4
4
|
"cpp/rnskia/RNDawnContext.h": {
|
|
5
|
-
"before": "
|
|
6
|
-
"after": "
|
|
5
|
+
"before": "ed26c1390ac4912089b354651c970801d42db7318ed40c51c19357b2252c52d4",
|
|
6
|
+
"after": "bceaaf3cabcde96d4d3843210d74e2de23991631e85427ec34c4b481ff30e5a0",
|
|
7
7
|
"edits": [
|
|
8
8
|
{
|
|
9
9
|
"before": " current->context->submit(syncToCpu);\n return inserted;\n }\n",
|
|
10
|
-
"after": " current->context->submit(syncToCpu);\n return inserted;\n }\n\n // Checked only for surfaces that explicitly opt into this contract: like\n // submitRecording, but a recording that would not run throws instead of\n // being dropped. A valid empty Recording is accepted; Recorder::snap()\n // returns nullptr on failure.\n void submitRecordingChecked(\n skgpu::graphite::Recorder *recorder,\n skgpu::graphite::SyncToCpu syncToCpu = skgpu::graphite::SyncToCpu::kNo) {\n if (recorder == nullptr) {\n throw std::runtime_error(\n \"Checked GPU submissions require a Graphite recorder\");\n }\n auto recording = recorder->snap();\n if (recording == nullptr) {\n throw std::runtime_error(\"Cannot create checked Graphite recording\");\n }\n std::lock_guard<std::mutex> lock(_mutex);\n auto *current = _current.load(std::memory_order_acquire);\n if (generationOf(recorder) != current->id ||\n current->lost.load(std::memory_order_acquire)) {\n throw std::runtime_error(\n \"The GPU device was lost; a checked recording cannot run\");\n }\n skgpu::graphite::InsertRecordingInfo info;\n info.fRecording = recording.get();\n auto status = current->context->insertRecording(info);\n if (!static_cast<bool>(status)) {\n throw std::runtime_error(\"Graphite rejected a checked recording: \" +\n status.message());\n }\n if (!current->context->submit(syncToCpu)) {\n throw std::runtime_error(\"Graphite checked submission failed\");\n }\n }\n"
|
|
10
|
+
"after": " current->context->submit(syncToCpu);\n return inserted;\n }\n\n // Checked only for surfaces that explicitly opt into this contract: like\n // submitRecording, but a recording that would not run throws instead of\n // being dropped. A valid empty Recording is accepted; Recorder::snap()\n // returns nullptr on failure.\n void submitRecordingChecked(\n skgpu::graphite::Recorder *recorder,\n skgpu::graphite::SyncToCpu syncToCpu = skgpu::graphite::SyncToCpu::kNo) {\n if (recorder == nullptr) {\n throw std::runtime_error(\n \"Checked GPU submissions require a Graphite recorder\");\n }\n // As in submitRecording: the snap compiles the pipelines the recording\n // needs, inline, GPU work the pipeline warmup stays behind (see\n // RNSkGpuActivity), as is the run.\n RNSkGpuActivity::Scope activity;\n auto recording = recorder->snap();\n if (recording == nullptr) {\n throw std::runtime_error(\"Cannot create checked Graphite recording\");\n }\n std::lock_guard<std::mutex> lock(_mutex);\n auto *current = _current.load(std::memory_order_acquire);\n if (generationOf(recorder) != current->id ||\n current->lost.load(std::memory_order_acquire)) {\n throw std::runtime_error(\n \"The GPU device was lost; a checked recording cannot run\");\n }\n skgpu::graphite::InsertRecordingInfo info;\n info.fRecording = recording.get();\n auto status = current->context->insertRecording(info);\n if (!static_cast<bool>(status)) {\n throw std::runtime_error(\"Graphite rejected a checked recording: \" +\n status.message());\n }\n if (!current->context->submit(syncToCpu)) {\n throw std::runtime_error(\"Graphite checked submission failed\");\n }\n }\n"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
},
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"cpp/api/JsiSkApi.h": {
|
|
41
|
-
"before": "
|
|
42
|
-
"after": "
|
|
41
|
+
"before": "07846e9776f57bd2638e2e3092931e5a1324d66710693a2f7e550f648fe13934",
|
|
42
|
+
"after": "5c0bf4231d658366c8a902221238c3a244b6444cf407468f80c1cd1718b2be16",
|
|
43
43
|
"edits": [
|
|
44
44
|
{
|
|
45
45
|
"before": " JSI_HOST_FUNCTION(getNativeDevice) {\n",
|
package/src/exportFrames.ts
CHANGED
|
@@ -223,7 +223,7 @@ const importYuvExportFrame = (
|
|
|
223
223
|
region!,
|
|
224
224
|
target.texture,
|
|
225
225
|
);
|
|
226
|
-
entry.image = Skia.Image.
|
|
226
|
+
entry.image = Skia.Image.MakeFromGPUTexture(target.texture);
|
|
227
227
|
if (!entry.image) throw new Error("Cannot wrap the decoded frame");
|
|
228
228
|
return entry;
|
|
229
229
|
} catch (error) {
|
|
@@ -290,7 +290,7 @@ export const importExportFrame = (
|
|
|
290
290
|
entry.texture = entry.memory.createTexture();
|
|
291
291
|
entry.memory.beginAccess(entry.texture, true);
|
|
292
292
|
entry.accessing = true;
|
|
293
|
-
entry.image = Skia.Image.
|
|
293
|
+
entry.image = Skia.Image.MakeFromGPUTexture(entry.texture);
|
|
294
294
|
if (!entry.image) throw new Error("Cannot wrap the decoded frame");
|
|
295
295
|
return entry;
|
|
296
296
|
} catch (error) {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type WorkletRuntime,
|
|
12
12
|
} from "react-native-worklets";
|
|
13
13
|
import { AlphaType, BlendMode, ColorType, Skia } from "react-native-skia";
|
|
14
|
-
import type {
|
|
14
|
+
import type { SkCanvas, SkSurface } from "react-native-skia";
|
|
15
15
|
import { Platform } from "react-native";
|
|
16
16
|
import type {
|
|
17
17
|
ExportOptions,
|
|
@@ -121,25 +121,11 @@ export type VideoExportArguments<T = undefined> = ExportOptions & {
|
|
|
121
121
|
onProfile?: (profile: ExportProfile) => void;
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
// Native Skia 3.4.4 accepts destination/stride in readPixels; its
|
|
125
|
-
// published Canvas type currently omits these arguments. Use only a private,
|
|
126
|
-
// full-length, zero-offset Uint8Array (the native implementation ignores offsets).
|
|
127
|
-
type ReadbackCanvas = {
|
|
128
|
-
readPixels: (
|
|
129
|
-
x: number,
|
|
130
|
-
y: number,
|
|
131
|
-
info: ImageInfo,
|
|
132
|
-
destination: Uint8Array,
|
|
133
|
-
bytesPerRow: number,
|
|
134
|
-
) => Uint8Array | Float32Array | null;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
124
|
type ExportState = {
|
|
138
125
|
encoder: VideoEncoder | null;
|
|
139
126
|
extractor: VideoCompositionFramesExtractorSync | null;
|
|
140
127
|
surface: SkSurface | null;
|
|
141
128
|
canvas: SkCanvas | null;
|
|
142
|
-
readback: Uint8Array | null;
|
|
143
129
|
/** Draw into a lent encoder buffer instead of reading pixels back. */
|
|
144
130
|
gpu: boolean;
|
|
145
131
|
target: EncoderTarget | null;
|
|
@@ -401,7 +387,6 @@ const executeExport = <T>(args: VideoExportArguments<T>): Promise<void> =>
|
|
|
401
387
|
}
|
|
402
388
|
if (cleanup.safeToRelease) {
|
|
403
389
|
state.frames = {};
|
|
404
|
-
state.readback = null;
|
|
405
390
|
delete storage.__rnskwgpuExport;
|
|
406
391
|
} else {
|
|
407
392
|
// One retained session at most; subsequent exports fail before
|
|
@@ -463,6 +448,10 @@ const executeExport = <T>(args: VideoExportArguments<T>): Promise<void> =>
|
|
|
463
448
|
await finish(null, true);
|
|
464
449
|
return false;
|
|
465
450
|
}
|
|
451
|
+
// CPU readback path only: the frame's pixels, still on their way.
|
|
452
|
+
const readback: {
|
|
453
|
+
pixels: ReturnType<SkCanvas["readPixels"]> | null;
|
|
454
|
+
} = { pixels: null };
|
|
466
455
|
const draw = () => {
|
|
467
456
|
"worklet";
|
|
468
457
|
if (state.gpu)
|
|
@@ -517,46 +506,14 @@ const executeExport = <T>(args: VideoExportArguments<T>): Promise<void> =>
|
|
|
517
506
|
time,
|
|
518
507
|
);
|
|
519
508
|
} else {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
alphaType: AlphaType.Premul,
|
|
529
|
-
},
|
|
530
|
-
state.readback!,
|
|
531
|
-
options.width * 4,
|
|
532
|
-
);
|
|
533
|
-
if (
|
|
534
|
-
!(pixels instanceof Uint8Array) ||
|
|
535
|
-
pixels.byteLength !== bytes ||
|
|
536
|
-
pixels.byteOffset !== 0 ||
|
|
537
|
-
pixels.buffer !== state.readback!.buffer
|
|
538
|
-
) {
|
|
539
|
-
throw new Error("Video export pixel readback failed");
|
|
540
|
-
}
|
|
541
|
-
// Fallback: consumed synchronously by the bounded native pool.
|
|
542
|
-
const storage =
|
|
543
|
-
pixels.byteOffset === 0 &&
|
|
544
|
-
pixels.buffer.byteLength === pixels.byteLength
|
|
545
|
-
? pixels.buffer
|
|
546
|
-
: pixels.buffer.slice(
|
|
547
|
-
pixels.byteOffset,
|
|
548
|
-
pixels.byteOffset + pixels.byteLength,
|
|
549
|
-
);
|
|
550
|
-
state.encoder!.encodeFrame(
|
|
551
|
-
{
|
|
552
|
-
kind: bgra ? "bgra" : "rgba",
|
|
553
|
-
data: storage,
|
|
554
|
-
width: options.width,
|
|
555
|
-
height: options.height,
|
|
556
|
-
bytesPerRow: options.width * 4,
|
|
557
|
-
},
|
|
558
|
-
time,
|
|
559
|
-
);
|
|
509
|
+
// Skia 3.9 reads back asynchronously, into a new array: the
|
|
510
|
+
// frame is encoded once it lands, after this scope.
|
|
511
|
+
readback.pixels = canvas.readPixels(0, 0, {
|
|
512
|
+
width: options.width,
|
|
513
|
+
height: options.height,
|
|
514
|
+
colorType: bgra ? ColorType.BGRA_8888 : ColorType.RGBA_8888,
|
|
515
|
+
alphaType: AlphaType.Premul,
|
|
516
|
+
});
|
|
560
517
|
}
|
|
561
518
|
lap("encodeMs");
|
|
562
519
|
} catch (error) {
|
|
@@ -593,6 +550,35 @@ const executeExport = <T>(args: VideoExportArguments<T>): Promise<void> =>
|
|
|
593
550
|
if (drawn.failed) throw drawn.failure;
|
|
594
551
|
};
|
|
595
552
|
runWithVideoAutoreleasePool(draw);
|
|
553
|
+
if (readback.pixels) {
|
|
554
|
+
const pixels = await readback.pixels;
|
|
555
|
+
lap("gpuMs");
|
|
556
|
+
// isView, not instanceof: true of a typed array of any realm.
|
|
557
|
+
if (!ArrayBuffer.isView(pixels) || pixels.byteLength !== bytes)
|
|
558
|
+
throw new Error("Video export pixel readback failed");
|
|
559
|
+
// Fallback: consumed synchronously by the bounded native pool.
|
|
560
|
+
const storage =
|
|
561
|
+
pixels.byteOffset === 0 &&
|
|
562
|
+
pixels.buffer.byteLength === pixels.byteLength
|
|
563
|
+
? pixels.buffer
|
|
564
|
+
: pixels.buffer.slice(
|
|
565
|
+
pixels.byteOffset,
|
|
566
|
+
pixels.byteOffset + pixels.byteLength,
|
|
567
|
+
);
|
|
568
|
+
runWithVideoAutoreleasePool(() =>
|
|
569
|
+
state.encoder!.encodeFrame(
|
|
570
|
+
{
|
|
571
|
+
kind: bgra ? "bgra" : "rgba",
|
|
572
|
+
data: storage,
|
|
573
|
+
width: options.width,
|
|
574
|
+
height: options.height,
|
|
575
|
+
bytesPerRow: options.width * 4,
|
|
576
|
+
},
|
|
577
|
+
time,
|
|
578
|
+
),
|
|
579
|
+
);
|
|
580
|
+
lap("encodeMs");
|
|
581
|
+
}
|
|
596
582
|
state.index++;
|
|
597
583
|
if (onProgress)
|
|
598
584
|
scheduleOnRN(onProgress, {
|
|
@@ -660,7 +646,6 @@ const executeExport = <T>(args: VideoExportArguments<T>): Promise<void> =>
|
|
|
660
646
|
extractor: null,
|
|
661
647
|
surface: null,
|
|
662
648
|
canvas: null,
|
|
663
|
-
readback: null,
|
|
664
649
|
gpu: false,
|
|
665
650
|
target: null,
|
|
666
651
|
window: null,
|
|
@@ -690,10 +675,9 @@ const executeExport = <T>(args: VideoExportArguments<T>): Promise<void> =>
|
|
|
690
675
|
state.reservations.push(
|
|
691
676
|
reserveVideoMemory(
|
|
692
677
|
bytes * 2,
|
|
693
|
-
"export
|
|
678
|
+
"export readback and transient raster",
|
|
694
679
|
),
|
|
695
680
|
);
|
|
696
|
-
state.readback = new Uint8Array(bytes);
|
|
697
681
|
state.hasRecorder = true;
|
|
698
682
|
state.surface = Skia.Surface.MakeOffscreen(
|
|
699
683
|
options.width,
|