feat: Allow build without Skia or Frame Processors (#1710)

* feat: Make Frame Processors optional in JS

* Allow Android build without Frame Processors

* fix: Fix `EncoderProfiles.width` null-error

* Update gradle.properties

* Update gradle.properties

* fix: Use `#ifdef` instead of `#if`

* Update JVisionCameraProxy.cpp

* fix: Fix definitions

* Revert "fix: Use `#ifdef` instead of `#if`"

This reverts commit b19f32e5ce7df558cadcc8c4b5006c9cdf2cbe66.

* fix: Fix build

* chore: Codestyle

* Update JFrameProcessor.cpp
This commit is contained in:
Marc Rousavy 2023-08-23 12:42:38 +02:00 committed by GitHub
parent 617c5607d4
commit 862e05b64f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 266 additions and 147 deletions

View File

@ -6,7 +6,6 @@ set(PACKAGE_NAME "VisionCamera")
set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build) set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSK_GL -DSK_GANESH -DSK_BUILD_FOR_ANDROID")
# Folly # Folly
include("${NODE_MODULES_DIR}/react-native/ReactAndroid/cmake-utils/folly-flags.cmake") include("${NODE_MODULES_DIR}/react-native/ReactAndroid/cmake-utils/folly-flags.cmake")
@ -15,25 +14,10 @@ add_compile_options(${folly_FLAGS})
# Third party libraries (Prefabs) # Third party libraries (Prefabs)
find_package(ReactAndroid REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG) find_package(fbjni REQUIRED CONFIG)
find_package(react-native-worklets-core REQUIRED CONFIG)
find_library(LOG_LIB log) find_library(LOG_LIB log)
set(RNSKIA_PATH ${NODE_MODULES_DIR}/@shopify/react-native-skia) add_definitions(-DVISION_CAMERA_ENABLE_FRAME_PROCESSORS=${ENABLE_FRAME_PROCESSORS} -DVISION_CAMERA_ENABLE_SKIA=${ENABLE_SKIA})
if(EXISTS ${RNSKIA_PATH})
find_package(shopify_react-native-skia REQUIRED CONFIG)
set(SKIA_PACKAGE shopify_react-native-skia::rnskia)
message("VisionCamera: Skia integration enabled!")
else()
message("VisionCamera: Skia integration disabled!")
ENDIF()
set (SKIA_LIBS_PATH "${RNSKIA_PATH}/libs/android/${ANDROID_ABI}")
add_library(skia STATIC IMPORTED)
set_property(TARGET skia PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libskia.a")
add_library(svg STATIC IMPORTED)
set_property(TARGET svg PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libsvg.a")
add_library(skshaper STATIC IMPORTED)
set_property(TARGET skshaper PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libskshaper.a")
# Add react-native-vision-camera sources # Add react-native-vision-camera sources
add_library( add_library(
@ -63,24 +47,6 @@ target_include_directories(
"${NODE_MODULES_DIR}/react-native/ReactCommon" "${NODE_MODULES_DIR}/react-native/ReactCommon"
"${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker" "${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker"
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper
# We need to include the headers from skia
# (Note: rnskia includes all their files without any relative path
# so for example "include/core/SkImage.h" becomes #include "SkImage.h".
# That's why for the prefab of rnskia, we flatten all cpp files into
# just one directory. HOWEVER, skia itself uses relative paths in
# their include statements, and so we have to include the path to skia)
"${RNSKIA_PATH}/cpp/skia"
"${RNSKIA_PATH}/cpp/skia/include/config/"
"${RNSKIA_PATH}/cpp/skia/include/core/"
"${RNSKIA_PATH}/cpp/skia/include/effects/"
"${RNSKIA_PATH}/cpp/skia/include/utils/"
"${RNSKIA_PATH}/cpp/skia/include/pathops/"
"${RNSKIA_PATH}/cpp/skia/modules/"
# "${RNSKIA_PATH}/cpp/skia/modules/skparagraph/include/"
"${RNSKIA_PATH}/cpp/skia/include/"
"${RNSKIA_PATH}/cpp/skia"
) )
# Link everything together # Link everything together
@ -92,12 +58,65 @@ target_link_libraries(
ReactAndroid::reactnativejni # <-- RN: React Native JNI bindings ReactAndroid::reactnativejni # <-- RN: React Native JNI bindings
ReactAndroid::folly_runtime # <-- RN: For casting JSI <> Java objects ReactAndroid::folly_runtime # <-- RN: For casting JSI <> Java objects
fbjni::fbjni # <-- fbjni fbjni::fbjni # <-- fbjni
react-native-worklets-core::rnworklets # <-- RN Worklets
GLESv2 # <-- Optional: OpenGL (for Skia)
EGL # <-- Optional: OpenGL (EGL) (for Skia)
${SKIA_PACKAGE} # <-- Optional: RN Skia
jnigraphics
skia
svg
skshaper
) )
# Optionally also add Frame Processors here
if(ENABLE_FRAME_PROCESSORS)
find_package(react-native-worklets-core REQUIRED CONFIG)
target_link_libraries(
${PACKAGE_NAME}
react-native-worklets-core::rnworklets
)
message("VisionCamera: Frame Processors enabled!")
# Optionally also add Skia Integration here
if(ENABLE_SKIA)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSK_GL -DSK_GANESH -DSK_BUILD_FOR_ANDROID")
find_package(shopify_react-native-skia REQUIRED CONFIG)
set(SKIA_PACKAGE shopify_react-native-skia::rnskia)
set(RNSKIA_PATH ${NODE_MODULES_DIR}/@shopify/react-native-skia)
set (SKIA_LIBS_PATH "${RNSKIA_PATH}/libs/android/${ANDROID_ABI}")
add_library(skia STATIC IMPORTED)
set_property(TARGET skia PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libskia.a")
add_library(svg STATIC IMPORTED)
set_property(TARGET svg PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libsvg.a")
add_library(skshaper STATIC IMPORTED)
set_property(TARGET skshaper PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libskshaper.a")
# We need to include the headers from skia
# (Note: rnskia includes all their files without any relative path
# so for example "include/core/SkImage.h" becomes #include "SkImage.h".
# That's why for the prefab of rnskia, we flatten all cpp files into
# just one directory. HOWEVER, skia itself uses relative paths in
# their include statements, and so we have to include the path to skia)
target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${RNSKIA_PATH}/cpp/skia"
"${RNSKIA_PATH}/cpp/skia/include/config/"
"${RNSKIA_PATH}/cpp/skia/include/core/"
"${RNSKIA_PATH}/cpp/skia/include/effects/"
"${RNSKIA_PATH}/cpp/skia/include/utils/"
"${RNSKIA_PATH}/cpp/skia/include/pathops/"
"${RNSKIA_PATH}/cpp/skia/modules/"
# "${RNSKIA_PATH}/cpp/skia/modules/skparagraph/include/"
"${RNSKIA_PATH}/cpp/skia/include/"
"${RNSKIA_PATH}/cpp/skia"
)
target_link_libraries(
${PACKAGE_NAME}
GLESv2 # <-- Optional: OpenGL (for Skia)
EGL # <-- Optional: OpenGL (EGL) (for Skia)
${SKIA_PACKAGE} # <-- Optional: RN Skia
jnigraphics
skia
svg
skshaper
)
message("VisionCamera: Skia enabled!")
endif()
endif()

View File

@ -65,6 +65,12 @@ static def findNodeModules(baseDir) {
def nodeModules = findNodeModules(projectDir) def nodeModules = findNodeModules(projectDir)
def hasWorklets = !safeExtGet("VisionCamera_disableFrameProcessors", false) && findProject(":react-native-worklets-core") != null
def hasSkia = !safeExtGet("VisionCamera_disableSkia", false) && findProject(":shopify_react-native-skia") != null
logger.warn("[VisionCamera] react-native-worklets-core ${hasWorklets ? "found" : "not found"}, Frame Processors ${hasWorklets ? "enabled" : "disabled"}!")
logger.warn("[VisionCamera] react-native-skia ${hasSkia ? "found" : "not found"}, Skia Frame Processors ${hasSkia ? "enabled" : "disabled"}!")
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
@ -98,7 +104,9 @@ android {
cmake { cmake {
cppFlags "-O2 -frtti -fexceptions -Wall -Wno-unused-variable -fstack-protector-all" cppFlags "-O2 -frtti -fexceptions -Wall -Wno-unused-variable -fstack-protector-all"
arguments "-DANDROID_STL=c++_shared", arguments "-DANDROID_STL=c++_shared",
"-DNODE_MODULES_DIR=${nodeModules}" "-DNODE_MODULES_DIR=${nodeModules}",
"-DENABLE_FRAME_PROCESSORS=${hasWorklets}",
"-DENABLE_SKIA=${hasWorklets && hasSkia}"
abiFilters (*reactNativeArchitectures()) abiFilters (*reactNativeArchitectures())
} }
} }
@ -141,11 +149,15 @@ dependencies {
implementation 'com.facebook.react:react-android:+' implementation 'com.facebook.react:react-android:+'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
// Frame Processor integration (optional) if (hasWorklets) {
implementation project(":react-native-worklets-core") // Frame Processor integration (optional)
implementation project(":react-native-worklets-core")
// Skia Frame Processor integration (optional) if (hasSkia) {
implementation project(":shopify_react-native-skia") // Skia Frame Processor integration (optional)
implementation project(":shopify_react-native-skia")
}
}
} }
// Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct". // Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct".

View File

@ -8,7 +8,6 @@
#include <fbjni/fbjni.h> #include <fbjni/fbjni.h>
#include <jni.h> #include <jni.h>
#include <react-native-worklets-core/WKTJsiHostObject.h>
#include "JSITypedArray.h" #include "JSITypedArray.h"
#include <vector> #include <vector>
@ -51,8 +50,39 @@ std::vector<jsi::PropNameID> FrameHostObject::getPropertyNames(jsi::Runtime& rt)
jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) { jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
auto name = propName.utf8(runtime); auto name = propName.utf8(runtime);
if (name == "incrementRefCount") {
jsi::HostFunctionType incrementRefCount = [=](jsi::Runtime& runtime,
const jsi::Value& thisArg,
const jsi::Value* args,
size_t count) -> jsi::Value {
// Increment retain count by one.
this->frame->incrementRefCount();
return jsi::Value::undefined();
};
return jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forUtf8(runtime, "incrementRefCount"),
0,
incrementRefCount);
}
if (name == "decrementRefCount") {
auto decrementRefCount = [=](jsi::Runtime& runtime,
const jsi::Value& thisArg,
const jsi::Value* args,
size_t count) -> jsi::Value {
// Decrement retain count by one. If the retain count is zero, the Frame gets closed.
this->frame->decrementRefCount();
return jsi::Value::undefined();
};
return jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forUtf8(runtime, "decrementRefCount"),
0,
decrementRefCount);
}
if (name == "toString") { if (name == "toString") {
auto toString = JSI_HOST_FUNCTION_LAMBDA { jsi::HostFunctionType toString = [=](jsi::Runtime& runtime,
const jsi::Value& thisArg,
const jsi::Value* args,
size_t count) -> jsi::Value {
if (!this->frame) { if (!this->frame) {
return jsi::String::createFromUtf8(runtime, "[closed frame]"); return jsi::String::createFromUtf8(runtime, "[closed frame]");
} }
@ -64,7 +94,10 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr
return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toString"), 0, toString); return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toString"), 0, toString);
} }
if (name == "toArrayBuffer") { if (name == "toArrayBuffer") {
auto toArrayBuffer = JSI_HOST_FUNCTION_LAMBDA { jsi::HostFunctionType toArrayBuffer = [=](jsi::Runtime& runtime,
const jsi::Value& thisArg,
const jsi::Value* args,
size_t count) -> jsi::Value {
auto buffer = this->frame->toByteArray(); auto buffer = this->frame->toByteArray();
auto arraySize = buffer->size(); auto arraySize = buffer->size();
@ -92,29 +125,6 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr
}; };
return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toArrayBuffer"), 0, toArrayBuffer); return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "toArrayBuffer"), 0, toArrayBuffer);
} }
if (name == "incrementRefCount") {
auto incrementRefCount = JSI_HOST_FUNCTION_LAMBDA {
// Increment retain count by one.
this->frame->incrementRefCount();
return jsi::Value::undefined();
};
return jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forUtf8(runtime, "incrementRefCount"),
0,
incrementRefCount);
}
if (name == "decrementRefCount") {
auto decrementRefCount = JSI_HOST_FUNCTION_LAMBDA {
// Decrement retain count by one. If the retain count is zero, the Frame gets closed.
this->frame->decrementRefCount();
return jsi::Value::undefined();
};
return jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forUtf8(runtime, "decrementRefCount"),
0,
decrementRefCount);
}
if (name == "isValid") { if (name == "isValid") {
return jsi::Value(this->frame && this->frame->getIsValid()); return jsi::Value(this->frame && this->frame->getIsValid());

View File

@ -9,9 +9,13 @@
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(vm, [] { return facebook::jni::initialize(vm, [] {
vision::VisionCameraInstaller::registerNatives(); vision::VisionCameraInstaller::registerNatives();
vision::JFrameProcessor::registerNatives();
vision::JVisionCameraProxy::registerNatives(); vision::JVisionCameraProxy::registerNatives();
vision::JVisionCameraScheduler::registerNatives(); vision::JVisionCameraScheduler::registerNatives();
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
vision::JFrameProcessor::registerNatives();
#endif
#if VISION_CAMERA_ENABLE_SKIA
vision::SkiaRenderer::registerNatives(); vision::SkiaRenderer::registerNatives();
#endif
}); });
} }

