/*-------------------------------------------------------------------------
* 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 <string>
#include <algorithm>
#include "glw.h"
namespace deqp
{
using tcu::Vec4;
using tcu::IVec4;
using tcu::TestLog;
using gls::FragmentOpUtil::QuadRenderer;
using gls::FragmentOpUtil::Quad;
using tcu::PixelFormat;
using tcu::Surface;
using de::Random;
using std::vector;
using std::string;
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<typename T>
static inline string choiceListStr (const vector<T>& 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 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
{
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<int> channelChoices;
channelChoices.push_back(de::min(channelMax, (int)deFloatCeil(scaledInput)));
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;
if (!checkColor(inputClr, renderedImg.getPixel(x, y), colorChoicesOk))
{
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();
bool colorHasChanged = false;
for (int incrPos = 0; incrPos < increasingDirectionSize; incrPos++)
{
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))
{
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));
}
}
}
}
} // Functional
} // gles3
} // deqp