Make Android integration more robust for API >= 11
authorPyry Haulos <phaulos@google.com>
Wed, 1 Oct 2014 22:49:17 +0000 (15:49 -0700)
committerPyry Haulos <phaulos@google.com>
Thu, 2 Oct 2014 00:26:33 +0000 (17:26 -0700)
Recent targetSdkLevel bump exposed robustness issues in the Android
port which were hidden by pre-honeycomb workarounds in
android.app.ActivityThread. For honeycomb and up when launching activity
with lockscreen on or screen off the system for some reason first starts
the activity once, puts it back to stopped state and then resumes again.
Original Android port assumed that stop event signaled that the test
execution should always stop and process should kill itself. This might
have been necessary workaround on some older Android versions where
onDestroy was never called even if app called finish().

This commit improves Android integration in multiple ways:

 * Android port can continue test execution even across stop-start
boundary in the Activity life-cycle.

 * eglu::GLRenderContext can recover from window loss by recreating
the window if NativeWindow::postIterate() throws WindowDestroyedError.

 * eglu::GLRenderContext refreshes window surface size in postIterate()
call. Note that most test cases might not like if surface is resized
during execution though.

 * Temporary window loss (recreation) no longer leads to test run abort,
but rather Android NativeWindow correctly signals that event allowing
eglu::GLRenderContext to recover.

Change-Id: Iaa240f16a2ae6cea7fb894c5772d1e7959a1cfac

android/package/AndroidManifest.xml
framework/egl/egluGLContextFactory.cpp
framework/egl/egluNativeWindow.hpp
framework/platform/android/tcuAndroidPlatform.cpp
framework/platform/android/tcuAndroidPlatform.hpp
framework/platform/android/tcuAndroidRenderActivity.cpp
framework/platform/android/tcuAndroidRenderActivity.hpp
framework/platform/android/tcuAndroidTestActivity.cpp
framework/platform/android/tcuAndroidTestActivity.hpp
framework/platform/android/tcuAndroidWindow.cpp
framework/platform/android/tcuAndroidWindow.hpp

index 12e3681..33380c2 100644 (file)
@@ -5,7 +5,6 @@
              android:versionName="1.0">
     <application android:label="dEQP Tests"
                                 android:icon="@drawable/deqp_app">
-       
                <activity android:name="com.drawelements.deqp.execserver.ExecServerActivity"
                                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                                  android:label="dEQP ExecServer"
                                 android:label="dEQP ExecServer Service"
                                 android:exported="true"
                                 android:process=":execserver" />
-
-               <!-- Tester process -->
                <activity android:name="android.app.NativeActivity"
                                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                                  android:label="dEQP Test Process"
-                                 android:configChanges="orientation|screenSize|keyboardHidden"
+                                 android:configChanges="keyboardHidden|orientation|screenSize"
                                  android:exported="true"
                                  android:process=":testercore">
                        <meta-data android:name="android.app.lib_name"
                        <meta-data android:name="android.app.func_name"
                                           android:value="createTestActivity" />
                </activity>
-    </application>
-       
-    <uses-sdk android:minSdkVersion="13"/>
+       </application>
+
+       <uses-sdk android:minSdkVersion="13"/>
+       <uses-sdk android:targetSdkVersion="19"/>
     <uses-feature android:glEsVersion="0x00020000"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.GET_TASKS" />
index b6f79c4..358edff 100644 (file)
@@ -133,19 +133,20 @@ public:
                                                                                RenderContext                   (const NativeDisplayFactory* displayFactory, const NativeWindowFactory* windowFactory, const NativePixmapFactory* pixmapFactory, const glu::RenderConfig& config);
        virtual                                                         ~RenderContext                  (void);
 
-       virtual glu::ContextType                        getType                                 (void) const { return m_glContextType;  }
-       virtual const glw::Functions&           getFunctions                    (void) const { return m_glFunctions;    }
-       virtual const tcu::RenderTarget&        getRenderTarget                 (void) const { return m_glRenderTarget; }
+       virtual glu::ContextType                        getType                                 (void) const { return m_renderConfig.type;      }
+       virtual const glw::Functions&           getFunctions                    (void) const { return m_glFunctions;            }
+       virtual const tcu::RenderTarget&        getRenderTarget                 (void) const { return m_glRenderTarget;         }
        virtual void                                            postIterate                             (void);
 
