#include "map_renderer.hpp" #include #include #include #include #include #include #include #include "attach_env.hpp" #include "android_renderer_backend.hpp" #include "map_renderer_runnable.hpp" #if MLN_RENDER_BACKEND_OPENGL #include namespace { std::string androidSysProp(const char* key) { assert(strlen(key) < PROP_NAME_MAX); if (__system_property_find(key) == nullptr) { return ""; } char prop[PROP_VALUE_MAX + 1]; __system_property_get(key, prop); return prop; } bool inEmulator() { return androidSysProp("ro.kernel.qemu") == "1" || androidSysProp("ro.boot.qemu") == "1" || androidSysProp("ro.hardware.egl") == "emulation"; } } // namespace #endif namespace mbgl { namespace android { MapRenderer::MapRenderer(jni::JNIEnv& _env, const jni::Object& obj, jni::jfloat pixelRatio_, const jni::String& localIdeographFontFamily_) : javaPeer(_env, obj), pixelRatio(pixelRatio_), localIdeographFontFamily(localIdeographFontFamily_ ? jni::Make(_env, localIdeographFontFamily_) : std::optional{}), threadPool(Scheduler::GetBackground(), {}), mailboxData(this) {} MapRenderer::MailboxData::MailboxData(Scheduler* scheduler_) : scheduler(scheduler_) { assert(scheduler); } std::shared_ptr MapRenderer::MailboxData::getMailbox() const noexcept { if (!mailbox) { mailbox = std::make_shared(*scheduler); } return mailbox; } MapRenderer::~MapRenderer() = default; void MapRenderer::reset() { try { destroyed = true; if (renderer) { // Make sure to destroy the renderer on the GL Thread auto self = ActorRef(*this, mailboxData.getMailbox()); self.ask(&MapRenderer::resetRenderer).wait(); } // Lock to make sure there is no concurrent initialisation on the gl thread std::scoped_lock lock(initialisationMutex); rendererObserver.reset(); } catch (const std::exception& exception) { Log::Error(Event::Android, std::string("MapRenderer::reset failed: ") + exception.what()); } } ActorRef MapRenderer::actor() const { return *rendererRef; } void MapRenderer::schedule(std::function&& scheduled) { MLN_TRACE_FUNC(); try { // Create a runnable android::UniqueEnv _env = android::AttachEnv(); auto runnable = std::make_unique(*_env, std::move(scheduled)); // Obtain ownership of the peer (gets transferred to the MapRenderer on the JVM for later GC) auto peer = runnable->peer(); // Queue the event on the Java Peer static auto& javaClass = jni::Class::Singleton(*_env); static auto queueEvent = javaClass.GetMethod)>(*_env, "queueEvent"); auto weakReference = javaPeer.get(*_env); if (weakReference) { MLN_TRACE_ZONE(java); weakReference.Call(*_env, queueEvent, peer); } // Release the c++ peer as it will be destroyed on GC of the Java Peer runnable.release(); } catch (const std::exception& exception) { Log::Error(Event::Android, std::string("MapRenderer::schedule failed: ") + exception.what()); } } void MapRenderer::waitForEmpty([[maybe_unused]] const util::SimpleIdentity tag) { try { android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto waitForEmpty = javaClass.GetMethod(*_env, "waitForEmpty"); if (auto weakReference = javaPeer.get(*_env)) { return weakReference.Call(*_env, waitForEmpty); } } catch (...) { Log::Error(Event::Android, "MapRenderer::waitForEmpty failed"); jni::ThrowJavaError(*android::AttachEnv(), std::current_exception()); } } void MapRenderer::requestRender() { try { android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); static auto onInvalidate = javaClass.GetMethod(*_env, "requestRender"); auto weakReference = javaPeer.get(*_env); if (weakReference) { weakReference.Call(*_env, onInvalidate); } } catch (const std::exception& exception) { Log::Error(Event::Android, std::string("MapRenderer::requestRender failed: ") + exception.what()); } } void MapRenderer::requestRender(JNIEnv& env, jni::Local>& ref) { try { static auto& javaClass = jni::Class::Singleton(env); static auto onInvalidate = javaClass.GetMethod(env, "requestRender"); if (ref) { ref.Call(env, onInvalidate); } } catch (const std::exception& exception) { Log::Error(Event::Android, std::string("MapRenderer::requestRender failed: ") + exception.what()); } } void MapRenderer::update(std::shared_ptr params) { try { // Lock on the parameters std::scoped_lock lock(updateMutex); updateParameters = std::move(params); } catch (const std::exception& exception) { Log::Error(Event::Android, std::string("MapRenderer::update failed: ") + exception.what()); } } void MapRenderer::setObserver(std::shared_ptr _rendererObserver) { try { // Lock as the initialization can come from the main thread or the GL thread first std::scoped_lock lock(initialisationMutex); rendererObserver = std::move(_rendererObserver); // Set the new observer on the Renderer implementation if (renderer) { renderer->setObserver(rendererObserver.get()); } } catch (const std::exception& exception) { Log::Error(Event::Android, std::string("MapRenderer::setObserver failed: ") + exception.what()); } } void MapRenderer::requestSnapshot(SnapshotCallback callback) { auto self = ActorRef(*this, mailboxData.getMailbox()); self.invoke( &MapRenderer::scheduleSnapshot, std::make_unique( [&, callback = std::move(callback), runloop = util::RunLoop::Get()](PremultipliedImage image) { runloop->invoke( [callback = std::move(callback), image = std::move(image), renderer = std::move(this)]() mutable { if (renderer && !renderer->destroyed) { callback(std::move(image)); } }); snapshotCallback.reset(); })); } // Called on OpenGL thread // void MapRenderer::resetRenderer() { renderer.reset(); if (!asyncRendererCleanup) { backend.reset(); } window.reset(); swapBehaviorFlush = false; } void MapRenderer::scheduleSnapshot(std::unique_ptr callback) { snapshotCallback = std::move(callback); if (backend) { gfx::BackendScope backendGuard{backend->getImpl()}; backend->enableFramebufferRead(true); } requestRender(); } void MapRenderer::render(JNIEnv&) { assert(renderer); std::shared_ptr params; { // Lock on the parameters std::unique_lock lock(updateMutex); if (!updateParameters) return; // Hold on to the update parameters during render params = updateParameters; } // Activate the backend assert(backend); gfx::BackendScope backendGuard{backend->getImpl()}; // Ensure that the "current" scheduler on the render thread is // this scheduler. Scheduler::SetCurrent(this); if (framebufferSizeChanged) { backend->updateViewPort(); framebufferSizeChanged = false; } renderer->render(params); // Deliver the snapshot if requested if (snapshotCallback) { snapshotCallback->operator()(backend->readFramebuffer()); snapshotCallback.reset(); } } void MapRenderer::onSurfaceCreated(JNIEnv& env, const jni::Object& surface) { // Lock as the initialization can come from the main thread or the GL thread first std::scoped_lock lock(initialisationMutex); UniqueANativeWindow window_ = nullptr; if (surface) { window_ = std::unique_ptr>( ANativeWindow_fromSurface(&env, reinterpret_cast(surface.get())), [](ANativeWindow* window_) { ANativeWindow_release(window_); }); } if (backend) { if (backend->createSurface(window_.get())) { window = std::move(window_); return; } // The android system will have already destroyed the underlying // GL resources if this is not the first initialization and an // attempt to clean them up will fail gfx::BackendScope backendGuard{backend->getImpl()}; backend->markContextLost(); } if (renderer) { renderer->markContextLost(); } // Reset in opposite order renderer.reset(); backend.reset(); window = std::move(window_); // Create the new backend and renderer backend = AndroidRendererBackend::Create(window.get()); renderer = std::make_unique(backend->getImpl(), pixelRatio, localIdeographFontFamily); rendererRef = std::make_unique>(*renderer, mailboxData.getMailbox()); #if MLN_RENDER_BACKEND_OPENGL // If we're running the emulator with the OpenGL backend, we're going to crash eventually, // unless we enable this mitigation. if (inEmulator()) { renderer->enableAndroidEmulatorGoldfishMitigation(true); } #endif backend->setSwapBehavior(swapBehaviorFlush ? gfx::Renderable::SwapBehaviour::Flush : gfx::Renderable::SwapBehaviour::NoFlush); // Set the observer on the new Renderer implementation if (rendererObserver) { renderer->setObserver(rendererObserver.get()); } } void MapRenderer::onSurfaceChanged([[maybe_unused]] JNIEnv& env, jint width, jint height) { #if MLN_RENDER_BACKEND_OPENGL if (!renderer) { // In case the surface has been destroyed (due to app back-grounding) jni::jobject* nullObj = nullptr; onSurfaceCreated(env, jni::Object(nullObj)); } #endif backend->resizeFramebuffer(width, height); framebufferSizeChanged = true; requestRender(); } void MapRenderer::onRendererReset(JNIEnv&) { // Make sure to destroy the renderer on the GL Thread auto self = ActorRef(*this, mailboxData.getMailbox()); self.ask(&MapRenderer::resetRenderer).wait(); } // needs to be called on GL thread void MapRenderer::onSurfaceDestroyed(JNIEnv&) { if (backend) { backend->destroySurface(); } window.reset(); } void MapRenderer::setSwapBehaviorFlush(JNIEnv&, jboolean flush) { swapBehaviorFlush = flush; if (backend) { backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush : gfx::Renderable::SwapBehaviour::NoFlush); } } // Static methods // void MapRenderer::registerNative(jni::JNIEnv& env) { // Lookup the class static auto& javaClass = jni::Class::Singleton(env); #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) // Register the peer jni::RegisterNativePeer( env, javaClass, "nativePtr", jni::MakePeer&, jni::jfloat, const jni::String&>, "nativeInitialize", "finalize", METHOD(&MapRenderer::render, "nativeRender"), METHOD(&MapRenderer::onRendererReset, "nativeReset"), METHOD(&MapRenderer::onSurfaceCreated, "nativeOnSurfaceCreated"), METHOD(&MapRenderer::onSurfaceChanged, "nativeOnSurfaceChanged"), METHOD(&MapRenderer::onSurfaceDestroyed, "nativeOnSurfaceDestroyed"), METHOD(&MapRenderer::setSwapBehaviorFlush, "nativeSetSwapBehaviorFlush")); } MapRenderer& MapRenderer::getNativePeer(JNIEnv& env, const jni::Object& jObject) { static auto& javaClass = jni::Class::Singleton(env); static auto field = javaClass.GetField(env, "nativePtr"); MapRenderer* mapRenderer = reinterpret_cast(jObject.Get(env, field)); assert(mapRenderer != nullptr); return *mapRenderer; } } // namespace android } // namespace mbgl