diff --git a/.rive_head b/.rive_head index 8da8b8d6..b3b37eab 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -22077bedae4c277e5e8a81c732167ab5d559917c +c4bdda67cc0767e7910c53c30f97c172611b5e76 diff --git a/kotlin/src/main/cpp/include/helpers/egl_worker.hpp b/kotlin/src/main/cpp/include/helpers/egl_worker.hpp index c84fddeb..c1ad4d58 100644 --- a/kotlin/src/main/cpp/include/helpers/egl_worker.hpp +++ b/kotlin/src/main/cpp/include/helpers/egl_worker.hpp @@ -7,19 +7,36 @@ namespace rive_android { -class EGLWorker : public WorkerThread, public rive::RefCnt +class EGLWorker : public WorkerThread { public: - static rive::rcp Current(const RendererType); + // Returns the current worker of the requested renderer type, or the current Skia worker if the + // requested type is not supported. + static rive::rcp CurrentOrSkia(RendererType); -private: - friend class rive::RefCnt; + // Returns the Rive renderer worker, or null if it is not supported. + static rive::rcp RiveWorker(); + + // Returns the current Skia renderer worker. + static rive::rcp SkiaWorker(); + + ~EGLWorker(); + + // These methods work with rive::rcp<> for tracking _external_ references. They don't + // necessarily delete this object when the external ref count goes to zero, and they may have + // other side effects as well. + void ref(); + void unref(); +private: EGLWorker(const RendererType rendererType) : WorkerThread("EGLWorker", Affinity::None, rendererType) {} - ~EGLWorker(); + + void externalRefCountDidReachZero(); + + size_t m_externalRefCount = 0; }; } // namespace rive_android -#endif // RIVE_ANDROID_EGL_WORKER_HPP \ No newline at end of file +#endif // RIVE_ANDROID_EGL_WORKER_HPP diff --git a/kotlin/src/main/cpp/include/models/worker_impl.hpp b/kotlin/src/main/cpp/include/models/worker_impl.hpp index f85a5f4e..17731589 100644 --- a/kotlin/src/main/cpp/include/models/worker_impl.hpp +++ b/kotlin/src/main/cpp/include/models/worker_impl.hpp @@ -126,9 +126,13 @@ class PLSWorkerImpl : public WorkerImpl } threadState->makeCurrent(m_eglSurface); - PLSThreadState* plsThreadState = PLSWorkerImpl::PlsThreadState(threadState); - auto plsContextImpl = - plsThreadState->plsContext()->static_impl_cast(); + rive::pls::PLSRenderContext* plsContext = + PLSWorkerImpl::PlsThreadState(threadState)->plsContext(); + if (plsContext == nullptr) + { + return; // PLS was not supported. + } + auto plsContextImpl = plsContext->static_impl_cast(); int width = ANativeWindow_getWidth(window); int height = ANativeWindow_getHeight(window); m_plsRenderTarget = plsContextImpl->wrapGLRenderTarget(0, width, height); @@ -140,7 +144,7 @@ class PLSWorkerImpl : public WorkerImpl { return; } - m_plsRenderer = std::make_unique(plsThreadState->plsContext()); + m_plsRenderer = std::make_unique(plsContext); *success = true; } diff --git a/kotlin/src/main/cpp/src/helpers/android_factories.cpp b/kotlin/src/main/cpp/src/helpers/android_factories.cpp index 11ff445d..46b49148 100644 --- a/kotlin/src/main/cpp/src/helpers/android_factories.cpp +++ b/kotlin/src/main/cpp/src/helpers/android_factories.cpp @@ -141,7 +141,7 @@ class AndroidPLSRenderBuffer : public PLSRenderBufferGLImpl public: AndroidPLSRenderBuffer(RenderBufferType type, RenderBufferFlags flags, size_t sizeInBytes) : PLSRenderBufferGLImpl(type, flags, sizeInBytes), - m_glWorker(rive_android::EGLWorker::Current(rive_android::RendererType::Rive)) + m_glWorker(rive_android::EGLWorker::RiveWorker()) { if (std::this_thread::get_id() != m_glWorker->threadID()) { @@ -244,7 +244,7 @@ class AndroidPLSImage : public PLSImage public: AndroidPLSImage(int width, int height, std::unique_ptr imageDataRGBAPtr) : PLSImage(width, height), - m_glWorker(rive_android::EGLWorker::Current(rive_android::RendererType::Rive)) + m_glWorker(rive_android::EGLWorker::RiveWorker()) { // Create the texture on the worker thread where the GL context is current. const uint8_t* imageDataRGBA = imageDataRGBAPtr.release(); diff --git a/kotlin/src/main/cpp/src/helpers/egl_worker.cpp b/kotlin/src/main/cpp/src/helpers/egl_worker.cpp index 0fba058c..08d69dcd 100644 --- a/kotlin/src/main/cpp/src/helpers/egl_worker.cpp +++ b/kotlin/src/main/cpp/src/helpers/egl_worker.cpp @@ -1,6 +1,7 @@ #include "helpers/egl_worker.hpp" #include "helpers/general.hpp" +#include "helpers/thread_state_pls.hpp" #include using namespace rive; @@ -8,34 +9,135 @@ using namespace rive; namespace rive_android { static std::mutex s_eglWorkerMutex; -static EGLWorker* s_currentWorkers[2] = {nullptr, nullptr}; -rcp EGLWorker::Current(const RendererType rendererType) +rcp EGLWorker::RiveWorker() { + static enum class RiveRendererSupport { unknown, no, yes } s_isSupported; + static std::unique_ptr s_riveWorker; + std::lock_guard lock(s_eglWorkerMutex); - int workerIdx = static_cast(rendererType); - if (s_currentWorkers[workerIdx] == nullptr) + + if (s_isSupported == RiveRendererSupport::unknown) { - LOGI("Created a new EGLWorker with type %s", - rendererType == RendererType::Skia ? "Skia" : "Rive"); - s_currentWorkers[workerIdx] = new EGLWorker(rendererType); + assert(s_riveWorker == nullptr); + LOGI("Creating a new EGLWorker with Rive"); + std::unique_ptr candidateWorker(new EGLWorker(RendererType::Rive)); + // Check if PLS is supported. + candidateWorker->runAndWait([](rive_android::EGLThreadState* threadState) { + PLSThreadState* plsThreadState = static_cast(threadState); + s_isSupported = plsThreadState->plsContext() != nullptr ? RiveRendererSupport::yes + : RiveRendererSupport::no; + }); + assert(s_isSupported != RiveRendererSupport::unknown); + if (s_isSupported == RiveRendererSupport::yes) + { + // The Rive renderer is supported! + s_riveWorker = std::move(candidateWorker); + } + else + { + LOGI("Rive renderer is not supported. Falling back on Skia."); + } } - else + + if (s_riveWorker != nullptr) { - LOGI("Referenced an existing EGLWorker."); - s_currentWorkers[workerIdx]->ref(); + ++s_riveWorker->m_externalRefCount; // Increment the external ref count. } - return rcp(s_currentWorkers[workerIdx]); + return rcp(s_riveWorker.get()); } -EGLWorker::~EGLWorker() +static std::unique_ptr s_skiaWorker; + +rcp EGLWorker::SkiaWorker() { std::lock_guard lock(s_eglWorkerMutex); - LOGI("Deleting the current %s EGLWorker.", - m_RendererType == RendererType::Skia ? "Skia" : "Rive"); - int workerIdx = static_cast(m_RendererType); - assert(s_currentWorkers[workerIdx] == this); + if (s_skiaWorker == nullptr) + { + LOGI("Creating a new EGLWorker with Skia"); + s_skiaWorker = std::unique_ptr(new EGLWorker(RendererType::Skia)); + } + ++s_skiaWorker->m_externalRefCount; // Increment the external ref count. + return rcp(s_skiaWorker.get()); +} + +rcp EGLWorker::CurrentOrSkia(RendererType rendererType) +{ + rcp currentOrSkia; + switch (rendererType) + { + case RendererType::Rive: + currentOrSkia = RiveWorker(); + break; + case RendererType::Skia: + currentOrSkia = SkiaWorker(); + break; + } + if (currentOrSkia == nullptr) + { + currentOrSkia = SkiaWorker(); + } + return currentOrSkia; +} + +static const char* renderer_name(RendererType rendererType) +{ + switch (rendererType) + { + case RendererType::Rive: + return "Rive"; + case RendererType::Skia: + return "Skia"; + } +} + +EGLWorker::~EGLWorker() +{ + LOGI("Deleting the EGLWorker with %s", renderer_name(rendererType())); terminateThread(); - s_currentWorkers[workerIdx] = nullptr; +} + +void EGLWorker::ref() +{ + std::lock_guard lock(s_eglWorkerMutex); + ++m_externalRefCount; +} + +void EGLWorker::unref() +{ + std::lock_guard lock(s_eglWorkerMutex); + assert(m_externalRefCount > 0); + if (--m_externalRefCount == 0) + { + externalRefCountDidReachZero(); + } +} + +void EGLWorker::externalRefCountDidReachZero() +{ + switch (rendererType()) + { + case RendererType::Rive: + // Release the Rive worker's GPU resources, but keep the GL context alive. We have + // simple way to release GPU resources here instead, without having to pay the hefty + // price of destroying and re-creating the entire GL context. + run([](rive_android::EGLThreadState* threadState) { + PLSThreadState* plsThreadState = static_cast(threadState); + rive::pls::PLSRenderContext* plsContext = plsThreadState->plsContext(); + if (plsContext != nullptr) + { + LOGI("Releasing GPU resources on the Rive renderer"); + plsContext->resetGPUResources(); + } + }); + break; + case RendererType::Skia: + { + // Delete the entire Skia context. + assert(s_skiaWorker.get() == this); + s_skiaWorker = nullptr; + break; + } + } } } // namespace rive_android diff --git a/kotlin/src/main/cpp/src/helpers/general.cpp b/kotlin/src/main/cpp/src/helpers/general.cpp index 2352246a..10c5d48a 100644 --- a/kotlin/src/main/cpp/src/helpers/general.cpp +++ b/kotlin/src/main/cpp/src/helpers/general.cpp @@ -1,5 +1,6 @@ #include "jni_refs.hpp" #include "helpers/android_factories.hpp" +#include "helpers/egl_worker.hpp" #include "helpers/general.hpp" #include "rive/file.hpp" @@ -155,10 +156,11 @@ rive::Alignment GetAlignment(JNIEnv* env, jobject jalignment) long Import(uint8_t* bytes, jint length, RendererType rendererType) { + rive::Factory* fileFactory = + (rendererType == RendererType::Rive && EGLWorker::RiveWorker() != nullptr) + ? static_cast(&g_RiveFactory) + : static_cast(&g_SkiaFactory); rive::ImportResult result; - rive::Factory* fileFactory = rendererType == RendererType::Skia - ? static_cast(&g_SkiaFactory) - : static_cast(&g_RiveFactory); rive::File* file = rive::File::import(rive::Span(bytes, length), fileFactory, &result) .release(); diff --git a/kotlin/src/main/cpp/src/models/jni_renderer.cpp b/kotlin/src/main/cpp/src/models/jni_renderer.cpp index 04a4545d..129a860f 100644 --- a/kotlin/src/main/cpp/src/models/jni_renderer.cpp +++ b/kotlin/src/main/cpp/src/models/jni_renderer.cpp @@ -25,7 +25,7 @@ JNIRenderer::JNIRenderer(jobject ktObject, // Grab a Global Ref to prevent Garbage Collection to clean up the object // from under us since the destructor will be called from the render thread // rather than the UI thread. - m_worker(EGLWorker::Current(rendererType)), + m_worker(EGLWorker::CurrentOrSkia(rendererType)), m_ktRenderer(GetJNIEnv()->NewGlobalRef(ktObject)), m_tracer(getTracer(trace)) {} diff --git a/kotlin/src/main/cpp/test/first_test.cpp b/kotlin/src/main/cpp/test/first_test.cpp index e6c67a3e..c2c702ae 100644 --- a/kotlin/src/main/cpp/test/first_test.cpp +++ b/kotlin/src/main/cpp/test/first_test.cpp @@ -15,7 +15,7 @@ TEST_CASE("Factorial of 0 is 1 (fail)", "[single-file]") { REQUIRE(Factorial(0) TEST_CASE("Factorials of 1 and higher are computed (pass)", "[single-file]") { - rive::rcp worker = EGLWorker::Current(); + rive::rcp worker = EGLWorker::CurrentOrSkia(); printf("AM I ALIVE?!\n"); // worker->run([=](EGLThreadState* ts) { printf("I am alive!?\n"); }); REQUIRE(Factorial(1) == 1); @@ -50,4 +50,4 @@ TEST_CASE("Test something in the Android world??", "[single-file]") // 010-TestCase.cpp:19: passed: Factorial(2) == 2 for: 2 == 2 // 010-TestCase.cpp:20: passed: Factorial(3) == 6 for: 6 == 6 // 010-TestCase.cpp:21: passed: Factorial(10) == 3628800 for: 3628800 (0x375f00) == 3628800 -// (0x375f00) Failed 1 test case, failed 1 assertion. \ No newline at end of file +// (0x375f00) Failed 1 test case, failed 1 assertion.