You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
550 lines
16 KiB
550 lines
16 KiB
/*------------------------------------------------------------------------- |
|
* drawElements Quality Program OpenGL ES 2.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 "es2fDitheringTests.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 "glw.h" |
|
|
|
#include <string> |
|
#include <algorithm> |
|
|
|
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 gles2 |
|
{ |
|
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_100_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(); |
|
|
|
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) |
|
{ |
|
// Allow color to change once to take into account possibly |
|
// discontinuity between triangles |
|
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 |
|
} // gles2 |
|
} // deqp
|
|
|