View File

@ -19,6 +19,10 @@
#include <string> #include <string>
#include <memory> #include <memory>
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
#include <react-native-worklets-core/WKTJsiWorkletContext.h>
#endif
namespace vision { namespace vision {
using namespace facebook; using namespace facebook;
@ -28,11 +32,6 @@ VisionCameraProxy::VisionCameraProxy(const jni::alias_ref<JVisionCameraProxy::ja
} }
VisionCameraProxy::~VisionCameraProxy() { VisionCameraProxy::~VisionCameraProxy() {
__android_log_write(ANDROID_LOG_INFO, TAG, "Destroying Context...");
// Destroy ArrayBuffer cache for both the JS and the Worklet Runtime.
auto workletContext = _javaProxy->cthis()->getWorkletContext();
invalidateArrayBufferCache(*workletContext->getJsRuntime());
invalidateArrayBufferCache(workletContext->getWorkletRuntime());
} }
std::vector<jsi::PropNameID> VisionCameraProxy::getPropertyNames(jsi::Runtime& runtime) { std::vector<jsi::PropNameID> VisionCameraProxy::getPropertyNames(jsi::Runtime& runtime) {
@ -45,24 +44,7 @@ std::vector<jsi::PropNameID> VisionCameraProxy::getPropertyNames(jsi::Runtime& r
} }
void VisionCameraProxy::setFrameProcessor(int viewTag, jsi::Runtime& runtime, const jsi::Object& object) { void VisionCameraProxy::setFrameProcessor(int viewTag, jsi::Runtime& runtime, const jsi::Object& object) {
auto frameProcessorType = object.getProperty(runtime, "type").asString(runtime).utf8(runtime); _javaProxy->cthis()->setFrameProcessor(viewTag, runtime, object);
auto worklet = std::make_shared<RNWorklet::JsiWorklet>(runtime, object.getProperty(runtime, "frameProcessor"));
auto workletContext = _javaProxy->cthis()->getWorkletContext();
jni::local_ref<JFrameProcessor::javaobject> frameProcessor;
if (frameProcessorType == "frame-processor") {
frameProcessor = JFrameProcessor::create(worklet, workletContext);
} else if (frameProcessorType == "skia-frame-processor") {
#if VISION_CAMERA_ENABLE_SKIA
throw std::runtime_error("system/skia-unavailable: Skia is not yet implemented on Android!");
#else
throw std::runtime_error("system/skia-unavailable: Skia is not installed!");
#endif
} else {
throw std::runtime_error("Unknown FrameProcessor.type passed! Received: " + frameProcessorType);
}
_javaProxy->cthis()->setFrameProcessor(viewTag, make_global(frameProcessor));
} }
void VisionCameraProxy::removeFrameProcessor(int viewTag) { void VisionCameraProxy::removeFrameProcessor(int viewTag) {
@ -143,7 +125,7 @@ void VisionCameraInstaller::install(jni::alias_ref<jni::JClass>,
jni::alias_ref<JVisionCameraProxy::javaobject> proxy) { jni::alias_ref<JVisionCameraProxy::javaobject> proxy) {
// global.VisionCameraProxy // global.VisionCameraProxy
auto visionCameraProxy = std::make_shared<VisionCameraProxy>(proxy); auto visionCameraProxy = std::make_shared<VisionCameraProxy>(proxy);
jsi::Runtime& runtime = *proxy->cthis()->getWorkletContext()->getJsRuntime(); jsi::Runtime& runtime = *proxy->cthis()->getJSRuntime();
runtime.global().setProperty(runtime, runtime.global().setProperty(runtime,
"VisionCameraProxy", "VisionCameraProxy",
jsi::Object::createFromHostObject(runtime, visionCameraProxy)); jsi::Object::createFromHostObject(runtime, visionCameraProxy));

View File

@ -2,8 +2,9 @@
// Created by Marc Rousavy on 29.09.21. // Created by Marc Rousavy on 29.09.21.
// //
#include "JFrameProcessor.h" #if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
#include "JFrameProcessor.h"
#include <jni.h> #include <jni.h>
#include <fbjni/fbjni.h> #include <fbjni/fbjni.h>
@ -50,8 +51,8 @@ void JFrameProcessor::callWithFrameHostObject(const std::shared_ptr<FrameHostObj
const std::string& message = jsError.getMessage(); const std::string& message = jsError.getMessage();
_workletContext->invokeOnJsThread([message](jsi::Runtime& jsRuntime) { _workletContext->invokeOnJsThread([message](jsi::Runtime& jsRuntime) {
auto logFn = jsRuntime.global().getPropertyAsObject(jsRuntime, "console").getPropertyAsFunction(jsRuntime, "error"); auto logFn = jsRuntime.global().getPropertyAsObject(jsRuntime, "console").getPropertyAsFunction(jsRuntime, "error");
logFn.call(jsRuntime, jsi::String::createFromUtf8(jsRuntime, "Frame Processor threw an error: " + message)); logFn.call(jsRuntime, jsi::String::createFromUtf8(jsRuntime, "Frame Processor threw an error: " + message));
}); });
} }
} }
@ -63,3 +64,5 @@ void JFrameProcessor::call(jni::alias_ref<JFrame::javaobject> frame) {
} }
} // namespace vision } // namespace vision
#endif

