blob: 0c3bd1931bd4cf1904bacf9b9373e33564a6a79c [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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