xref: /aosp_15_r20/external/deqp/modules/gles2/functional/es2fDitheringTests.cpp (revision 35238bce31c2a825756842865a792f8cf7f89930)
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