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