1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
3 * ----------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief RenderActivity base class.
22 *//*--------------------------------------------------------------------*/
23
24#include "tcuAndroidRenderActivity.hpp"
25#include "deSemaphore.hpp"
26
27#include <android/window.h>
28
29#include <string>
30#include <stdlib.h>
31
32using std::string;
33
34#if defined(DE_DEBUG)
35#	define DBG_PRINT(X) print X
36#else
37#	define DBG_PRINT(X)
38#endif
39
40namespace tcu
41{
42namespace Android
43{
44
45enum
46{
47	MESSAGE_QUEUE_SIZE = 8 //!< Length of RenderThread message queue.
48};
49
50#if defined(DE_DEBUG)
51static const char* getMessageTypeName (MessageType type)
52{
53	static const char* s_names[] =
54	{
55		"RESUME",
56		"PAUSE",
57		"FINISH",
58		"WINDOW_CREATED",
59		"WINDOW_RESIZED",
60		"WINDOW_DESTROYED",
61		"INPUT_QUEUE_CREATED",
62		"INPUT_QUEUE_DESTROYED",
63		"SYNC"
64	};
65	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == MESSAGETYPE_LAST);
66	return s_names[type];
67}
68#endif
69
70// RenderThread
71
72RenderThread::RenderThread (NativeActivity& activity)
73	: m_activity		(activity)
74	, m_msgQueue		(MESSAGE_QUEUE_SIZE)
75	, m_threadRunning	(false)
76	, m_inputQueue		(DE_NULL)
77	, m_windowState		(WINDOWSTATE_NOT_CREATED)
78	, m_window			(DE_NULL)
79	, m_paused			(false)
80	, m_finish			(false)
81	, m_receivedFirstResize(false)
82{
83}
84
85RenderThread::~RenderThread (void)
86{
87}
88
89void RenderThread::start (void)
90{
91	m_threadRunning = true;
92	Thread::start();
93}
94
95void RenderThread::stop (void)
96{
97	// Queue finish command
98	enqueue(Message(MESSAGE_FINISH));
99
100	// Wait for thread to terminate
101	join();
102
103	m_threadRunning = false;
104}
105
106void RenderThread::enqueue (const Message& message)
107{
108	// \note Thread must be running or otherwise nobody is going to drain the queue.
109	DE_ASSERT(m_threadRunning);
110	m_msgQueue.pushFront(message);
111}
112
113void RenderThread::pause (void)
114{
115	enqueue(Message(MESSAGE_PAUSE));
116}
117
118void RenderThread::resume (void)
119{
120	enqueue(Message(MESSAGE_RESUME));
121}
122
123void RenderThread::sync (void)
124{
125	de::Semaphore waitSem(0);
126	enqueue(Message(MESSAGE_SYNC, &waitSem));
127	waitSem.decrement();
128}
129
130void RenderThread::processMessage (const Message& message)
131{
132	DBG_PRINT(("RenderThread::processMessage(): message = { %s, %p }\n", getMessageTypeName(message.type), message.payload.window));
133
134	switch (message.type)
135	{
136		case MESSAGE_RESUME:	m_paused = false;	break;
137		case MESSAGE_PAUSE:		m_paused = true;	break;
138		case MESSAGE_FINISH:	m_finish = true;	break;
139
140		// \note While Platform / WindowRegistry are currently multi-window -capable,
141		//		 the fact that platform gives us windows too late / at unexpected times
142		//		 forces us to do some quick checking and limit system to one window here.
143		case MESSAGE_WINDOW_CREATED:
144			if (m_windowState != WINDOWSTATE_NOT_CREATED && m_windowState != WINDOWSTATE_DESTROYED)
145				throw InternalError("Got unexpected onNativeWindowCreated() event from system");
146
147			// The documented behavior for the callbacks is that the native activity
148			// will get a call to onNativeWindowCreated(), at which point it should have
149			// a surface to render to, and can then start immediately.
150			//
151			// The actual creation process has the framework making calls to both
152			// onNativeWindowCreated() and then onNativeWindowResized(). The test
153			// waits for that first resize before it considers the window ready for
154			// rendering.
155			//
156			// However subsequent events in the framework may cause the window to be
157			// recreated at a new position without a size change, which sends on
158			// onNativeWindowDestroyed(), and then on onNativeWindowCreated() without
159			// a follow-up onNativeWindowResized(). If this happens, the test will
160			// stop rendering as it is no longer in the ready state, and a watchdog
161			// thread will eventually kill the test, causing it to fail. We therefore
162			// set the window state back to READY and process the window creation here
163			// if we have already observed that first resize call.
164			if (!m_receivedFirstResize) {
165				m_windowState	= WINDOWSTATE_NOT_INITIALIZED;
166			} else {
167				m_windowState	= WINDOWSTATE_READY;
168				onWindowCreated(message.payload.window);
169			}
170			m_window		= message.payload.window;
171			break;
172
173		case MESSAGE_WINDOW_RESIZED:
174			if (m_window != message.payload.window)
175				throw InternalError("Got onNativeWindowResized() event targeting different window");
176
177			// Record that we've the first resize event, in case the window is
178			// recreated later without a resize.
179			m_receivedFirstResize = true;
180
181			if (m_windowState == WINDOWSTATE_NOT_INITIALIZED)
182			{
183				// Got first resize event, window is ready for use.
184				m_windowState = WINDOWSTATE_READY;
185				onWindowCreated(message.payload.window);
186			}
187			else if (m_windowState == WINDOWSTATE_READY)
188				onWindowResized(message.payload.window);
189			else
190				throw InternalError("Got unexpected onNativeWindowResized() event from system");
191
192			break;
193
194		case MESSAGE_WINDOW_DESTROYED:
195			if (m_window != message.payload.window)
196				throw InternalError("Got onNativeWindowDestroyed() event targeting different window");
197
198			if (m_windowState != WINDOWSTATE_NOT_INITIALIZED && m_windowState != WINDOWSTATE_READY)
199				throw InternalError("Got unexpected onNativeWindowDestroyed() event from system");
200
201			if (m_windowState == WINDOWSTATE_READY)
202				onWindowDestroyed(message.payload.window);
203
204			m_windowState	= WINDOWSTATE_DESTROYED;
205			m_window		= DE_NULL;
206			break;
207
208		case MESSAGE_INPUT_QUEUE_CREATED:
209			m_inputQueue = message.payload.inputQueue;
210			break;
211
212		case MESSAGE_INPUT_QUEUE_DESTROYED:
213			m_inputQueue = message.payload.inputQueue;
214			break;
215
216		case MESSAGE_SYNC:
217			message.payload.semaphore->increment();
218			break;
219
220		default:
221			throw std::runtime_error("Unknown message type");
222			break;
223	}
224}
225
226void RenderThread::run (void)
227{
228	// Init state
229	m_windowState	= WINDOWSTATE_NOT_CREATED;
230	m_paused		= true;
231	m_finish		= false;
232
233	try
234	{
235		while (!m_finish)
236		{
237			if (m_paused || m_windowState != WINDOWSTATE_READY)
238			{
239				// Block until we are not paused and window is ready.
240				Message msg = m_msgQueue.popBack();
241				processMessage(msg);
242				continue;
243			}
244
245			// Process available commands
246			{
247				Message msg;
248				if (m_msgQueue.tryPopBack(msg))
249				{
250					processMessage(msg);
251					continue;
252				}
253			}
254
255			DE_ASSERT(m_windowState == WINDOWSTATE_READY);
256
257			// Process input events.
258			// \todo [2013-05-08 pyry] What if system fills up the input queue before we have window ready?
259			while (m_inputQueue &&
260				   AInputQueue_hasEvents(m_inputQueue) > 0)
261			{
262				AInputEvent* event;
263				TCU_CHECK(AInputQueue_getEvent(m_inputQueue, &event) >= 0);
264				onInputEvent(event);
265				AInputQueue_finishEvent(m_inputQueue, event, 1);
266			}
267
268			// Everything set up - safe to render.
269			if (!render())
270			{
271				DBG_PRINT(("RenderThread::run(): render\n"));
272				break;
273			}
274		}
275	}
276	catch (const std::exception& e)
277	{
278		print("RenderThread: %s\n", e.what());
279	}
280
281	// Tell activity to finish.
282	DBG_PRINT(("RenderThread::run(): done, waiting for FINISH\n"));
283	m_activity.finish();
284
285	// Thread must keep draining message queue until FINISH message is encountered.
286	try
287	{
288		while (!m_finish)
289		{
290			Message msg = m_msgQueue.popBack();
291
292			// Ignore all but SYNC and FINISH messages.
293			if (msg.type == MESSAGE_SYNC || msg.type == MESSAGE_FINISH)
294				processMessage(msg);
295		}
296	}
297	catch (const std::exception& e)
298	{
299		die("RenderThread: %s\n", e.what());
300	}
301
302	DBG_PRINT(("RenderThread::run(): exiting...\n"));
303}
304
305// RenderActivity
306
307RenderActivity::RenderActivity (ANativeActivity* activity)
308	: NativeActivity(activity)
309	, m_thread		(DE_NULL)
310{
311	DBG_PRINT(("RenderActivity::RenderActivity()"));
312}
313
314RenderActivity::~RenderActivity (void)
315{
316	DBG_PRINT(("RenderActivity::~RenderActivity()"));
317}
318
319void RenderActivity::setThread (RenderThread* thread)
320{
321	m_thread = thread;
322}
323
324void RenderActivity::onStart (void)
325{
326	DBG_PRINT(("RenderActivity::onStart()"));
327}
328
329void RenderActivity::onResume (void)
330{
331	DBG_PRINT(("RenderActivity::onResume()"));
332
333	// Resume (or start) test execution
334	m_thread->resume();
335}
336
337void RenderActivity::onPause (void)
338{
339	DBG_PRINT(("RenderActivity::onPause()"));
340
341	// Pause test execution
342	m_thread->pause();
343}
344
345void RenderActivity::onStop (void)
346{
347	DBG_PRINT(("RenderActivity::onStop()"));
348}
349
350void RenderActivity::onDestroy (void)
351{
352	DBG_PRINT(("RenderActivity::onDestroy()"));
353}
354
355void RenderActivity::onNativeWindowCreated (ANativeWindow* window)
356{
357	DBG_PRINT(("RenderActivity::onNativeWindowCreated()"));
358	m_thread->enqueue(Message(MESSAGE_WINDOW_CREATED, window));
359}
360
361void RenderActivity::onNativeWindowResized (ANativeWindow* window)
362{
363	DBG_PRINT(("RenderActivity::onNativeWindowResized()"));
364	m_thread->enqueue(Message(MESSAGE_WINDOW_RESIZED, window));
365}
366
367void RenderActivity::onNativeWindowRedrawNeeded (ANativeWindow* window)
368{
369	DE_UNREF(window);
370}
371
372void RenderActivity::onNativeWindowDestroyed (ANativeWindow* window)
373{
374	DBG_PRINT(("RenderActivity::onNativeWindowDestroyed()"));
375	m_thread->enqueue(Message(MESSAGE_WINDOW_DESTROYED, window));
376	m_thread->sync(); // Block until thread has processed all messages.
377}
378
379void RenderActivity::onInputQueueCreated (AInputQueue* queue)
380{
381	DBG_PRINT(("RenderActivity::onInputQueueCreated()"));
382	m_thread->enqueue(Message(MESSAGE_INPUT_QUEUE_CREATED, queue));
383}
384
385void RenderActivity::onInputQueueDestroyed (AInputQueue* queue)
386{
387	DBG_PRINT(("RenderActivity::onInputQueueDestroyed()"));
388	m_thread->enqueue(Message(MESSAGE_INPUT_QUEUE_DESTROYED, queue));
389	m_thread->sync();
390}
391
392} // Android
393} // tcu
394