react-native-skia-webgpu-video 0.1.0-beta.3 → 0.1.0-beta.4

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 (65) hide show
  1. package/README.md +39 -32
  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 +1 -1
  14. package/docs/MIGRATION.md +1 -1
  15. package/docs/UPSTREAM_NOTES.md +15 -2
  16. package/lib/module/canvas.js +3 -5
  17. package/lib/module/canvas.js.map +1 -1
  18. package/lib/module/exportFrames.js +6 -8
  19. package/lib/module/exportFrames.js.map +1 -1
  20. package/lib/module/exportTarget.js +183 -40
  21. package/lib/module/exportTarget.js.map +1 -1
  22. package/lib/module/exportVideoComposition.js +53 -12
  23. package/lib/module/exportVideoComposition.js.map +1 -1
  24. package/lib/module/frameInterop.js +9 -12
  25. package/lib/module/frameInterop.js.map +1 -1
  26. package/lib/module/gpu.js +0 -2
  27. package/lib/module/gpu.js.map +1 -1
  28. package/lib/module/gpuDevice.js +12 -0
  29. package/lib/module/gpuDevice.js.map +1 -1
  30. package/lib/module/previewLifecycle.js +0 -1
  31. package/lib/module/previewLifecycle.js.map +1 -1
  32. package/lib/module/yuvBlit.js +28 -21
  33. package/lib/module/yuvBlit.js.map +1 -1
  34. package/lib/typescript/src/canvas.d.ts.map +1 -1
  35. package/lib/typescript/src/exportFrames.d.ts +3 -2
  36. package/lib/typescript/src/exportFrames.d.ts.map +1 -1
  37. package/lib/typescript/src/exportTarget.d.ts +53 -14
  38. package/lib/typescript/src/exportTarget.d.ts.map +1 -1
  39. package/lib/typescript/src/exportVideoComposition.d.ts +2 -1
  40. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -1
  41. package/lib/typescript/src/frameInterop.d.ts.map +1 -1
  42. package/lib/typescript/src/gpu.d.ts.map +1 -1
  43. package/lib/typescript/src/gpuDevice.d.ts +5 -1
  44. package/lib/typescript/src/gpuDevice.d.ts.map +1 -1
  45. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -1
  46. package/lib/typescript/src/types.d.ts +10 -5
  47. package/lib/typescript/src/types.d.ts.map +1 -1
  48. package/lib/typescript/src/yuvBlit.d.ts +16 -14
  49. package/lib/typescript/src/yuvBlit.d.ts.map +1 -1
  50. package/package.json +6 -6
  51. package/patches/react-native-skia+3.7.4.patch +146 -0
  52. package/scripts/skia-canvas-patch.json +22 -258
  53. package/src/canvas.ts +3 -7
  54. package/src/exportFrames.ts +8 -9
  55. package/src/exportTarget.ts +217 -56
  56. package/src/exportVideoComposition.ts +87 -26
  57. package/src/frameInterop.ts +9 -11
  58. package/src/gpu.ts +0 -2
  59. package/src/gpuDevice.ts +26 -1
  60. package/src/previewLifecycle.ts +0 -1
  61. package/src/types.ts +10 -5
  62. package/src/yuvBlit.ts +43 -28
  63. package/android/cpp/EncoderTargets.cpp +0 -226
  64. package/android/cpp/EncoderTargets.h +0 -70
  65. package/patches/react-native-skia+3.7.1.patch +0 -502
package/src/gpuDevice.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  importDevice,
9
9
  installWebGPU,
10
10
  } from "react-native-webgpu";
11
- import type { NativeVideoFrame } from "react-native-webgpu";
11
+ import type { NativeVideoFrame, RNCanvasContext } from "react-native-webgpu";
12
12
  import RNSkiaVideoModule from "./RNSkiaVideoModule";
13
13
 
14
14
  // Evaluate on the RN runtime. NativeObject serializers carry these objects to
