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