1/*-------------------------------------------------------------------------
2 * drawElements Quality Program EGL Module
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 Tests for resizing the native window of a surface.
22 *//*--------------------------------------------------------------------*/
23
24#include "teglResizeTests.hpp"
25
26#include "teglSimpleConfigCase.hpp"
27
28#include "tcuImageCompare.hpp"
29#include "tcuSurface.hpp"
30#include "tcuPlatform.hpp"
31#include "tcuTestLog.hpp"
32#include "tcuInterval.hpp"
33
34#include "egluNativeDisplay.hpp"
35#include "egluNativeWindow.hpp"
36#include "egluNativePixmap.hpp"
37#include "egluUnique.hpp"
38#include "egluUtil.hpp"
39
40#include "gluDefs.hpp"
41#include "glwFunctions.hpp"
42#include "glwEnums.hpp"
43
44#include "tcuTestLog.hpp"
45#include "tcuVector.hpp"
46
47#include "deThread.h"
48#include "deUniquePtr.hpp"
49
50#include <sstream>
51
52namespace deqp
53{
54namespace egl
55{
56
57using	std::vector;
58using	std::string;
59using	std::ostringstream;
60using	de::MovePtr;
61using	tcu::CommandLine;
62using	tcu::ConstPixelBufferAccess;
63using	tcu::Interval;
64using	tcu::IVec2;
65using	tcu::Vec3;
66using	tcu::Vec4;
67using	tcu::UVec4;
68using	tcu::ResultCollector;
69using	tcu::Surface;
70using	tcu::TestLog;
71using	tcu::egl::Display;
72using	eglu::AttribMap;
73using	eglu::NativeDisplay;
74using	eglu::NativeWindow;
75using	eglu::ScopedCurrentContext;
76using	eglu::UniqueSurface;
77using	eglu::UniqueContext;
78
79typedef	eglu::WindowParams::Visibility	Visibility;
80typedef	TestCase::IterateResult			IterateResult;
81
82struct ResizeParams
83{
84	string	name;
85	string	description;
86	IVec2	oldSize;
87	IVec2	newSize;
88};
89
90class ResizeTest : public TestCase
91{
92public:
93								ResizeTest	(EglTestContext&		eglTestCtx,
94											 const ResizeParams&	params)
95									: TestCase	(eglTestCtx,
96												 params.name.c_str(),
97												 params.description.c_str())
98									, m_oldSize	(params.oldSize)
99									, m_newSize	(params.newSize)
100									, m_display	(EGL_NO_DISPLAY)
101									, m_config	(DE_NULL)
102									, m_log		(m_testCtx.getLog())
103									, m_status	(m_log) {}
104
105	void						init		(void);
106	void						deinit		(void);
107
108protected:
109	virtual EGLenum				surfaceType	(void) const { return EGL_WINDOW_BIT; }
110	void						resize		(IVec2 size);
111
112	const IVec2					m_oldSize;
113	const IVec2					m_newSize;
114	EGLDisplay					m_display;
115	EGLConfig					m_config;
116	MovePtr<NativeWindow>		m_nativeWindow;
117	MovePtr<UniqueSurface>		m_surface;
118	MovePtr<UniqueContext>		m_context;
119	TestLog&					m_log;
120	ResultCollector				m_status;
121	glw::Functions				m_gl;
122};
123
124EGLConfig getEGLConfig (const EGLDisplay eglDisplay, EGLenum surfaceType)
125{
126	AttribMap attribMap;
127
128	attribMap[EGL_SURFACE_TYPE]		= surfaceType;
129	attribMap[EGL_RENDERABLE_TYPE]	= EGL_OPENGL_ES2_BIT;
130
131	return eglu::chooseSingleConfig(eglDisplay, attribMap);
132}
133
134void ResizeTest::init (void)
135{
136	TestCase::init();
137
138	const CommandLine&		cmdLine			= m_testCtx.getCommandLine();
139	const EGLDisplay		eglDisplay		= m_eglTestCtx.getDisplay().getEGLDisplay();
140	const EGLConfig			eglConfig		= getEGLConfig(eglDisplay, surfaceType());
141	const EGLint			ctxAttribs[]	=
142	{
143		EGL_CONTEXT_CLIENT_VERSION, 2,
144		EGL_NONE
145	};
146	EGLContext				eglContext		= eglCreateContext(eglDisplay,
147															   eglConfig,
148															   EGL_NO_CONTEXT,
149															   ctxAttribs);
150	EGLU_CHECK_MSG("eglCreateContext()");
151	MovePtr<UniqueContext>	context			(new UniqueContext(eglDisplay, eglContext));
152	const EGLint			configId		= eglu::getConfigAttribInt(eglDisplay,
153																	   eglConfig,
154																	   EGL_CONFIG_ID);
155	const Visibility		visibility		= eglu::parseWindowVisibility(cmdLine);
156	NativeDisplay&			nativeDisplay	= m_eglTestCtx.getNativeDisplay();
157	MovePtr<NativeWindow>	nativeWindow	(m_eglTestCtx.createNativeWindow(eglDisplay,
158																			 eglConfig,
159																			 DE_NULL,
160																			 m_oldSize.x(),
161																			 m_oldSize.y(),
162																			 visibility));
163	const EGLSurface		eglSurface		= eglu::createWindowSurface(nativeDisplay,
164																		*nativeWindow,
165																		eglDisplay,
166																		eglConfig,
167																		DE_NULL);
168	MovePtr<UniqueSurface>	surface			(new UniqueSurface(eglDisplay, eglSurface));
169
170	m_log << TestLog::Message
171		  << "Chose EGLConfig with id " << configId << ".\n"
172		  << "Created initial surface with size " << m_oldSize
173		  << TestLog::EndMessage;
174
175	m_eglTestCtx.getGLFunctions(m_gl, glu::ApiType::es(2, 0));
176	m_config		= eglConfig;
177	m_surface		= surface;
178	m_context		= context;
179	m_display		= eglDisplay;
180	m_nativeWindow	= nativeWindow;
181	EGLU_CHECK();
182}
183
184void ResizeTest::deinit (void)
185{
186	m_config		= DE_NULL;
187	m_display		= EGL_NO_DISPLAY;
188	m_context.clear();
189	m_surface.clear();
190	m_nativeWindow.clear();
191}
192
193void ResizeTest::resize (IVec2 size)
194{
195	m_nativeWindow->setSurfaceSize(size);
196	m_testCtx.getPlatform().processEvents();
197	m_log << TestLog::Message
198		  << "Resized surface to size " << size
199		  << TestLog::EndMessage;
200}
201
202class ChangeSurfaceSizeCase : public ResizeTest
203{
204public:
205					ChangeSurfaceSizeCase	(EglTestContext&		eglTestCtx,
206											 const ResizeParams&	params)
207						: ResizeTest(eglTestCtx, params) {}
208
209	IterateResult	iterate					(void);
210};
211
212void drawRectangle (const glw::Functions& gl, IVec2 pos, IVec2 size, Vec3 color)
213{
214	gl.clearColor(color.x(), color.y(), color.z(), 1.0);
215	gl.scissor(pos.x(), pos.y(), size.x(), size.y());
216	gl.enable(GL_SCISSOR_TEST);
217	gl.clear(GL_COLOR_BUFFER_BIT);
218	gl.disable(GL_SCISSOR_TEST);
219	GLU_EXPECT_NO_ERROR(gl.getError(),
220						"Rectangle drawing with glScissor and glClear failed.");
221}
222
223void initSurface (const glw::Functions& gl, IVec2 oldSize)
224{
225	const Vec3	frameColor	(0.0f, 0.0f, 1.0f);
226	const Vec3	fillColor	(1.0f, 0.0f, 0.0f);
227	const Vec3	markColor	(0.0f, 1.0f, 0.0f);
228
229	drawRectangle(gl, IVec2(0, 0), oldSize, frameColor);
230	drawRectangle(gl, IVec2(2, 2), oldSize - IVec2(4, 4), fillColor);
231
232	drawRectangle(gl, IVec2(0, 0), IVec2(8, 4), markColor);
233	drawRectangle(gl, oldSize - IVec2(16, 16), IVec2(8, 4), markColor);
234	drawRectangle(gl, IVec2(0, oldSize.y() - 16), IVec2(8, 4), markColor);
235	drawRectangle(gl, IVec2(oldSize.x() - 16, 0), IVec2(8, 4), markColor);
236}
237
238bool compareRectangles (const ConstPixelBufferAccess& rectA,
239						const ConstPixelBufferAccess& rectB)
240{
241	const int width		= rectA.getWidth();
242	const int height	= rectA.getHeight();
243	const int depth		= rectA.getDepth();
244
245	if (rectB.getWidth() != width || rectB.getHeight() != height || rectB.getDepth() != depth)
246		return false;
247
248	for (int z = 0; z < depth; ++z)
249		for (int y = 0; y < height; ++y)
250			for (int x = 0; x < width; ++x)
251				if (rectA.getPixel(x, y, z) != rectB.getPixel(x, y, z))
252					return false;
253
254	return true;
255}
256
257// Check whether `oldSurface` and `newSurface` share a common corner.
258bool compareCorners (const Surface& oldSurface, const Surface& newSurface)
259{
260	const int	oldWidth	= oldSurface.getWidth();
261	const int	oldHeight	= oldSurface.getHeight();
262	const int	newWidth	= newSurface.getWidth();
263	const int	newHeight	= newSurface.getHeight();
264	const int	minWidth	= de::min(oldWidth, newWidth);
265	const int	minHeight	= de::min(oldHeight, newHeight);
266
267	for (int xCorner = 0; xCorner < 2; ++xCorner)
268	{
269		const int oldX = xCorner == 0 ? 0 : oldWidth - minWidth;
270		const int newX = xCorner == 0 ? 0 : newWidth - minWidth;
271
272		for (int yCorner = 0; yCorner < 2; ++yCorner)
273		{
274			const int				oldY		= yCorner == 0 ? 0 : oldHeight - minHeight;
275			const int				newY		= yCorner == 0 ? 0 : newHeight - minHeight;
276			ConstPixelBufferAccess	oldAccess	=
277				oldSurface.getSubAccess(oldX, oldY, minWidth, minHeight);
278			ConstPixelBufferAccess	newAccess	=
279				newSurface.getSubAccess(newX, newY, minWidth, minHeight);
280
281			if (compareRectangles(oldAccess, newAccess))
282				return true;
283		}
284	}
285
286	return false;
287}
288
289Surface readSurface (const glw::Functions& gl, IVec2 size)
290{
291	Surface ret (size.x(), size.y());
292	gl.readPixels(0, 0, size.x(), size.y(), GL_RGBA, GL_UNSIGNED_BYTE,
293				  ret.getAccess().getDataPtr());
294
295	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels() failed");
296	return ret;
297}
298
299template <typename T>
300inline bool hasBits (T bitSet, T requiredBits)
301{
302	return (bitSet & requiredBits) == requiredBits;
303}
304
305IVec2 getNativeSurfaceSize (const NativeWindow& nativeWindow,
306							IVec2				reqSize)
307{
308	if (hasBits(nativeWindow.getCapabilities(), NativeWindow::CAPABILITY_GET_SURFACE_SIZE))
309		return nativeWindow.getSurfaceSize();
310	return reqSize; // assume we got the requested size
311}
312
313IVec2 checkSurfaceSize (EGLDisplay			eglDisplay,
314						EGLSurface			eglSurface,
315						const NativeWindow&	nativeWindow,
316						IVec2				reqSize,
317						ResultCollector&	status)
318{
319	const IVec2		nativeSize	= getNativeSurfaceSize(nativeWindow, reqSize);
320	IVec2			eglSize		= eglu::getSurfaceSize(eglDisplay, eglSurface);
321	ostringstream	oss;
322
323	oss << "Size of EGL surface " << eglSize
324		<< " differs from size of native window " << nativeSize;
325	status.check(eglSize == nativeSize, oss.str());
326
327	return eglSize;
328}
329
330IterateResult ChangeSurfaceSizeCase::iterate (void)
331{
332	EGLU_CHECK();
333	Surface					oldSurface;
334	Surface					newSurface;
335	ScopedCurrentContext	currentCtx	(m_display, **m_surface, **m_surface, **m_context);
336	IVec2					oldEglSize	= checkSurfaceSize(m_display,
337														   **m_surface,
338														   *m_nativeWindow,
339														   m_oldSize,
340														   m_status);
341
342	initSurface(m_gl, oldEglSize);
343
344	this->resize(m_newSize);
345
346	eglSwapBuffers(m_display, **m_surface);
347	checkSurfaceSize(m_display, **m_surface, *m_nativeWindow, m_newSize, m_status);
348
349	m_status.setTestContextResult(m_testCtx);
350	return STOP;
351}
352
353class PreserveBackBufferCase : public ResizeTest
354{
355public:
356					PreserveBackBufferCase	(EglTestContext&		eglTestCtx,
357											 const ResizeParams&	params)
358						: ResizeTest(eglTestCtx, params) {}
359
360	IterateResult	iterate					(void);
361	EGLenum			surfaceType				(void) const;
362};
363
364EGLenum PreserveBackBufferCase::surfaceType (void) const
365{
366	return EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
367}
368
369IterateResult PreserveBackBufferCase::iterate (void)
370{
371	ScopedCurrentContext	currentCtx	(m_display, **m_surface, **m_surface, **m_context);
372
373	EGLU_CHECK_CALL(
374		eglSurfaceAttrib(m_display, **m_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
375
376	GLU_EXPECT_NO_ERROR(m_gl.getError(), "GL state erroneous upon initialization!");
377
378	{
379		const IVec2 oldEglSize = eglu::getSurfaceSize(m_display, **m_surface);
380		initSurface(m_gl, oldEglSize);
381
382		m_gl.finish();
383		GLU_EXPECT_NO_ERROR(m_gl.getError(), "glFinish() failed");
384
385		{
386			const Surface oldSurface = readSurface(m_gl, oldEglSize);
387
388			eglSwapBuffers(m_display, **m_surface);
389			this->resize(m_newSize);
390			eglSwapBuffers(m_display, **m_surface);
391
392			{
393				const IVec2		newEglSize	= eglu::getSurfaceSize(m_display, **m_surface);
394				const Surface	newSurface	= readSurface(m_gl, newEglSize);
395
396				m_log << TestLog::ImageSet("Corner comparison",
397										   "Comparing old and new surfaces at all corners")
398					  << TestLog::Image("Before resizing", "Before resizing", oldSurface)
399					  << TestLog::Image("After resizing", "After resizing", newSurface)
400					  << TestLog::EndImageSet;
401
402				m_status.check(compareCorners(oldSurface, newSurface),
403							   "Resizing the native window changed the contents of "
404							   "the EGL surface");
405			}
406		}
407	}
408
409	m_status.setTestContextResult(m_testCtx);
410	return STOP;
411}
412
413typedef tcu::Vector<Interval, 2> IvVec2;
414
415class UpdateResolutionCase : public ResizeTest
416{
417public:
418					UpdateResolutionCase	(EglTestContext&		eglTestCtx,
419											 const ResizeParams&	params)
420						: ResizeTest(eglTestCtx, params) {}
421
422	IterateResult	iterate					(void);
423
424private:
425	IvVec2			getNativePixelsPerInch	(void);
426};
427
428IvVec2 ivVec2 (const IVec2& vec)
429{
430	return IvVec2(double(vec.x()), double(vec.y()));
431}
432
433Interval approximateInt (int i)
434{
435	return (Interval(i) + Interval(-1.0, 1.0)) & Interval(0.0, TCU_INFINITY);
436}
437
438IvVec2 UpdateResolutionCase::getNativePixelsPerInch	(void)
439{
440	const int		inchPer10km	= 254 * EGL_DISPLAY_SCALING;
441	const IVec2		bufSize		= eglu::getSurfaceSize(m_display, **m_surface);
442	const IVec2		winSize		= m_nativeWindow->getScreenSize();
443	const IVec2		bufPp10km	= eglu::getSurfaceResolution(m_display, **m_surface);
444	const Interval	margin		(-1.0, 1.0); // The resolution may be rounded
445	const IvVec2	bufPpiI		= (IvVec2(approximateInt(bufPp10km.x()),
446										  approximateInt(bufPp10km.y()))
447								   / Interval(inchPer10km));
448	const IvVec2	winPpiI		= ivVec2(winSize) * bufPpiI / ivVec2(bufSize);
449	const IVec2		winPpi		(int(winPpiI.x().midpoint()), int(winPpiI.y().midpoint()));
450
451	m_log << TestLog::Message
452		  << "EGL surface size: "							<< bufSize		<< "\n"
453		  << "EGL surface pixel density (pixels / 10 km): "	<< bufPp10km	<< "\n"
454		  << "Native window size: "							<< winSize		<< "\n"
455		  << "Native pixel density (ppi): "					<< winPpi		<< "\n"
456		  << TestLog::EndMessage;
457
458	m_status.checkResult(bufPp10km.x() >= 1 && bufPp10km.y() >= 1,
459						 QP_TEST_RESULT_QUALITY_WARNING,
460						 "Surface pixel density is less than one pixel per 10 km. "
461						 "Is the surface really visible from space?");
462
463	return winPpiI;
464}
465
466IterateResult UpdateResolutionCase::iterate (void)
467{
468	ScopedCurrentContext	currentCtx	(m_display, **m_surface, **m_surface, **m_context);
469
470	if (!hasBits(m_nativeWindow->getCapabilities(),
471				 NativeWindow::CAPABILITY_GET_SCREEN_SIZE))
472		TCU_THROW(NotSupportedError, "Unable to determine surface size in screen pixels");
473
474	{
475		const IVec2 oldEglSize = eglu::getSurfaceSize(m_display, **m_surface);
476		initSurface(m_gl, oldEglSize);
477	}
478	{
479		const IvVec2 oldPpi = this->getNativePixelsPerInch();
480		this->resize(m_newSize);
481		eglSwapBuffers(m_display, **m_surface);
482		{
483			const IvVec2 newPpi = this->getNativePixelsPerInch();
484			m_status.check(oldPpi.x().intersects(newPpi.x()) &&
485						   oldPpi.y().intersects(newPpi.y()),
486						   "Window PPI differs after resizing");
487		}
488	}
489
490	m_status.setTestContextResult(m_testCtx);
491	return STOP;
492}
493
494ResizeTests::ResizeTests (EglTestContext& eglTestCtx)
495	: TestCaseGroup(eglTestCtx, "resize", "Tests resizing the native surface")
496{
497}
498
499template <class Case>
500TestCaseGroup* createCaseGroup(EglTestContext&	eglTestCtx,
501							   const string&	name,
502							   const string&	desc)
503{
504	const ResizeParams		params[]	=
505	{
506		{ "shrink",			"Shrink in both dimensions",
507		  IVec2(128, 128),	IVec2(32, 32) },
508		{ "grow",			"Grow in both dimensions",
509		  IVec2(32, 32),	IVec2(128, 128) },
510		{ "stretch_width",	"Grow horizontally, shrink vertically",
511		  IVec2(32, 128),	IVec2(128, 32) },
512		{ "stretch_height",	"Grow vertically, shrink horizontally",
513		  IVec2(128, 32),	IVec2(32, 128) },
514	};
515	TestCaseGroup* const	group		=
516		new TestCaseGroup(eglTestCtx, name.c_str(), desc.c_str());
517
518	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(params); ++ndx)
519		group->addChild(new Case(eglTestCtx, params[ndx]));
520
521	return group;
522}
523
524void ResizeTests::init (void)
525{
526	addChild(createCaseGroup<ChangeSurfaceSizeCase>(m_eglTestCtx,
527													"surface_size",
528													"EGL surface size update"));
529	addChild(createCaseGroup<PreserveBackBufferCase>(m_eglTestCtx,
530													 "back_buffer",
531													 "Back buffer contents"));
532	addChild(createCaseGroup<UpdateResolutionCase>(m_eglTestCtx,
533												   "pixel_density",
534												   "Pixel density"));
535}
536
537} // egl
538} // deqp
539