@@ -61,11 +61,36 @@ export const createNativeVideoGpuFrame = (
61
61
  return nativeWebGPU.createVideoFrameFromNativeBuffer(pointer);
62
62
  };
63
63
 
64
+ /** Android: a canvas context presenting to a native window that is not a
65
+ * view, such as an encoder's input Surface. */
66
+ export const createNativeWindowGpuContext = (
67
+ window: bigint,
68
+ width: number,
69
+ height: number,
70
+ ): RNCanvasContext => {
71
+ "worklet";
72
+ if (
73
+ typeof nativeWebGPU?.MakeWebGPUCanvasContextFromNativeWindow !== "function"
74
+ )
75
+ throw new Error(
76
+ "React Native WebGPU cannot present to a native window; 0.19.0 or later is required",
77
+ );
78
+ return nativeWebGPU.MakeWebGPUCanvasContextFromNativeWindow(
79
+ window,
80
+ width,
81
+ height,
82
+ );
83
+ };
84
+
64
85
  export const nativeVideoTextureUsage =
65
86
  GPUTextureUsage.RENDER_ATTACHMENT |
66
87
  GPUTextureUsage.TEXTURE_BINDING |
67
88
  GPUTextureUsage.COPY_SRC;
68
89
 
90
+ // An encoder's input window only receives copies of the frames Skia drew.
91
+ export const encoderWindowTextureUsage =
92
+ GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST;
93
+
69
94
  // CPU RGBA uploads additionally need COPY_DST. Keep the native-buffer blit's
70
95
  // descriptor unchanged: that path renders into its destination attachment.
71
96
  export const rgbaVideoTextureUsage =
@@ -33,7 +33,6 @@ export const disposePreviewResources = (resources: PreviewResources) => {
33
33
  frame.dispose?.();
34
34
  resources.frames.shift();
35
35
  }
36
- resources.canvas?.dispose();
37
36
  resources.canvas = null;
38
37
  resources.surface?.dispose();
39
38
  resources.surface = null;
