react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.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/LICENSE +22 -0
- package/README.md +303 -2
- package/android/BUILD_VALIDATION.md +62 -0
- package/android/CMakeLists.txt +99 -0
- package/android/build-validation.json +262 -0
- package/android/build.gradle +185 -0
- package/android/cpp/EncoderTargets.cpp +226 -0
- package/android/cpp/EncoderTargets.h +70 -0
- package/android/cpp/JNIHelpers.cpp +15 -0
- package/android/cpp/JNIHelpers.h +20 -0
- package/android/cpp/NativeEventDispatcher.cpp +59 -0
- package/android/cpp/NativeEventDispatcher.h +31 -0
- package/android/cpp/NativeHardwareBuffer.cpp +397 -0
- package/android/cpp/NativeHardwareBuffer.h +34 -0
- package/android/cpp/NativeRgbaBuffer.cpp +45 -0
- package/android/cpp/NativeRgbaBuffer.h +9 -0
- package/android/cpp/ReturnedVideoFrames.h +37 -0
- package/android/cpp/RgbaFrameLease.h +53 -0
- package/android/cpp/VideoCapabilities.cpp +87 -0
- package/android/cpp/VideoCapabilities.h +44 -0
- package/android/cpp/VideoComposition.cpp +170 -0
- package/android/cpp/VideoComposition.h +36 -0
- package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
- package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
- package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
- package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
- package/android/cpp/VideoEncoderHostObject.cpp +318 -0
- package/android/cpp/VideoEncoderHostObject.h +76 -0
- package/android/cpp/VideoFrame.cpp +293 -0
- package/android/cpp/VideoFrame.h +27 -0
- package/android/cpp/VideoPlayer.cpp +96 -0
- package/android/cpp/VideoPlayer.h +52 -0
- package/android/cpp/VideoPlayerHostObject.cpp +225 -0
- package/android/cpp/VideoPlayerHostObject.h +43 -0
- package/android/cpp/cpp-adapter.cpp +316 -0
- package/android/gradle.properties +5 -0
- package/android/instrumentation-inventory.json +178 -0
- package/android/proguard-rules.pro +4 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
- package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
- package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
- package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
- package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
- package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
- package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
- package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
- package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
- package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +35 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
- package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +83 -0
- package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
- package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
- package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
- package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +772 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +354 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
- package/cpp/RNSVCheckedSizes.h +54 -0
- package/cpp/RNSVEventEmitter.cpp +199 -0
- package/cpp/RNSVEventEmitter.h +31 -0
- package/cpp/RNSVHostObject.cpp +79 -0
- package/cpp/RNSVHostObject.h +67 -0
- package/cpp/RNSVMemoryBudget.cpp +68 -0
- package/cpp/RNSVMemoryBudget.h +100 -0
- package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
- package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
- package/docs/ANDROID_DEVICE_306.md +95 -0
- package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
- package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
- package/docs/BENCHMARKS.md +368 -0
- package/docs/IOS_DEVICE_TESTS.md +115 -0
- package/docs/IOS_SIMULATOR_TESTS.md +74 -0
- package/docs/MIGRATION.md +82 -0
- package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
- package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
- package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
- package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
- package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
- package/docs/PERFORMANCE_SIMULATOR.md +89 -0
- package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
- package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
- package/docs/UPSTREAM_NOTES.md +188 -0
- package/docs/assets/banner.svg +26 -0
- package/examples/webgpu-overlay.ts +147 -0
- package/ios/AVAssetTrackUtils.h +24 -0
- package/ios/AudioCompositionUtils.h +27 -0
- package/ios/AudioCompositionUtils.mm +113 -0
- package/ios/DecoderWindow.h +52 -0
- package/ios/NativeResourceStats.h +32 -0
- package/ios/RNSVColorSpace.h +50 -0
- package/ios/RNSVJSIUtils.h +46 -0
- package/ios/RNSVScopedPixelBuffer.h +17 -0
- package/ios/RNSVVideoPlayer.h +46 -0
- package/ios/RNSVVideoPlayer.mm +410 -0
- package/ios/ReactNativeSkiaVideo.h +8 -0
- package/ios/ReactNativeSkiaVideo.mm +310 -0
- package/ios/SeekPolicy.h +27 -0
- package/ios/VideoComposition.h +148 -0
- package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
- package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
- package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
- package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
- package/ios/VideoCompositionItemDecoder.h +60 -0
- package/ios/VideoCompositionItemDecoder.mm +480 -0
- package/ios/VideoEncoderHostObject.h +83 -0
- package/ios/VideoEncoderHostObject.mm +706 -0
- package/ios/VideoFrame.h +66 -0
- package/ios/VideoFrame.mm +108 -0
- package/ios/VideoPlayerHostObject.h +52 -0
- package/ios/VideoPlayerHostObject.mm +332 -0
- package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
- package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
- package/lib/module/RNSkiaVideoModule.js +20 -0
- package/lib/module/RNSkiaVideoModule.js.map +1 -0
- package/lib/module/canvas.js +18 -0
- package/lib/module/canvas.js.map +1 -0
- package/lib/module/exportCache.js +22 -0
- package/lib/module/exportCache.js.map +1 -0
- package/lib/module/exportFrames.js +338 -0
- package/lib/module/exportFrames.js.map +1 -0
- package/lib/module/exportQueue.js +52 -0
- package/lib/module/exportQueue.js.map +1 -0
- package/lib/module/exportTarget.js +137 -0
- package/lib/module/exportTarget.js.map +1 -0
- package/lib/module/exportVideoComposition.js +492 -0
- package/lib/module/exportVideoComposition.js.map +1 -0
- package/lib/module/frameInterop.js +532 -0
- package/lib/module/frameInterop.js.map +1 -0
- package/lib/module/gpu.js +222 -0
- package/lib/module/gpu.js.map +1 -0
- package/lib/module/gpuDevice.js +54 -0
- package/lib/module/gpuDevice.js.map +1 -0
- package/lib/module/index.js +36 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/memory.js +38 -0
- package/lib/module/memory.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/previewLifecycle.js +52 -0
- package/lib/module/previewLifecycle.js.map +1 -0
- package/lib/module/runtimeYield.js +10 -0
- package/lib/module/runtimeYield.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/useEventListener.js +18 -0
- package/lib/module/utils/useEventListener.js.map +1 -0
- package/lib/module/videoCompositionPlayer.js +348 -0
- package/lib/module/videoCompositionPlayer.js.map +1 -0
- package/lib/module/videoFrameImage.js +185 -0
- package/lib/module/videoFrameImage.js.map +1 -0
- package/lib/module/videoPlayer.js +172 -0
- package/lib/module/videoPlayer.js.map +1 -0
- package/lib/module/yuvBlit.js +299 -0
- package/lib/module/yuvBlit.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
- package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
- package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
- package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
- package/lib/typescript/src/canvas.d.ts +4 -0
- package/lib/typescript/src/canvas.d.ts.map +1 -0
- package/lib/typescript/src/exportCache.d.ts +6 -0
- package/lib/typescript/src/exportCache.d.ts.map +1 -0
- package/lib/typescript/src/exportFrames.d.ts +57 -0
- package/lib/typescript/src/exportFrames.d.ts.map +1 -0
- package/lib/typescript/src/exportQueue.d.ts +5 -0
- package/lib/typescript/src/exportQueue.d.ts.map +1 -0
- package/lib/typescript/src/exportTarget.d.ts +28 -0
- package/lib/typescript/src/exportTarget.d.ts.map +1 -0
- package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
- package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
- package/lib/typescript/src/frameInterop.d.ts +17 -0
- package/lib/typescript/src/frameInterop.d.ts.map +1 -0
- package/lib/typescript/src/gpu.d.ts +39 -0
- package/lib/typescript/src/gpu.d.ts.map +1 -0
- package/lib/typescript/src/gpuDevice.d.ts +11 -0
- package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +21 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/memory.d.ts +25 -0
- package/lib/typescript/src/memory.d.ts.map +1 -0
- package/lib/typescript/src/previewLifecycle.d.ts +22 -0
- package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/runtimeYield.d.ts +4 -0
- package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +631 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
- package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
- package/lib/typescript/src/videoCompositionPlayer.d.ts +85 -0
- package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
- package/lib/typescript/src/videoFrameImage.d.ts +60 -0
- package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
- package/lib/typescript/src/videoPlayer.d.ts +99 -0
- package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
- package/lib/typescript/src/yuvBlit.d.ts +50 -0
- package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
- package/package.json +154 -4
- package/patches/react-native-skia+3.7.0.patch +502 -0
- package/react-native-skia-webgpu-video.podspec +35 -0
- package/scripts/apply-skia-canvas-patch.mjs +48 -0
- package/scripts/skia-canvas-patch.json +291 -0
- package/src/NativeReactNativeSkiaVideo.ts +8 -0
- package/src/RNSkiaVideoModule.ts +27 -0
- package/src/canvas.ts +22 -0
- package/src/exportCache.ts +27 -0
- package/src/exportFrames.ts +421 -0
- package/src/exportQueue.ts +65 -0
- package/src/exportTarget.ts +179 -0
- package/src/exportVideoComposition.ts +718 -0
- package/src/frameInterop.ts +684 -0
- package/src/gpu.ts +290 -0
- package/src/gpuDevice.ts +80 -0
- package/src/index.tsx +45 -0
- package/src/memory.ts +48 -0
- package/src/previewLifecycle.ts +60 -0
- package/src/runtimeYield.ts +6 -0
- package/src/types.ts +661 -0
- package/src/utils/useEventListener.ts +22 -0
- package/src/videoCompositionPlayer.ts +520 -0
- package/src/videoFrameImage.ts +205 -0
- package/src/videoPlayer.ts +318 -0
- package/src/yuvBlit.ts +331 -0
|
@@ -0,0 +1,226 @@
|
|
|
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
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#include "JNIHelpers.h"
|
|
2
|
+
|
|
3
|
+
namespace RNSkiaVideo {
|
|
4
|
+
|
|
5
|
+
std::shared_ptr<facebook::react::CallInvoker> JNIHelpers::getCallInvoker() {
|
|
6
|
+
auto clazz = javaClassStatic();
|
|
7
|
+
static const auto getCallInvokerMethod =
|
|
8
|
+
clazz->getStaticMethod<jobject()>("getCallInvoker");
|
|
9
|
+
auto callInvoker =
|
|
10
|
+
static_ref_cast<facebook::react::CallInvokerHolder::javaobject>(
|
|
11
|
+
getCallInvokerMethod(clazz));
|
|
12
|
+
return callInvoker->cthis()->getCallInvoker();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <ReactCommon/CallInvokerHolder.h>
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
|
|
6
|
+
namespace RNSkiaVideo {
|
|
7
|
+
|
|
8
|
+
using namespace facebook;
|
|
9
|
+
using namespace jni;
|
|
10
|
+
|
|
11
|
+
using namespace facebook;
|
|
12
|
+
using namespace jni;
|
|
13
|
+
|
|
14
|
+
struct JNIHelpers : public JavaClass<JNIHelpers> {
|
|
15
|
+
static constexpr auto kJavaDescriptor = "Lcom/azzapp/rnskv/JNIHelpers;";
|
|
16
|
+
|
|
17
|
+
static std::shared_ptr<facebook::react::CallInvoker> getCallInvoker();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#include "NativeEventDispatcher.h"
|
|
2
|
+
#include <atomic>
|
|
3
|
+
#include <mutex>
|
|
4
|
+
#include <unordered_map>
|
|
5
|
+
|
|
6
|
+
namespace RNSkiaVideo {
|
|
7
|
+
using namespace facebook;
|
|
8
|
+
using namespace jni;
|
|
9
|
+
namespace {
|
|
10
|
+
std::atomic<jlong> nextReceiver{1};
|
|
11
|
+
std::mutex receiversMutex;
|
|
12
|
+
std::unordered_map<jlong, std::weak_ptr<JEventReceiver>> receivers;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
jni::local_ref<NativeEventDispatcher>
|
|
16
|
+
NativeEventDispatcher::create(const std::weak_ptr<JEventReceiver>& receiver) {
|
|
17
|
+
const auto id = nextReceiver.fetch_add(1);
|
|
18
|
+
{
|
|
19
|
+
std::lock_guard<std::mutex> lock(receiversMutex);
|
|
20
|
+
receivers.emplace(id, receiver);
|
|
21
|
+
}
|
|
22
|
+
try { return newInstance(id); }
|
|
23
|
+
catch (...) {
|
|
24
|
+
std::lock_guard<std::mutex> lock(receiversMutex);
|
|
25
|
+
receivers.erase(id);
|
|
26
|
+
throw;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void NativeEventDispatcher::registerNatives() {
|
|
31
|
+
javaClassStatic()->registerNatives({makeNativeMethod(
|
|
32
|
+
"nativeDispatchEvent", NativeEventDispatcher::dispatchEvent),
|
|
33
|
+
makeNativeMethod("nativeInvalidateReceiver", NativeEventDispatcher::invalidateReceiver)});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void NativeEventDispatcher::invalidate() const {
|
|
37
|
+
static const auto method = getClass()->getMethod<void()>("invalidate");
|
|
38
|
+
method(self());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void NativeEventDispatcher::dispatchEvent(alias_ref<JClass>, jlong receiverPtr,
|
|
42
|
+
std::string eventName,
|
|
43
|
+
alias_ref<jobject> data) {
|
|
44
|
+
std::shared_ptr<JEventReceiver> receiver;
|
|
45
|
+
{
|
|
46
|
+
std::lock_guard<std::mutex> lock(receiversMutex);
|
|
47
|
+
auto found = receivers.find(receiverPtr);
|
|
48
|
+
if (found != receivers.end()) receiver = found->second.lock();
|
|
49
|
+
}
|
|
50
|
+
// Keep the receiver alive for this callback without a Java/native cycle.
|
|
51
|
+
if (receiver) receiver->handleEvent(std::move(eventName), data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void NativeEventDispatcher::invalidateReceiver(alias_ref<JClass>, jlong id) {
|
|
55
|
+
std::lock_guard<std::mutex> lock(receiversMutex);
|
|
56
|
+
receivers.erase(id);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "RNSVEventEmitter.h"
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
#include <jsi/jsi.h>
|
|
6
|
+
|
|
7
|
+
namespace RNSkiaVideo {
|
|
8
|
+
using namespace facebook;
|
|
9
|
+
using namespace jni;
|
|
10
|
+
|
|
11
|
+
class JEventReceiver {
|
|
12
|
+
public:
|
|
13
|
+
virtual ~JEventReceiver() = default;
|
|
14
|
+
virtual void handleEvent(std::string eventName, alias_ref<jobject> data) = 0;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
struct NativeEventDispatcher : public JavaClass<NativeEventDispatcher> {
|
|
18
|
+
static constexpr auto kJavaDescriptor =
|
|
19
|
+
"Lcom/azzapp/rnskv/NativeEventDispatcher;";
|
|
20
|
+
|
|
21
|
+
static jni::local_ref<NativeEventDispatcher>
|
|
22
|
+
create(const std::weak_ptr<JEventReceiver>& receiver);
|
|
23
|
+
|
|
24
|
+
static void registerNatives();
|
|
25
|
+
void invalidate() const;
|
|
26
|
+
|
|
27
|
+
static void dispatchEvent(alias_ref<JClass>, jlong receiver,
|
|
28
|
+
std::string eventName, alias_ref<jobject> data);
|
|
29
|
+
static void invalidateReceiver(alias_ref<JClass>, jlong receiver);
|
|
30
|
+
};
|
|
31
|
+
} // namespace RNSkiaVideo
|