diff --git a/.rive_head b/.rive_head index 25113036..f9c43c44 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -99a7282e629feb48b6d7fb8d37a7bef9cf98386b +ca1d2229bb518848920c1ef0d275a8617eedb84f diff --git a/.rive_renderer b/.rive_renderer index b011b345..f6f37beb 100644 --- a/.rive_renderer +++ b/.rive_renderer @@ -1 +1 @@ -39965129acbc42977031f0eb2db4e5999e0906ad +bcd112a0c434f69b7dcfecf7a7ea3f8c25de67e2 diff --git a/kotlin/src/main/cpp/include/helpers/worker_thread.hpp b/kotlin/src/main/cpp/include/helpers/worker_thread.hpp index 16be3802..12f245e9 100644 --- a/kotlin/src/main/cpp/include/helpers/worker_thread.hpp +++ b/kotlin/src/main/cpp/include/helpers/worker_thread.hpp @@ -34,6 +34,7 @@ class WorkerThread public: using Work = std::function; using WorkID = uint64_t; + constexpr static WorkID kWorkIDAlwaysFinished = 0; WorkerThread(const char* name, Affinity affinity, const RendererType rendererType) : m_RendererType(rendererType), mName(name), mAffinity(affinity), mWorkMutex{} @@ -44,6 +45,16 @@ class WorkerThread ~WorkerThread() { terminateThread(); } + const std::thread::id threadID() const { return mThread.get_id(); } + + // Only accessible on the worker thread. + EGLThreadState* threadState() const + { + assert(std::this_thread::get_id() == threadID()); + assert(m_threadState != nullptr); + return m_threadState.get(); + } + WorkID run(Work&& work) { assert(work != nullptr); // Clients can't push the null termination token. @@ -107,7 +118,7 @@ class WorkerThread pthread_setname_np(pthread_self(), mName.c_str()); GetJNIEnv(); // Attach thread to JVM. - std::unique_ptr threadState = MakeThreadState(m_RendererType); + m_threadState = MakeThreadState(m_RendererType); std::unique_lock lock(mWorkMutex); for (;;) @@ -126,20 +137,21 @@ class WorkerThread } lock.unlock(); - work(threadState.get()); + work(m_threadState.get()); lock.lock(); ++m_lastCompletedWorkID; m_workedCompletedCondition.notify_all(); } + m_threadState.reset(); DetachThread(); } const std::string mName; const Affinity mAffinity; - WorkID m_lastPushedWorkID = 0; - std::atomic m_lastCompletedWorkID = 0; + WorkID m_lastPushedWorkID = kWorkIDAlwaysFinished; + std::atomic m_lastCompletedWorkID = kWorkIDAlwaysFinished; bool mIsTerminated = false; std::queue> mWorkQueue; @@ -148,5 +160,6 @@ class WorkerThread std::mutex mWorkMutex; std::thread mThread; + std::unique_ptr m_threadState; }; } // namespace rive_android diff --git a/kotlin/src/main/cpp/src/helpers/android_factories.cpp b/kotlin/src/main/cpp/src/helpers/android_factories.cpp index 2435e2ea..11ff445d 100644 --- a/kotlin/src/main/cpp/src/helpers/android_factories.cpp +++ b/kotlin/src/main/cpp/src/helpers/android_factories.cpp @@ -9,6 +9,7 @@ #include "helpers/thread_state_pls.hpp" #include "rive/math/math_types.hpp" #include "rive/pls/pls_image.hpp" +#include "rive/pls/gl/pls_render_buffer_gl_impl.hpp" #include "rive/pls/gl/pls_render_context_gl_impl.hpp" #include @@ -133,6 +134,111 @@ std::vector AndroidSkiaFactory::platformDecode(Span enco return pixels; } +// PLSRenderBufferGLImpl specialization that can take advantage EGLWorker and be used on or off the +// GL thread. +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)) + { + if (std::this_thread::get_id() != m_glWorker->threadID()) + { + // We aren't on the GL thread. Init this object on the GL thread. + // Keep this class alive until the worker thread finishes initializing it. + rcp thisRef = ref_rcp(this); + m_bufferCreationWorkID = + m_glWorker->run([thisRef](rive_android::EGLThreadState* threadState) { + auto plsState = static_cast(threadState); + auto* glImpl = + plsState->plsContext()->static_impl_cast(); + thisRef->init(ref_rcp(glImpl->state())); + }); + } + else + { + auto plsState = static_cast(m_glWorker->threadState()); + auto* glImpl = plsState->plsContext()->static_impl_cast(); + init(ref_rcp(glImpl->state())); + m_bufferCreationWorkID = rive_android::WorkerThread::kWorkIDAlwaysFinished; + } + } + + ~AndroidPLSRenderBuffer() + { + if (std::this_thread::get_id() != m_glWorker->threadID()) + { + // Ensure we are done initializing the buffers before we turn around and delete them. + m_glWorker->waitUntilComplete(m_bufferCreationWorkID); + // We aren't on the GL thread. Intercept the buffers before ~PLSRenderBufferGLImpl(), + // and then marshal them off to the GL thread for deletion. + std::array buffersToDelete = detachBuffers(); + rcp glState = ref_rcp(state()); + m_glWorker->run([buffersToDelete, glState](rive_android::EGLThreadState* threadState) { + for (GLuint bufferID : buffersToDelete) + { + if (bufferID != 0) + { + glState->deleteBuffer(bufferID); + } + } + }); + } + } + + void* onMap() override + { + if (std::this_thread::get_id() != m_glWorker->threadID()) + { + // We aren't on the GL thread. Allocate a side buffer to fill. + assert(m_offThreadBufferDataMirror == nullptr); + m_offThreadBufferDataMirror.reset(new uint8_t[sizeInBytes()]); + return m_offThreadBufferDataMirror.get(); + } + else + { + return PLSRenderBufferGLImpl::onMap(); + } + } + + void onUnmap() override + { + if (std::this_thread::get_id() != m_glWorker->threadID()) + { + // We aren't on the GL thread. Marshal our side buffer to the GL thread to update the + // buffer. + const uint8_t* sideBufferData = m_offThreadBufferDataMirror.release(); + assert(sideBufferData != nullptr); + // Keep this class alive until the worker thread finishes updating the buffer. + rcp thisRef = ref_rcp(this); + m_glWorker->run([sideBufferData, thisRef](rive_android::EGLThreadState* threadState) { + void* ptr = thisRef->PLSRenderBufferGLImpl::onMap(); + memcpy(ptr, sideBufferData, thisRef->sizeInBytes()); + thisRef->PLSRenderBufferGLImpl::onUnmap(); + delete[] sideBufferData; + }); + } + else + { + assert(!m_offThreadBufferDataMirror); + PLSRenderBufferGLImpl::onUnmap(); + } + } + +protected: + const rcp m_glWorker; + std::unique_ptr m_offThreadBufferDataMirror; + rive_android::EGLWorker::WorkID m_bufferCreationWorkID; +}; + +rcp AndroidPLSFactory::makeRenderBuffer(RenderBufferType type, + RenderBufferFlags flags, + size_t sizeInBytes) +{ + return make_rcp(type, flags, sizeInBytes); +} + class AndroidPLSImage : public PLSImage { public: @@ -167,17 +273,10 @@ class AndroidPLSImage : public PLSImage } private: - rcp m_glWorker; + const rcp m_glWorker; rive_android::EGLWorker::WorkID m_textureCreationWorkID; }; -rcp AndroidPLSFactory::makeRenderBuffer(RenderBufferType type, - RenderBufferFlags flags, - size_t sizeInBytes) -{ - return nullptr; -} - std::unique_ptr AndroidPLSFactory::decodeImage(Span encodedBytes) { uint32_t width, height;