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
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, 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 bool incTol) 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
getPatternTypeName(const PatternType type)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
DitheringCase(tcu::TestContext & testCtx,glu::RenderContext & renderCtx,const char * const name,const char * const description,const bool ditheringEnabled,const PatternType patternType,const Vec4 & color)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
~DitheringCase(void)150 DitheringCase::~DitheringCase (void)
151 {
152 DitheringCase::deinit();
153 }
154
init(void)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
deinit(void)162 void DitheringCase::deinit (void)
163 {
164 delete m_renderer;
165 m_renderer = DE_NULL;
166 }
167
checkColor(const Vec4 & inputClr,const tcu::RGBA & renderedClr,const bool logErrors,const bool incTol) const168 bool DitheringCase::checkColor (const Vec4& inputClr, const tcu::RGBA& renderedClr, const bool logErrors, const bool incTol) 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 // Allow for more tolerance for small dimension render targets
186 if (incTol)
187 {
188 channelChoices.push_back(de::max(0,(int)deFloatCeil(scaledInput) - 2));
189 channelChoices.push_back(de::max(0,(int)deFloatCeil(scaledInput) + 1));
190 }
191
192 // If the input color results in a scaled value that is very close to an integer, account for a little bit of possible inaccuracy.
193 if (useRoundingMargin)
194 {
195 if (scaledInput > deFloatRound(scaledInput))
196 channelChoices.push_back((int)deFloatCeil(scaledInput) - 2);
197 else
198 channelChoices.push_back((int)deFloatCeil(scaledInput) + 1);
199 }
200
201 std::sort(channelChoices.begin(), channelChoices.end());
202
203 {
204 const int renderedClrInFormat = (int)deFloatRound((float)(renderedClr.toIVec()[chanNdx] * channelMax) / 255.0f);
205 bool goodChannel = false;
206
207 for (int i = 0; i < (int)channelChoices.size(); i++)
208 {
209 if (renderedClrInFormat == channelChoices[i])
210 {
211 goodChannel = true;
212 break;
213 }
214 }
215
216 if (!goodChannel)
217 {
218 if (logErrors)
219 {
220 m_testCtx.getLog() << TestLog::Message
221 << "Failure: " << channelBits[chanNdx] << "-bit " << s_channelNames[chanNdx] << " channel is " << renderedClrInFormat
222 << ", should be " << choiceListStr(channelChoices)
223 << " (corresponding fragment color channel is " << inputClr[chanNdx] << ")"
224 << TestLog::EndMessage
225 << TestLog::Message
226 << "Note: " << inputClr[chanNdx] << " * (" << channelMax + 1 << "-1) = " << scaledInput
227 << TestLog::EndMessage;
228
229 if (useRoundingMargin)
230 {
231 m_testCtx.getLog() << TestLog::Message
232 << "Note: one extra color candidate was allowed because fragmentColorChannel * (2^bits-1) is close to an integer"
233 << TestLog::EndMessage;
234 }
235 }
236
237 allChannelsOk = false;
238 }
239 }
240 }
241
242 return allChannelsOk;
243 }
244
drawAndCheckGradient(const bool isVerticallyIncreasing,const Vec4 & highColor) const245 bool DitheringCase::drawAndCheckGradient (const bool isVerticallyIncreasing, const Vec4& highColor) const
246 {
247 TestLog& log = m_testCtx.getLog();
248 Random rnd (deStringHash(getName()));
249 const int maxViewportWid = 256;
250 const int maxViewportHei = 256;
251 const int viewportWid = de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
252 const int viewportHei = de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
253 const int viewportX = rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
254 const int viewportY = rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
255 const Vec4 quadClr0 (0.0f, 0.0f, 0.0f, 0.0f);
256 const Vec4& quadClr1 = highColor;
257 Quad quad;
258 Surface renderedImg (viewportWid, viewportHei);
259
260 GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
261
262 log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
263
264 if (m_ditheringEnabled)
265 GLU_CHECK_CALL(glEnable(GL_DITHER));
266 else
267 GLU_CHECK_CALL(glDisable(GL_DITHER));
268
269 log << TestLog::Message << "Drawing a " << (isVerticallyIncreasing ? "vertically" : "horizontally") << " increasing gradient" << TestLog::EndMessage;
270
271 quad.color[0] = quadClr0;
272 quad.color[1] = isVerticallyIncreasing ? quadClr1 : quadClr0;
273 quad.color[2] = isVerticallyIncreasing ? quadClr0 : quadClr1;
274 quad.color[3] = quadClr1;
275
276 m_renderer->render(quad);
277
278 glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
279 GLU_CHECK_MSG("glReadPixels()");
280
281 log << TestLog::Image(isVerticallyIncreasing ? "VerGradient" : "HorGradient",
282 isVerticallyIncreasing ? "Vertical gradient" : "Horizontal gradient",
283 renderedImg);
284
285 // Validate, at each pixel, that each color channel is one of its two allowed values.
286
287 {
288 Surface errorMask (viewportWid, viewportHei);
289 bool colorChoicesOk = true;
290
291 for (int y = 0; y < renderedImg.getHeight(); y++)
292 {
293 for (int x = 0; x < renderedImg.getWidth(); x++)
294 {
295 const float inputF = ((float)(isVerticallyIncreasing ? y : x) + 0.5f) / (float)(isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth());
296 const Vec4 inputClr = (1.0f-inputF)*quadClr0 + inputF*quadClr1;
297 const bool increaseTol = ((renderedImg.getWidth() < 300) || (renderedImg.getHeight() < 300)) ? true : false;
298
299 if (!checkColor(inputClr, renderedImg.getPixel(x, y), colorChoicesOk, increaseTol))
300 {
301 errorMask.setPixel(x, y, tcu::RGBA::red());
302
303 if (colorChoicesOk)
304 {
305 log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
306 colorChoicesOk = false;
307 }
308 }
309 else
310 errorMask.setPixel(x, y, tcu::RGBA::green());
311 }
312 }
313
314 if (!colorChoicesOk)
315 {
316 log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
317 return false;
318 }
319 }
320
321 // When dithering is disabled, the color selection must be coordinate-independent - i.e. the colors must be constant in the gradient's constant direction.
322
323 if (!m_ditheringEnabled)
324 {
325 const int increasingDirectionSize = isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth();
326 const int constantDirectionSize = isVerticallyIncreasing ? renderedImg.getWidth() : renderedImg.getHeight();
327
328 for (int incrPos = 0; incrPos < increasingDirectionSize; incrPos++)
329 {
330 bool colorHasChanged = false;
331 tcu::RGBA prevConstantDirectionPix;
332
333 for (int constPos = 0; constPos < constantDirectionSize; constPos++)
334 {
335 const int x = isVerticallyIncreasing ? constPos : incrPos;
336 const int y = isVerticallyIncreasing ? incrPos : constPos;
337 const tcu::RGBA clr = renderedImg.getPixel(x, y);
338
339 if (constPos > 0 && clr != prevConstantDirectionPix)
340 {
341 if (colorHasChanged)
342 {
343 log << TestLog::Message
344 << "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column")
345 << " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr
346 << " and does not equal the color at (" << (isVerticallyIncreasing ? x-1 : x) << ", " << (isVerticallyIncreasing ? y : y-1) << "), which is " << prevConstantDirectionPix
347 << TestLog::EndMessage;
348
349 return false;
350 }
351 else
352 colorHasChanged = true;
353 }
354
355 prevConstantDirectionPix = clr;
356 }
357 }
358 }
359
360 return true;
361 }
362
drawAndCheckUnicoloredQuad(const Vec4 & quadColor) const363 bool DitheringCase::drawAndCheckUnicoloredQuad (const Vec4& quadColor) const
364 {
365 TestLog& log = m_testCtx.getLog();
366 Random rnd (deStringHash(getName()));
367 const int maxViewportWid = 32;
368 const int maxViewportHei = 32;
369 const int viewportWid = de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid);
370 const int viewportHei = de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei);
371 const int viewportX = rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid);
372 const int viewportY = rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei);
373 Quad quad;
374 Surface renderedImg (viewportWid, viewportHei);
375
376 GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei));
377
378 log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage;
379
380 if (m_ditheringEnabled)
381 GLU_CHECK_CALL(glEnable(GL_DITHER));
382 else
383 GLU_CHECK_CALL(glDisable(GL_DITHER));
384
385 log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage;
386
387 quad.color[0] = quadColor;
388 quad.color[1] = quadColor;
389 quad.color[2] = quadColor;
390 quad.color[3] = quadColor;
391
392 m_renderer->render(quad);
393
394 glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess());
395 GLU_CHECK_MSG("glReadPixels()");
396
397 log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg);
398
399 // Validate, at each pixel, that each color channel is one of its two allowed values.
400
401 {
402 Surface errorMask (viewportWid, viewportHei);
403 bool colorChoicesOk = true;
404
405 for (int y = 0; y < renderedImg.getHeight(); y++)
406 {
407 for (int x = 0; x < renderedImg.getWidth(); x++)
408 {
409 if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk, false))
410 {
411 errorMask.setPixel(x, y, tcu::RGBA::red());
412
413 if (colorChoicesOk)
414 {
415 log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage;
416 colorChoicesOk = false;
417 }
418 }
419 else
420 errorMask.setPixel(x, y, tcu::RGBA::green());
421 }
422 }
423
424 if (!colorChoicesOk)
425 {
426 log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask);
427 return false;
428 }
429 }
430
431 // When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored.
432
433 if (!m_ditheringEnabled)
434 {
435 const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0);
436
437 for (int y = 0; y < renderedImg.getHeight(); y++)
438 {
439 for (int x = 0; x < renderedImg.getWidth(); x++)
440 {
441 const tcu::RGBA curClr = renderedImg.getPixel(x, y);
442
443 if (curClr != renderedClr00)
444 {
445 log << TestLog::Message
446 << "Failure: color at (" << x << ", " << y << ") is " << curClr
447 << " and does not equal the color at (0, 0), which is " << renderedClr00
448 << TestLog::EndMessage;
449
450 return false;
451 }
452 }
453 }
454 }
455
456 return true;
457 }
458
iterate(void)459 DitheringCase::IterateResult DitheringCase::iterate (void)
460 {
461 if (m_patternType == PATTERNTYPE_GRADIENT)
462 {
463 // Draw horizontal and vertical gradients.
464
465 DE_ASSERT(m_iteration < 2);
466
467 const bool success = drawAndCheckGradient(m_iteration == 1, m_color);
468
469 if (!success)
470 {
471 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
472 return STOP;
473 }
474
475 if (m_iteration == 1)
476 {
477 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
478 return STOP;
479 }
480 }
481 else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD)
482 {
483 const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30;
484
485 DE_ASSERT(m_iteration < numQuads);
486
487 const Vec4 quadColor = (float)m_iteration / (float)(numQuads-1) * m_color;
488 const bool success = drawAndCheckUnicoloredQuad(quadColor);
489
490 if (!success)
491 {
492 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
493 return STOP;
494 }
495
496 if (m_iteration == numQuads - 1)
497 {
498 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
499 return STOP;
500 }
501 }
502 else
503 DE_ASSERT(false);
504
505 m_iteration++;
506
507 return CONTINUE;
508 }
509
DitheringTests(Context & context)510 DitheringTests::DitheringTests (Context& context)
511 : TestCaseGroup(context, "dither", "Dithering tests")
512 {
513 }
514
~DitheringTests(void)515 DitheringTests::~DitheringTests (void)
516 {
517 }
518
init(void)519 void DitheringTests::init (void)
520 {
521 static const struct
522 {
523 const char* name;
524 Vec4 color;
525 } caseColors[] =
526 {
527 { "white", Vec4(1.0f, 1.0f, 1.0f, 1.0f) },
528 { "red", Vec4(1.0f, 0.0f, 0.0f, 1.0f) },
529 { "green", Vec4(0.0f, 1.0f, 0.0f, 1.0f) },
530 { "blue", Vec4(0.0f, 0.0f, 1.0f, 1.0f) },
531 { "alpha", Vec4(0.0f, 0.0f, 0.0f, 1.0f) }
532 };
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 = string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name;
546
547 group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color));
548 }
549 }
550 }
551 }
552
553 } // Functional
554 } // gles3
555 } // deqp
556