Home | History | Annotate | Download | only in egl
      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 
     57 namespace deqp
     58 {
     59 namespace egl
     60 {
     61 
     62 using std::vector;
     63 using std::string;
     64 using std::ostringstream;
     65 using de::MovePtr;
     66 using tcu::CommandLine;
     67 using tcu::ConstPixelBufferAccess;
     68 using tcu::Interval;
     69 using tcu::IVec2;
     70 using tcu::Vec3;
     71 using tcu::Vec4;
     72 using tcu::UVec4;
     73 using tcu::ResultCollector;
     74 using tcu::Surface;
     75 using tcu::TestLog;
     76 using eglu::AttribMap;
     77 using eglu::NativeDisplay;
     78 using eglu::NativeWindow;
     79 using eglu::ScopedCurrentContext;
     80 using eglu::UniqueSurface;
     81 using eglu::UniqueContext;
     82 using eglu::NativeWindowFactory;
     83 using eglu::WindowParams;
     84 using namespace eglw;
     85 
     86 typedef	eglu::WindowParams::Visibility	Visibility;
     87 typedef	TestCase::IterateResult			IterateResult;
     88 
     89 struct ResizeParams
     90 {
     91 	string	name;
     92 	string	description;
     93 	IVec2	oldSize;
     94 	IVec2	newSize;
     95 };
     96 
     97 class ResizeTest : public TestCase
     98 {
     99 public:
    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 
    115 protected:
    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 
    131 EGLConfig 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 
    141 void 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 
    196 void 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 
    208 void 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 
    217 class ChangeSurfaceSizeCase : public ResizeTest
    218 {
    219 public:
    220 					ChangeSurfaceSizeCase	(EglTestContext&		eglTestCtx,
    221 											 const ResizeParams&	params)
    222 						: ResizeTest(eglTestCtx, params) {}
    223 
    224 	IterateResult	iterate					(void);
    225 };
    226 
    227 void 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 
    238 void 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 
    253 bool 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.
    273 bool 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 
    304 Surface 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 
    314 template <typename T>
    315 inline bool hasBits (T bitSet, T requiredBits)
    316 {
    317 	return (bitSet & requiredBits) == requiredBits;
    318 }
    319 
    320 IVec2 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 
    328 IVec2 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 
    346 IterateResult 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 
    369 class PreserveBackBufferCase : public ResizeTest
    370 {
    371 public:
    372 					PreserveBackBufferCase	(EglTestContext&		eglTestCtx,
    373 											 const ResizeParams&	params)
    374 						: ResizeTest(eglTestCtx, params) {}
    375 
    376 	IterateResult	iterate					(void);
    377 	EGLenum			surfaceType				(void) const;
    378 };
    379 
    380 EGLenum PreserveBackBufferCase::surfaceType (void) const
    381 {
    382 	return EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
    383 }
    384 
    385 IterateResult 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.checkResult(compareCorners(oldSurface, newSurface),
    420 									 QP_TEST_RESULT_QUALITY_WARNING,
    421 									 "Resizing the native window changed the contents of "
    422 									 "the EGL surface");
    423 			}
    424 		}
    425 	}
    426 
    427 	m_status.setTestContextResult(m_testCtx);
    428 	return STOP;
    429 }
    430 
    431 typedef tcu::Vector<Interval, 2> IvVec2;
    432 
    433 class UpdateResolutionCase : public ResizeTest
    434 {
    435 public:
    436 					UpdateResolutionCase	(EglTestContext&		eglTestCtx,
    437 											 const ResizeParams&	params)
    438 						: ResizeTest(eglTestCtx, params) {}
    439 
    440 	IterateResult	iterate					(void);
    441 
    442 private:
    443 	IvVec2			getNativePixelsPerInch	(void);
    444 };
    445 
    446 IvVec2 ivVec2 (const IVec2& vec)
    447 {
    448 	return IvVec2(double(vec.x()), double(vec.y()));
    449 }
    450 
    451 Interval approximateInt (int i)
    452 {
    453 	const Interval margin(-1.0, 1.0); // The resolution may be rounded
    454 
    455 	return (Interval(i) + margin) & Interval(0.0, TCU_INFINITY);
    456 }
    457 
    458 IvVec2 UpdateResolutionCase::getNativePixelsPerInch	(void)
    459 {
    460 	const Library&	egl			= m_eglTestCtx.getLibrary();
    461 	const int		inchPer10km	= 254 * EGL_DISPLAY_SCALING;
    462 	const IVec2		bufSize		= eglu::getSurfaceSize(egl, m_display, **m_surface);
    463 	const IVec2		winSize		= m_nativeWindow->getScreenSize();
    464 	const IVec2		bufPp10km	= eglu::getSurfaceResolution(egl, m_display, **m_surface);
    465 	const IvVec2	bufPpiI		= (IvVec2(approximateInt(bufPp10km.x()),
    466 										  approximateInt(bufPp10km.y()))
    467 								   / Interval(inchPer10km));
    468 	const IvVec2	winPpiI		= ivVec2(winSize) * bufPpiI / ivVec2(bufSize);
    469 	const IVec2		winPpi		(int(winPpiI.x().midpoint()), int(winPpiI.y().midpoint()));
    470 
    471 	m_log << TestLog::Message
    472 		  << "EGL surface size: "							<< bufSize		<< "\n"
    473 		  << "EGL surface pixel density (pixels / 10 km): "	<< bufPp10km	<< "\n"
    474 		  << "Native window size: "							<< winSize		<< "\n"
    475 		  << "Native pixel density (ppi): "					<< winPpi		<< "\n"
    476 		  << TestLog::EndMessage;
    477 
    478 	m_status.checkResult(bufPp10km.x() >= 1 && bufPp10km.y() >= 1,
    479 						 QP_TEST_RESULT_QUALITY_WARNING,
    480 						 "Surface pixel density is less than one pixel per 10 km. "
    481 						 "Is the surface really visible from space?");
    482 
    483 	return winPpiI;
    484 }
    485 
    486 IterateResult UpdateResolutionCase::iterate (void)
    487 {
    488 	const Library&			egl			= m_eglTestCtx.getLibrary();
    489 	ScopedCurrentContext	currentCtx	(egl, m_display, **m_surface, **m_surface, **m_context);
    490 
    491 	if (!hasBits(m_nativeWindow->getCapabilities(),
    492 				 NativeWindow::CAPABILITY_GET_SCREEN_SIZE))
    493 		TCU_THROW(NotSupportedError, "Unable to determine surface size in screen pixels");
    494 
    495 	{
    496 		const IVec2 oldEglSize = eglu::getSurfaceSize(egl, m_display, **m_surface);
    497 		initSurface(m_gl, oldEglSize);
    498 	}
    499 	{
    500 		const IvVec2 oldPpi = this->getNativePixelsPerInch();
    501 		this->resize(m_newSize);
    502 		EGLU_CHECK_CALL(egl, swapBuffers(m_display, **m_surface));
    503 		{
    504 			const IvVec2 newPpi = this->getNativePixelsPerInch();
    505 			m_status.check(oldPpi.x().intersects(newPpi.x()) &&
    506 						   oldPpi.y().intersects(newPpi.y()),
    507 						   "Window PPI differs after resizing");
    508 		}
    509 	}
    510 
    511 	m_status.setTestContextResult(m_testCtx);
    512 	return STOP;
    513 }
    514 
    515 ResizeTests::ResizeTests (EglTestContext& eglTestCtx)
    516 	: TestCaseGroup(eglTestCtx, "resize", "Tests resizing the native surface")
    517 {
    518 }
    519 
    520 template <class Case>
    521 TestCaseGroup* createCaseGroup(EglTestContext&	eglTestCtx,
    522 							   const string&	name,
    523 							   const string&	desc)
    524 {
    525 	const ResizeParams		params[]	=
    526 	{
    527 		{ "shrink",			"Shrink in both dimensions",
    528 		  IVec2(128, 128),	IVec2(32, 32) },
    529 		{ "grow",			"Grow in both dimensions",
    530 		  IVec2(32, 32),	IVec2(128, 128) },
    531 		{ "stretch_width",	"Grow horizontally, shrink vertically",
    532 		  IVec2(32, 128),	IVec2(128, 32) },
    533 		{ "stretch_height",	"Grow vertically, shrink horizontally",
    534 		  IVec2(128, 32),	IVec2(32, 128) },
    535 	};
    536 	TestCaseGroup* const	group		=
    537 		new TestCaseGroup(eglTestCtx, name.c_str(), desc.c_str());
    538 
    539 	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(params); ++ndx)
    540 		group->addChild(new Case(eglTestCtx, params[ndx]));
    541 
    542 	return group;
    543 }
    544 
    545 void ResizeTests::init (void)
    546 {
    547 	addChild(createCaseGroup<ChangeSurfaceSizeCase>(m_eglTestCtx,
    548 													"surface_size",
    549 													"EGL surface size update"));
    550 	addChild(createCaseGroup<PreserveBackBufferCase>(m_eglTestCtx,
    551 													 "back_buffer",
    552 													 "Back buffer contents"));
    553 	addChild(createCaseGroup<UpdateResolutionCase>(m_eglTestCtx,
    554 												   "pixel_density",
    555 												   "Pixel density"));
    556 }
    557 
    558 } // egl
    559 } // deqp
    560