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