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 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_300_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 if (colorHasChanged) 334 { 335 log << TestLog::Message 336 << "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column") 337 << " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr 338 << " and does not equal the color at (" << (isVerticallyIncreasing ? x-1 : x) << ", " << (isVerticallyIncreasing ? y : y-1) << "), which is " << prevConstantDirectionPix 339 << TestLog::EndMessage; 340 341 return false; 342 } 343 else 344 colorHasChanged = true; 345 } 346 347 prevConstantDirectionPix = clr; 348 } 349 } 350 } 351 352 return true; 353 } 354 355 bool DitheringCase::drawAndCheckUnicoloredQuad (const Vec4& quadColor) const 356 { 357 TestLog& log = m_testCtx.getLog(); 358 Random rnd (deStringHash(getName())); 359 const int maxViewportWid = 32; 360 const int maxViewportHei = 32; 361 const int viewportWid = de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid); 362 const int viewportHei = de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei); 363 const int viewportX = rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid); 364 const int viewportY = rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei); 365 Quad quad; 366 Surface renderedImg (viewportWid, viewportHei); 367 368 GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei)); 369 370 log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage; 371 372 if (m_ditheringEnabled) 373 GLU_CHECK_CALL(glEnable(GL_DITHER)); 374 else 375 GLU_CHECK_CALL(glDisable(GL_DITHER)); 376 377 log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage; 378 379 quad.color[0] = quadColor; 380 quad.color[1] = quadColor; 381 quad.color[2] = quadColor; 382 quad.color[3] = quadColor; 383 384 m_renderer->render(quad); 385 386 glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess()); 387 GLU_CHECK_MSG("glReadPixels()"); 388 389 log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg); 390 391 // Validate, at each pixel, that each color channel is one of its two allowed values. 392 393 { 394 Surface errorMask (viewportWid, viewportHei); 395 bool colorChoicesOk = true; 396 397 for (int y = 0; y < renderedImg.getHeight(); y++) 398 { 399 for (int x = 0; x < renderedImg.getWidth(); x++) 400 { 401 if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk)) 402 { 403 errorMask.setPixel(x, y, tcu::RGBA::red()); 404 405 if (colorChoicesOk) 406 { 407 log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage; 408 colorChoicesOk = false; 409 } 410 } 411 else 412 errorMask.setPixel(x, y, tcu::RGBA::green()); 413 } 414 } 415 416 if (!colorChoicesOk) 417 { 418 log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask); 419 return false; 420 } 421 } 422 423 // When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored. 424 425 if (!m_ditheringEnabled) 426 { 427 const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0); 428 429 for (int y = 0; y < renderedImg.getHeight(); y++) 430 { 431 for (int x = 0; x < renderedImg.getWidth(); x++) 432 { 433 const tcu::RGBA curClr = renderedImg.getPixel(x, y); 434 435 if (curClr != renderedClr00) 436 { 437 log << TestLog::Message 438 << "Failure: color at (" << x << ", " << y << ") is " << curClr 439 << " and does not equal the color at (0, 0), which is " << renderedClr00 440 << TestLog::EndMessage; 441 442 return false; 443 } 444 } 445 } 446 } 447 448 return true; 449 } 450 451 DitheringCase::IterateResult DitheringCase::iterate (void) 452 { 453 if (m_patternType == PATTERNTYPE_GRADIENT) 454 { 455 // Draw horizontal and vertical gradients. 456 457 DE_ASSERT(m_iteration < 2); 458 459 const bool success = drawAndCheckGradient(m_iteration == 1, m_color); 460 461 if (!success) 462 { 463 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); 464 return STOP; 465 } 466 467 if (m_iteration == 1) 468 { 469 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); 470 return STOP; 471 } 472 } 473 else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD) 474 { 475 const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30; 476 477 DE_ASSERT(m_iteration < numQuads); 478 479 const Vec4 quadColor = (float)m_iteration / (float)(numQuads-1) * m_color; 480 const bool success = drawAndCheckUnicoloredQuad(quadColor); 481 482 if (!success) 483 { 484 m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); 485 return STOP; 486 } 487 488 if (m_iteration == numQuads - 1) 489 { 490 m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); 491 return STOP; 492 } 493 } 494 else 495 DE_ASSERT(false); 496 497 m_iteration++; 498 499 return CONTINUE; 500 } 501 502 DitheringTests::DitheringTests (Context& context) 503 : TestCaseGroup(context, "dither", "Dithering tests") 504 { 505 } 506 507 DitheringTests::~DitheringTests (void) 508 { 509 } 510 511 void DitheringTests::init (void) 512 { 513 static const struct 514 { 515 const char* name; 516 Vec4 color; 517 } caseColors[] = 518 { 519 { "white", Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, 520 { "red", Vec4(1.0f, 0.0f, 0.0f, 1.0f) }, 521 { "green", Vec4(0.0f, 1.0f, 0.0f, 1.0f) }, 522 { "blue", Vec4(0.0f, 0.0f, 1.0f, 1.0f) }, 523 { "alpha", Vec4(0.0f, 0.0f, 0.0f, 1.0f) } 524 }; 525 526 for (int ditheringEnabledI = 0; ditheringEnabledI <= 1; ditheringEnabledI++) 527 { 528 const bool ditheringEnabled = ditheringEnabledI != 0; 529 TestCaseGroup* const group = new TestCaseGroup(m_context, ditheringEnabled ? "enabled" : "disabled", ""); 530 addChild(group); 531 532 for (int patternTypeI = 0; patternTypeI < DitheringCase::PATTERNTYPE_LAST; patternTypeI++) 533 { 534 for (int caseColorNdx = 0; caseColorNdx < DE_LENGTH_OF_ARRAY(caseColors); caseColorNdx++) 535 { 536 const DitheringCase::PatternType patternType = (DitheringCase::PatternType)patternTypeI; 537 const string caseName = string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name; 538 539 group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color)); 540 } 541 } 542 } 543 } 544 545 } // Functional 546 } // gles3 547 } // deqp 548