Skip to content

Commit

Permalink
Fall back on Skia when PLS isn't supported on Android
Browse files Browse the repository at this point in the history
Adds a check to make sure PLS is supported, and falls back on Skia if not.

Also keeps the PLS worker alive when the external ref count reaches zero, and instead frees its GPU resources. This has a similar effect, without having to pay the hefty price of destroying and re-creating the entire GL context

Diffs=
c4bdda67c Fall back on Skia when PLS isn't supported on Android (#6080)

Co-authored-by: Chris Dalton <[email protected]>
  • Loading branch information
csmartdalton and csmartdalton committed Oct 10, 2023
1 parent f57aac2 commit 0c5c1a5
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22077bedae4c277e5e8a81c732167ab5d559917c
c4bdda67cc0767e7910c53c30f97c172611b5e76
29 changes: 23 additions & 6 deletions kotlin/src/main/cpp/include/helpers/egl_worker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,36 @@

namespace rive_android
{
class EGLWorker : public WorkerThread, public rive::RefCnt<EGLWorker>
class EGLWorker : public WorkerThread
{
public:
static rive::rcp<EGLWorker> 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<EGLWorker> CurrentOrSkia(RendererType);

private:
friend class rive::RefCnt<EGLWorker>;
// Returns the Rive renderer worker, or null if it is not supported.
static rive::rcp<EGLWorker> RiveWorker();

// Returns the current Skia renderer worker.
static rive::rcp<EGLWorker> 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
#endif // RIVE_ANDROID_EGL_WORKER_HPP
12 changes: 8 additions & 4 deletions kotlin/src/main/cpp/include/models/worker_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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::PLSRenderContextGLImpl>();
rive::pls::PLSRenderContext* plsContext =
PLSWorkerImpl::PlsThreadState(threadState)->plsContext();
if (plsContext == nullptr)
{
return; // PLS was not supported.
}
auto plsContextImpl = plsContext->static_impl_cast<rive::pls::PLSRenderContextGLImpl>();
int width = ANativeWindow_getWidth(window);
int height = ANativeWindow_getHeight(window);
m_plsRenderTarget = plsContextImpl->wrapGLRenderTarget(0, width, height);
Expand All @@ -140,7 +144,7 @@ class PLSWorkerImpl : public WorkerImpl
{
return;
}
m_plsRenderer = std::make_unique<rive::pls::PLSRenderer>(plsThreadState->plsContext());
m_plsRenderer = std::make_unique<rive::pls::PLSRenderer>(plsContext);
*success = true;
}

Expand Down
4 changes: 2 additions & 2 deletions kotlin/src/main/cpp/src/helpers/android_factories.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down Expand Up @@ -244,7 +244,7 @@ class AndroidPLSImage : public PLSImage
public:
AndroidPLSImage(int width, int height, std::unique_ptr<const uint8_t[]> 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();
Expand Down
136 changes: 119 additions & 17 deletions kotlin/src/main/cpp/src/helpers/egl_worker.cpp
Original file line number Diff line number Diff line change
@@ -1,41 +1,143 @@
#include "helpers/egl_worker.hpp"

#include "helpers/general.hpp"
#include "helpers/thread_state_pls.hpp"
#include <thread>

using namespace rive;

namespace rive_android
{
static std::mutex s_eglWorkerMutex;
static EGLWorker* s_currentWorkers[2] = {nullptr, nullptr};

rcp<EGLWorker> EGLWorker::Current(const RendererType rendererType)
rcp<EGLWorker> EGLWorker::RiveWorker()
{
static enum class RiveRendererSupport { unknown, no, yes } s_isSupported;
static std::unique_ptr<EGLWorker> s_riveWorker;

std::lock_guard lock(s_eglWorkerMutex);
int workerIdx = static_cast<int>(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<EGLWorker> candidateWorker(new EGLWorker(RendererType::Rive));
// Check if PLS is supported.
candidateWorker->runAndWait([](rive_android::EGLThreadState* threadState) {
PLSThreadState* plsThreadState = static_cast<PLSThreadState*>(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<EGLWorker> s_skiaWorker;

rcp<EGLWorker> EGLWorker::SkiaWorker()
{
std::lock_guard lock(s_eglWorkerMutex);
LOGI("Deleting the current %s EGLWorker.",
m_RendererType == RendererType::Skia ? "Skia" : "Rive");
int workerIdx = static_cast<int>(m_RendererType);
assert(s_currentWorkers[workerIdx] == this);
if (s_skiaWorker == nullptr)
{
LOGI("Creating a new EGLWorker with Skia");
s_skiaWorker = std::unique_ptr<EGLWorker>(new EGLWorker(RendererType::Skia));
}
++s_skiaWorker->m_externalRefCount; // Increment the external ref count.
return rcp(s_skiaWorker.get());
}

rcp<EGLWorker> EGLWorker::CurrentOrSkia(RendererType rendererType)
{
rcp<EGLWorker> 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<PLSThreadState*>(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
8 changes: 5 additions & 3 deletions kotlin/src/main/cpp/src/helpers/general.cpp
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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<rive::Factory*>(&g_RiveFactory)
: static_cast<rive::Factory*>(&g_SkiaFactory);
rive::ImportResult result;
rive::Factory* fileFactory = rendererType == RendererType::Skia
? static_cast<rive::Factory*>(&g_SkiaFactory)
: static_cast<rive::Factory*>(&g_RiveFactory);
rive::File* file =
rive::File::import(rive::Span<const uint8_t>(bytes, length), fileFactory, &result)
.release();
Expand Down
2 changes: 1 addition & 1 deletion kotlin/src/main/cpp/src/models/jni_renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{}
Expand Down
4 changes: 2 additions & 2 deletions kotlin/src/main/cpp/test/first_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EGLWorker> worker = EGLWorker::Current();
rive::rcp<EGLWorker> worker = EGLWorker::CurrentOrSkia();
printf("AM I ALIVE?!\n");
// worker->run([=](EGLThreadState* ts) { printf("I am alive!?\n"); });
REQUIRE(Factorial(1) == 1);
Expand Down Expand Up @@ -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.
// (0x375f00) Failed 1 test case, failed 1 assertion.

0 comments on commit 0c5c1a5

Please sign in to comment.