package/src/types.ts CHANGED
@@ -421,14 +421,19 @@ export type VideoEncoder = {
421
421
  */
422
422
  encodeFrame(texture: unknown, time: number): void;
423
423
  /**
424
- * Lends the next pool buffer to draw into on the GPU: a CVPixelBuffer on
425
- * iOS, an RGBA AHardwareBuffer on Android. Pass it back as a native-buffer
426
- * frame (on Android with the sync-fds ending its GPU access, in `fences`),
427
- * or release it. One at a time.
424
+ * iOS: lends the next pool buffer (a CVPixelBuffer) to draw into on the
425
+ * GPU. Pass it back as a native-buffer frame, or release it. One at a time.
428
426
  */
429
427
  acquireFrameBuffer?(): bigint;
430
- /** Returns a lent buffer that will not be encoded. */
428
+ /** iOS: returns a lent buffer that will not be encoded. */
431
429
  releaseFrameBuffer?(): void;
430
+ /**
431
+ * Android: the codec's input Surface, as an ANativeWindow pointer for a
432
+ * WebGPU canvas context to present to. Each frame is then encoded as
433
+ * `{ kind: "window", present }`: the encoder sets the frame's time on the
434
+ * window and calls `present`, which presents the context.
435
+ */
436
+ acquireInputWindow?(): bigint;
432
437
  /*
433
438
  * Finish writing the video to the output file.
434
439
  */
package/src/yuvBlit.ts CHANGED
@@ -68,40 +68,52 @@ struct VsOut { @builtin(position) position: vec4f, @location(0) uv: vec2f };
68
68
  // primaries to sRGB, and every export brightens the midtones again.
69
69
  const sourceColorSpace = { primaries: "srgb", transfer: "srgb" } as const;
70
70
 
71
- /** The corner of a decoder buffer's crop rectangle once the buffer is turned
72
- * clockwise for display: the blit takes its source origin in that turned
73
- * space. Throws unless the rectangle lies in the buffer and turns into the
74
- * frame's display size. */
75
- export const yuvFrameOrigin = (
71
+ /** A decoder buffer's crop rectangle once the buffer is turned clockwise for
72
+ * display: the blit samples that upright region, and draws it at the size of
73
+ * its target, which a capped or explicit frame size makes smaller. Throws
74
+ * unless the rectangle lies in the buffer. */
75
+ export const yuvFrameRegion = (
76
76
  region: { x: number; y: number; width: number; height: number },
77
77
  rotation: number,
78
78
  buffer: { width: number; height: number },
79
- frame: { width: number; height: number },
80
- ): { x: number; y: number } => {
79
+ ): { x: number; y: number; width: number; height: number } => {
81
80
  "worklet";
82
81
  const turned = rotation === 90 || rotation === 270;
83
82
  if (
84
83
  (rotation !== 0 && !turned && rotation !== 180) ||
85
84
  region.x < 0 ||
86
85
  region.y < 0 ||
86
+ region.width <= 0 ||
87
+ region.height <= 0 ||
87
88
  region.x + region.width > buffer.width ||
88
- region.y + region.height > buffer.height ||
89
- (turned ? region.height : region.width) !== frame.width ||
90
- (turned ? region.width : region.height) !== frame.height
89
+ region.y + region.height > buffer.height
91
90
  ) {
92
91
  throw new Error("Native video buffer region or YUV layout mismatch");
93
92
  }
94
- const { width, height } = buffer;
93
+ const width = turned ? region.height : region.width;
94
+ const height = turned ? region.width : region.height;
95
95
  if (rotation === 90)
96
- return { x: height - region.y - region.height, y: region.x };
96
+ return {
97
+ x: buffer.height - region.y - region.height,
98
+ y: region.x,
99
+ width,
100
+ height,
101
+ };
97
102
  if (rotation === 180)
98
103
  return {
99
- x: width - region.x - region.width,
100
- y: height - region.y - region.height,
104
+ x: buffer.width - region.x - region.width,
105
+ y: buffer.height - region.y - region.height,
106
+ width,
107
+ height,
101
108
  };
102
109
  if (rotation === 270)
103
- return { x: region.y, y: width - region.x - region.width };
104
- return { x: region.x, y: region.y };
110
+ return {
111
+ x: region.y,
112
+ y: buffer.width - region.x - region.width,
113
+ width,
114
+ height,
115
+ };
116
+ return { x: region.x, y: region.y, width, height };
105
117
  };
106
118
 
107
119
  const getYuvBlit = (device: GPUDevice): YuvBlit => {
@@ -251,10 +263,11 @@ const importYuvBuffer = (
251
263
  };
252
264
 
253
265
  /**
254
- * Draws the upright `width`×`height` region at `origin` of a decoder buffer
255
- * into `target`, converting YUV to RGB; the buffer is imported on its first
256
- * frame. Submits the draw: the caller waits for it before releasing the
257
- * frame's lease or `source`, as after copyExternalImageToTexture.
266
+ * Draws the upright `region` of a decoder buffer (see yuvFrameRegion) into
267
+ * the whole of `target`, converting YUV to RGB and scaling it to the target's
268
+ * size; the buffer is imported on its first frame. Submits the draw: the
269
+ * caller waits for it before releasing the frame's lease or `source`, as
270
+ * after copyExternalImageToTexture.
258
271
  */
259
272
  export const blitYuvFrame = (
260
273
  device: GPUDevice,
@@ -262,9 +275,7 @@ export const blitYuvFrame = (
262
275
  producer: number,
263
276
  source: NativeVideoFrame,
264
277
  rotation: number,
265
- origin: { x: number; y: number },
266
- width: number,
267
- height: number,
278
+ region: { x: number; y: number; width: number; height: number },
268
279
  target: GPUTexture,
269
280
  ): void => {
270
281
  "worklet";
@@ -297,10 +308,10 @@ export const blitYuvFrame = (
297
308
  const frameWidth = turned ? source.height : source.width;
298
309
  const frameHeight = turned ? source.width : source.height;
299
310
  const mapping = [
300
- width / frameWidth,
301
- height / frameHeight,
302
- origin.x / frameWidth,
303
- origin.y / frameHeight,
311
+ region.width / frameWidth,
312
+ region.height / frameHeight,
313
+ region.x / frameWidth,
314
+ region.y / frameHeight,
304
315
  ];
305
316
  if (mapping.some((value, i) => value !== entry.mapping[i])) {
306
317
  device.queue.writeBuffer(entry.uniforms, 0, new Float32Array(mapping));
@@ -355,7 +366,11 @@ export const blitNv12Frame = (
355
366
  size: 16,
356
367
  usage: videoUniformBufferUsage,
357
368
  });
358
- device.queue.writeBuffer(blit.wholeFrame, 0, new Float32Array([1, 1, 0, 0]));
369
+ device.queue.writeBuffer(
370
+ blit.wholeFrame,
371
+ 0,
372
+ new Float32Array([1, 1, 0, 0]),
373
+ );
359
374
  }
360
375
  const external = device.importExternalTexture({
361
376
  label: "video decoder buffer",
@@ -1,226 +0,0 @@
1
- #include "EncoderTargets.h"
2
- #include "RNSVMemoryBudget.h"
3
- #include <GLES2/gl2ext.h>
4
- #include <cerrno>
5
- #include <poll.h>
6
- #include <stdexcept>
7
- #include <string>
8
- #include <unistd.h>
9
-
10
- namespace RNSkiaVideo {
11
- namespace {
12
- constexpr EGLTimeKHR kWaitNanoseconds = 5'000'000'000ULL;
13
- constexpr int kFenceTimeoutMs = 5000;
14
-
15
- struct EglFunctions {
16
- PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC clientBuffer;
17
- PFNEGLCREATEIMAGEKHRPROC createImage;
18
- PFNEGLDESTROYIMAGEKHRPROC destroyImage;
19
- PFNGLEGLIMAGETARGETTEXTURE2DOESPROC imageTarget;
20
- PFNEGLCREATESYNCKHRPROC createSync;
21
- PFNEGLCLIENTWAITSYNCKHRPROC clientWaitSync;
22
- PFNEGLDESTROYSYNCKHRPROC destroySync;
23
- };
24
-
25
- const EglFunctions& egl() {
26
- static const EglFunctions functions = [] {
27
- EglFunctions f{
28
- reinterpret_cast<PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC>(
29
- eglGetProcAddress("eglGetNativeClientBufferANDROID")),
30
- reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR")),
31
- reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR")),
32
- reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
33
- eglGetProcAddress("glEGLImageTargetTexture2DOES")),
34
- reinterpret_cast<PFNEGLCREATESYNCKHRPROC>(eglGetProcAddress("eglCreateSyncKHR")),
35
- reinterpret_cast<PFNEGLCLIENTWAITSYNCKHRPROC>(eglGetProcAddress("eglClientWaitSyncKHR")),
36
- reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC>(eglGetProcAddress("eglDestroySyncKHR")),
37
- };
38
- if (!f.clientBuffer || !f.createImage || !f.destroyImage || !f.imageTarget ||
39
- !f.createSync || !f.clientWaitSync || !f.destroySync)
40
- throw std::runtime_error("EGL hardware buffer or fence entry points are unavailable");
41
- return f;
42
- }();
43
- return functions;
44
- }
45
-
46
- /** Waits for a sync-fd to signal, then closes it. */
47
- void waitFence(int& fd) {
48
- if (fd < 0) return; // Already signaled.
49
- pollfd entry{fd, POLLIN, 0};
50
- int result;
51
- do {
52
- result = poll(&entry, 1, kFenceTimeoutMs);
53
- } while (result < 0 && errno == EINTR);
54
- const int error = errno;
55
- close(fd);
56
- fd = -1;
57
- if (result == 0) throw std::runtime_error("GPU export frame did not complete within 5 seconds");
58
- if (result < 0) throw std::runtime_error("Cannot wait on GPU export fence: errno " + std::to_string(error));
59
- if (entry.revents & (POLLERR | POLLNVAL))
60
- throw std::runtime_error("Invalid GPU export fence");
61
- }
62
- } // namespace
63
-
64
- EncoderTargets::EncoderTargets(int width, int height) : width(width), height(height) {
65
- if (width <= 0 || height <= 0 || width > 16384 || height > 16384)
66
- throw std::invalid_argument("Invalid encoder buffer dimensions");
67
- }
68
-
69
- EncoderTargets::~EncoderTargets() {
70
- // clear() could not run with the encoder context current: its textures went
71
- // with that context. Images, syncs and buffers need no current context.
72
- for (auto& slot : slots) {
73
- if (slot.reads != EGL_NO_SYNC_KHR) egl().destroySync(slot.display, slot.reads);
74
- if (slot.image != EGL_NO_IMAGE_KHR) egl().destroyImage(slot.display, slot.image);
75
- if (slot.buffer) AHardwareBuffer_release(slot.buffer);
76
- if (slot.reservation) MemoryBudget::instance().release(slot.reservation);
77
- }
78
- }
79
-
80
- bool EncoderTargets::waitReads(Slot& slot, EGLTimeKHR timeout) {
81
- if (slot.reads == EGL_NO_SYNC_KHR) return true;
82
- const auto status = egl().clientWaitSync(slot.display, slot.reads, 0, timeout);
83
- if (status == EGL_TIMEOUT_EXPIRED_KHR) return false;
84
- if (status != EGL_CONDITION_SATISFIED_KHR)
85
- throw std::runtime_error("Cannot wait on encoder buffer reads: EGL error " + std::to_string(eglGetError()));
86
- egl().destroySync(slot.display, slot.reads);
87
- slot.reads = EGL_NO_SYNC_KHR;
88
- return true;
89
- }
90
-
91
- void EncoderTargets::allocate(Slot& slot) {
92
- slot.reservation = MemoryBudget::instance().reserve(
93
- static_cast<uint64_t>(width) * height * 4, "android encoder ahardwarebuffer");
94
- AHardwareBuffer_Desc descriptor{};
95
- descriptor.width = width;
96
- descriptor.height = height;
97
- descriptor.layers = 1;
98
- descriptor.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
99
- descriptor.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;
100
- const int result = AHardwareBuffer_allocate(&descriptor, &slot.buffer);
101
- if (result != 0 || !slot.buffer)
102
- throw std::runtime_error("Encoder AHardwareBuffer allocation failed: " + std::to_string(result));
103
- AHardwareBuffer_Desc actual{};
104
- AHardwareBuffer_describe(slot.buffer, &actual);
105
- if (actual.width != descriptor.width || actual.height != descriptor.height ||
106
- actual.format != descriptor.format || actual.stride < descriptor.width ||
107
- (actual.usage & descriptor.usage) != descriptor.usage)
108
- throw std::runtime_error("Invalid encoder AHardwareBuffer layout");
109
- MemoryBudget::instance().resize(slot.reservation, static_cast<uint64_t>(actual.stride) * height * 4,
110
- "android encoder ahardwarebuffer padded stride");
111
- }
112
-
113
- void EncoderTargets::bindTexture(Slot& slot) {
114
- if (slot.texture) return;
115
- const auto display = eglGetCurrentDisplay();
116
- if (display == EGL_NO_DISPLAY || eglGetCurrentContext() == EGL_NO_CONTEXT)
117
- throw std::runtime_error("Encoder buffer import needs the encoder EGL context");
118
- const EGLint attributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
119
- slot.display = display;
120
- slot.image = egl().createImage(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
121
- egl().clientBuffer(slot.buffer), attributes);
122
- if (slot.image == EGL_NO_IMAGE_KHR)
123
- throw std::runtime_error("Cannot import encoder AHardwareBuffer: EGL error " + std::to_string(eglGetError()));
124
- GLint previous = 0;
125
- glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous);
126
- glGenTextures(1, &slot.texture);
127
- glBindTexture(GL_TEXTURE_2D, slot.texture);
128
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
129
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
130
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
131
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
132
- egl().imageTarget(GL_TEXTURE_2D, slot.image);
133
- glBindTexture(GL_TEXTURE_2D, previous);
134
- const auto error = glGetError();
135
- if (error != GL_NO_ERROR)
136
- throw std::runtime_error("Encoder AHardwareBuffer texture binding failed: GL error " + std::to_string(error));
137
- }
138
-
139
- AHardwareBuffer* EncoderTargets::lend() {
140
- if (lent >= 0 || encoding >= 0)
141
- throw std::runtime_error("Previous encoder frame buffer was neither encoded nor released");
142
- // A buffer whose last frame the encoder already sampled.
143
- for (size_t i = 0; i < slots.size(); ++i) {
144
- if (waitReads(slots[i], 0)) {
145
- lent = static_cast<int>(i);
146
- return slots[i].buffer;
147
- }
148
- }
149
- if (slots.size() < kCapacity) {
150
- slots.emplace_back();
151
- try {
152
- allocate(slots.back());
153
- } catch (...) {
154
- auto& slot = slots.back();
155
- if (slot.buffer) AHardwareBuffer_release(slot.buffer);
156
- if (slot.reservation) MemoryBudget::instance().release(slot.reservation);
157
- slots.pop_back();
158
- throw;
159
- }
160
- lent = static_cast<int>(slots.size() - 1);
161
- return slots.back().buffer;
162
- }
163
- for (size_t i = 0; i < slots.size(); ++i) {
164
- if (waitReads(slots[i], kWaitNanoseconds)) {
165
- lent = static_cast<int>(i);
166
- return slots[i].buffer;
167
- }
168
- }
169
- throw std::runtime_error("Encoder buffers stayed in use for 5 seconds");
170
- }
171
-
172
- void EncoderTargets::giveBack() { lent = -1; }
173
-
174
- bool EncoderTargets::isLent(const AHardwareBuffer* buffer) const {
175
- return lent >= 0 && buffer && slots[lent].buffer == buffer;
176
- }
177
-
178
- GLuint EncoderTargets::beginEncode(const AHardwareBuffer* buffer, std::vector<int>& fences) {
179
- if (!isLent(buffer)) throw std::runtime_error("Encoder frame buffer is not the one lent");
180
- // The renderer's writes, and its release of the buffer, end before GL reads.
181
- for (auto& fence : fences) waitFence(fence);
182
- auto& slot = slots[lent];
183
- bindTexture(slot);
184
- encoding = lent;
185
- lent = -1;
186
- return slot.texture;
187
- }
188
-
189
- void EncoderTargets::finishEncode() {
190
- if (encoding < 0) return;
191
- auto& slot = slots[encoding];
192
- encoding = -1;
193
- const auto display = eglGetCurrentDisplay();
194
- slot.reads = display == EGL_NO_DISPLAY ? EGL_NO_SYNC_KHR
195
- : egl().createSync(display, EGL_SYNC_FENCE_KHR, nullptr);
196
- if (slot.reads == EGL_NO_SYNC_KHR) {
197
- // No fence to poll later: complete the reads now.
198
- glFinish();
199
- return;
200
- }
201
- slot.display = display;
202
- glFlush();
203
- }
204
-
205
- void EncoderTargets::clear() {
206
- for (auto& slot : slots) {
207
- if (!waitReads(slot, kWaitNanoseconds)) glFinish();
208
- if (slot.reads != EGL_NO_SYNC_KHR) {
209
- egl().destroySync(slot.display, slot.reads);
210
- slot.reads = EGL_NO_SYNC_KHR;
211
- }
212
- if (slot.texture) glDeleteTextures(1, &slot.texture);
213
- slot.texture = 0;
214
- if (slot.image != EGL_NO_IMAGE_KHR) egl().destroyImage(slot.display, slot.image);
215
- slot.image = EGL_NO_IMAGE_KHR;
216
- if (slot.buffer) AHardwareBuffer_release(slot.buffer);
217
- slot.buffer = nullptr;
218
- if (slot.reservation) MemoryBudget::instance().release(slot.reservation);
219
- slot.reservation = 0;
220
- }
221
- slots.clear();
222
- lent = -1;
223
- encoding = -1;
224
- }
225
-
226
- } // namespace RNSkiaVideo
@@ -1,70 +0,0 @@
1
- #pragma once
2
-
3
- #include <EGL/egl.h>
4
- #include <EGL/eglext.h>
5
- #include <GLES2/gl2.h>
6
- #include <android/hardware_buffer.h>
7
- #include <array>
8
- #include <cstdint>
9
- #include <vector>
10
-
11
- namespace RNSkiaVideo {
12
-
13
- /**
14
- * RGBA AHardwareBuffers lent to JS for the GPU export: Skia renders into one
15
- * through WebGPU, then the encoder's EGL context samples it into the codec's
16
- * input Surface. No CPU readback or upload. Bounded, one lent at a time.
17
- *
18
- * Methods that touch GL objects require the encoder's EGL context current.
19
- * A buffer is lent again only once the GL reads of its previous frame are
20
- * complete; the renderer's own completion is the sync-fd it hands back.
21
- */
22
- class EncoderTargets {
23
- public:
24
- static constexpr size_t kCapacity = 3;
25
-
26
- EncoderTargets(int width, int height);
27
- ~EncoderTargets();
28
- EncoderTargets(const EncoderTargets&) = delete;
29
- EncoderTargets& operator=(const EncoderTargets&) = delete;
30
-
31
- /** A buffer without pending GL reads; allocates up to kCapacity. */
32
- AHardwareBuffer* lend();
33
- /** Returns the lent buffer unencoded. Its renderer must have finished. */
34
- void giveBack();
35
- bool isLent(const AHardwareBuffer* buffer) const;
36
- /**
37
- * Waits on the renderer's sync-fds (taking ownership of each, -1 meaning
38
- * already signaled) and returns the lent buffer's texture, bound to its
39
- * EGLImage in the current context. The buffer stays reserved until
40
- * finishEncode().
41
- */
42
- GLuint beginEncode(const AHardwareBuffer* buffer, std::vector<int>& fences);
43
- /** After the draw sampling the texture, with the encoder context current. */
44
- void finishEncode();
45
- /** Encoder context current: waits for pending reads and frees everything. */
46
- void clear();
47
- bool empty() const { return slots.empty(); }
48
-
49
- private:
50
- struct Slot {
51
- AHardwareBuffer* buffer = nullptr;
52
- uint64_t reservation = 0;
53
- EGLDisplay display = EGL_NO_DISPLAY;
54
- EGLImageKHR image = EGL_NO_IMAGE_KHR;
55
- GLuint texture = 0;
56
- EGLSyncKHR reads = EGL_NO_SYNC_KHR;
57
- };
58
- int width;
59
- int height;
60
- std::vector<Slot> slots;
61
- // The lent slot, then the slot being encoded; -1 when none.
62
- int lent = -1;
63
- int encoding = -1;
64
-
65
- bool waitReads(Slot& slot, EGLTimeKHR timeout);
66
- void allocate(Slot& slot);
67
- void bindTexture(Slot& slot);
68
- };
69
-
70
- } // namespace RNSkiaVideo