-       virtual EGLDisplay                                      getEGLDisplay                   (void) const { return m_eglDisplay;             }
-       virtual EGLContext                                      getEGLContext                   (void) const { return m_eglContext;             }
+       virtual EGLDisplay                                      getEGLDisplay                   (void) const { return m_eglDisplay;                     }
+       virtual EGLContext                                      getEGLContext                   (void) const { return m_eglContext;                     }
 
 private:
        void                                                            create                                  (const NativeDisplayFactory* displayFactory, const NativeWindowFactory* windowFactory, const NativePixmapFactory* pixmapFactory, const glu::RenderConfig& config);
        void                                                            destroy                                 (void);
 
-       const glu::ContextType                          m_glContextType;
+       const glu::RenderConfig                         m_renderConfig;
+       const NativeWindowFactory* const        m_nativeWindowFactory;  // Stored in case window must be re-created
 
        NativeDisplay*                                          m_display;
        NativeWindow*                                           m_window;
@@ -162,7 +163,8 @@ private:
 };
 
 RenderContext::RenderContext (const NativeDisplayFactory* displayFactory, const NativeWindowFactory* windowFactory, const NativePixmapFactory* pixmapFactory, const glu::RenderConfig& config)
-       : m_glContextType               (config.type)
+       : m_renderConfig                (config)
+       , m_nativeWindowFactory (windowFactory)
        , m_display                             (DE_NULL)
        , m_window                              (DE_NULL)
        , m_pixmap                              (DE_NULL)
