1/*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL ES 2.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 "es2fDitheringTests.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 "glw.h"
42
43#include <string>
44#include <algorithm>
45
46namespace deqp
47{
48
49using tcu::Vec4;
50using tcu::IVec4;
51using tcu::TestLog;
52using gls::FragmentOpUtil::QuadRenderer;
53using gls::FragmentOpUtil::Quad;
54using tcu::PixelFormat;
55using tcu::Surface;
56using de::Random;
57using std::vector;
58using std::string;
59
60namespace gles2
61{
62namespace Functional
63{
64
65static const char* const s_channelNames[4] = { "red", "green", "blue", "alpha" };
66
67static inline IVec4 pixelFormatToIVec4 (const PixelFormat& format)
68{
69	return IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits);
70}
71
72template<typename T>
73static 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
87class DitheringCase : public tcu::TestCase
88{
89public:
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
107private:
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
125const 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
138DitheringCase::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
150DitheringCase::~DitheringCase (void)
151{
152	DitheringCase::deinit();
153}
154
155void DitheringCase::init (void)
156{
157	DE_ASSERT(!m_renderer);
158	m_renderer = new QuadRenderer(m_renderCtx, glu::GLSL_VERSION_100_ES);
159	m_iteration = 0;
160}
161
162void DitheringCase::deinit (void)
163{
164	delete m_renderer;
165	m_renderer = DE_NULL;
166}
167
168bool 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
239bool 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					// Allow color to change once to take into account possibly
334					// discontinuity between triangles
335					if (colorHasChanged)
336					{
337						log << TestLog::Message
338							<< "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column")
339							<< " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr
340							<< " and does not equal the color at (" << (isVerticallyIncreasing ? x-1 : x) << ", " << (isVerticallyIncreasing ? y : y-1) << "), which is " << prevConstantDirectionPix
341							<< TestLog::EndMessage;
342
343						return false;
344					}
345					else
346						colorHasChanged = true;
347				}
348
349				prevConstantDirectionPix = clr;
350			}
351		}
352	}
353
354	return true;
355}
356
357bool DitheringCase::drawAndCheckUnicoloredQuad (const Vec4& quadColor) const
358{
359	TestLog&					log					= m_testCtx.getLog();
360	Random						rnd					(deStringHash(getName()));
361	const int					maxViewportWid		= 32;
362	const int					maxViewportHei		= 32;
363	const int					viewportWid			= de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
364	const int					viewportHei			= de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
365	const int					viewportX			= rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
366	const int					viewportY			= rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
367	Quad						quad;
368	Surface						renderedImg			(viewportWid, viewportHei);
369
370	GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
371
372	log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
373
374	if (m_ditheringEnabled)
375		GLU_CHECK_CALL(glEnable(GL_DITHER));
376	else
377		GLU_CHECK_CALL(glDisable(GL_DITHER));
378
379	log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage;
380
381	quad.color[0] = quadColor;
382	quad.color[1] = quadColor;
383	quad.color[2] = quadColor;
384	quad.color[3] = quadColor;
385
386	m_renderer->render(quad);
387
388	glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
389	GLU_CHECK_MSG("glReadPixels()");
390
391	log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg);
392
393	// Validate, at each pixel, that each color channel is one of its two allowed values.
394
395	{
396		Surface		errorMask		(viewportWid, viewportHei);
397		bool		colorChoicesOk	= true;
398
399		for (int y = 0; y < renderedImg.getHeight(); y++)
400		{
401			for (int x = 0; x < renderedImg.getWidth(); x++)
402			{
403				if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk))
404				{
405					errorMask.setPixel(x, y, tcu::RGBA::red());
406
407					if (colorChoicesOk)
408					{
409						log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
410						colorChoicesOk = false;
411					}
412				}
413				else
414					errorMask.setPixel(x, y, tcu::RGBA::green());
415			}
416		}
417
418		if (!colorChoicesOk)
419		{
420			log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
421			return false;
422		}
423	}
424
425	// When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored.
426
427	if (!m_ditheringEnabled)
428	{
429		const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0);
430
431		for (int y = 0; y < renderedImg.getHeight(); y++)
432		{
433			for (int x = 0; x < renderedImg.getWidth(); x++)
434			{
435				const tcu::RGBA curClr = renderedImg.getPixel(x, y);
436
437				if (curClr != renderedClr00)
438				{
439					log << TestLog::Message
440						<< "Failure: color at (" << x << ", " << y << ") is " << curClr
441						<< " and does not equal the color at (0, 0), which is " << renderedClr00
442						<< TestLog::EndMessage;
443
444					return false;
445				}
446			}
447		}
448	}
449
450	return true;
451}
452
453DitheringCase::IterateResult DitheringCase::iterate (void)
454{
455	if (m_patternType == PATTERNTYPE_GRADIENT)
456	{
457		// Draw horizontal and vertical gradients.
458
459		DE_ASSERT(m_iteration < 2);
460
461		const bool success = drawAndCheckGradient(m_iteration == 1, m_color);
462
463		if (!success)
464		{
465			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
466			return STOP;
467		}
468
469		if (m_iteration == 1)
470		{
471			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
472			return STOP;
473		}
474	}
475	else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD)
476	{
477		const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30;
478
479		DE_ASSERT(m_iteration < numQuads);
480
481		const Vec4 quadColor	= (float)m_iteration / (float)(numQuads-1) * m_color;
482		const bool success		=  drawAndCheckUnicoloredQuad(quadColor);
483
484		if (!success)
485		{
486			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
487			return STOP;
488		}
489
490		if (m_iteration == numQuads - 1)
491		{
492			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
493			return STOP;
494		}
495	}
496	else
497		DE_ASSERT(false);
498
499	m_iteration++;
500
501	return CONTINUE;
502}
503
504DitheringTests::DitheringTests (Context& context)
505	: TestCaseGroup(context, "dither", "Dithering tests")
506{
507}
508
509DitheringTests::~DitheringTests (void)
510{
511}
512
513void DitheringTests::init (void)
514{
515	static const struct
516	{
517		const char*		name;
518		Vec4			color;
519	} caseColors[] =
520	{
521		{ "white",		Vec4(1.0f, 1.0f, 1.0f, 1.0f) },
522		{ "red",		Vec4(1.0f, 0.0f, 0.0f, 1.0f) },
523		{ "green",		Vec4(0.0f, 1.0f, 0.0f, 1.0f) },
524		{ "blue",		Vec4(0.0f, 0.0f, 1.0f, 1.0f) },
525		{ "alpha",		Vec4(0.0f, 0.0f, 0.0f, 1.0f) }
526	};
527
528	for (int ditheringEnabledI = 0; ditheringEnabledI <= 1; ditheringEnabledI++)
529	{
530		const bool				ditheringEnabled	= ditheringEnabledI != 0;
531		TestCaseGroup* const	group				= new TestCaseGroup(m_context, ditheringEnabled ? "enabled" : "disabled", "");
532		addChild(group);
533
534		for (int patternTypeI = 0; patternTypeI < DitheringCase::PATTERNTYPE_LAST; patternTypeI++)
535		{
536			for (int caseColorNdx = 0; caseColorNdx < DE_LENGTH_OF_ARRAY(caseColors); caseColorNdx++)
537			{
538				const DitheringCase::PatternType	patternType		= (DitheringCase::PatternType)patternTypeI;
539				const string						caseName		= string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name;
540
541				group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color));
542			}
543		}
544	}
545}
546
547} // Functional
548} // gles2
549} // deqp
550