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 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 gles2 61 { 62 namespace Functional 63 { 64 65 static const char* const s_channelNames[4] = { "red", "green", "blue", "alpha" }; 66 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> 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; 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 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 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 150 DitheringCase::~DitheringCase (void) 151 { 152 DitheringCase::deinit(); 153 } 154 155 void 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 162 void DitheringCase::deinit (void) 163 { 164 delete m_renderer; 165 m_renderer = DE_NULL; 166 } 167 168 bool 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 239 bool 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 357 bool 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 453 DitheringCase::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 504 DitheringTests::DitheringTests (Context& context) 505 : TestCaseGroup(context, "dither", "Dithering tests") 506 { 507 } 508 509 DitheringTests::~DitheringTests (void) 510 { 511 } 512 513 void 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