@@ -639,8 +641,89 @@ void RenderContext::postIterate (void)
 {
        if (m_window)
        {
-               EGLU_CHECK_CALL(eglSwapBuffers(m_eglDisplay, m_eglSurface));
-               m_window->processEvents();
+               EGLBoolean      swapOk  = eglSwapBuffers(m_eglDisplay, m_eglSurface);
+               EGLint          error   = eglGetError();
+
+               if (!swapOk && error != EGL_BAD_SURFACE)
+               {
+                       if (error == EGL_BAD_ALLOC)
+                               throw BadAllocError("eglSwapBuffers()");
+                       else if (error == EGL_CONTEXT_LOST)
+                               throw tcu::ResourceError("eglSwapBuffers() failed, context lost");
+                       else
+                               throw Error(error, "eglSwapBuffers()");
+               }
+
+               try
+               {
+                       m_window->processEvents();
+               }
+               catch (const WindowDestroyedError&)
+               {
+                       tcu::print("Warning: Window destroyed, recreating...\n");
+
+                       EGLU_CHECK_CALL(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+                       EGLU_CHECK_CALL(eglDestroySurface(m_eglDisplay, m_eglSurface));
+                       m_eglSurface = EGL_NO_SURFACE;
+
+                       delete m_window;
+                       m_window = DE_NULL;
+
+                       try
+                       {
+                               WindowSurfacePair windowSurface = createWindow(m_display, m_nativeWindowFactory, m_eglDisplay, m_eglConfig, m_renderConfig);
+                               m_window                = windowSurface.first;
+                               m_eglSurface    = windowSurface.second;
+
+                               EGLU_CHECK_CALL(eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext));
+
+                               swapOk  = EGL_TRUE;
+                               error   = EGL_SUCCESS;
+                       }
+                       catch (const std::exception& e)
+                       {
+                               if (m_eglSurface)
+                               {
+                                       eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+                                       eglDestroySurface(m_eglDisplay, m_eglSurface);
+                                       m_eglSurface = EGL_NO_SURFACE;
+                               }
+
+                               delete m_window;
+                               m_window = DE_NULL;
+
+                               throw tcu::ResourceError(string("Failed to re-create window: ") + e.what());
+                       }
+               }
+
+               if (!swapOk)
+               {
+                       DE_ASSERT(error == EGL_BAD_SURFACE);
+                       throw Error(error, "eglSwapBuffers()");
+               }
+
+               // Refresh dimensions
+               {
+                       int     newWidth        = 0;
+                       int     newHeight       = 0;
+
+                       eglQuerySurface(m_eglDisplay, m_eglSurface, EGL_WIDTH,  &newWidth);
+                       eglQuerySurface(m_eglDisplay, m_eglSurface, EGL_HEIGHT, &newHeight);
+                       EGLU_CHECK_MSG("Failed to query window size");
+
+                       if (newWidth    != m_glRenderTarget.getWidth() ||
+                               newHeight       != m_glRenderTarget.getHeight())
+                       {
+                               tcu::print("Warning: Window size changed (%dx%d -> %dx%d), test results might be invalid!\n",
+                                                  m_glRenderTarget.getWidth(), m_glRenderTarget.getHeight(), newWidth, newHeight);
+
+                               m_glRenderTarget = tcu::RenderTarget(newWidth, newHeight,
+                                                                                                        m_glRenderTarget.getPixelFormat(),
+                                                                                                        m_glRenderTarget.getDepthBits(),
+                                                                                                        m_glRenderTarget.getStencilBits(),
+                                                                                                        m_glRenderTarget.getNumSamples());
+                       }
+               }
        }
        else
        {
index 616fe02..f404013 100644 (file)
@@ -64,6 +64,12 @@ struct WindowParams
        WindowParams (int width_, int height_, Visibility visibility_) : width(width_), height(height_), visibility(visibility_) {}
 };
 
+class WindowDestroyedError : public tcu::ResourceError
+{
+public:
+       WindowDestroyedError (const std::string& message) : tcu::ResourceError(message) {}
+};
+
 class NativeWindow
 {
 public:
index a137ea3..d2cf29e 100644 (file)
@@ -50,7 +50,7 @@ public:
 class NativeDisplayFactory : public eglu::NativeDisplayFactory
 {
 public:
-                                                                       NativeDisplayFactory    (Window& window);
+                                                                       NativeDisplayFactory    (WindowRegistry& windowRegistry);
                                                                        ~NativeDisplayFactory   (void) {}
 
        virtual eglu::NativeDisplay*    createDisplay                   (const EGLAttrib* attribList) const;
@@ -59,70 +59,70 @@ public:
 class NativeWindow : public eglu::NativeWindow
 {
 public:
-                                                                       NativeWindow                    (Window& window, int width, int height, int32_t format);
+                                                                       NativeWindow                    (Window* window, int width, int height, int32_t format);
        virtual                                                 ~NativeWindow                   (void);
 
-       virtual EGLNativeWindowType             getLegacyNative                 (void)                  { return m_window.getNativeWindow();    }
-       IVec2                                                   getScreenSize                   (void) const    { return m_window.getSize();                    }
+       virtual EGLNativeWindowType             getLegacyNative                 (void)                  { return m_window->getNativeWindow();   }
+       IVec2                                                   getScreenSize                   (void) const    { return m_window->getSize();                   }
 
        void                                                    setSurfaceSize                  (IVec2 size);
 
        virtual void                                    processEvents                   (void);
 
 private:
-       Window&                                                 m_window;
+       Window*                                                 m_window;
        int32_t                                                 m_format;
 };
 
 class NativeWindowFactory : public eglu::NativeWindowFactory
 {
 public:
-                                                                       NativeWindowFactory             (Window& window);
+                                                                       NativeWindowFactory             (WindowRegistry& windowRegistry);
                                                                        ~NativeWindowFactory    (void);
 
        virtual eglu::NativeWindow*             createWindow                    (eglu::NativeDisplay* nativeDisplay, const eglu::WindowParams& params) const;
        virtual eglu::NativeWindow*             createWindow                    (eglu::NativeDisplay* nativeDisplay, EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, const eglu::WindowParams& params) const;
 
 private:
-       Window&                                                 m_window;
+       virtual eglu::NativeWindow*             createWindow                    (const eglu::WindowParams& params, int32_t format) const;
+
+       WindowRegistry&                                 m_windowRegistry;
 };
 
 // NativeWindow
 
-NativeWindow::NativeWindow (Window& window, int width, int height, int32_t format)
+NativeWindow::NativeWindow (Window* window, int width, int height, int32_t format)
        : eglu::NativeWindow    (WINDOW_CAPABILITIES)
        , m_window                              (window)
        , m_format                              (format)
 {
-       // Try to acquire window.
-       if (!m_window.tryAcquire())
-               throw ResourceError("Native window is not available", "", __FILE__, __LINE__);
-
        // Set up buffers.
        setSurfaceSize(IVec2(width, height));
 }
 
 NativeWindow::~NativeWindow (void)
 {
-       m_window.release();
+       m_window->release();
 }
 
 void NativeWindow::processEvents (void)
 {
+       if (m_window->isPendingDestroy())
+               throw eglu::WindowDestroyedError("Window has been destroyed");
 }
 
 void NativeWindow::setSurfaceSize (tcu::IVec2 size)
 {
-       m_window.setBuffersGeometry(size.x() != eglu::WindowParams::SIZE_DONT_CARE ? size.x() : 0,
-                                                               size.y() != eglu::WindowParams::SIZE_DONT_CARE ? size.y() : 0,
-                                                               m_format);
+       m_window->setBuffersGeometry(size.x() != eglu::WindowParams::SIZE_DONT_CARE ? size.x() : 0,
+                                                                size.y() != eglu::WindowParams::SIZE_DONT_CARE ? size.y() : 0,
+                                                                m_format);
 }
 
 // NativeWindowFactory
 
-NativeWindowFactory::NativeWindowFactory (Window& window)
+NativeWindowFactory::NativeWindowFactory (WindowRegistry& windowRegistry)
        : eglu::NativeWindowFactory     ("default", "Default display", WINDOW_CAPABILITIES)
-       , m_window                                      (window)
+       , m_windowRegistry                      (windowRegistry)
 {
 }
 
@@ -133,22 +133,33 @@ NativeWindowFactory::~NativeWindowFactory (void)
 eglu::NativeWindow* NativeWindowFactory::createWindow (eglu::NativeDisplay* nativeDisplay, const eglu::WindowParams& params) const
 {
        DE_UNREF(nativeDisplay);
-       return new NativeWindow(m_window, params.width, params.height, WINDOW_FORMAT_RGBA_8888);
+       return createWindow(params, WINDOW_FORMAT_RGBA_8888);
 }
 
 eglu::NativeWindow* NativeWindowFactory::createWindow (eglu::NativeDisplay* nativeDisplay, EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, const eglu::WindowParams& params) const
 {
        const int32_t format = (int32_t)eglu::getConfigAttribInt(display, config, EGL_NATIVE_VISUAL_ID);
        DE_UNREF(nativeDisplay && attribList);
-       return new NativeWindow(m_window, params.width, params.height, format);
+       return createWindow(params, format);
+}
+
+
+eglu::NativeWindow* NativeWindowFactory::createWindow (const eglu::WindowParams& params, int32_t format) const
+{
+       Window* window = m_windowRegistry.tryAcquireWindow();
+
+       if (!window)
+               throw ResourceError("Native window is not available", DE_NULL, __FILE__, __LINE__);
+
+       return new NativeWindow(window, params.width, params.height, format);
 }
 
 // NativeDisplayFactory
 
-NativeDisplayFactory::NativeDisplayFactory (Window& window)
+NativeDisplayFactory::NativeDisplayFactory (WindowRegistry& windowRegistry)
        : eglu::NativeDisplayFactory("default", "Default display", DISPLAY_CAPABILITIES)
 {
-       m_nativeWindowRegistry.registerFactory(new NativeWindowFactory(window));
+       m_nativeWindowRegistry.registerFactory(new NativeWindowFactory(windowRegistry));
 }
 
 eglu::NativeDisplay* NativeDisplayFactory::createDisplay (const EGLAttrib* attribList) const
@@ -159,10 +170,9 @@ eglu::NativeDisplay* NativeDisplayFactory::createDisplay (const EGLAttrib* attri
 
 // Platform
 
-Platform::Platform (ANativeWindow* window)
-       : m_window(window)
+Platform::Platform (void)
 {
-       m_nativeDisplayFactoryRegistry.registerFactory(new NativeDisplayFactory(m_window));
+       m_nativeDisplayFactoryRegistry.registerFactory(new NativeDisplayFactory(m_windowRegistry));
        m_contextFactoryRegistry.registerFactory(new eglu::GLContextFactory(m_nativeDisplayFactoryRegistry));
 }
 
@@ -170,5 +180,11 @@ Platform::~Platform (void)
 {
 }
 
+bool Platform::processEvents (void)
+{
+       m_windowRegistry.garbageCollect();
+       return true;
+}
+
 } // Android
 } // tcu
index d6df153..d8463c3 100644 (file)
@@ -37,14 +37,18 @@ namespace Android
 class Platform : public tcu::Platform, private eglu::Platform, private glu::Platform
 {
 public:
-                                                                       Platform                        (ANativeWindow* window);
+                                                                       Platform                        (void);
        virtual                                                 ~Platform                       (void);
 
+       virtual bool                                    processEvents           (void);
+
        virtual const glu::Platform&    getGLPlatform           (void) const { return static_cast<const glu::Platform&>(*this);         }
        virtual const eglu::Platform&   getEGLPlatform          (void) const { return static_cast<const eglu::Platform&>(*this);        }
 
+       WindowRegistry&                                 getWindowRegistry       (void) { return m_windowRegistry; }
+
 private:
-       Window                                                  m_window;
+       WindowRegistry                                  m_windowRegistry;
 };
 
 } // Android
index 075f30c..142435e 100644 (file)
@@ -47,6 +47,26 @@ enum
        MESSAGE_QUEUE_SIZE = 8 //!< Length of RenderThread message queue.
 };
 