View File

@ -4,6 +4,8 @@
#pragma once #pragma once
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
#include <string> #include <string>
#include <memory> #include <memory>
#include <jni.h> #include <jni.h>
@ -47,3 +49,5 @@ struct JFrameProcessor : public jni::HybridClass<JFrameProcessor> {
}; };
} // namespace vision } // namespace vision
#endif

View File

@ -11,10 +11,13 @@
#include <jsi/jsi.h> #include <jsi/jsi.h>
#include <react/jni/ReadableNativeMap.h> #include <react/jni/ReadableNativeMap.h>
#include "FrameProcessorPluginHostObject.h"
#include "JSITypedArray.h"
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
#include <react-native-worklets-core/WKTJsiWorklet.h> #include <react-native-worklets-core/WKTJsiWorklet.h>
#include <react-native-worklets-core/WKTJsiWorkletContext.h> #include <react-native-worklets-core/WKTJsiWorkletContext.h>
#endif
#include "FrameProcessorPluginHostObject.h"
namespace vision { namespace vision {
@ -28,7 +31,9 @@ JVisionCameraProxy::JVisionCameraProxy(const jni::alias_ref<JVisionCameraProxy::
const std::shared_ptr<facebook::react::CallInvoker>& callInvoker, const std::shared_ptr<facebook::react::CallInvoker>& callInvoker,
const jni::global_ref<JVisionCameraScheduler::javaobject>& scheduler) { const jni::global_ref<JVisionCameraScheduler::javaobject>& scheduler) {
_javaPart = make_global(javaThis); _javaPart = make_global(javaThis);
_runtime = runtime;
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
__android_log_write(ANDROID_LOG_INFO, TAG, "Creating Worklet Context..."); __android_log_write(ANDROID_LOG_INFO, TAG, "Creating Worklet Context...");
auto runOnJS = [callInvoker](std::function<void()>&& f) { auto runOnJS = [callInvoker](std::function<void()>&& f) {
@ -46,14 +51,53 @@ JVisionCameraProxy::JVisionCameraProxy(const jni::alias_ref<JVisionCameraProxy::
runOnJS, runOnJS,
runOnWorklet); runOnWorklet);
__android_log_write(ANDROID_LOG_INFO, TAG, "Worklet Context created!"); __android_log_write(ANDROID_LOG_INFO, TAG, "Worklet Context created!");
#else
__android_log_write(ANDROID_LOG_INFO, TAG, "Frame Processors are disabled!");
#endif
#ifdef VISION_CAMERA_ENABLE_SKIA
__android_log_write(ANDROID_LOG_INFO, TAG, "Skia is enabled!");
#else
__android_log_write(ANDROID_LOG_INFO, TAG, "Skia is disabled!");
#endif
}
JVisionCameraProxy::~JVisionCameraProxy() {
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
__android_log_write(ANDROID_LOG_INFO, TAG, "Destroying Context...");
// Destroy ArrayBuffer cache for both the JS and the Worklet Runtime.
invalidateArrayBufferCache(*_workletContext->getJsRuntime());
invalidateArrayBufferCache(_workletContext->getWorkletRuntime());
#endif
} }
void JVisionCameraProxy::setFrameProcessor(int viewTag, void JVisionCameraProxy::setFrameProcessor(int viewTag,
const alias_ref<JFrameProcessor::javaobject>& frameProcessor) { jsi::Runtime& runtime,
const jsi::Object& frameProcessorObject) {
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
auto frameProcessorType = frameProcessorObject.getProperty(runtime, "type").asString(runtime).utf8(runtime);
auto worklet = std::make_shared<RNWorklet::JsiWorklet>(runtime, frameProcessorObject.getProperty(runtime, "frameProcessor"));
jni::local_ref<JFrameProcessor::javaobject> frameProcessor;
if (frameProcessorType == "frame-processor") {
frameProcessor = JFrameProcessor::create(worklet, _workletContext);
} else if (frameProcessorType == "skia-frame-processor") {
#if VISION_CAMERA_ENABLE_SKIA
throw std::runtime_error("system/skia-unavailable: Skia is not yet implemented on Android!");
#else
throw std::runtime_error("system/skia-unavailable: Skia is not installed!");
#endif
} else {
throw std::runtime_error("Unknown FrameProcessor.type passed! Received: " + frameProcessorType);
}
auto setFrameProcessorMethod = javaClassLocal()->getMethod<void(int, alias_ref<JFrameProcessor::javaobject>)>("setFrameProcessor"); auto setFrameProcessorMethod = javaClassLocal()->getMethod<void(int, alias_ref<JFrameProcessor::javaobject>)>("setFrameProcessor");
setFrameProcessorMethod(_javaPart, viewTag, frameProcessor); setFrameProcessorMethod(_javaPart, viewTag, frameProcessor);
#else
throw std::runtime_error("system/frame-processors-unavailable: Frame Processors are disabled!");
#endif
} }
void JVisionCameraProxy::removeFrameProcessor(int viewTag) { void JVisionCameraProxy::removeFrameProcessor(int viewTag) {

View File

@ -6,8 +6,8 @@
#include <fbjni/fbjni.h> #include <fbjni/fbjni.h>
#include <jsi/jsi.h> #include <jsi/jsi.h>
#include <react-native-worklets-core/WKTJsiWorkletContext.h>
#include <react/jni/ReadableNativeMap.h> #include <react/jni/ReadableNativeMap.h>
#include <ReactCommon/CallInvokerHolder.h>
#include "JFrameProcessorPlugin.h" #include "JFrameProcessorPlugin.h"
#include "JVisionCameraScheduler.h" #include "JVisionCameraScheduler.h"
@ -16,29 +16,36 @@
#include <string> #include <string>
#include <memory> #include <memory>
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
#include <react-native-worklets-core/WKTJsiWorkletContext.h>
#endif
namespace vision { namespace vision {
using namespace facebook; using namespace facebook;
class JVisionCameraProxy : public jni::HybridClass<JVisionCameraProxy> { class JVisionCameraProxy : public jni::HybridClass<JVisionCameraProxy> {
public: public:
~JVisionCameraProxy();
static void registerNatives(); static void registerNatives();
void setFrameProcessor(int viewTag, void setFrameProcessor(int viewTag,
const jni::alias_ref<JFrameProcessor::javaobject>& frameProcessor); jsi::Runtime& runtime,
const jsi::Object& frameProcessor);
void removeFrameProcessor(int viewTag); void removeFrameProcessor(int viewTag);
jni::local_ref<JFrameProcessorPlugin::javaobject> getFrameProcessorPlugin(const std::string& name, jni::local_ref<JFrameProcessorPlugin::javaobject> getFrameProcessorPlugin(const std::string& name,
jni::local_ref<react::ReadableNativeMap::javaobject> options); jni::local_ref<react::ReadableNativeMap::javaobject> options);
public: jsi::Runtime* getJSRuntime() { return _runtime; }
std::shared_ptr<RNWorklet::JsiWorkletContext> getWorkletContext() { return _workletContext; }
private:
std::shared_ptr<RNWorklet::JsiWorkletContext> _workletContext;
private: private:
friend HybridBase; friend HybridBase;
jni::global_ref<JVisionCameraProxy::javaobject> _javaPart; jni::global_ref<JVisionCameraProxy::javaobject> _javaPart;
jsi::Runtime* _runtime;
#if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
std::shared_ptr<RNWorklet::JsiWorkletContext> _workletContext;
#endif
static auto constexpr TAG = "VisionCameraProxy"; static auto constexpr TAG = "VisionCameraProxy";
static auto constexpr kJavaDescriptor = "Lcom/mrousavy/camera/frameprocessor/VisionCameraProxy;"; static auto constexpr kJavaDescriptor = "Lcom/mrousavy/camera/frameprocessor/VisionCameraProxy;";

View File

@ -4,6 +4,8 @@
#pragma once #pragma once
#if VISION_CAMERA_ENABLE_SKIA
#include <string> #include <string>
#include <stdexcept> #include <stdexcept>
#include <GLES2/gl2.h> #include <GLES2/gl2.h>
@ -24,3 +26,5 @@ class OpenGLError: public std::runtime_error {
}; };
} // namespace vision } // namespace vision
#endif

View File

@ -2,6 +2,8 @@
// Created by Marc Rousavy on 10.08.23. // Created by Marc Rousavy on 10.08.23.
// //
#if VISION_CAMERA_ENABLE_SKIA
#include "SkiaRenderer.h" #include "SkiaRenderer.h"
#include <android/log.h> #include <android/log.h>
#include "OpenGLError.h" #include "OpenGLError.h"
@ -325,3 +327,5 @@ void SkiaRenderer::registerNatives() {
} }
} // namespace vision } // namespace vision
#endif

View File

@ -4,6 +4,8 @@
#pragma once #pragma once
#if VISION_CAMERA_ENABLE_SKIA
#include <jni.h> #include <jni.h>
#include <fbjni/fbjni.h> #include <fbjni/fbjni.h>
#include <fbjni/ByteBuffer.h> #include <fbjni/ByteBuffer.h>
@ -75,3 +77,5 @@ class SkiaRenderer: public jni::HybridClass<SkiaRenderer> {
}; };
} // namespace vision } // namespace vision
#endif

View File

@ -34,8 +34,10 @@ private fun getMaximumVideoSize(cameraId: String): Size? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val profiles = CamcorderProfile.getAll(cameraId, CamcorderProfile.QUALITY_HIGH) val profiles = CamcorderProfile.getAll(cameraId, CamcorderProfile.QUALITY_HIGH)
if (profiles != null) { if (profiles != null) {
val largestProfile = profiles.videoProfiles.maxBy { it.width * it.height } val largestProfile = profiles.videoProfiles.filterNotNull().maxByOrNull { it.width * it.height }
return Size(largestProfile.width, largestProfile.height) if (largestProfile != null) {
return Size(largestProfile.width, largestProfile.height)
}
} }
} }

