/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES 3.0 Module * ------------------------------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Dithering tests. *//*--------------------------------------------------------------------*/ #include "es3fDitheringTests.hpp" #include "gluRenderContext.hpp" #include "gluDefs.hpp" #include "glsFragmentOpUtil.hpp" #include "gluPixelTransfer.hpp" #include "tcuRenderTarget.hpp" #include "tcuRGBA.hpp" #include "tcuVector.hpp" #include "tcuPixelFormat.hpp" #include "tcuTestLog.hpp" #include "tcuSurface.hpp" #include "tcuCommandLine.hpp" #include "deRandom.hpp" #include "deStringUtil.hpp" #include "deString.h" #include "deMath.h" #include #include #include "glw.h" namespace deqp { using de::Random; using gls::FragmentOpUtil::Quad; using gls::FragmentOpUtil::QuadRenderer; using std::string; using std::vector; using tcu::IVec4; using tcu::PixelFormat; using tcu::Surface; using tcu::TestLog; using tcu::Vec4; namespace gles3 { namespace Functional { static const char *const s_channelNames[4] = {"red", "green", "blue", "alpha"}; static inline IVec4 pixelFormatToIVec4(const PixelFormat &format) { return IVec4(format.redBits, format.greenBits, format.blueBits, format.alphaBits); } template static inline string choiceListStr(const vector &choices) { string result; for (int i = 0; i < (int)choices.size(); i++) { if (i == (int)choices.size() - 1) result += " or "; else if (i > 0) result += ", "; result += de::toString(choices[i]); } return result; } class DitheringCase : public tcu::TestCase { public: enum PatternType { PATTERNTYPE_GRADIENT = 0, PATTERNTYPE_UNICOLORED_QUAD, PATTERNTYPE_LAST }; DitheringCase(tcu::TestContext &testCtx, glu::RenderContext &renderCtx, const char *name, const char *description, bool isEnabled, PatternType patternType, const tcu::Vec4 &color); ~DitheringCase(void); IterateResult iterate(void); void init(void); void deinit(void); static const char *getPatternTypeName(PatternType type); private: bool checkColor(const tcu::Vec4 &inputClr, const tcu::RGBA &renderedClr, bool logErrors, const bool incTol) const; bool drawAndCheckGradient(bool isVerticallyIncreasing, const tcu::Vec4 &highColor) const; bool drawAndCheckUnicoloredQuad(const tcu::Vec4 &color) const; const glu::RenderContext &m_renderCtx; const bool m_ditheringEnabled; const PatternType m_patternType; const tcu::Vec4 m_color; const tcu::PixelFormat m_renderFormat; const QuadRenderer *m_renderer; int m_iteration; }; const char *DitheringCase::getPatternTypeName(const PatternType type) { switch (type) { case PATTERNTYPE_GRADIENT: return "gradient"; case PATTERNTYPE_UNICOLORED_QUAD: return "unicolored_quad"; default: DE_ASSERT(false); return DE_NULL; } } 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) : TestCase(testCtx, name, description) , m_renderCtx(renderCtx) , m_ditheringEnabled(ditheringEnabled) , m_patternType(patternType) , m_color(color) , m_renderFormat(renderCtx.getRenderTarget().getPixelFormat()) , m_renderer(DE_NULL) , m_iteration(0) { } DitheringCase::~DitheringCase(void) { DitheringCase::deinit(); } void DitheringCase::init(void) { DE_ASSERT(!m_renderer); m_renderer = new QuadRenderer(m_renderCtx, glu::GLSL_VERSION_300_ES); m_iteration = 0; } void DitheringCase::deinit(void) { delete m_renderer; m_renderer = DE_NULL; } bool DitheringCase::checkColor(const Vec4 &inputClr, const tcu::RGBA &renderedClr, const bool logErrors, const bool incTol) const { const IVec4 channelBits = pixelFormatToIVec4(m_renderFormat); bool allChannelsOk = true; for (int chanNdx = 0; chanNdx < 4; chanNdx++) { if (channelBits[chanNdx] == 0) continue; const int channelMax = (1 << channelBits[chanNdx]) - 1; const float scaledInput = inputClr[chanNdx] * (float)channelMax; const bool useRoundingMargin = deFloatAbs(scaledInput - deFloatRound(scaledInput)) < 0.0001f; vector channelChoices; channelChoices.push_back(de::min(channelMax, (int)deFloatCeil(scaledInput))); channelChoices.push_back(de::max(0, (int)deFloatCeil(scaledInput) - 1)); // Allow for more tolerance for small dimension render targets if (incTol) { channelChoices.push_back(de::max(0, (int)deFloatCeil(scaledInput) - 2)); channelChoices.push_back(de::max(0, (int)deFloatCeil(scaledInput) + 1)); } // If the input color results in a scaled value that is very close to an integer, account for a little bit of possible inaccuracy. if (useRoundingMargin) { if (scaledInput > deFloatRound(scaledInput)) channelChoices.push_back((int)deFloatCeil(scaledInput) - 2); else channelChoices.push_back((int)deFloatCeil(scaledInput) + 1); } std::sort(channelChoices.begin(), channelChoices.end()); { const int renderedClrInFormat = (int)deFloatRound((float)(renderedClr.toIVec()[chanNdx] * channelMax) / 255.0f); bool goodChannel = false; for (int i = 0; i < (int)channelChoices.size(); i++) { if (renderedClrInFormat == channelChoices[i]) { goodChannel = true; break; } } if (!goodChannel) { if (logErrors) { m_testCtx.getLog() << TestLog::Message << "Failure: " << channelBits[chanNdx] << "-bit " << s_channelNames[chanNdx] << " channel is " << renderedClrInFormat << ", should be " << choiceListStr(channelChoices) << " (corresponding fragment color channel is " << inputClr[chanNdx] << ")" << TestLog::EndMessage << TestLog::Message << "Note: " << inputClr[chanNdx] << " * (" << channelMax + 1 << "-1) = " << scaledInput << TestLog::EndMessage; if (useRoundingMargin) { m_testCtx.getLog() << TestLog::Message << "Note: one extra color candidate was allowed because " "fragmentColorChannel * (2^bits-1) is close to an integer" << TestLog::EndMessage; } } allChannelsOk = false; } } } return allChannelsOk; } bool DitheringCase::drawAndCheckGradient(const bool isVerticallyIncreasing, const Vec4 &highColor) const { TestLog &log = m_testCtx.getLog(); Random rnd(deStringHash(getName())); const int maxViewportWid = 256; const int maxViewportHei = 256; const int viewportWid = de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid); const int viewportHei = de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei); const int viewportX = rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid); const int viewportY = rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei); const Vec4 quadClr0(0.0f, 0.0f, 0.0f, 0.0f); const Vec4 &quadClr1 = highColor; Quad quad; Surface renderedImg(viewportWid, viewportHei); GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei)); log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage; if (m_ditheringEnabled) GLU_CHECK_CALL(glEnable(GL_DITHER)); else GLU_CHECK_CALL(glDisable(GL_DITHER)); log << TestLog::Message << "Drawing a " << (isVerticallyIncreasing ? "vertically" : "horizontally") << " increasing gradient" << TestLog::EndMessage; quad.color[0] = quadClr0; quad.color[1] = isVerticallyIncreasing ? quadClr1 : quadClr0; quad.color[2] = isVerticallyIncreasing ? quadClr0 : quadClr1; quad.color[3] = quadClr1; m_renderer->render(quad); glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess()); GLU_CHECK_MSG("glReadPixels()"); log << TestLog::Image(isVerticallyIncreasing ? "VerGradient" : "HorGradient", isVerticallyIncreasing ? "Vertical gradient" : "Horizontal gradient", renderedImg); // Validate, at each pixel, that each color channel is one of its two allowed values. { Surface errorMask(viewportWid, viewportHei); bool colorChoicesOk = true; for (int y = 0; y < renderedImg.getHeight(); y++) { for (int x = 0; x < renderedImg.getWidth(); x++) { const float inputF = ((float)(isVerticallyIncreasing ? y : x) + 0.5f) / (float)(isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth()); const Vec4 inputClr = (1.0f - inputF) * quadClr0 + inputF * quadClr1; const bool increaseTol = ((renderedImg.getWidth() < 300) || (renderedImg.getHeight() < 300)) ? true : false; if (!checkColor(inputClr, renderedImg.getPixel(x, y), colorChoicesOk, increaseTol)) { errorMask.setPixel(x, y, tcu::RGBA::red()); if (colorChoicesOk) { log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage; colorChoicesOk = false; } } else errorMask.setPixel(x, y, tcu::RGBA::green()); } } if (!colorChoicesOk) { log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask); return false; } } // When dithering is disabled, the color selection must be coordinate-independent - i.e. the colors must be constant in the gradient's constant direction. if (!m_ditheringEnabled) { const int increasingDirectionSize = isVerticallyIncreasing ? renderedImg.getHeight() : renderedImg.getWidth(); const int constantDirectionSize = isVerticallyIncreasing ? renderedImg.getWidth() : renderedImg.getHeight(); for (int incrPos = 0; incrPos < increasingDirectionSize; incrPos++) { bool colorHasChanged = false; tcu::RGBA prevConstantDirectionPix; for (int constPos = 0; constPos < constantDirectionSize; constPos++) { const int x = isVerticallyIncreasing ? constPos : incrPos; const int y = isVerticallyIncreasing ? incrPos : constPos; const tcu::RGBA clr = renderedImg.getPixel(x, y); if (constPos > 0 && clr != prevConstantDirectionPix) { if (colorHasChanged) { log << TestLog::Message << "Failure: colors should be constant per " << (isVerticallyIncreasing ? "row" : "column") << " (since dithering is disabled), but the color at position (" << x << ", " << y << ") is " << clr << " and does not equal the color at (" << (isVerticallyIncreasing ? x - 1 : x) << ", " << (isVerticallyIncreasing ? y : y - 1) << "), which is " << prevConstantDirectionPix << TestLog::EndMessage; return false; } else colorHasChanged = true; } prevConstantDirectionPix = clr; } } } return true; } bool DitheringCase::drawAndCheckUnicoloredQuad(const Vec4 &quadColor) const { TestLog &log = m_testCtx.getLog(); Random rnd(deStringHash(getName())); const int maxViewportWid = 32; const int maxViewportHei = 32; const int viewportWid = de::min(m_renderCtx.getRenderTarget().getWidth(), maxViewportWid); const int viewportHei = de::min(m_renderCtx.getRenderTarget().getHeight(), maxViewportHei); const int viewportX = rnd.getInt(0, m_renderCtx.getRenderTarget().getWidth() - viewportWid); const int viewportY = rnd.getInt(0, m_renderCtx.getRenderTarget().getHeight() - viewportHei); Quad quad; Surface renderedImg(viewportWid, viewportHei); GLU_CHECK_CALL(glViewport(viewportX, viewportY, viewportWid, viewportHei)); log << TestLog::Message << "Dithering is " << (m_ditheringEnabled ? "enabled" : "disabled") << TestLog::EndMessage; if (m_ditheringEnabled) GLU_CHECK_CALL(glEnable(GL_DITHER)); else GLU_CHECK_CALL(glDisable(GL_DITHER)); log << TestLog::Message << "Drawing an unicolored quad with color " << quadColor << TestLog::EndMessage; quad.color[0] = quadColor; quad.color[1] = quadColor; quad.color[2] = quadColor; quad.color[3] = quadColor; m_renderer->render(quad); glu::readPixels(m_renderCtx, viewportX, viewportY, renderedImg.getAccess()); GLU_CHECK_MSG("glReadPixels()"); log << TestLog::Image(("Quad" + de::toString(m_iteration)).c_str(), ("Quad " + de::toString(m_iteration)).c_str(), renderedImg); // Validate, at each pixel, that each color channel is one of its two allowed values. { Surface errorMask(viewportWid, viewportHei); bool colorChoicesOk = true; for (int y = 0; y < renderedImg.getHeight(); y++) { for (int x = 0; x < renderedImg.getWidth(); x++) { if (!checkColor(quadColor, renderedImg.getPixel(x, y), colorChoicesOk, false)) { errorMask.setPixel(x, y, tcu::RGBA::red()); if (colorChoicesOk) { log << TestLog::Message << "First failure at pixel (" << x << ", " << y << ") (not printing further errors)" << TestLog::EndMessage; colorChoicesOk = false; } } else errorMask.setPixel(x, y, tcu::RGBA::green()); } } if (!colorChoicesOk) { log << TestLog::Image("ColorChoiceErrorMask", "Error mask for color choices", errorMask); return false; } } // When dithering is disabled, the color selection must be coordinate-independent - i.e. the entire rendered image must be unicolored. if (!m_ditheringEnabled) { const tcu::RGBA renderedClr00 = renderedImg.getPixel(0, 0); for (int y = 0; y < renderedImg.getHeight(); y++) { for (int x = 0; x < renderedImg.getWidth(); x++) { const tcu::RGBA curClr = renderedImg.getPixel(x, y); if (curClr != renderedClr00) { log << TestLog::Message << "Failure: color at (" << x << ", " << y << ") is " << curClr << " and does not equal the color at (0, 0), which is " << renderedClr00 << TestLog::EndMessage; return false; } } } } return true; } DitheringCase::IterateResult DitheringCase::iterate(void) { if (m_patternType == PATTERNTYPE_GRADIENT) { // Draw horizontal and vertical gradients. DE_ASSERT(m_iteration < 2); const bool success = drawAndCheckGradient(m_iteration == 1, m_color); if (!success) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); return STOP; } if (m_iteration == 1) { m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } } else if (m_patternType == PATTERNTYPE_UNICOLORED_QUAD) { const int numQuads = m_testCtx.getCommandLine().getTestIterationCount() > 0 ? m_testCtx.getCommandLine().getTestIterationCount() : 30; DE_ASSERT(m_iteration < numQuads); const Vec4 quadColor = (float)m_iteration / (float)(numQuads - 1) * m_color; const bool success = drawAndCheckUnicoloredQuad(quadColor); if (!success) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); return STOP; } if (m_iteration == numQuads - 1) { m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } } else DE_ASSERT(false); m_iteration++; return CONTINUE; } DitheringTests::DitheringTests(Context &context) : TestCaseGroup(context, "dither", "Dithering tests") { } DitheringTests::~DitheringTests(void) { } void DitheringTests::init(void) { static const struct { const char *name; Vec4 color; } caseColors[] = {{"white", Vec4(1.0f, 1.0f, 1.0f, 1.0f)}, {"red", Vec4(1.0f, 0.0f, 0.0f, 1.0f)}, {"green", Vec4(0.0f, 1.0f, 0.0f, 1.0f)}, {"blue", Vec4(0.0f, 0.0f, 1.0f, 1.0f)}, {"alpha", Vec4(0.0f, 0.0f, 0.0f, 1.0f)}}; for (int ditheringEnabledI = 0; ditheringEnabledI <= 1; ditheringEnabledI++) { const bool ditheringEnabled = ditheringEnabledI != 0; TestCaseGroup *const group = new TestCaseGroup(m_context, ditheringEnabled ? "enabled" : "disabled", ""); addChild(group); for (int patternTypeI = 0; patternTypeI < DitheringCase::PATTERNTYPE_LAST; patternTypeI++) { for (int caseColorNdx = 0; caseColorNdx < DE_LENGTH_OF_ARRAY(caseColors); caseColorNdx++) { const DitheringCase::PatternType patternType = (DitheringCase::PatternType)patternTypeI; const string caseName = string("") + DitheringCase::getPatternTypeName(patternType) + "_" + caseColors[caseColorNdx].name; group->addChild(new DitheringCase(m_context.getTestContext(), m_context.getRenderContext(), caseName.c_str(), "", ditheringEnabled, patternType, caseColors[caseColorNdx].color)); } } } } } // namespace Functional } // namespace gles3 } // namespace deqp