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 46namespace deqp 47{ 48 49using tcu::Vec4; 50using tcu::IVec4; 51using tcu::TestLog; 52using gls::FragmentOpUtil::QuadRenderer; 53using gls::FragmentOpUtil::Quad; 54using tcu::PixelFormat; 55using tcu::Surface; 56using de::Random; 57using std::vector; 58using std::string; 59 60namespace gles2 61{ 62namespace Functional 63{ 64 65static const char* const s_channelNames[4] = { "red", "green", "blue", "alpha" }; 66 67static inline IVec4 pixelFormatToIVec4 (const PixelFormat& format) 68{ 69 return IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits); 70} 71 72template<typename T> 73static 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 87class DitheringCase : public tcu::TestCase 88{ 89public: 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 107private: 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 125const 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 138DitheringCase::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 150DitheringCase::~DitheringCase (void) 151{ 152 DitheringCase::deinit(); 153} 154 155void 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 162void DitheringCase::deinit (void) 163{ 164 delete m_renderer; 165 m_renderer = DE_NULL; 166} 167 168bool 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 239bool 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 357bool 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 453DitheringCase::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 504DitheringTests::DitheringTests (Context& context) 505 : TestCaseGroup(context, "dither", "Dithering tests") 506{ 507} 508 509DitheringTests::~DitheringTests (void) 510{ 511} 512 513void 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