| /*------------------------------------------------------------------------- |
| * drawElements Quality Program Tester Core |
| * ---------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief RenderActivity base class. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "tcuAndroidRenderActivity.hpp" |
| #include "deSemaphore.hpp" |
| |
| #include <android/window.h> |
| |
| #include <string> |
| #include <stdlib.h> |
| |
| using std::string; |
| |
| #if defined(DE_DEBUG) |
| # define DBG_PRINT(X) print X |
| #else |
| # define DBG_PRINT(X) |
| #endif |
| |
| namespace tcu |
| { |
| namespace Android |
| { |
| |
| 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) |
| : m_activity (activity) |
| , m_msgQueue (MESSAGE_QUEUE_SIZE) |
| , m_threadRunning (false) |
| , m_inputQueue (DE_NULL) |
| , m_windowState (WINDOWSTATE_NOT_CREATED) |
| , m_window (DE_NULL) |
| , m_paused (false) |
| , m_finish (false) |
| , m_receivedFirstResize(false) |
| { |
| } |
| |
| RenderThread::~RenderThread (void) |
| { |
| } |
| |
| void RenderThread::start (void) |
| { |
| m_threadRunning = true; |
| Thread::start(); |
| } |
| |
| void RenderThread::stop (void) |
| { |
| // Queue finish command |
| enqueue(Message(MESSAGE_FINISH)); |
| |
| // Wait for thread to terminate |
| join(); |
| |
| m_threadRunning = false; |
| } |
| |
| void RenderThread::enqueue (const Message& message) |
| { |
| // \note Thread must be running or otherwise nobody is going to drain the queue. |
| DE_ASSERT(m_threadRunning); |
| m_msgQueue.pushFront(message); |
| } |
| |
| void RenderThread::pause (void) |
| { |
| enqueue(Message(MESSAGE_PAUSE)); |
| } |
| |
| void RenderThread::resume (void) |
| { |
| enqueue(Message(MESSAGE_RESUME)); |
| } |
| |
| void RenderThread::sync (void) |
| { |
| de::Semaphore waitSem(0); |
| enqueue(Message(MESSAGE_SYNC, &waitSem)); |
| waitSem.decrement(); |
| } |
| |
| 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"); |
| |
| // The documented behavior for the callbacks is that the native activity |
| // will get a call to onNativeWindowCreated(), at which point it should have |
| // a surface to render to, and can then start immediately. |
| // |
| // The actual creation process has the framework making calls to both |
| // onNativeWindowCreated() and then onNativeWindowResized(). The test |
| // waits for that first resize before it considers the window ready for |
| // rendering. |
| // |
| // However subsequent events in the framework may cause the window to be |
| // recreated at a new position without a size change, which sends on |
| // onNativeWindowDestroyed(), and then on onNativeWindowCreated() without |
| // a follow-up onNativeWindowResized(). If this happens, the test will |
| // stop rendering as it is no longer in the ready state, and a watchdog |
| // thread will eventually kill the test, causing it to fail. We therefore |
| // set the window state back to READY and process the window creation here |
| // if we have already observed that first resize call. |
| if (!m_receivedFirstResize) { |
| m_windowState = WINDOWSTATE_NOT_INITIALIZED; |
| } else { |
| m_windowState = WINDOWSTATE_READY; |
| onWindowCreated(message.payload.window); |
| } |
| m_window = message.payload.window; |
| break; |
| |
| case MESSAGE_WINDOW_RESIZED: |
| if (m_window != message.payload.window) |
| throw InternalError("Got onNativeWindowResized() event targeting different window"); |
| |
| // Record that we've the first resize event, in case the window is |
| // recreated later without a resize. |
| m_receivedFirstResize = true; |
| |
| if (m_windowState == WINDOWSTATE_NOT_INITIALIZED) |
| { |
| // Got first resize event, window is ready for use. |
| m_windowState = WINDOWSTATE_READY; |
| onWindowCreated(message.payload.window); |
| } |
| else if (m_windowState == WINDOWSTATE_READY) |
| onWindowResized(message.payload.window); |
| else |
| throw InternalError("Got unexpected onNativeWindowResized() event from system"); |
| |
| break; |
| |
| case MESSAGE_WINDOW_DESTROYED: |
| 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"); |
| |
| if (m_windowState == WINDOWSTATE_READY) |
| onWindowDestroyed(message.payload.window); |
| |
| m_windowState = WINDOWSTATE_DESTROYED; |
| m_window = DE_NULL; |
| break; |
| |
| case MESSAGE_INPUT_QUEUE_CREATED: |
| m_inputQueue = message.payload.inputQueue; |
| break; |
| |
| case MESSAGE_INPUT_QUEUE_DESTROYED: |
| m_inputQueue = message.payload.inputQueue; |
| break; |
| |
| case MESSAGE_SYNC: |
| message.payload.semaphore->increment(); |
| break; |
| |
| default: |
| throw std::runtime_error("Unknown message type"); |
| break; |
| } |
| } |
| |
| void RenderThread::run (void) |
| { |
| // Init state |
| m_windowState = WINDOWSTATE_NOT_CREATED; |
| m_paused = true; |
| m_finish = false; |
| |
| try |
| { |
| while (!m_finish) |
| { |
| if (m_paused || m_windowState != WINDOWSTATE_READY) |
| { |
| // Block until we are not paused and window is ready. |
| Message msg = m_msgQueue.popBack(); |
| processMessage(msg); |
| continue; |
| } |
| |
| // Process available commands |
| { |
| Message msg; |
| if (m_msgQueue.tryPopBack(msg)) |
| { |
| processMessage(msg); |
| continue; |
| } |
| } |
| |
| DE_ASSERT(m_windowState == WINDOWSTATE_READY); |
| |
| // Process input events. |
| // \todo [2013-05-08 pyry] What if system fills up the input queue before we have window ready? |
| while (m_inputQueue && |
| AInputQueue_hasEvents(m_inputQueue) > 0) |
| { |
| AInputEvent* event; |
| TCU_CHECK(AInputQueue_getEvent(m_inputQueue, &event) >= 0); |
| onInputEvent(event); |
| AInputQueue_finishEvent(m_inputQueue, event, 1); |
| } |
| |
| // Everything set up - safe to render. |
| if (!render()) |
| { |
| DBG_PRINT(("RenderThread::run(): render\n")); |
| break; |
| } |
| } |
| } |
| catch (const std::exception& e) |
| { |
| print("RenderThread: %s\n", e.what()); |
| } |
| |
| // Tell activity to finish. |
| DBG_PRINT(("RenderThread::run(): done, waiting for FINISH\n")); |
| m_activity.finish(); |
| |
| // Thread must keep draining message queue until FINISH message is encountered. |
| try |
| { |
| while (!m_finish) |
| { |
| Message msg = m_msgQueue.popBack(); |
| |
| // Ignore all but SYNC and FINISH messages. |
| if (msg.type == MESSAGE_SYNC || msg.type == MESSAGE_FINISH) |
| processMessage(msg); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| die("RenderThread: %s\n", e.what()); |
| } |
| |
| DBG_PRINT(("RenderThread::run(): exiting...\n")); |
| } |
| |
| // RenderActivity |
| |
| RenderActivity::RenderActivity (ANativeActivity* activity) |
| : NativeActivity(activity) |
| , m_thread (DE_NULL) |
| { |
| DBG_PRINT(("RenderActivity::RenderActivity()")); |
| } |
| |
| RenderActivity::~RenderActivity (void) |
| { |
| DBG_PRINT(("RenderActivity::~RenderActivity()")); |
| } |
| |
| void RenderActivity::setThread (RenderThread* thread) |
| { |
| m_thread = thread; |
| } |
| |
| void RenderActivity::onStart (void) |
| { |
| DBG_PRINT(("RenderActivity::onStart()")); |
| } |
| |
| void RenderActivity::onResume (void) |
| { |
| DBG_PRINT(("RenderActivity::onResume()")); |
| |
| // Resume (or start) test execution |
| m_thread->resume(); |
| } |
| |
| void RenderActivity::onPause (void) |
| { |
| DBG_PRINT(("RenderActivity::onPause()")); |
| |
| // Pause test execution |
| m_thread->pause(); |
| } |
| |
| void RenderActivity::onStop (void) |
| { |
| DBG_PRINT(("RenderActivity::onStop()")); |
| } |
| |
| void RenderActivity::onDestroy (void) |
| { |
| DBG_PRINT(("RenderActivity::onDestroy()")); |
| } |
| |
| void RenderActivity::onNativeWindowCreated (ANativeWindow* window) |
| { |
| DBG_PRINT(("RenderActivity::onNativeWindowCreated()")); |
| m_thread->enqueue(Message(MESSAGE_WINDOW_CREATED, window)); |
| } |
| |
| void RenderActivity::onNativeWindowResized (ANativeWindow* window) |
| { |
| DBG_PRINT(("RenderActivity::onNativeWindowResized()")); |
| m_thread->enqueue(Message(MESSAGE_WINDOW_RESIZED, window)); |
| } |
| |
| void RenderActivity::onNativeWindowRedrawNeeded (ANativeWindow* window) |
| { |
| DE_UNREF(window); |
| } |
| |
| void RenderActivity::onNativeWindowDestroyed (ANativeWindow* window) |
| { |
| DBG_PRINT(("RenderActivity::onNativeWindowDestroyed()")); |
| m_thread->enqueue(Message(MESSAGE_WINDOW_DESTROYED, window)); |
| m_thread->sync(); // Block until thread has processed all messages. |
| } |
| |
| void RenderActivity::onInputQueueCreated (AInputQueue* queue) |
| { |
| DBG_PRINT(("RenderActivity::onInputQueueCreated()")); |
| m_thread->enqueue(Message(MESSAGE_INPUT_QUEUE_CREATED, queue)); |
| } |
| |
| void RenderActivity::onInputQueueDestroyed (AInputQueue* queue) |
| { |
| DBG_PRINT(("RenderActivity::onInputQueueDestroyed()")); |
| m_thread->enqueue(Message(MESSAGE_INPUT_QUEUE_DESTROYED, queue)); |
| m_thread->sync(); |
| } |
| |
| } // Android |
| } // tcu |