+#if defined(DE_DEBUG)
+static const char* getMessageTypeName (MessageType type)
+{
+       static const char* s_names[] =
+       {
+               "RESUME",
+               "PAUSE",
+               "FINISH",
+               "WINDOW_CREATED",
+               "WINDOW_RESIZED",
+               "WINDOW_DESTROYED",
+               "INPUT_QUEUE_CREATED",
+               "INPUT_QUEUE_DESTROYED",
+               "SYNC"
+       };
+       DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == MESSAGETYPE_LAST);
+       return s_names[type];
+}
+#endif
+
 // RenderThread
 
 RenderThread::RenderThread (NativeActivity& activity)
@@ -71,7 +91,7 @@ void RenderThread::start (void)
        Thread::start();
 }
 
-void RenderThread::destroy (void)
+void RenderThread::stop (void)
 {
        // Queue finish command
        enqueue(Message(MESSAGE_FINISH));
@@ -108,21 +128,29 @@ void RenderThread::sync (void)
 
 void RenderThread::processMessage (const Message& message)
 {
+       DBG_PRINT(("RenderThread::processMessage(): message = { %s, %p }\n", getMessageTypeName(message.type), message.payload.window));
+
        switch (message.type)
        {
                case MESSAGE_RESUME:    m_paused = false;       break;
                case MESSAGE_PAUSE:             m_paused = true;        break;
                case MESSAGE_FINISH:    m_finish = true;        break;
 
+               // \note While Platform / WindowRegistry are currently multi-window -capable,
+               //               the fact that platform gives us windows too late / at unexpected times
+               //               forces us to do some sanity checking and limit system to one window here.
                case MESSAGE_WINDOW_CREATED:
                        if (m_windowState != WINDOWSTATE_NOT_CREATED && m_windowState != WINDOWSTATE_DESTROYED)
                                throw InternalError("Got unexpected onNativeWindowCreated() event from system");
+
                        m_windowState   = WINDOWSTATE_NOT_INITIALIZED;
                        m_window                = message.payload.window;
                        break;
 
                case MESSAGE_WINDOW_RESIZED:
-                       DE_ASSERT(m_window == message.payload.window);
+                       if (m_window != message.payload.window)
+                               throw InternalError("Got onNativeWindowResized() event targeting different window");
+
                        if (m_windowState == WINDOWSTATE_NOT_INITIALIZED)
                        {
                                // Got first resize event, window is ready for use.
@@ -133,14 +161,19 @@ void RenderThread::processMessage (const Message& message)
                                onWindowResized(message.payload.window);
                        else
                                throw InternalError("Got unexpected onNativeWindowResized() event from system");
+
                        break;
 
                case MESSAGE_WINDOW_DESTROYED:
-                       DE_ASSERT(m_window == message.payload.window);
+                       if (m_window != message.payload.window)
+                               throw InternalError("Got onNativeWindowDestroyed() event targeting different window");
+
                        if (m_windowState != WINDOWSTATE_NOT_INITIALIZED && m_windowState != WINDOWSTATE_READY)
                                throw InternalError("Got unexpected onNativeWindowDestroyed() event from system");
 
-                       onWindowDestroyed(message.payload.window);
+                       if (m_windowState == WINDOWSTATE_READY)
+                               onWindowDestroyed(message.payload.window);
+
                        m_windowState   = WINDOWSTATE_DESTROYED;
                        m_window                = DE_NULL;
                        break;
@@ -207,15 +240,10 @@ void RenderThread::run (void)
 
                        // Everything set up - safe to render.
                        if (!render())
+                       {
+                               DBG_PRINT(("RenderThread::run(): render\n"));
                                break;
-               }
-
-               if (m_windowState == WINDOWSTATE_READY)
-               {
-                       // Clean up window use
-                       onWindowDestroyed(m_window);
-                       m_windowState   = WINDOWSTATE_DESTROYED;
-                       m_window                = DE_NULL;
+                       }
                }
        }
        catch (const std::exception& e)
@@ -270,9 +298,6 @@ void RenderActivity::setThread (RenderThread* thread)
 void RenderActivity::onStart (void)
 {
        DBG_PRINT(("RenderActivity::onStart()"));
-
-       // Launch tester thread when activity is created. It will remain in paused state until resume() is called.
-       m_thread->start();
 }
 
 void RenderActivity::onResume (void)
@@ -294,7 +319,6 @@ void RenderActivity::onPause (void)
 void RenderActivity::onStop (void)
 {
        DBG_PRINT(("RenderActivity::onStop()"));
-       m_thread->destroy();
 }
 
 void RenderActivity::onDestroy (void)
index 676c295..e2e7331 100644 (file)
@@ -126,7 +126,8 @@ public:
        void                                    start                                           (void);
        void                                    resume                                          (void);
        void                                    pause                                           (void);
-       void                                    destroy                                         (void);
+       void                                    stop                                            (void);
+
        void                                    enqueue                                         (const Message& message);
        void                                    sync                                            (void);
 
index d2a7cd1..25d0b7f 100644 (file)
@@ -36,33 +36,15 @@ namespace tcu
 namespace Android
 {
 
-// TestApp
-
-TestApp::TestApp (NativeActivity& activity, ANativeWindow* window, const CommandLine& cmdLine)
-       : m_cmdLine             (cmdLine)
-       , m_platform    (window)
-       , m_archive             (activity.getNativeActivity()->assetManager)
-       , m_log                 (m_cmdLine.getLogFileName())
-       , m_app                 (m_platform, m_archive, m_log, m_cmdLine)
-{
-}
-
-TestApp::~TestApp (void)
-{
-}
-
-bool TestApp::iterate (void)
-{
-       return m_app.iterate();
-}
-
 // TestThread
 
 TestThread::TestThread (NativeActivity& activity, const CommandLine& cmdLine)
        : RenderThread  (activity)
        , m_cmdLine             (cmdLine)
-       , m_testApp             (DE_NULL)
-       , m_done                (false)
+       , m_archive             (activity.getNativeActivity()->assetManager)
+       , m_log                 (m_cmdLine.getLogFileName())
+       , m_app                 (m_platform, m_archive, m_log, m_cmdLine)
+       , m_finished    (false)
 {
 }
 
@@ -74,45 +56,29 @@ TestThread::~TestThread (void)
 void TestThread::run (void)
 {
        RenderThread::run();
-
-       delete m_testApp;
-       m_testApp = DE_NULL;
 }
 
 void TestThread::onWindowCreated (ANativeWindow* window)
 {
-       DE_ASSERT(!m_testApp);
-       m_testApp = new TestApp(getNativeActivity(), window, m_cmdLine);
+       m_platform.getWindowRegistry().addWindow(window);
 }
 
 void TestThread::onWindowDestroyed (ANativeWindow* window)
 {
-       DE_UNREF(window);
-       DE_ASSERT(m_testApp);
-       delete m_testApp;
-       m_testApp = DE_NULL;
-
-       if (!m_done)
-       {
-               // \note We could just throw exception here and RenderThread would gracefully terminate.
-               //               However, native window is often destroyed when app is closed and android may not
-               //               end up calling onStop().
-               die("Window was destroyed during execution");
-       }
+       m_platform.getWindowRegistry().destroyWindow(window);
 }
 
 void TestThread::onWindowResized (ANativeWindow* window)
 {
-       // \todo [2013-05-12 pyry] Handle this in some sane way.
        DE_UNREF(window);
        print("Warning: Native window was resized, results may be undefined");
 }
 
 bool TestThread::render (void)
 {
-       DE_ASSERT(m_testApp);
-       m_done = !m_testApp->iterate();
-       return !m_done;
+       if (!m_finished)
+               m_finished = !m_app.iterate();
+       return !m_finished;
 }
 
 // TestActivity
@@ -121,10 +87,8 @@ TestActivity::TestActivity (ANativeActivity* activity)
        : RenderActivity        (activity)
        , m_cmdLine                     (getIntentStringExtra(activity, "cmdLine"))
        , m_testThread          (*this, m_cmdLine)
+       , m_started                     (false)
 {
-       // Provide RenderThread
-       setThread(&m_testThread);
-
        // Set initial orientation.
        setRequestedOrientation(getNativeActivity(), mapScreenRotation(m_cmdLine.getScreenRotation()));
 
@@ -139,9 +103,28 @@ TestActivity::~TestActivity (void)
 {
 }
 
-void TestActivity::onStop (void)
+void TestActivity::onStart (void)
 {
-       RenderActivity::onStop();
+       if (!m_started)
+       {
+               setThread(&m_testThread);
+               m_testThread.start();
+               m_started = true;
+       }
+
+       RenderActivity::onStart();
+}
+
+void TestActivity::onDestroy (void)
+{
+       if (m_started)
+       {
+               setThread(DE_NULL);
+               m_testThread.stop();
+               m_started = false;
+       }
+
+       RenderActivity::onDestroy();
 
        // Kill this process.
        print("Done, killing process");
index 9effc35..58c28e6 100644 (file)
@@ -36,22 +36,6 @@ namespace tcu
 namespace Android
 {
 
-class TestApp
-{
-public:
-                                                       TestApp                                         (NativeActivity& activity, ANativeWindow* window, const CommandLine& cmdLine);
-                                                       ~TestApp                                        (void);
-
-       bool                                    iterate                                         (void);
-
-private:
-       const CommandLine&              m_cmdLine;
-       Platform                                m_platform;
-       AssetArchive                    m_archive;
-       TestLog                                 m_log;
-       App                                             m_app;
-};
-
 class TestThread : public RenderThread
 {
 public:
@@ -67,8 +51,11 @@ private:
        virtual bool                    render                                          (void);
 
        const CommandLine&              m_cmdLine;
-       TestApp*                                m_testApp;
-       bool                                    m_done;                                         //!< Is execution finished.
+       Platform                                m_platform;
+       AssetArchive                    m_archive;
+       TestLog                                 m_log;
+       App                                             m_app;
+       bool                                    m_finished;                                     //!< Is execution finished.
 };
 
 class TestActivity : public RenderActivity
@@ -77,12 +64,14 @@ public:
                                                        TestActivity                            (ANativeActivity* nativeActivity);
                                                        ~TestActivity                           (void);
 
-       virtual void                    onStop                                          (void);
+       virtual void                    onStart                                         (void);
+       virtual void                    onDestroy                                       (void);
        virtual void                    onConfigurationChanged          (void);
 
 private:
        CommandLine                             m_cmdLine;
        TestThread                              m_testThread;
+       bool                                    m_started;
 };
 
 } // Android
index 147583b..aad607e 100644 (file)
@@ -28,9 +28,13 @@ namespace tcu
 namespace Android
 {
 
+using std::vector;
+
+// Window
+
 Window::Window (ANativeWindow* window)
-       : m_window              (window)
-       , m_semaphore   (1)
+       : m_window      (window)
+       , m_state       (STATE_AVAILABLE)
 {
 }
 
@@ -50,5 +54,143 @@ IVec2 Window::getSize (void) const
        return IVec2(width, height);
 }
 
+bool Window::tryAcquire (void)
+{
+       de::ScopedLock lock(m_stateLock);
+
+       if (m_state == STATE_AVAILABLE)
+       {
+               m_state = STATE_IN_USE;
+               return true;
+       }
+       else
+               return false;
+}
+
+void Window::release (void)
+{
+       de::ScopedLock lock(m_stateLock);
+
+       if (m_state == STATE_IN_USE)
+               m_state = STATE_AVAILABLE;
+       else if (m_state == STATE_PENDING_DESTROY)
+               m_state = STATE_READY_FOR_DESTROY;
+       else
+               DE_ASSERT(!"Invalid window state");
+}
+
+void Window::markForDestroy (void)
+{
+       de::ScopedLock lock(m_stateLock);
+
+       if (m_state == STATE_AVAILABLE)
+               m_state = STATE_READY_FOR_DESTROY;
+       else if (m_state == STATE_IN_USE)
+               m_state = STATE_PENDING_DESTROY;
+       else
+               DE_ASSERT(!"Invalid window state");
+}
+
+bool Window::isPendingDestroy (void) const
+{
+       de::ScopedLock lock(m_stateLock);
+       return m_state == STATE_PENDING_DESTROY;
+}
+
+bool Window::tryAcquireForDestroy (bool onlyMarked)
+{
+       de::ScopedLock lock(m_stateLock);
+
+       if (m_state == STATE_READY_FOR_DESTROY ||
+               (!onlyMarked && m_state == STATE_AVAILABLE))
+       {
+               m_state = STATE_ACQUIRED_FOR_DESTROY;
+               return true;
+       }
+       else
+               return false;
+}
+
+// WindowRegistry
+
+WindowRegistry::WindowRegistry (void)
+{
+}
+
+WindowRegistry::~WindowRegistry (void)
+{
+       for (vector<Window*>::const_iterator winIter = m_windows.begin(); winIter != m_windows.end(); winIter++)
+       {
+               Window* const window = *winIter;
+
+               if (window->tryAcquireForDestroy(false))
+                       delete window;
+               else
+               {
+                       print("ERROR: Window was not available for deletion, leaked tcu::Android::Window!\n");
+                       DE_ASSERT(!"Window leaked");
+               }
+       }
+}
+
+void WindowRegistry::addWindow (ANativeWindow* window)
+{
+       m_windows.reserve(m_windows.size()+1);
+       m_windows.push_back(new Window(window));
+}
+
+void WindowRegistry::destroyWindow (ANativeWindow* rawHandle)
+{
+       for (int ndx = 0; ndx < (int)m_windows.size(); ++ndx)
+       {
+               Window* const window = m_windows[ndx];
+
+               if (window->getNativeWindow() == rawHandle)
+               {
+                       if (window->tryAcquireForDestroy(false))
+                       {
+                               delete window;
+                               m_windows[ndx] = m_windows.back();
+                               m_windows.pop_back();
+                       }
+                       else
+                               window->markForDestroy();
+
+                       return;
+               }
+       }
+
+       throw tcu::InternalError("Window not registered", DE_NULL, __FILE__, __LINE__);
+}
+
+Window* WindowRegistry::tryAcquireWindow (void)
+{
+       for (int ndx = 0; ndx < (int)m_windows.size(); ++ndx)
+       {
+               Window* const window = m_windows[ndx];
+
+               if (window->tryAcquire())
+                       return window;
+       }
+
+       return DE_NULL;
+}
+
+void WindowRegistry::garbageCollect (void)
+{
+       for (int ndx = 0; ndx < (int)m_windows.size(); ++ndx)
+       {
+               Window* const window = m_windows[ndx];
+
+               if (window->tryAcquireForDestroy(true))
+               {
+                       delete window;
+                       m_windows[ndx] = m_windows.back();
+                       m_windows.pop_back();
+                       ndx -= 1;
+               }
+       }
+}
+
 } // Android
 } // tcu
index 171318e..dd6bc35 100644 (file)
@@ -26,6 +26,9 @@
 #include "tcuDefs.hpp"
 #include "tcuVector.hpp"
 #include "deSemaphore.hpp"
+#include "deMutex.hpp"
+
+#include <vector>
 
 #include <android/native_window.h>
 
@@ -34,28 +37,62 @@ namespace tcu
 namespace Android
 {
 
+// \note Window is thread-safe, WindowRegistry is not
+
 class Window
 {
 public:
-                                               Window                                          (ANativeWindow* window);
-                                               ~Window                                         (void);
+       enum State
+       {
+               STATE_AVAILABLE = 0,
+               STATE_IN_USE,
+               STATE_PENDING_DESTROY,
+               STATE_READY_FOR_DESTROY,
+               STATE_ACQUIRED_FOR_DESTROY,
+
+               STATE_LAST
+       };
+
+                                                       Window                          (ANativeWindow* window);
+                                                       ~Window                         (void);
 
-       void                            acquire                                         (void)  { m_semaphore.decrement();                              }
-       bool                            tryAcquire                                      (void)  { return m_semaphore.tryDecrement();    }
-       void                            release                                         (void)  { m_semaphore.increment();                              }
+       bool                                    tryAcquire                      (void);
+       void                                    release                         (void);
 
-       ANativeWindow*          getNativeWindow                         (void)  { return m_window;                                              }
+       void                                    markForDestroy          (void);
+       bool                                    isPendingDestroy        (void) const;
+       bool                                    tryAcquireForDestroy(bool onlyMarked);
 
-       void                            setBuffersGeometry                      (int width, int height, int32_t format);
+       ANativeWindow*                  getNativeWindow         (void)  { return m_window;      }
 
-       IVec2                           getSize                                         (void) const;
+       void                                    setBuffersGeometry      (int width, int height, int32_t format);
+
+       IVec2                                   getSize                         (void) const;
 
 private:
-                                               Window                                          (const Window& other);
-       Window&                         operator=                                       (const Window& other);
+                                                       Window                          (const Window& other);
+       Window&                                 operator=                       (const Window& other);
+
+       ANativeWindow*                  m_window;
+       mutable de::Mutex               m_stateLock;
+       State                                   m_state;
+};
 
-       ANativeWindow*          m_window;
-       de::Semaphore           m_semaphore;
+class WindowRegistry
+{
+public:
+                                                       WindowRegistry          (void);
+                                                       ~WindowRegistry         (void);
+
+       void                                    addWindow                       (ANativeWindow* window);
+       void                                    destroyWindow           (ANativeWindow* window);
+
+       Window*                                 tryAcquireWindow        (void);
+
+       void                                    garbageCollect          (void);
+
+private:
+       std::vector<Window*>    m_windows;
 };
 
 } // Android