react-native-skia-webgpu-video 0.1.0-beta.1 → 0.1.0-beta.3
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/LICENSE +0 -1
- package/README.md +32 -11
- package/android/BUILD_VALIDATION.md +1 -1
- package/android/cpp/VideoComposition.cpp +12 -0
- package/android/cpp/VideoCompositionFramesExtractor.cpp +6 -0
- package/android/cpp/VideoCompositionFramesExtractor.h +1 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +1 -1
- package/android/src/main/java/com/azzapp/rnskv/PreviewFrameRateVote.java +104 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +4 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +4 -2
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +42 -4
- package/docs/FRAME_PIXEL_FORMAT.md +124 -0
- package/docs/UPSTREAM_NOTES.md +2 -0
- package/docs/VERIFICATION_0.1.0-beta.3.md +147 -0
- package/ios/NativeResourceStats.h +9 -0
- package/ios/RNSVColorSpace.h +35 -4
- package/ios/ReactNativeSkiaVideo.mm +4 -0
- package/ios/VideoComposition.h +20 -0
- package/ios/VideoCompositionFramesExtractorHostObject.h +5 -0
- package/ios/VideoCompositionFramesExtractorHostObject.mm +31 -2
- package/ios/VideoCompositionItemDecoder.mm +21 -8
- package/ios/VideoFrame.mm +6 -0
- package/lib/module/exportFrames.js +25 -18
- package/lib/module/exportFrames.js.map +1 -1
- package/lib/module/frameInterop.js +7 -1
- package/lib/module/frameInterop.js.map +1 -1
- package/lib/module/videoCompositionPlayer.js +13 -5
- package/lib/module/videoCompositionPlayer.js.map +1 -1
- package/lib/module/yuvBlit.js +70 -0
- package/lib/module/yuvBlit.js.map +1 -1
- package/lib/typescript/src/exportFrames.d.ts +8 -8
- package/lib/typescript/src/exportFrames.d.ts.map +1 -1
- package/lib/typescript/src/frameInterop.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +26 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/videoCompositionPlayer.d.ts +5 -3
- package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -1
- package/lib/typescript/src/yuvBlit.d.ts +10 -0
- package/lib/typescript/src/yuvBlit.d.ts.map +1 -1
- package/package.json +7 -5
- package/scripts/skia-canvas-patch.json +1 -1
- package/src/exportFrames.ts +52 -34
- package/src/frameInterop.ts +18 -1
- package/src/types.ts +27 -0
- package/src/videoCompositionPlayer.ts +19 -7
- package/src/yuvBlit.ts +66 -0
- /package/patches/{react-native-skia+3.7.0.patch → react-native-skia+3.7.1.patch} +0 -0
package/src/exportFrames.ts
CHANGED
|
@@ -18,13 +18,18 @@ import {
|
|
|
18
18
|
reserveVideoMemory,
|
|
19
19
|
} from "./memory";
|
|
20
20
|
import type { VideoFrame } from "./types";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
blitNv12Frame,
|
|
23
|
+
blitYuvFrame,
|
|
24
|
+
releaseYuvImports,
|
|
25
|
+
yuvFrameOrigin,
|
|
26
|
+
} from "./yuvBlit";
|
|
22
27
|
import type { YuvImport } from "./yuvBlit";
|
|
23
28
|
|
|
24
|
-
/** A decoded frame sampled in place, without an owned snapshot:
|
|
25
|
-
* IOSurface
|
|
26
|
-
*
|
|
27
|
-
* image is in use. */
|
|
29
|
+
/** A decoded frame sampled in place, without an owned snapshot: a BGRA
|
|
30
|
+
* frame's IOSurface on iOS, or for a YUV frame (Android, iOS NV12) its
|
|
31
|
+
* producer's RGBA texture, which the decoder buffer was just converted into.
|
|
32
|
+
* The decoder lease stays open while the image is in use. */
|
|
28
33
|
export type ExportFrameImport = {
|
|
29
34
|
id: number;
|
|
30
35
|
frame: VideoFrame;
|
|
@@ -37,7 +42,7 @@ export type ExportFrameImport = {
|
|
|
37
42
|
aliases?: WeakSet<VideoFrame>;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
|
-
/**
|
|
45
|
+
/** The texture a producer's YUV frames are converted into. */
|
|
41
46
|
type ProducerTexture = { texture: GPUTexture; reservation: number };
|
|
42
47
|
|
|
43
48
|
/** Per export session, keyed by producer. `enabled` turns off after a first
|
|
@@ -48,7 +53,7 @@ export type ExportFrameImports = {
|
|
|
48
53
|
producers: Set<number>;
|
|
49
54
|
enabled: boolean;
|
|
50
55
|
imported: boolean;
|
|
51
|
-
/**
|
|
56
|
+
/** One RGBA texture per producer of YUV frames, rewritten by each frame. */
|
|
52
57
|
textures: Map<number, ProducerTexture>;
|
|
53
58
|
/** Android: decoder buffers imported once (see yuvBlit.ts). */
|
|
54
59
|
yuv: Map<number, YuvImport[]>;
|
|
@@ -65,6 +70,8 @@ type NativeBufferTexture = {
|
|
|
65
70
|
nativeBuffer: bigint;
|
|
66
71
|
region?: { x: number; y: number; width: number; height: number };
|
|
67
72
|
rotation?: number;
|
|
73
|
+
/** iOS: the decoder's NV12 buffer (`framePixelFormat: 'yuv'`), or BGRA. */
|
|
74
|
+
format?: "nv12" | "bgra8";
|
|
68
75
|
};
|
|
69
76
|
|
|
70
77
|
// Captured on the RN runtime. Android samples its ImageReader YUV frames
|
|
@@ -117,8 +124,8 @@ export const releaseExportFrame = (entry: ExportFrameImport): void => {
|
|
|
117
124
|
entry.frame.dispose?.();
|
|
118
125
|
};
|
|
119
126
|
|
|
120
|
-
/**
|
|
121
|
-
*
|
|
127
|
+
/** Releases a producer's texture and decoder buffer imports. Call only once
|
|
128
|
+
* its last frame's GPU work has completed. */
|
|
122
129
|
const releaseProducerTexture = (
|
|
123
130
|
imports: ExportFrameImports,
|
|
124
131
|
producer: number,
|
|
@@ -133,10 +140,11 @@ const releaseProducerTexture = (
|
|
|
133
140
|
}
|
|
134
141
|
};
|
|
135
142
|
|
|
136
|
-
/**
|
|
137
|
-
* texture, wrapped as a Skia image. The
|
|
138
|
-
* frame's drawing, on the same queue;
|
|
139
|
-
* both before the lease or the texture
|
|
143
|
+
/** Converts a decoded YUV frame (an Android ImageReader buffer, or an iOS
|
|
144
|
+
* NV12 one) into its producer's RGBA texture, wrapped as a Skia image. The
|
|
145
|
+
* conversion is submitted ahead of the frame's drawing, on the same queue;
|
|
146
|
+
* the export's checked flush waits for both before the lease or the texture
|
|
147
|
+
* is reused. */
|
|
140
148
|
const importYuvExportFrame = (
|
|
141
149
|
imports: ExportFrameImports,
|
|
142
150
|
frame: VideoFrame,
|
|
@@ -158,12 +166,17 @@ const importYuvExportFrame = (
|
|
|
158
166
|
try {
|
|
159
167
|
entry.nativeFrame = createNativeVideoGpuFrame(source.nativeBuffer);
|
|
160
168
|
const rotation = source.rotation ?? 0;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
frame
|
|
166
|
-
|
|
169
|
+
// iOS NV12: the whole buffer, in decoded orientation.
|
|
170
|
+
const nv12 = source.format === "nv12";
|
|
171
|
+
if (
|
|
172
|
+
nv12 &&
|
|
173
|
+
(entry.nativeFrame.width !== frame.width ||
|
|
174
|
+
entry.nativeFrame.height !== frame.height)
|
|
175
|
+
)
|
|
176
|
+
throw new Error("Native video buffer dimensions mismatch");
|
|
177
|
+
const origin = nv12
|
|
178
|
+
? { x: 0, y: 0 }
|
|
179
|
+
: yuvFrameOrigin(source.region!, rotation, entry.nativeFrame, frame);
|
|
167
180
|
const device = getVideoGpuDevice();
|
|
168
181
|
target = imports.textures.get(producer);
|
|
169
182
|
if (
|
|
@@ -199,17 +212,19 @@ const importYuvExportFrame = (
|
|
|
199
212
|
}
|
|
200
213
|
// Can throw after submitting: drain below before giving the lease back.
|
|
201
214
|
submitted = true;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
if (nv12) blitNv12Frame(device, entry.nativeFrame, target.texture);
|
|
216
|
+
else
|
|
217
|
+
blitYuvFrame(
|
|
218
|
+
device,
|
|
219
|
+
imports.yuv,
|
|
220
|
+
producer,
|
|
221
|
+
entry.nativeFrame,
|
|
222
|
+
rotation,
|
|
223
|
+
origin,
|
|
224
|
+
frame.width,
|
|
225
|
+
frame.height,
|
|
226
|
+
target.texture,
|
|
227
|
+
);
|
|
213
228
|
entry.image = Skia.Image.MakeImageFromGPUTexture(target.texture);
|
|
214
229
|
if (!entry.image) throw new Error("Cannot wrap the decoded frame");
|
|
215
230
|
return entry;
|
|
@@ -233,15 +248,18 @@ const importYuvExportFrame = (
|
|
|
233
248
|
};
|
|
234
249
|
|
|
235
250
|
/** Wraps a decoded BGRA CVPixelBuffer's IOSurface as a Skia image, read-only.
|
|
236
|
-
* The decoder already finished writing it; no producer fence is needed.
|
|
237
|
-
* Android,
|
|
251
|
+
* The decoder already finished writing it; no producer fence is needed. A
|
|
252
|
+
* YUV frame (Android, iOS NV12) is converted instead (see importYuvExportFrame). */
|
|
238
253
|
export const importExportFrame = (
|
|
239
254
|
frame: VideoFrame,
|
|
240
255
|
imports?: ExportFrameImports,
|
|
241
256
|
): ExportFrameImport => {
|
|
242
257
|
"worklet";
|
|
243
|
-
if (
|
|
244
|
-
|
|
258
|
+
if (
|
|
259
|
+
convertsYuv ||
|
|
260
|
+
(frame.texture as NativeBufferTexture).format === "nv12"
|
|
261
|
+
) {
|
|
262
|
+
if (!imports) throw new Error("YUV in-place frames need their imports");
|
|
245
263
|
return importYuvExportFrame(imports, frame);
|
|
246
264
|
}
|
|
247
265
|
const entry: ExportFrameImport = {
|
package/src/frameInterop.ts
CHANGED
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
releaseVideoMemory,
|
|
16
16
|
reserveVideoMemory,
|
|
17
17
|
} from "./memory";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
blitNv12Frame,
|
|
20
|
+
blitYuvFrame,
|
|
21
|
+
releaseYuvImports,
|
|
22
|
+
yuvFrameOrigin,
|
|
23
|
+
} from "./yuvBlit";
|
|
19
24
|
import type { YuvImport } from "./yuvBlit";
|
|
20
25
|
|
|
21
26
|
type FrameTexture =
|
|
@@ -26,6 +31,8 @@ type FrameTexture =
|
|
|
26
31
|
* this crop rectangle turned clockwise by `rotation` for display. */
|
|
27
32
|
region?: { x: number; y: number; width: number; height: number };
|
|
28
33
|
rotation?: number;
|
|
34
|
+
/** iOS: the decoder's NV12 buffer (`framePixelFormat: 'yuv'`), or BGRA. */
|
|
35
|
+
format?: "nv12" | "bgra8";
|
|
29
36
|
}
|
|
30
37
|
| {
|
|
31
38
|
kind: "rgba";
|
|
@@ -441,6 +448,7 @@ export const imageFromVideoFrame = (frame: VideoFrame): SkImage | null => {
|
|
|
441
448
|
nativeSource = createNativeVideoGpuFrame(texture.nativeBuffer);
|
|
442
449
|
const region = texture.region;
|
|
443
450
|
const rotation = texture.rotation ?? 0;
|
|
451
|
+
const nv12 = !region && texture.format === "nv12";
|
|
444
452
|
let origin = { x: 0, y: 0 };
|
|
445
453
|
if (region) {
|
|
446
454
|
// An Android codec buffer: padded, YUV, in encoded orientation. The
|
|
@@ -449,6 +457,12 @@ export const imageFromVideoFrame = (frame: VideoFrame): SkImage | null => {
|
|
|
449
457
|
// driver-defined YUV formats "bgra8", yet Dawn imports them as YUV:
|
|
450
458
|
// the native side checked the format, so the label is not read.
|
|
451
459
|
origin = yuvFrameOrigin(region, rotation, nativeSource, frame);
|
|
460
|
+
} else if (nv12) {
|
|
461
|
+
if (
|
|
462
|
+
nativeSource.width !== frame.width ||
|
|
463
|
+
nativeSource.height !== frame.height
|
|
464
|
+
)
|
|
465
|
+
throw new Error("Native video buffer dimensions mismatch");
|
|
452
466
|
} else if (
|
|
453
467
|
// RN WebGPU labels a single-plane Android RGBA AHB "bgra8". Dawn
|
|
454
468
|
// reads the actual AHB format; the string distinguishes RGB from
|
|
@@ -478,6 +492,9 @@ export const imageFromVideoFrame = (frame: VideoFrame): SkImage | null => {
|
|
|
478
492
|
frame.height,
|
|
479
493
|
cache.uploadTexture!,
|
|
480
494
|
);
|
|
495
|
+
} else if (nv12) {
|
|
496
|
+
// Decoded orientation, as below: drawVideoFrame applies rotation.
|
|
497
|
+
blitNv12Frame(device, nativeSource, cache.uploadTexture!);
|
|
481
498
|
} else {
|
|
482
499
|
// Keep decoded orientation: drawVideoFrame applies rotation once.
|
|
483
500
|
device.queue.copyExternalImageToTexture(
|
package/src/types.ts
CHANGED
|
@@ -149,6 +149,26 @@ export type VideoPlayer = {
|
|
|
149
149
|
on(name: "error", listener: (error: any) => void): () => void;
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The pixel format decoded frames are handed over in, before they are drawn.
|
|
154
|
+
*
|
|
155
|
+
* - `yuv`: the decoder's own YUV 4:2:0 buffers, converted to RGB on the GPU
|
|
156
|
+
* when a frame is drawn. A frame takes 1.5 bytes a pixel rather than 4
|
|
157
|
+
* (12.4 MB rather than 33.2 MB in 4K), so the decoder holds more frames
|
|
158
|
+
* ahead for the same memory. On Android only 8-bit SDR tracks decoded at
|
|
159
|
+
* their own size; other tracks stay `rgba`.
|
|
160
|
+
* - `rgba`: frames converted to RGB as they are decoded (iOS: by VideoToolbox,
|
|
161
|
+
* Android: by an OpenGL pass), which a GPU export samples in place.
|
|
162
|
+
* - `auto`: `yuv`, on both platforms.
|
|
163
|
+
*
|
|
164
|
+
* Camera footage (Rec.709, or HLG tone mapped to Rec.709) renders the same
|
|
165
|
+
* either way: both convert with the frame's YCbCr matrix and range. On iOS,
|
|
166
|
+
* `rgba` also converts a file tagged with other primaries or another SDR
|
|
167
|
+
* transfer function (Display P3, sRGB) to Rec.709; `yuv` takes its samples as
|
|
168
|
+
* Rec.709, as Android does in both formats.
|
|
169
|
+
*/
|
|
170
|
+
export type VideoFramePixelFormat = "auto" | "yuv" | "rgba";
|
|
171
|
+
|
|
152
172
|
/**
|
|
153
173
|
* Represents a video composition.
|
|
154
174
|
*/
|
|
@@ -171,6 +191,13 @@ export type VideoComposition = {
|
|
|
171
191
|
* until its decoder has opened. The export always waits for it.
|
|
172
192
|
*/
|
|
173
193
|
lazyDecoders?: boolean;
|
|
194
|
+
/**
|
|
195
|
+
* The pixel format the video items' frames are decoded into, for the
|
|
196
|
+
* preview and the export, see {@link VideoFramePixelFormat}.
|
|
197
|
+
*
|
|
198
|
+
* @default 'auto'
|
|
199
|
+
*/
|
|
200
|
+
framePixelFormat?: VideoFramePixelFormat;
|
|
174
201
|
};
|
|
175
202
|
|
|
176
203
|
type VideoCompositionItemBase = {
|
|
@@ -80,7 +80,8 @@ type UseVideoCompositionPlayerOptions<T = undefined> = {
|
|
|
80
80
|
* video only changes at its own frame rate. A drawer whose picture depends on
|
|
81
81
|
* time only through the frames (or only at known moments, a transition say)
|
|
82
82
|
* returns false elsewhere, and the player then redraws when a new frame is
|
|
83
|
-
* decoded instead.
|
|
83
|
+
* decoded instead. Ignored when `drawWhenPaused` is `true`, which redraws at
|
|
84
|
+
* every vsync. Must be a worklet.
|
|
84
85
|
*/
|
|
85
86
|
redrawOnTime?: (previousTime: number, currentTime: number) => boolean;
|
|
86
87
|
/**
|
|
@@ -88,8 +89,9 @@ type UseVideoCompositionPlayerOptions<T = undefined> = {
|
|
|
88
89
|
* of both in the same one. On Android the import (YUV blit and its checked
|
|
89
90
|
* GPU wait) and the draw (snapshot and its checked GPU wait) cost about as
|
|
90
91
|
* much as each other, and together they overrun a 90–120 Hz frame budget on
|
|
91
|
-
* the UI thread. The picture then trails the
|
|
92
|
-
* The first frame of a session is still drawn as soon
|
|
92
|
+
* the UI thread. The option applies on iOS too. The picture then trails the
|
|
93
|
+
* clock by one vsync. The first frame of a session is still drawn as soon
|
|
94
|
+
* as it is imported.
|
|
93
95
|
* @default false
|
|
94
96
|
*/
|
|
95
97
|
splitImportAndDraw?: boolean;
|
|
@@ -317,6 +319,8 @@ export const useVideoComposition = <T = undefined>({
|
|
|
317
319
|
const lastImportedVersion = useSharedValue(-1);
|
|
318
320
|
// Whether the previous vsync imported and left its draw to this one.
|
|
319
321
|
const drawDeferred = useSharedValue(false);
|
|
322
|
+
// Whether the previous vsync brought new frames.
|
|
323
|
+
const importedLastVsync = useSharedValue(false);
|
|
320
324
|
// A new drawFrame can draw something else from the same time and frames.
|
|
321
325
|
useEffect(() => {
|
|
322
326
|
lastDrawnTime.value = -1;
|
|
@@ -382,14 +386,22 @@ export const useVideoComposition = <T = undefined>({
|
|
|
382
386
|
sourcesGeneration.value = generation;
|
|
383
387
|
const currentTime = framesExtractor.currentTime;
|
|
384
388
|
const framesVersion = framesExtractor.framesVersion;
|
|
385
|
-
|
|
389
|
+
const imported =
|
|
390
|
+
splitImportAndDraw && framesVersion !== lastImportedVersion.value;
|
|
391
|
+
const importedBefore = importedLastVsync.value;
|
|
392
|
+
// Version 0: no frame decoded yet.
|
|
393
|
+
importedLastVsync.value = imported && framesVersion > 0;
|
|
394
|
+
if (imported) {
|
|
386
395
|
// ownVideoFrames above has just imported the new frames: leave the
|
|
387
|
-
// draw to the next vsync, which finds them in the import cache.
|
|
388
|
-
//
|
|
389
|
-
//
|
|
396
|
+
// draw to the next vsync, which finds them in the import cache. Not
|
|
397
|
+
// when the previous vsync brought new frames too: frames arriving at
|
|
398
|
+
// every vsync (60 fps, or a display gone down to 30 Hz) leave no vsync
|
|
399
|
+
// free for the draw, and the next frame would replace this one before
|
|
400
|
+
// it is shown. Never twice running either, for the same reason.
|
|
390
401
|
lastImportedVersion.value = framesVersion;
|
|
391
402
|
if (
|
|
392
403
|
!drawDeferred.value &&
|
|
404
|
+
!importedBefore &&
|
|
393
405
|
currentFrame.value !== null &&
|
|
394
406
|
frameGeneration.value === generation
|
|
395
407
|
) {
|
package/src/yuvBlit.ts
CHANGED
|
@@ -30,6 +30,8 @@ type YuvBlit = {
|
|
|
30
30
|
sampler: GPUSampler;
|
|
31
31
|
module: GPUShaderModule;
|
|
32
32
|
pipelines: Map<GPUTextureFormat, GPURenderPipeline>;
|
|
33
|
+
/** The whole frame, as is: the uniforms of blitNv12Frame, written once. */
|
|
34
|
+
wholeFrame?: GPUBuffer;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
// An ImageReader holds four buffers (GLFrameExtractor.READER_IMAGES); the
|
|
@@ -329,3 +331,67 @@ export const blitYuvFrame = (
|
|
|
329
331
|
entry.wrapper.endAccess()?.fence.destroy();
|
|
330
332
|
}
|
|
331
333
|
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* iOS: draws a whole NV12 decoder buffer into `target`, converting YUV to RGB
|
|
337
|
+
* with the buffer's YCbCr matrix and range, in its decoded orientation (the
|
|
338
|
+
* frame's rotation applies when it is drawn, as for BGRA frames). Submits the
|
|
339
|
+
* draw. AVAssetReader hands out buffers from a pool rather than a fixed few,
|
|
340
|
+
* so each frame is imported for this draw alone, unlike Android's: the import
|
|
341
|
+
* ends right after the submit (Dawn keeps the surface for the in-flight
|
|
342
|
+
* draw), while the caller holds the frame's lease until the draw completed.
|
|
343
|
+
*/
|
|
344
|
+
export const blitNv12Frame = (
|
|
345
|
+
device: GPUDevice,
|
|
346
|
+
source: NativeVideoFrame,
|
|
347
|
+
target: GPUTexture,
|
|
348
|
+
): void => {
|
|
349
|
+
"worklet";
|
|
350
|
+
const blit = getYuvBlit(device);
|
|
351
|
+
const pipeline = getPipeline(device, blit, target.format);
|
|
352
|
+
if (!blit.wholeFrame) {
|
|
353
|
+
blit.wholeFrame = device.createBuffer({
|
|
354
|
+
label: "video NV12 blit uniforms",
|
|
355
|
+
size: 16,
|
|
356
|
+
usage: videoUniformBufferUsage,
|
|
357
|
+
});
|
|
358
|
+
device.queue.writeBuffer(blit.wholeFrame, 0, new Float32Array([1, 1, 0, 0]));
|
|
359
|
+
}
|
|
360
|
+
const external = device.importExternalTexture({
|
|
361
|
+
label: "video decoder buffer",
|
|
362
|
+
source,
|
|
363
|
+
sourceColorSpace,
|
|
364
|
+
});
|
|
365
|
+
try {
|
|
366
|
+
const encoder = device.createCommandEncoder({ label: "video NV12 blit" });
|
|
367
|
+
const pass = encoder.beginRenderPass({
|
|
368
|
+
label: "video NV12 blit",
|
|
369
|
+
colorAttachments: [
|
|
370
|
+
{
|
|
371
|
+
view: target.createView(),
|
|
372
|
+
loadOp: "clear",
|
|
373
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
374
|
+
storeOp: "store",
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
});
|
|
378
|
+
pass.setPipeline(pipeline);
|
|
379
|
+
pass.setBindGroup(
|
|
380
|
+
0,
|
|
381
|
+
device.createBindGroup({
|
|
382
|
+
label: "video NV12 blit",
|
|
383
|
+
layout: blit.layout,
|
|
384
|
+
entries: [
|
|
385
|
+
{ binding: 0, resource: external },
|
|
386
|
+
{ binding: 1, resource: blit.sampler },
|
|
387
|
+
{ binding: 2, resource: { buffer: blit.wholeFrame } },
|
|
388
|
+
],
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
pass.draw(3);
|
|
392
|
+
pass.end();
|
|
393
|
+
device.queue.submit([encoder.finish()]);
|
|
394
|
+
} finally {
|
|
395
|
+
(external as GPUExternalTexture & { destroy?: () => void }).destroy?.();
|
|
396
|
+
}
|
|
397
|
+
};
|
|
File without changes
|