View File

@ -235,7 +235,7 @@ The Frame Processor API spawns a secondary JavaScript Runtime which consumes a s
Inside your `gradle.properties` file, add the `disableFrameProcessors` flag: Inside your `gradle.properties` file, add the `disableFrameProcessors` flag:
```groovy ```groovy
disableFrameProcessors=true VisionCamera_disableFrameProcessors=true
``` ```
Then, clean and rebuild your project. Then, clean and rebuild your project.

View File

@ -91,7 +91,7 @@ Skia Frame Processors ship with additional C++ files which might slightly increa
Inside your `gradle.properties` file, add the `disableSkia` flag: Inside your `gradle.properties` file, add the `disableSkia` flag:
```groovy ```groovy
disableSkia=true VisionCamera_disableSkia=true
``` ```
Then, clean and rebuild your project. Then, clean and rebuild your project.

View File

@ -85,8 +85,8 @@ Before opening an issue, make sure you try the following:
``` ```
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
``` ```
7. Try building without Skia. Set `disableSkia = true` in your `gradle.properties`, and try rebuilding. 7. Try building without Skia. Set `VisionCamera_disableSkia = true` in your `gradle.properties`, and try rebuilding.
8. Try building without Frame Processors. Set `disableFrameProcessors = true` in your `gradle.properties`, and try rebuilding. 8. Try building without Frame Processors. Set `VisionCamera_disableFrameProcessors = true` in your `gradle.properties`, and try rebuilding.
### Runtime Issues ### Runtime Issues

