Home | History | Annotate | Download | only in functional
      1 /*-------------------------------------------------------------------------
      2  * drawElements Quality Program OpenGL ES 3.0 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 Dithering tests.
     22  *//*--------------------------------------------------------------------*/
     23 
     24 #include "es3fDitheringTests.hpp"
     25 #include "gluRenderContext.hpp"
     26 #include "gluDefs.hpp"
     27 #include "glsFragmentOpUtil.hpp"
     28 #include "gluPixelTransfer.hpp"
     29 #include "tcuRenderTarget.hpp"
     30 #include "tcuRGBA.hpp"
     31 #include "tcuVector.hpp"
     32 #include "tcuPixelFormat.hpp"
     33 #include "tcuTestLog.hpp"
     34 #include "tcuSurface.hpp"
     35 #include "tcuCommandLine.hpp"
     36 #include "deRandom.hpp"
     37 #include "deStringUtil.hpp"
     38 #include "deString.h"
     39 #include "deMath.h"
     40 
     41 #include <string>
     42 #include <algorithm>
     43 
     44 #include "glw.h"
     45 
     46 namespace deqp
     47 {
     48 
     49 using tcu::Vec4;
     50 using tcu::IVec4;
     51 using tcu::TestLog;
     52 using gls::FragmentOpUtil::QuadRenderer;
     53 using gls::FragmentOpUtil::Quad;
     54 using tcu::PixelFormat;
     55 using tcu::Surface;
     56 using de::Random;
     57 using std::vector;
     58 using std::string;
     59 
     60 namespace gles3
     61 {
     62 namespace Functional
     63 {
     64 
     65 static const char* const s_channelNames[4] = { "red", "green", "blue", "alpha" };
     66 
     67 static inline IVec4 pixelFormatToIVec4 (const PixelFormat& format)
     68 {
     69 	return IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits);
     70 }
     71 
     72 template<typename T>
     73 static inline string choiceListStr (const vector<T>& choices)
     74 {
     75 	string result;
     76 	for (int i = 0; i < (int)choices.size(); i++)
     77 	{
     78 		if (i == (int)choices.size()-1)
     79 			result += " or ";
     80 		else if (i > 0)
     81 			result += ", ";
     82 		result += de::toString(choices[i]);
     83 	}
     84 	return result;
     85 }
     86 
     87 class DitheringCase : public tcu::TestCase
     88 {
     89 public:
     90 	enum PatternType
     91 	{
     92 		PATTERNTYPE_GRADIENT = 0,
     93 		PATTERNTYPE_UNICOLORED_QUAD,
     94 
     95 		PATTERNTYPE_LAST
     96 	};
     97 
     98 											DitheringCase				(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, bool isEnabled, PatternType patternType, const tcu::Vec4& color);
     99 											~DitheringCase				(void);
    100 
    101 	IterateResult							iterate						(void);
    102 	void									init						(void);
    103 	void									deinit						(void);
    104 
    105 	static const char*						getPatternTypeName			(PatternType type);
    106 
    107 private:
    108 	bool									checkColor					(const tcu::Vec4& inputClr, const tcu::RGBA& renderedClr, bool logErrors) const;
    109 
    110 	bool									drawAndCheckGradient		(bool isVerticallyIncreasing, const tcu::Vec4& highColor) const;
    111 	bool									drawAndCheckUnicoloredQuad	(const tcu::Vec4& color) const;
    112 
    113 	const glu::RenderContext&				m_renderCtx;
    114 
    115 	const bool								m_ditheringEnabled;
    116 	const PatternType						m_patternType;
    117 	const tcu::Vec4							m_color;
    118 
    119 	const tcu::PixelFormat					m_renderFormat;
    120 
    121 	const QuadRenderer*						m_renderer;
    122 	int										m_iteration;
    123 };
    124 
    125 const char* DitheringCase::getPatternTypeName (const PatternType type)
    126 {
    127 	switch (type)
    128 	{
    129 		case PATTERNTYPE_GRADIENT:			return "gradient";
    130 		case PATTERNTYPE_UNICOLORED_QUAD:	return "unicolored_quad";
    131 		default:
    132 			DE_ASSERT(false);
    133 			return DE_NULL;
    134 	}
    135 }
    136 
    137 
    138 DitheringCase::DitheringCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* const name, const char* const description, const bool ditheringEnabled, const PatternType patternType, const Vec4& color)
    139 	: TestCase				(testCtx, name, description)
    140 	, m_renderCtx			(renderCtx)
    141 	, m_ditheringEnabled	(ditheringEnabled)
    142 	, m_patternType			(patternType)
    143 	, m_color				(color)
    144 	, m_renderFormat		(renderCtx.getRenderTarget().getPixelFormat())
    145 	, m_renderer			(DE_NULL)
    146 	, m_iteration			(0)
    147 {
    148 }
    149 
    150 DitheringCase::~DitheringCase (void)
    151 {
    152 	DitheringCase::deinit();
    153 }
    154 
    155 void DitheringCase::init (void)
    156 {
    157 	DE_ASSERT(!m_renderer);
    158 	m_renderer = new QuadRenderer(m_renderCtx, glu::GLSL_VERSION_300_ES);
    159 	m_iteration = 0;
    160 }
    161 
    162 void DitheringCase::deinit (void)
    163 {
    164 	delete m_renderer;
    165 	m_renderer = DE_NULL;
    166 }
    167 
    168 bool DitheringCase::checkColor (const Vec4& inputClr, const tcu::RGBA& renderedClr, const bool logErrors) const
    169 {
    170 	const IVec4		channelBits		= pixelFormatToIVec4(m_renderFormat);
    171 	bool			allChannelsOk	= true;
    172 
    173 	for (int chanNdx = 0; chanNdx < 4; chanNdx++)
    174 	{
    175 		if (channelBits[chanNdx] == 0)
    176 			continue;
    177 
    178 		const int		channelMax			= (1 << channelBits[chanNdx]) - 1;
    179 		const float		scaledInput			= inputClr[chanNdx] * (float)channelMax;
    180 		const bool		useRoundingMargin	= deFloatAbs(scaledInput - deFloatRound(scaledInput)) < 0.0001f;
    181 		vector<int>		channelChoices;
    182 
    183 		channelChoices.push_back(de::min(channelMax,	(int)deFloatCeil(scaledInput)));
    184 		channelChoices.push_back(de::max(0,				(int)deFloatCeil(scaledInput) - 1));
    185 
    186 		// If the input color results in a scaled value that is very close to an integer, account for a little bit of possible inaccuracy.
    187 		if (useRoundingMargin)
    188 		{
    189 			if (scaledInput > deFloatRound(scaledInput))
    190 				channelChoices.push_back((int)deFloatCeil(scaledInput) - 2);
    191 			else
    192 				channelChoices.push_back((int)deFloatCeil(scaledInput) + 1);
    193 		}
    194 
    195 		std::sort(channelChoices.begin(), channelChoices.end());
    196 
    197 		{
    198 			const int		renderedClrInFormat	= (int)deFloatRound((float)(renderedClr.toIVec()[chanNdx] * channelMax) / 255.0f);
    199 			bool			goodChannel			= false;
    200 
    201 			for (int i = 0; i < (int)channelChoices.size(); i++)
    202 			{
    203 				if (renderedClrInFormat == channelChoices[i])
    204 				{
    205 					goodChannel = true;
    206 					break;
    207 				}
    208 			}
    209 
    210 			if (!goodChannel)
    211 			{
    212 				if (logErrors)
    213 				{
    214 					m_testCtx.getLog() << TestLog::Message
    215 									   << "Failure: " << channelBits[chanNdx] << "-bit " << s_channelNames[chanNdx] << " channel is " << renderedClrInFormat
    216 									   << ", should be " << choiceListStr(channelChoices)
    217 									   << " (corresponding fragment color channel is " << inputClr[chanNdx] << ")"
    218 									   << TestLog::EndMessage
    219 									   << TestLog::Message
    220 									   << "Note: " << inputClr[chanNdx] << " * (" << channelMax + 1 << "-1) = " << scaledInput
    221 									   << TestLog::EndMessage;
    222 
    223 					if (useRoundingMargin)
    224 					{
    225 						m_testCtx.getLog() << TestLog::Message
    226 										   << "Note: one extra color candidate was allowed because fragmentColorChannel * (2^bits-1) is close to an integer"
    227 										   << TestLog::EndMessage;
    228 					}
    229 				}
    230 
    231 				allChannelsOk = false;
    232 			}
    233 		}
    234 	}
    235 
    236 	return allChannelsOk;
    237 }
    238 
    239 bool DitheringCase::drawAndCheckGradient (const bool isVerticallyIncreasing, const Vec4& highColor) const
    240 {
    241 	TestLog&					log					= m_testCtx.getLog();
    242 	Random						rnd					(deStringHash(getName()));
    243 	const int					maxViewportWid		= 256;
    244 	const int					maxViewportHei		= 256;
    245 	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
    246 	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
    247 	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
    248 	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
    249 	const Vec4					quadClr0			(0.0f, 0.0f, 0.0f, 0.0f);
    250 	const Vec4&					quadClr1			= highColor;
    251 	Quad						quad;
    252 	Surface						renderedImg			(viewportWid, viewportHei);
    253 
    254 	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
    255 
    256 	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
    257 
    258 	if (m_ditheringEnabled)
    259 		GLU_CHECK_CALL(glEnable(GL_DITHER));
    260 	else
    261 		GLU_CHECK_CALL(glDisable(GL_DITHER));
    262 
    263 	log << TestLog::Message << "Drawing a " << (isVerticallyIncreasing ? "vertically" : "horizontally") << " increasing gradient" << TestLog::EndMessage;
    264 
    265 	quad.color[0] = quadClr0;
    266 	quad.color[1] = isVerticallyIncreasing ? quadClr1 : quadClr0;
    267 	quad.color[2] = isVerticallyIncreasing ? quadClr0 : quadClr1;
    268 	quad.color[3] = quadClr1;
    269 
    270 	m_renderer->render(quad);
    271 
    272 	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
    273 	GLU_CHECK_MSG("glReadPixels()");
    274 
    275 	log << TestLog::Image(isVerticallyIncreasing ? "VerGradient"		: "HorGradient",
    276 						  isVerticallyIncreasing ? "Vertical gradient"	: "Horizontal gradient",
    277 						  renderedImg);
    278 
    279 	// Validate, at each pixel, that each color channel is one of its two allowed values.
    280 
    281 	{
    282 		Surface		errorMask		(viewportWid, viewportHei);
    283 		bool		colorChoicesOk	= true;
    284 
    285 		for (int y = 0; y < renderedImg.getHeight(); y++)
    286 		{
    287 			for (int x = 0; x < renderedImg.getWidth(); x++)
    288 			{
    289 				const float		inputF		= ((float)(isVerticallyIncreasing ? y : x) + 0.5f) / (float)(isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth());
    290 				const Vec4		inputClr	= (1.0f-inputF)*quadClr0 + inputF*quadClr1;
    291 
    292 				if (!checkColor(inputClr, renderedImg.getPixel(x, y), colorChoicesOk))
    293 				{
    294 					errorMask.setPixel(x, y, tcu::RGBA::red());
    295 
    296 					if (colorChoicesOk)
    297 					{
    298 						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
    299 						colorChoicesOk = false;
    300 					}
    301 				}
    302 				else
    303 					errorMask.setPixel(x, y, tcu::RGBA::green());
    304 			}
    305 		}
    306 
    307 		if (!colorChoicesOk)
    308 		{
    309 			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
    310 			return false;
    311 		}
    312 	}
    313 
    314 	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the colors must be constant in the gradient's constant direction.
    315 
    316 	if (!m_ditheringEnabled)
    317 	{
    318 		const int	increasingDirectionSize	= isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth();
    319 		const int	constantDirectionSize	= isVerticallyIncreasing ? renderedImg.getWidth() : renderedImg.getHeight();
    320 		bool		colorHasChanged			= false;
    321 
    322 		for (int incrPos = 0; incrPos < increasingDirectionSize; incrPos++)
    323 		{
    324 			tcu::RGBA prevConstantDirectionPix;
    325 			for (int constPos = 0; constPos < constantDirectionSize; constPos++)
    326 			{
    327 				const int			x		= isVerticallyIncreasing ? constPos : incrPos;
    328 				const int			y		= isVerticallyIncreasing ? incrPos : constPos;
    329 				const tcu::RGBA		clr		= renderedImg.getPixel(x, y);
    330 
    331 				if (constPos > 0 && clr != prevConstantDirectionPix)
    332 				{
    333 					if (colorHasChanged)
    334 					{
    335 						log << TestLog::Message
    336 							<< "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column")
    337 							<< " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr
    338 							<< " and does not equal the color at (" << (isVerticallyIncreasing ? x-1 : x) << ", " << (isVerticallyIncreasing ? y : y-1) << "), which is " << prevConstantDirectionPix
    339 							<< TestLog::EndMessage;
    340 
    341 						return false;
    342 					}
    343 					else
    344 						colorHasChanged = true;
    345 				}
    346 
    347 				prevConstantDirectionPix = clr;
    348 			}
    349 		}
    350 	}
    351 
    352 	return true;
    353 }
    354 
    355 bool DitheringCase::drawAndCheckUnicoloredQuad (const Vec4& quadColor) const
    356 {
    357 	TestLog&					log					= m_testCtx.getLog();
    358 	Random						rnd					(deStringHash(getName()));
    359 	const int					maxViewportWid		= 32;
    360 	const int					maxViewportHei		= 32;
    361 	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
    362 	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
    363 	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
    364 	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
    365 	Quad						quad;
    366 	Surface						renderedImg			(viewportWid, viewportHei);
    367 
    368 	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
    369 
    370 	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
    371 
    372 	if (m_ditheringEnabled)
    373 		GLU_CHECK_CALL(glEnable(GL_DITHER));
    374 	else
    375 		GLU_CHECK_CALL(glDisable(GL_DITHER));
    376 
    377 	log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage;
    378 
    379 	quad.color[0] = quadColor;
    380 	quad.color[1] = quadColor;
    381 	quad.color[2] = quadColor;
    382 	quad.color[3] = quadColor;
    383 
    384 	m_renderer->render(quad);
    385 
    386 	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
    387 	GLU_CHECK_MSG("glReadPixels()");
    388 
    389 	log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg);
    390 
    391 	// Validate, at each pixel, that each color channel is one of its two allowed values.
    392 
    393 	{
    394 		Surface		errorMask		(viewportWid, viewportHei);
    395 		bool		colorChoicesOk	= true;
    396 
    397 		for (int y = 0; y < renderedImg.getHeight(); y++)
    398 		{
    399 			for (int x = 0; x < renderedImg.getWidth(); x++)
    400 			{
    401 				if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk))
    402 				{
    403 					errorMask.setPixel(x, y, tcu::RGBA::red());
    404 
    405 					if (colorChoicesOk)
    406 					{
    407 						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
    408 						colorChoicesOk = false;
    409 					}
    410 				}
    411 				else
    412 					errorMask.setPixel(x, y, tcu::RGBA::green());
    413 			}
    414 		}
    415 
    416 		if (!colorChoicesOk)
    417 		{
    418 			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
    419 			return false;
    420 		}
    421 	}
    422 
    423 	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored.
    424 
    425 	if (!m_ditheringEnabled)
    426 	{
    427 		const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0);
    428 
    429 		for (int y = 0; y < renderedImg.getHeight(); y++)
    430 		{
    431 			for (int x = 0; x < renderedImg.getWidth(); x++)
    432 			{
    433 				const tcu::RGBA curClr = renderedImg.getPixel(x, y);
    434 
    435 				if (curClr != renderedClr00)
    436 				{
    437 					log << TestLog::Message
    438 						<< "Failure: color at (" << x << ", " << y << ") is " << curClr
    439 						<< " and does not equal the color at (0, 0), which is " << renderedClr00
    440 						<< TestLog::EndMessage;
    441 
    442 					return false;
    443 				}
    444 			}
    445 		}
    446 	}
    447 
    448 	return true;
    449 }
    450 
    451 DitheringCase::IterateResult DitheringCase::iterate (void)
    452 {
    453 	if (m_patternType == PATTERNTYPE_GRADIENT)
    454 	{
    455 		// Draw horizontal and vertical gradients.
    456 
    457 		DE_ASSERT(m_iteration < 2);
    458 
    459 		const bool success = drawAndCheckGradient(m_iteration == 1, m_color);
    460 
    461 		if (!success)
    462 		{
    463 			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
    464 			return STOP;
    465 		}
    466 
    467 		if (m_iteration == 1)
    468 		{
    469 			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    470 			return STOP;
    471 		}
    472 	}
    473 	else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD)
    474 	{
    475 		const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30;
    476 
    477 		DE_ASSERT(m_iteration < numQuads);
    478 
    479 		const Vec4 quadColor	= (float)m_iteration / (float)(numQuads-1) * m_color;
    480 		const bool success		=  drawAndCheckUnicoloredQuad(quadColor);
    481 
    482 		if (!success)
    483 		{
    484 			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
    485 			return STOP;
    486 		}
    487 
    488 		if (m_iteration == numQuads - 1)
    489 		{
    490 			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    491 			return STOP;
    492 		}
    493 	}
    494 	else
    495 		DE_ASSERT(false);
    496 
    497 	m_iteration++;
    498 
    499 	return CONTINUE;
    500 }
    501 
    502 DitheringTests::DitheringTests (Context& context)
    503 	: TestCaseGroup(context, "dither", "Dithering tests")
    504 {
    505 }
    506 
    507 DitheringTests::~DitheringTests (void)
    508 {
    509 }
    510 
    511 void DitheringTests::init (void)
    512 {
    513 	static const struct
    514 	{
    515 		const char*		name;
    516 		Vec4			color;
    517 	} caseColors[] =
    518 	{
    519 		{ "white",		Vec4(1.0f, 1.0f, 1.0f, 1.0f) },
    520 		{ "red",		Vec4(1.0f, 0.0f, 0.0f, 1.0f) },
    521 		{ "green",		Vec4(0.0f, 1.0f, 0.0f, 1.0f) },
    522 		{ "blue",		Vec4(0.0f, 0.0f, 1.0f, 1.0f) },
    523 		{ "alpha",		Vec4(0.0f, 0.0f, 0.0f, 1.0f) }
    524 	};
    525 
    526 	for (int ditheringEnabledI = 0; ditheringEnabledI <= 1; ditheringEnabledI++)
    527 	{
    528 		const bool				ditheringEnabled	= ditheringEnabledI != 0;
    529 		TestCaseGroup* const	group				= new TestCaseGroup(m_context, ditheringEnabled ? "enabled" : "disabled", "");
    530 		addChild(group);
    531 
    532 		for (int patternTypeI = 0; patternTypeI < DitheringCase::PATTERNTYPE_LAST; patternTypeI++)
    533 		{
    534 			for (int caseColorNdx = 0; caseColorNdx < DE_LENGTH_OF_ARRAY(caseColors); caseColorNdx++)
    535 			{
    536 				const DitheringCase::PatternType	patternType		= (DitheringCase::PatternType)patternTypeI;
    537 				const string						caseName		= string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name;
    538 
    539 				group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color));
    540 			}
    541 		}
    542 	}
    543 }
    544 
    545 } // Functional
    546 } // gles3
    547 } // deqp
    548