diff --git a/android/cpp/JNI.cpp b/android/cpp/JNI.cpp index e1731aa99..3b1801c4c 100644 --- a/android/cpp/JNI.cpp +++ b/android/cpp/JNI.cpp @@ -221,6 +221,20 @@ void JNICALL nativeStop(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { native_map_view->stop(); } +void JNICALL nativePause(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { + LOG_VERBOSE("nativePause"); + LOG_ASSERT(native_map_view_ptr != 0); + NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); + native_map_view->pause(); +} + +void JNICALL nativeResume(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { + LOG_VERBOSE("nativeResume"); + LOG_ASSERT(native_map_view_ptr != 0); + NativeMapView* native_map_view = reinterpret_cast(native_map_view_ptr); + native_map_view->resume(); +} + void JNICALL nativeRun(JNIEnv* env, jobject obj, jlong native_map_view_ptr) { LOG_VERBOSE("nativeRun"); LOG_ASSERT(native_map_view_ptr != 0); @@ -739,7 +753,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { } // NOTE: if you get java.lang.UnsatisfiedLinkError you likely forgot to set the size of the array correctly (too large) - std::array methods = {{ // Can remove the extra brace in C++14 + std::array methods = {{ // Can remove the extra brace in C++14 { "nativeCreate", "(Ljava/lang/String;)J", reinterpret_cast(&nativeCreate) }, { "nativeDestroy", "(J)V", reinterpret_cast(&nativeDestroy) }, { "nativeInitializeDisplay", "(J)V", reinterpret_cast(&nativeInitializeDisplay) }, @@ -750,6 +764,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { { "nativeDestroySurface", "(J)V", reinterpret_cast(&nativeDestroySurface) }, { "nativeStart", "(J)V", reinterpret_cast(&nativeStart) }, { "nativeStop", "(J)V", reinterpret_cast(&nativeStop) }, + { "nativePause", "(J)V", reinterpret_cast(&nativePause) }, + { "nativeResume", "(J)V", reinterpret_cast(&nativeResume) }, { "nativeRun", "(J)V", reinterpret_cast(&nativeRun) }, { "nativeRerender", "(J)V", reinterpret_cast(&nativeRerender) }, { "nativeUpdate", "(J)V", reinterpret_cast(&nativeUpdate) }, diff --git a/android/cpp/NativeMapView.cpp b/android/cpp/NativeMapView.cpp index 394e01bdb..6761f3ede 100644 --- a/android/cpp/NativeMapView.cpp +++ b/android/cpp/NativeMapView.cpp @@ -149,12 +149,6 @@ bool NativeMapView::initializeDisplay() { // Now try 8888 use565 = false; - // TODO removed these due to different indices - //config_attribs[9] = 32; // EGL_BUFFER_SIZE // Ensure we get 32bit color buffer on Tegra, 24 bit will be sorted first without it (slow software mode) - //config_attribs[11] = 8; // EGL_RED_SIZE - //config_attribs[13] = 8; // EGL_GREEN_SIZE - //config_attribs[15] = 8; // EGL_BLUE_SIZE - config_attribs[5] = 32; // EGL_BUFFER_SIZE // Ensure we get 32bit color buffer on Tegra, 24 bit will be sorted first without it (slow software mode) config_attribs[7] = 8; // EGL_RED_SIZE config_attribs[9] = 8; // EGL_GREEN_SIZE @@ -296,7 +290,25 @@ bool NativeMapView::createSurface(ANativeWindow* window_) { return false; } - start(); + if (!eglMakeCurrent(display, surface, surface, context)) { + LOG_ERROR("eglMakeCurrent() returned error %d", eglGetError()); + } + + log_gl_string(GL_VENDOR, "Vendor"); + log_gl_string(GL_RENDERER, "Renderer"); + log_gl_string(GL_VERSION, "Version"); + log_gl_string(GL_SHADING_LANGUAGE_VERSION, "SL Version"); // In the emulator this returns NULL with error code 0? https://code.google.com/p/android/issues/detail?id=78977 + log_gl_string(GL_EXTENSIONS, "Extensions"); + + loadExtensions(); + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + LOG_ERROR("eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", + eglGetError()); + } + + resume(); return true; } @@ -304,7 +316,7 @@ bool NativeMapView::createSurface(ANativeWindow* window_) { void NativeMapView::destroySurface() { LOG_VERBOSE("NativeMapView::destroySurface"); - map->terminate(); + pause(); if (surface != EGL_NO_SURFACE) { if (!eglDestroySurface(display, surface)) { @@ -464,31 +476,10 @@ void NativeMapView::start() { initializeContext(); } - if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE) - && (context != EGL_NO_CONTEXT)) { - - if (!eglMakeCurrent(display, surface, surface, context)) { - LOG_ERROR("eglMakeCurrent() returned error %d", eglGetError()); - } - - log_gl_string(GL_VENDOR, "Vendor"); - log_gl_string(GL_RENDERER, "Renderer"); - log_gl_string(GL_VERSION, "Version"); - log_gl_string(GL_SHADING_LANGUAGE_VERSION, "SL Version"); // In the emulator this returns NULL with error code 0? https://code.google.com/p/android/issues/detail?id=78977 - log_gl_string(GL_EXTENSIONS, "Extensions"); - - loadExtensions(); - - if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT)) { - LOG_ERROR("eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", - eglGetError()); - } - - map->start(); - } else { - LOG_DEBUG("Not starting because we are not ready"); - } + LOG_ASSERT(display != EGL_NO_DISPLAY); + LOG_ASSERT(context != EGL_NO_CONTEXT); + + map->start(); } void NativeMapView::loadExtensions() { @@ -519,9 +510,33 @@ void NativeMapView::loadExtensions() { void NativeMapView::stop() { LOG_VERBOSE("NativeMapView::stop"); - if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE) - && (context != EGL_NO_CONTEXT)) { - map->stop(); + + LOG_ASSERT(display != EGL_NO_DISPLAY); + LOG_ASSERT(context != EGL_NO_CONTEXT); + + map->stop(); +} + + +void NativeMapView::pause() { + LOG_VERBOSE("NativeMapView::pause"); + + LOG_ASSERT(display != EGL_NO_DISPLAY); + LOG_ASSERT(context != EGL_NO_CONTEXT); + + map->pause(); +} + +void NativeMapView::resume() { + LOG_VERBOSE("NativeMapView::resume"); + + LOG_ASSERT(display != EGL_NO_DISPLAY); + LOG_ASSERT(context != EGL_NO_CONTEXT); + + if (surface != EGL_NO_SURFACE) { + map->resume(); + } else { + LOG_DEBUG("Not resuming because we are not ready"); } } diff --git a/android/cpp/NativeMapView.hpp b/android/cpp/NativeMapView.hpp index aafd76189..05aa11b88 100644 --- a/android/cpp/NativeMapView.hpp +++ b/android/cpp/NativeMapView.hpp @@ -62,6 +62,9 @@ public: void start(); void stop(); + void resume(); + void pause(); + void notifyMapChange(); private: diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java index e9e815ded..2b150e8ba 100644 --- a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java @@ -373,6 +373,7 @@ public class MapView extends SurfaceView { } mNativeMapView.initializeDisplay(); + mNativeMapView.initializeContext(); } // Called when we need to save instance state @@ -392,6 +393,7 @@ public class MapView extends SurfaceView { // Must be called from Activity onDestroy public void onDestroy() { Log.v(TAG, "onDestroy"); + mNativeMapView.terminateContext(); mNativeMapView.terminateDisplay(); } @@ -399,21 +401,22 @@ public class MapView extends SurfaceView { // Must be called from Activity onStart public void onStart() { Log.v(TAG, "onStart"); - mNativeMapView.initializeContext(); + mNativeMapView.pause(); + mNativeMapView.start(); } // Called when we need to terminate the GL context // Must be called from Activity onPause public void onStop() { Log.v(TAG, "onStop"); - mNativeMapView.terminateContext(); + mNativeMapView.stop(); } // Called when we need to stop the render thread // Must be called from Activity onPause public void onPause() { Log.v(TAG, "onPause"); - mNativeMapView.stop(); + mNativeMapView.pause(); } // Called when we need to start the render thread @@ -421,7 +424,7 @@ public class MapView extends SurfaceView { public void onResume() { Log.v(TAG, "onResume"); - mNativeMapView.start(); + mNativeMapView.resume(); } // This class handles SurfaceHolder callbacks @@ -464,6 +467,8 @@ public class MapView extends SurfaceView { } } + // TODO examine how GLSurvaceView hadles attach/detach from window + // Called when view is no longer connected @Override protected void onDetachedFromWindow() { diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java index 01ee430df..974c408b6 100644 --- a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java @@ -80,6 +80,14 @@ class NativeMapView { nativeStop(mNativeMapViewPtr); } + public void pause() { + nativePause(mNativeMapViewPtr); + } + + public void resume() { + nativeResume(mNativeMapViewPtr); + } + public void run() { nativeRun(mNativeMapViewPtr); } @@ -420,6 +428,10 @@ class NativeMapView { private native void nativeStop(long nativeMapViewPtr); + private native void nativePause(long nativeMapViewPtr); + + private native void nativeResume(long nativeMapViewPtr); + private native void nativeRun(long nativeMapViewPtr); private native void nativeRerender(long nativeMapViewPtr); diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 43402fe2e..f2f10665e 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -45,6 +45,12 @@ public: // function. void stop(stop_callback cb = nullptr, void *data = nullptr); + // Pauses the render thread. The render thread will stop running but will not be terminated and will not lose state until resumed. + void pause(); + + // Resumes a paused render thread + void resume(); + // Runs the map event loop. ONLY run this function when you want to get render a single frame // with this map object. It will *not* spawn a separate thread and instead block until the // frame is completely rendered. @@ -145,6 +151,9 @@ private: static void terminate(uv_async_t *async); static void cleanup(uv_async_t *async); + // Checks if render thread needs to pause + void check_for_pause(); + // Setup void setup(); @@ -167,6 +176,12 @@ private: std::unique_ptr async_render; std::unique_ptr async_cleanup; +private: + bool terminating = false; + bool is_paused = false; + std::unique_ptr mutex_pause; + std::unique_ptr cond_resume; + private: // If cleared, the next time the render thread attempts to render the map, it will *actually* // render the map. diff --git a/include/mbgl/util/uv.hpp b/include/mbgl/util/uv.hpp index 3c533cfbf..0e828bb4f 100644 --- a/include/mbgl/util/uv.hpp +++ b/include/mbgl/util/uv.hpp @@ -21,6 +21,8 @@ class thread; class rwlock; class loop; class worker; +class mutex; +class cond; } diff --git a/include/mbgl/util/uv_detail.hpp b/include/mbgl/util/uv_detail.hpp index 3bbfa4ac5..112339c1b 100644 --- a/include/mbgl/util/uv_detail.hpp +++ b/include/mbgl/util/uv_detail.hpp @@ -52,6 +52,7 @@ private: }; class mutex { + friend class cond; public: inline mutex() { if (uv_mutex_init(&mtx) != 0) { @@ -66,6 +67,23 @@ private: uv_mutex_t mtx; }; +class cond { +public: + inline cond() { + if (uv_cond_init(&cnd) != 0) { + throw std::runtime_error("failed to initialize condition variable"); + } + } + inline ~cond() { uv_cond_destroy(&cnd); } + inline void signal() { uv_cond_signal(&cnd); } + inline void broadcast() { uv_cond_broadcast(&cnd); } + inline void wait(mutex &mtx) { uv_cond_wait(&cnd, &mtx.mtx); } + inline void timedwait(mutex &mtx, uint64_t timeout) { uv_cond_timedwait(&cnd, &mtx.mtx, timeout); } + +private: + uv_cond_t cnd; +}; + class lock { public: lock(mutex &mtx_) : mtx(mtx_) { mtx.lock(); } diff --git a/src/map/map.cpp b/src/map/map.cpp index b720f663e..ed105375f 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -36,6 +36,8 @@ Map::Map(View& view_) async_terminate(new uv_async_t()), async_render(new uv_async_t()), async_cleanup(new uv_async_t()), + mutex_pause(std::make_unique()), + cond_resume(std::make_unique()), view(view_), #ifndef NDEBUG main_thread(uv_thread_self()), @@ -76,7 +78,7 @@ uv::worker &Map::getWorker() { return *workers; } -void Map::start() { +void Map::start() { // TODO add pause arg assert(uv_thread_self() == main_thread); assert(!async); @@ -136,6 +138,8 @@ void Map::stop(stop_callback cb, void *data) { uv_async_send(async_terminate.get()); + resume(); + if (cb) { // Wait until the render thread stopped. We are using this construct instead of plainly // relying on the thread_join because the system might need to run things in the current @@ -155,6 +159,45 @@ void Map::stop(stop_callback cb, void *data) { async = false; } +void Map::pause() { + assert(uv_thread_self() == main_thread); + Log::Info(Event::General, "Map::pause() start"); + + // TODO wait until paused before return + + + Log::Info(Event::General, "Map::pause() locking"); + mutex_pause->lock(); + Log::Info(Event::General, "Map::pause() locked"); + is_paused = true; + Log::Info(Event::General, "Map::pause() set is_paused = true"); + Log::Info(Event::General, "Map::pause() unlocking"); + mutex_pause->unlock(); + Log::Info(Event::General, "Map::pause() unlocked"); + + Log::Info(Event::General, "Map::pause() stopping loop"); + uv_stop(**loop); + Log::Info(Event::General, "Map::pause() stopping done"); +} + +void Map::resume() { + assert(uv_thread_self() == main_thread); + Log::Info(Event::General, "Map::resume() start"); + + Log::Info(Event::General, "Map::resume() locking"); + mutex_pause->lock(); + Log::Info(Event::General, "Map::resume() locked"); + is_paused = false; + Log::Info(Event::General, "Map::resume() set is_paused = false"); + Log::Info(Event::General, "Map::resume() broadcasting"); + cond_resume->broadcast(); + Log::Info(Event::General, "Map::resume() broadcasted"); + Log::Info(Event::General, "Map::resume() unlocking"); + mutex_pause->unlock(); + Log::Info(Event::General, "Map::resume() unlocked"); + Log::Info(Event::General, "Map::resume() done"); +} + void Map::run() { #ifndef NDEBUG if (!async) { @@ -163,9 +206,28 @@ void Map::run() { #endif assert(uv_thread_self() == map_thread); + check_for_pause(); + setup(); prepare(); - uv_run(**loop, UV_RUN_DEFAULT); + + Log::Info(Event::General, "Map::run() start"); + terminating = false; + Log::Info(Event::General, "Map::run() set terminating = false"); + + Log::Info(Event::General, "Map::run() entering run loop"); + while(!terminating) { + Log::Info(Event::General, "Map::run() next run loop, terminating = %s", terminating ? "true" : "false"); + Log::Info(Event::General, "Map::run() uv_run enter"); + uv_run(**loop, UV_RUN_DEFAULT); + Log::Info(Event::General, "Map::run() exit"); + + check_for_pause(); + + //uv_run(**loop, UV_RUN_DEFAULT); + } + + Log::Info(Event::General, "Map::run() run loop exit"); // Run the event loop once more to make sure our async delete handlers are called. uv_run(**loop, UV_RUN_ONCE); @@ -180,6 +242,23 @@ void Map::run() { } } +void Map::check_for_pause() { + Log::Info(Event::General, "Map::check_for_pause() locking"); + mutex_pause->lock(); + Log::Info(Event::General, "Map::check_for_pause() locked"); + Log::Info(Event::General, "Map::check_for_pause() entering pause loop"); + while (is_paused) { + Log::Info(Event::General, "Map::check_for_pause() next pause loop, is_paused = %s", is_paused ? "true" : "false"); + Log::Info(Event::General, "Map::check_for_pause() waiting"); + cond_resume->wait(*mutex_pause); + Log::Info(Event::General, "Map::check_for_pause() waited"); + } + Log::Info(Event::General, "Map::check_for_pause() exit pause loop"); + Log::Info(Event::General, "Map::check_for_pause() unlocking"); + mutex_pause->unlock(); + Log::Info(Event::General, "Map::check_for_pause() unlocked"); +} + void Map::rerender() { // We only send render events if we want to continuously update the map // (== async rendering). @@ -250,6 +329,7 @@ void Map::render(uv_async_t *async) { } void Map::terminate(uv_async_t *async) { + Log::Info(Event::General, "Map::terminate() start"); // Closes all open handles on the loop. This means that the loop will automatically terminate. Map *map = static_cast(async->data); assert(uv_thread_self() == map->map_thread); @@ -263,9 +343,14 @@ void Map::terminate(uv_async_t *async) { map->view.make_inactive(); + map->terminating = true; + Log::Info(Event::General, "Map::terminate() set terminating = true"); + uv_close((uv_handle_t *)map->async_cleanup.get(), nullptr); uv_close((uv_handle_t *)map->async_render.get(), nullptr); uv_close((uv_handle_t *)map->async_terminate.get(), nullptr); + + Log::Info(Event::General, "Map::terminate() done"); } #pragma mark - Setup