View File

@ -38,3 +38,7 @@ newArchEnabled=false
# Use this property to enable or disable the Hermes JS engine. # Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead. # If set to false, you will be using JSC instead.
hermesEnabled=true hermesEnabled=true
# Can be set to true to disable the build setup
#VisionCamera_disableFrameProcessors=true
#VisionCamera_disableSkia=true

View File

@ -1,9 +1,11 @@
import type { Frame, FrameInternal } from './Frame'; import type { Frame, FrameInternal } from './Frame';
import type { FrameProcessor } from './CameraProps'; import type { FrameProcessor } from './CameraProps';
import { Camera } from './Camera'; import { Camera } from './Camera';
import { Worklets } from 'react-native-worklets-core';
import { CameraRuntimeError } from './CameraError'; import { CameraRuntimeError } from './CameraError';
// only import typescript types
import type TWorklets from 'react-native-worklets-core';
type BasicParameterType = string | number | boolean | undefined; type BasicParameterType = string | number | boolean | undefined;
type ParameterType = BasicParameterType | BasicParameterType[] | Record<string, BasicParameterType | undefined>; type ParameterType = BasicParameterType | BasicParameterType[] | Record<string, BasicParameterType | undefined>;
@ -28,17 +30,48 @@ interface TVisionCameraProxy {
isSkiaEnabled: boolean; isSkiaEnabled: boolean;
} }
Camera.installFrameProcessorBindings(); let isAsyncContextBusy = { value: false };
let runOnAsyncContext = (_frame: Frame, _func: () => void): void => {
throw new CameraRuntimeError(
'system/frame-processors-unavailable',
'Frame Processors are not available, react-native-worklets-core is not installed!',
);
};
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { Worklets } = require('react-native-worklets-core') as typeof TWorklets;
// Install native Frame Processor Runtime Manager
Camera.installFrameProcessorBindings();
// @ts-expect-error global is untyped, it's a C++ host-object
if (global.VisionCameraProxy == null) {
throw new CameraRuntimeError(
'system/frame-processors-unavailable',
'Failed to install VisionCameraProxy. Are Frame Processors properly enabled?',
);
}
isAsyncContextBusy = Worklets.createSharedValue(false);
const asyncContext = Worklets.createContext('VisionCamera.async');
runOnAsyncContext = Worklets.createRunInContextFn((frame: Frame, func: () => void) => {
'worklet';
try {
// Call long-running function
func();
} finally {
// Potentially delete Frame if we were the last ref
(frame as FrameInternal).decrementRefCount();
isAsyncContextBusy.value = false;
}
}, asyncContext);
} catch (e) {
// Worklets are not installed, so Frame Processors are disabled.
}
// @ts-expect-error global is untyped, it's a C++ host-object // @ts-expect-error global is untyped, it's a C++ host-object
export const VisionCameraProxy = global.VisionCameraProxy as TVisionCameraProxy; export const VisionCameraProxy = global.VisionCameraProxy as TVisionCameraProxy;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (VisionCameraProxy == null) {
throw new CameraRuntimeError(
'system/frame-processors-unavailable',
'Failed to install VisionCameraProxy. Are Frame Processors properly enabled?',
);
}
declare global { declare global {
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
@ -96,21 +129,6 @@ export function runAtTargetFps<T>(fps: number, func: () => T): T | undefined {
return undefined; return undefined;
} }
const isAsyncContextBusy = Worklets.createSharedValue(false);
const asyncContext = Worklets.createContext('VisionCamera.async');
const runOnAsyncContext = Worklets.createRunInContextFn((frame: Frame, func: () => void) => {
'worklet';
try {
// Call long-running function
func();
} finally {
// Potentially delete Frame if we were the last ref
(frame as FrameInternal).decrementRefCount();
isAsyncContextBusy.value = false;
}
}, asyncContext);
/** /**
* Runs the given function asynchronously, while keeping a strong reference to the Frame. * Runs the given function asynchronously, while keeping a strong reference to the Frame.
* *

View File

@ -1,11 +1,11 @@
import { ConfigPlugin, withGradleProperties } from '@expo/config-plugins'; import { ConfigPlugin, withGradleProperties } from '@expo/config-plugins';
/** /**
* Set the `disableFrameProcessors` value in the static `gradle.properties` file. * Set the `VisionCamera_disableFrameProcessors` value in the static `gradle.properties` file.
* This is used to disable frame processors if you don't need it for android. * This is used to disable frame processors if you don't need it for android.
*/ */
export const withDisableFrameProcessorsAndroid: ConfigPlugin = (c) => { export const withDisableFrameProcessorsAndroid: ConfigPlugin = (c) => {
const disableFrameProcessorsKey = 'disableFrameProcessors'; const disableFrameProcessorsKey = 'VisionCamera_disableFrameProcessors';
return withGradleProperties(c, (config) => { return withGradleProperties(c, (config) => {
config.modResults = config.modResults.filter((item) => { config.modResults = config.modResults.filter((item) => {
if (item.type === 'property' && item.key === disableFrameProcessorsKey) return false; if (item.type === 'property' && item.key === disableFrameProcessorsKey) return false;

View File

@ -1,8 +1,6 @@
import { DependencyList, useMemo } from 'react'; import { DependencyList, useMemo } from 'react';
import type { DrawableFrame, Frame, FrameInternal } from '../Frame'; import type { DrawableFrame, Frame, FrameInternal } from '../Frame';
import { FrameProcessor } from '../CameraProps'; import { FrameProcessor } from '../CameraProps';
// Install RN Worklets by importing it
import 'react-native-worklets-core';
export function createFrameProcessor(frameProcessor: FrameProcessor['frameProcessor'], type: FrameProcessor['type']): FrameProcessor { export function createFrameProcessor(frameProcessor: FrameProcessor['frameProcessor'], type: FrameProcessor['type']): FrameProcessor {
return { return {