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