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