/*------------------------------------------------------------------------- * 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 Multisampling tests. *//*--------------------------------------------------------------------*/ #include "es3fMultisampleTests.hpp" #include "gluStrUtil.hpp" #include "gluShaderProgram.hpp" #include "gluPixelTransfer.hpp" #include "tcuSurface.hpp" #include "tcuImageCompare.hpp" #include "tcuRenderTarget.hpp" #include "tcuTestLog.hpp" #include "tcuTextureUtil.hpp" #include "tcuCommandLine.hpp" #include "deStringUtil.hpp" #include "deRandom.hpp" #include "deMath.h" #include "deString.h" #include #include #include "glw.h" namespace deqp { namespace gles3 { namespace Functional { using tcu::Vec2; using tcu::Vec3; using tcu::Vec4; using tcu::IVec2; using tcu::IVec4; using tcu::TestLog; using std::vector; static const GLenum FBO_COLOR_FORMAT = GL_RGBA8; static const float SQRT_HALF = 0.707107f; namespace { struct QuadCorners { Vec2 p0; Vec2 p1; Vec2 p2; Vec2 p3; QuadCorners(const Vec2& p0_, const Vec2& p1_, const Vec2& p2_, const Vec2& p3_) : p0(p0_), p1(p1_), p2(p2_), p3(p3_) {} }; } // anonymous static inline int getIterationCount (const tcu::TestContext& ctx, int defaultCount) { int cmdLineValue = ctx.getCommandLine().getTestIterationCount(); return cmdLineValue > 0 ? cmdLineValue : defaultCount; } static inline int getGLInteger (GLenum name) { int result; GLU_CHECK_CALL(glGetIntegerv(name, &result)); return result; } template static inline T min4 (T a, T b, T c, T d) { return de::min(de::min(de::min(a, b), c), d); } template static inline T max4 (T a, T b, T c, T d) { return de::max(de::max(de::max(a, b), c), d); } static inline bool isInsideQuad (const IVec2& point, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3) { int dot0 = (point.x()-p0.x()) * (p1.y()-p0.y()) + (point.y()-p0.y()) * (p0.x()-p1.x()); int dot1 = (point.x()-p1.x()) * (p2.y()-p1.y()) + (point.y()-p1.y()) * (p1.x()-p2.x()); int dot2 = (point.x()-p2.x()) * (p3.y()-p2.y()) + (point.y()-p2.y()) * (p2.x()-p3.x()); int dot3 = (point.x()-p3.x()) * (p0.y()-p3.y()) + (point.y()-p3.y()) * (p3.x()-p0.x()); return (dot0 > 0) == (dot1 > 0) && (dot1 > 0) == (dot2 > 0) && (dot2 > 0) == (dot3 > 0); } /*--------------------------------------------------------------------*//*! * \brief Check if a region in an image is unicolored. * * Checks if the pixels in img inside the convex quadilateral defined by * p0, p1, p2 and p3 are all (approximately) of the same color. *//*--------------------------------------------------------------------*/ static bool isPixelRegionUnicolored (const tcu::Surface& img, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3) { int xMin = de::clamp(min4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1); int yMin = de::clamp(min4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1); int xMax = de::clamp(max4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1); int yMax = de::clamp(max4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1); bool insideEncountered = false; //!< Whether we have already seen at least one pixel inside the region. tcu::RGBA insideColor; //!< Color of the first pixel inside the region. for (int y = yMin; y <= yMax; y++) for (int x = xMin; x <= xMax; x++) { if (isInsideQuad(IVec2(x, y), p0, p1, p2, p3)) { tcu::RGBA pixColor = img.getPixel(x, y); if (insideEncountered) { if (!tcu::compareThreshold(pixColor, insideColor, tcu::RGBA(3, 3, 3, 3))) // Pixel color differs from already-detected color inside same region - region not unicolored. return false; } else { insideEncountered = true; insideColor = pixColor; } } } return true; } static bool drawUnicolorTestErrors (tcu::Surface& img, const tcu::PixelBufferAccess& errorImg, const IVec2& p0, const IVec2& p1, const IVec2& p2, const IVec2& p3) { int xMin = de::clamp(min4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1); int yMin = de::clamp(min4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1); int xMax = de::clamp(max4(p0.x(), p1.x(), p2.x(), p3.x()), 0, img.getWidth()-1); int yMax = de::clamp(max4(p0.y(), p1.y(), p2.y(), p3.y()), 0, img.getHeight()-1); tcu::RGBA refColor = img.getPixel((xMin + xMax) / 2, (yMin + yMax) / 2); for (int y = yMin; y <= yMax; y++) for (int x = xMin; x <= xMax; x++) { if (isInsideQuad(IVec2(x, y), p0, p1, p2, p3)) { if (!tcu::compareThreshold(img.getPixel(x, y), refColor, tcu::RGBA(3, 3, 3, 3))) { img.setPixel(x, y, tcu::RGBA::red()); errorImg.setPixel(Vec4(1.0f, 0.0f, 0.0f, 1.0f), x, y); } } } return true; } /*--------------------------------------------------------------------*//*! * \brief Abstract base class handling common stuff for multisample cases. *//*--------------------------------------------------------------------*/ class MultisampleCase : public TestCase { public: struct FboParams { bool useFbo; int numSamples; //!< If 0, use implementation-defined maximum. bool useDepth; bool useStencil; FboParams (int numSamples_, bool useDepth_, bool useStencil_) : useFbo (true) , numSamples (numSamples_) , useDepth (useDepth_) , useStencil (useStencil_) { } FboParams (void) : useFbo (false) , numSamples (-1) , useDepth (false) , useStencil (false) { } }; MultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize, const FboParams& fboParams = FboParams()); virtual ~MultisampleCase (void); virtual void init (void); virtual void deinit (void); protected: void renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const; void renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const; void renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const; void renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const; void renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const; void renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const; void renderLine (const Vec2& p0, const Vec2& p1, const Vec4& color) const; void randomizeViewport (void); void readImage (tcu::Surface& dst) const; IVec2 getRenderTargetSize (void) const { return IVec2(m_renderWidth, m_renderHeight); } int m_numSamples; int m_viewportSize; private: MultisampleCase (const MultisampleCase& other); MultisampleCase& operator= (const MultisampleCase& other); const int m_desiredViewportSize; const FboParams m_fboParams; deUint32 m_msColorRbo; deUint32 m_msDepthStencilRbo; deUint32 m_resolveColorRbo; deUint32 m_msFbo; deUint32 m_resolveFbo; glu::ShaderProgram* m_program; int m_attrPositionLoc; int m_attrColorLoc; int m_renderWidth; int m_renderHeight; int m_viewportX; int m_viewportY; de::Random m_rnd; }; MultisampleCase::MultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize, const FboParams& fboParams) : TestCase (context, name, desc) , m_numSamples (0) , m_viewportSize (0) , m_desiredViewportSize (desiredViewportSize) , m_fboParams (fboParams) , m_msColorRbo (0) , m_msDepthStencilRbo (0) , m_resolveColorRbo (0) , m_msFbo (0) , m_resolveFbo (0) , m_program (DE_NULL) , m_attrPositionLoc (-1) , m_attrColorLoc (-1) , m_renderWidth (fboParams.useFbo ? 2*desiredViewportSize : context.getRenderTarget().getWidth()) , m_renderHeight (fboParams.useFbo ? 2*desiredViewportSize : context.getRenderTarget().getHeight()) , m_viewportX (0) , m_viewportY (0) , m_rnd (deStringHash(name)) { if (m_fboParams.useFbo) DE_ASSERT(m_fboParams.numSamples >= 0); } MultisampleCase::~MultisampleCase (void) { MultisampleCase::deinit(); } void MultisampleCase::deinit (void) { delete m_program; m_program = DE_NULL; GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); if (m_msColorRbo != 0) { GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_msColorRbo)); m_msColorRbo = 0; } if (m_msDepthStencilRbo != 0) { GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_msDepthStencilRbo)); m_msDepthStencilRbo = 0; } if (m_resolveColorRbo != 0) { GLU_CHECK_CALL(glDeleteRenderbuffers(1, &m_resolveColorRbo)); m_resolveColorRbo = 0; } if (m_msFbo != 0) { GLU_CHECK_CALL(glDeleteFramebuffers(1, &m_msFbo)); m_msFbo = 0; } if (m_resolveFbo != 0) { GLU_CHECK_CALL(glDeleteFramebuffers(1, &m_resolveFbo)); m_resolveFbo = 0; } } void MultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const { float vertexPositions[] = { p0.x(), p0.y(), p0.z(), 1.0f, p1.x(), p1.y(), p1.z(), 1.0f, p2.x(), p2.y(), p2.z(), 1.0f }; float vertexColors[] = { c0.x(), c0.y(), c0.z(), c0.w(), c1.x(), c1.y(), c1.z(), c1.w(), c2.x(), c2.y(), c2.z(), c2.w(), }; GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrPositionLoc)); GLU_CHECK_CALL(glVertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, &vertexPositions[0])); GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrColorLoc)); GLU_CHECK_CALL(glVertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, &vertexColors[0])); GLU_CHECK_CALL(glUseProgram(m_program->getProgram())); GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3)); } void MultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const { renderTriangle(p0, p1, p2, color, color, color); } void MultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const { renderTriangle(Vec3(p0.x(), p0.y(), 0.0f), Vec3(p1.x(), p1.y(), 0.0f), Vec3(p2.x(), p2.y(), 0.0f), c0, c1, c2); } void MultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const { renderTriangle(p0, p1, p2, color, color, color); } void MultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const { renderTriangle(p0, p1, p2, c0, c1, c2); renderTriangle(p2, p1, p3, c2, c1, c3); } void MultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const { renderQuad(p0, p1, p2, p3, color, color, color, color); } void MultisampleCase::renderLine (const Vec2& p0, const Vec2& p1, const Vec4& color) const { float vertexPositions[] = { p0.x(), p0.y(), 0.0f, 1.0f, p1.x(), p1.y(), 0.0f, 1.0f }; float vertexColors[] = { color.x(), color.y(), color.z(), color.w(), color.x(), color.y(), color.z(), color.w() }; GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrPositionLoc)); GLU_CHECK_CALL(glVertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, &vertexPositions[0])); GLU_CHECK_CALL(glEnableVertexAttribArray(m_attrColorLoc)); GLU_CHECK_CALL(glVertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, &vertexColors[0])); GLU_CHECK_CALL(glUseProgram(m_program->getProgram())); GLU_CHECK_CALL(glDrawArrays(GL_LINES, 0, 2)); } void MultisampleCase::randomizeViewport (void) { m_viewportX = m_rnd.getInt(0, m_renderWidth - m_viewportSize); m_viewportY = m_rnd.getInt(0, m_renderHeight - m_viewportSize); GLU_CHECK_CALL(glViewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize)); } void MultisampleCase::readImage (tcu::Surface& dst) const { if (m_fboParams.useFbo) { GLU_CHECK_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo)); GLU_CHECK_CALL(glBlitFramebuffer(0, 0, m_renderWidth, m_renderHeight, 0, 0, m_renderWidth, m_renderHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST)); GLU_CHECK_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo)); glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess()); GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo)); } else glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess()); } void MultisampleCase::init (void) { static const char* vertShaderSource = "#version 300 es\n" "in highp vec4 a_position;\n" "in mediump vec4 a_color;\n" "out mediump vec4 v_color;\n" "void main()\n" "{\n" " gl_Position = a_position;\n" " v_color = a_color;\n" "}\n"; static const char* fragShaderSource = "#version 300 es\n" "in mediump vec4 v_color;\n" "layout(location = 0) out mediump vec4 o_color;\n" "void main()\n" "{\n" " o_color = v_color;\n" "}\n"; TestLog& log = m_testCtx.getLog(); if (!m_fboParams.useFbo && m_context.getRenderTarget().getNumSamples() <= 1) throw tcu::NotSupportedError("No multisample buffers"); if (m_fboParams.useFbo) { if (m_fboParams.numSamples > 0) m_numSamples = m_fboParams.numSamples; else { log << TestLog::Message << "Querying maximum number of samples for " << glu::getTextureFormatName(FBO_COLOR_FORMAT) << " with glGetInternalformativ()" << TestLog::EndMessage; GLU_CHECK_CALL(glGetInternalformativ(GL_RENDERBUFFER, FBO_COLOR_FORMAT, GL_SAMPLES, 1, &m_numSamples)); } log << TestLog::Message << "Using FBO of size (" << m_renderWidth << ", " << m_renderHeight << ") with " << m_numSamples << " samples" << TestLog::EndMessage; } else { // Query and log number of samples per pixel. m_numSamples = getGLInteger(GL_SAMPLES); log << TestLog::Message << "GL_SAMPLES = " << m_numSamples << TestLog::EndMessage; } // Prepare program. DE_ASSERT(!m_program); m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource)); if (!m_program->isOk()) throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__); GLU_CHECK_CALL(m_attrPositionLoc = glGetAttribLocation(m_program->getProgram(), "a_position")); GLU_CHECK_CALL(m_attrColorLoc = glGetAttribLocation(m_program->getProgram(), "a_color")); if (m_attrPositionLoc < 0 || m_attrColorLoc < 0) { delete m_program; throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__); } if (m_fboParams.useFbo) { DE_STATIC_ASSERT(sizeof(deUint32) == sizeof(GLuint)); // Setup ms color RBO. GLU_CHECK_CALL(glGenRenderbuffers(1, &m_msColorRbo)); GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_msColorRbo)); // If glRenderbufferStorageMultisample() fails, check if it's because of a too high sample count. // \note We don't do the check until now because some implementations can't handle the GL_SAMPLES query with glGetInternalformativ(), // and we don't want that to be the cause of test case failure. try { GLU_CHECK_CALL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, FBO_COLOR_FORMAT, m_renderWidth, m_renderHeight)); } catch (const glu::Error&) { GLint maxSampleCount = -1; GLU_CHECK_CALL(glGetInternalformativ(GL_RENDERBUFFER, FBO_COLOR_FORMAT, GL_SAMPLES, 1, &maxSampleCount)); if (maxSampleCount < m_numSamples) throw tcu::NotSupportedError(std::string("") + "Maximum sample count returned by glGetInternalformativ() for " + glu::getTextureFormatName(FBO_COLOR_FORMAT) + " is only " + de::toString(maxSampleCount)); else throw; } if (m_fboParams.useDepth || m_fboParams.useStencil) { // Setup ms depth & stencil RBO. GLU_CHECK_CALL(glGenRenderbuffers(1, &m_msDepthStencilRbo)); GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_msDepthStencilRbo)); GLU_CHECK_CALL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, GL_DEPTH24_STENCIL8, m_renderWidth, m_renderHeight)); } // Setup ms FBO. GLU_CHECK_CALL(glGenFramebuffers(1, &m_msFbo)); GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo)); GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_msColorRbo)); GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_msDepthStencilRbo)); // Setup resolve color RBO. GLU_CHECK_CALL(glGenRenderbuffers(1, &m_resolveColorRbo)); GLU_CHECK_CALL(glBindRenderbuffer(GL_RENDERBUFFER, m_resolveColorRbo)); GLU_CHECK_CALL(glRenderbufferStorage(GL_RENDERBUFFER, FBO_COLOR_FORMAT, m_renderWidth, m_renderHeight)); // Setup resolve FBO. GLU_CHECK_CALL(glGenFramebuffers(1, &m_resolveFbo)); GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo)); GLU_CHECK_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_resolveColorRbo)); // Use ms FBO. GLU_CHECK_CALL(glBindFramebuffer(GL_FRAMEBUFFER, m_msFbo)); } // Get suitable viewport size. m_viewportSize = de::min(m_desiredViewportSize, de::min(m_renderWidth, m_renderHeight)); randomizeViewport(); } /*--------------------------------------------------------------------*//*! * \brief Base class for cases testing the value of sample count. * * Draws a test pattern (defined by renderPattern() of an inheriting class) * and counts the number of distinct colors in the resulting image. That * number should be at least the value of sample count plus one. This is * repeated with increased values of m_currentIteration until this correct * number of colors is detected or m_currentIteration reaches * m_maxNumIterations. *//*--------------------------------------------------------------------*/ class NumSamplesCase : public MultisampleCase { public: NumSamplesCase (Context& context, const char* name, const char* description, const FboParams& fboParams = FboParams()); ~NumSamplesCase (void) {} IterateResult iterate (void); protected: virtual void renderPattern (void) const = 0; int m_currentIteration; private: enum { DEFAULT_MAX_NUM_ITERATIONS = 16 }; const int m_maxNumIterations; vector m_detectedColors; }; NumSamplesCase::NumSamplesCase (Context& context, const char* name, const char* description, const FboParams& fboParams) : MultisampleCase (context, name, description, 256, fboParams) , m_currentIteration (0) , m_maxNumIterations (getIterationCount(m_testCtx, DEFAULT_MAX_NUM_ITERATIONS)) { } NumSamplesCase::IterateResult NumSamplesCase::iterate (void) { TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); randomizeViewport(); GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); renderPattern(); // Read and log rendered image. readImage(renderedImg); log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG); // Detect new, previously unseen colors from image. int requiredNumDistinctColors = m_numSamples + 1; for (int y = 0; y < renderedImg.getHeight() && (int)m_detectedColors.size() < requiredNumDistinctColors; y++) for (int x = 0; x < renderedImg.getWidth() && (int)m_detectedColors.size() < requiredNumDistinctColors; x++) { tcu::RGBA color = renderedImg.getPixel(x, y); int i; for (i = 0; i < (int)m_detectedColors.size(); i++) { if (tcu::compareThreshold(color, m_detectedColors[i], tcu::RGBA(3, 3, 3, 3))) break; } if (i == (int)m_detectedColors.size()) m_detectedColors.push_back(color); // Color not previously detected. } // Log results. log << TestLog::Message << "Number of distinct colors detected so far: " << ((int)m_detectedColors.size() >= requiredNumDistinctColors ? "at least " : "") << de::toString(m_detectedColors.size()) << TestLog::EndMessage; if ((int)m_detectedColors.size() < requiredNumDistinctColors) { // Haven't detected enough different colors yet. m_currentIteration++; if (m_currentIteration >= m_maxNumIterations) { const IVec2 targetSize = getRenderTargetSize(); const int detectedNumSamples = (int)m_detectedColors.size() - 1; // One color is the background log << TestLog::Message << "Failure: Number of distinct colors detected is lower than sample count+1" << TestLog::EndMessage; // For high resolution render targets the lack of samples is not likely detected by a human // and for GLES 3.0 the application cannot observe the sample count directly. So, it only // warrants a quality warning. if ((targetSize.x() >= 2048 || targetSize.y() >= 2048) && (detectedNumSamples >= (m_numSamples/2))) m_context.getTestContext().setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Measured sample count below the advertised count"); else m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } else { log << TestLog::Message << "The number of distinct colors detected is lower than sample count+1 - trying again with a slightly altered pattern" << TestLog::EndMessage; return CONTINUE; } } else { log << TestLog::Message << "Success: The number of distinct colors detected is at least sample count+1" << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed"); return STOP; } } class PolygonNumSamplesCase : public NumSamplesCase { public: PolygonNumSamplesCase (Context& context, const char* name, const char* description, int numFboSamples = 0); ~PolygonNumSamplesCase (void) {} protected: void renderPattern (void) const; }; PolygonNumSamplesCase::PolygonNumSamplesCase (Context& context, const char* name, const char* description, int numFboSamples) : NumSamplesCase(context, name, description, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams()) { } void PolygonNumSamplesCase::renderPattern (void) const { // The test pattern consists of several triangles with edges at different angles. const int numTriangles = 25; for (int i = 0; i < numTriangles; i++) { float angle0 = 2.0f*DE_PI * (float)i / (float)numTriangles + 0.001f*(float)m_currentIteration; float angle1 = 2.0f*DE_PI * ((float)i + 0.5f) / (float)numTriangles + 0.001f*(float)m_currentIteration; renderTriangle(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f), Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f), Vec4(1.0f)); } } class LineNumSamplesCase : public NumSamplesCase { public: LineNumSamplesCase (Context& context, const char* name, const char* description, int numFboSamples = 0); ~LineNumSamplesCase (void) {} protected: void renderPattern (void) const; }; LineNumSamplesCase::LineNumSamplesCase (Context& context, const char* name, const char* description, int numFboSamples) : NumSamplesCase (context, name, description, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams()) { } void LineNumSamplesCase::renderPattern (void) const { // The test pattern consists of several lines at different angles. // We scale the number of lines based on the viewport size. This is because a gl line's thickness is // constant in pixel units, i.e. they get relatively thicker as viewport size decreases. Thus we must // decrease the number of lines in order to decrease the extent of overlap among the lines in the // center of the pattern. const int numLines = (int)(100.0f * deFloatSqrt((float)m_viewportSize / 256.0f)); for (int i = 0; i < numLines; i++) { float angle = 2.0f*DE_PI * (float)i / (float)numLines + 0.001f*(float)m_currentIteration; renderLine(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle)*0.95f, deFloatSin(angle)*0.95f), Vec4(1.0f)); } } /*--------------------------------------------------------------------*//*! * \brief Case testing behaviour of common edges when multisampling. * * Draws a number of test patterns, each with a number of quads, each made * of two triangles, rotated at different angles. The inner edge inside the * quad (i.e. the common edge of the two triangles) still should not be * visible, despite multisampling - i.e. the two triangles forming the quad * should never get any common coverage bits in any pixel. *//*--------------------------------------------------------------------*/ class CommonEdgeCase : public MultisampleCase { public: enum CaseType { CASETYPE_SMALL_QUADS = 0, //!< Draw several small quads per iteration. CASETYPE_BIGGER_THAN_VIEWPORT_QUAD, //!< Draw one bigger-than-viewport quad per iteration. CASETYPE_FIT_VIEWPORT_QUAD, //!< Draw one exactly viewport-sized, axis aligned quad per iteration. CASETYPE_LAST }; CommonEdgeCase (Context& context, const char* name, const char* description, CaseType caseType, int numFboSamples = 0); ~CommonEdgeCase (void) {} void init (void); IterateResult iterate (void); private: enum { DEFAULT_SMALL_QUADS_ITERATIONS = 16, DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS = 8*8 // \note With CASETYPE_FIT_VIEWPORT_QUAD, we don't do rotations other than multiples of 90 deg -> constant number of iterations. }; const CaseType m_caseType; const int m_numIterations; int m_currentIteration; }; CommonEdgeCase::CommonEdgeCase (Context& context, const char* name, const char* description, CaseType caseType, int numFboSamples) : MultisampleCase (context, name, description, caseType == CASETYPE_SMALL_QUADS ? 128 : 32, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams()) , m_caseType (caseType) , m_numIterations (caseType == CASETYPE_SMALL_QUADS ? getIterationCount(m_testCtx, DEFAULT_SMALL_QUADS_ITERATIONS) : caseType == CASETYPE_BIGGER_THAN_VIEWPORT_QUAD ? getIterationCount(m_testCtx, DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS) : 8) , m_currentIteration (0) { } void CommonEdgeCase::init (void) { MultisampleCase::init(); if (m_caseType == CASETYPE_SMALL_QUADS) { // Check for a big enough viewport. With too small viewports the test case can't analyze the resulting image well enough. const int minViewportSize = 32; if (m_viewportSize < minViewportSize) throw tcu::InternalError("Render target width or height too low (is " + de::toString(m_viewportSize) + ", should be at least " + de::toString(minViewportSize) + ")"); } GLU_CHECK_CALL(glEnable(GL_BLEND)); GLU_CHECK_CALL(glBlendEquation(GL_FUNC_ADD)); GLU_CHECK_CALL(glBlendFunc(GL_ONE, GL_ONE)); m_testCtx.getLog() << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage; } CommonEdgeCase::IterateResult CommonEdgeCase::iterate (void) { TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); tcu::Surface errorImg (m_viewportSize, m_viewportSize); randomizeViewport(); GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); // Draw test pattern. Test patterns consist of quads formed with two triangles. // After drawing the pattern, we check that the interior pixels of each quad are // all the same color - this is meant to verify that there are no artifacts on the inner edge. vector unicoloredRegions; if (m_caseType == CASETYPE_SMALL_QUADS) { // Draw several quads, rotated at different angles. const float quadDiagLen = 2.0f / 3.0f * 0.9f; // \note Fit 3 quads in both x and y directions. float angleCos; float angleSin; // \note First and second iteration get exact 0 (and 90, 180, 270) and 45 (and 135, 225, 315) angle quads, as they are kind of a special case. if (m_currentIteration == 0) { angleCos = 1.0f; angleSin = 0.0f; } else if (m_currentIteration == 1) { angleCos = SQRT_HALF; angleSin = SQRT_HALF; } else { float angle = 0.5f * DE_PI * (float)(m_currentIteration-1) / (float)(m_numIterations-1); angleCos = deFloatCos(angle); angleSin = deFloatSin(angle); } Vec2 corners[4] = { 0.5f * quadDiagLen * Vec2( angleCos, angleSin), 0.5f * quadDiagLen * Vec2(-angleSin, angleCos), 0.5f * quadDiagLen * Vec2(-angleCos, -angleSin), 0.5f * quadDiagLen * Vec2( angleSin, -angleCos) }; unicoloredRegions.reserve(8); // Draw 8 quads. // First four are rotated at angles angle+0, angle+90, angle+180 and angle+270. // Last four are rotated the same angles as the first four, but the ordering of the last triangle's vertices is reversed. for (int quadNdx = 0; quadNdx < 8; quadNdx++) { Vec2 center = (2.0f-quadDiagLen) * Vec2((float)(quadNdx%3), (float)(quadNdx/3)) / 2.0f - 0.5f*(2.0f-quadDiagLen); renderTriangle(corners[(0+quadNdx) % 4] + center, corners[(1+quadNdx) % 4] + center, corners[(2+quadNdx) % 4] + center, Vec4(0.5f, 0.5f, 0.5f, 1.0f)); if (quadNdx >= 4) { renderTriangle(corners[(3+quadNdx) % 4] + center, corners[(2+quadNdx) % 4] + center, corners[(0+quadNdx) % 4] + center, Vec4(0.5f, 0.5f, 0.5f, 1.0f)); } else { renderTriangle(corners[(0+quadNdx) % 4] + center, corners[(2+quadNdx) % 4] + center, corners[(3+quadNdx) % 4] + center, Vec4(0.5f, 0.5f, 0.5f, 1.0f)); } // The size of the "interior" of a quad is assumed to be approximately unicolorRegionScale*. // By "interior" we here mean the region of non-boundary pixels of the rendered quad for which we can safely assume // that it has all coverage bits set to 1, for every pixel. float unicolorRegionScale = 1.0f - 6.0f*2.0f / (float)m_viewportSize / quadDiagLen; unicoloredRegions.push_back(QuadCorners((center + corners[0]*unicolorRegionScale), (center + corners[1]*unicolorRegionScale), (center + corners[2]*unicolorRegionScale), (center + corners[3]*unicolorRegionScale))); } } else if (m_caseType == CASETYPE_BIGGER_THAN_VIEWPORT_QUAD) { // Draw a bigger-than-viewport quad, rotated at an angle depending on m_currentIteration. int quadBaseAngleNdx = m_currentIteration / 8; int quadSubAngleNdx = m_currentIteration % 8; float angleCos; float angleSin; if (quadBaseAngleNdx == 0) { angleCos = 1.0f; angleSin = 0.0f; } else if (quadBaseAngleNdx == 1) { angleCos = SQRT_HALF; angleSin = SQRT_HALF; } else { float angle = 0.5f * DE_PI * (float)(m_currentIteration-1) / (float)(m_numIterations-1); angleCos = deFloatCos(angle); angleSin = deFloatSin(angle); } float quadDiagLen = 2.5f / de::max(angleCos, angleSin); Vec2 corners[4] = { 0.5f * quadDiagLen * Vec2( angleCos, angleSin), 0.5f * quadDiagLen * Vec2(-angleSin, angleCos), 0.5f * quadDiagLen * Vec2(-angleCos, -angleSin), 0.5f * quadDiagLen * Vec2( angleSin, -angleCos) }; renderTriangle(corners[(0+quadSubAngleNdx) % 4], corners[(1+quadSubAngleNdx) % 4], corners[(2+quadSubAngleNdx) % 4], Vec4(0.5f, 0.5f, 0.5f, 1.0f)); if (quadSubAngleNdx >= 4) { renderTriangle(corners[(3+quadSubAngleNdx) % 4], corners[(2+quadSubAngleNdx) % 4], corners[(0+quadSubAngleNdx) % 4], Vec4(0.5f, 0.5f, 0.5f, 1.0f)); } else { renderTriangle(corners[(0+quadSubAngleNdx) % 4], corners[(2+quadSubAngleNdx) % 4], corners[(3+quadSubAngleNdx) % 4], Vec4(0.5f, 0.5f, 0.5f, 1.0f)); } float unicolorRegionScale = 1.0f - 6.0f*2.0f / (float)m_viewportSize / quadDiagLen; unicoloredRegions.push_back(QuadCorners((corners[0]*unicolorRegionScale), (corners[1]*unicolorRegionScale), (corners[2]*unicolorRegionScale), (corners[3]*unicolorRegionScale))); } else if (m_caseType == CASETYPE_FIT_VIEWPORT_QUAD) { // Draw an exactly viewport-sized quad, rotated by multiples of 90 degrees angle depending on m_currentIteration. int quadSubAngleNdx = m_currentIteration % 8; Vec2 corners[4] = { Vec2( 1.0f, 1.0f), Vec2(-1.0f, 1.0f), Vec2(-1.0f, -1.0f), Vec2( 1.0f, -1.0f) }; renderTriangle(corners[(0+quadSubAngleNdx) % 4], corners[(1+quadSubAngleNdx) % 4], corners[(2+quadSubAngleNdx) % 4], Vec4(0.5f, 0.5f, 0.5f, 1.0f)); if (quadSubAngleNdx >= 4) { renderTriangle(corners[(3+quadSubAngleNdx) % 4], corners[(2+quadSubAngleNdx) % 4], corners[(0+quadSubAngleNdx) % 4], Vec4(0.5f, 0.5f, 0.5f, 1.0f)); } else { renderTriangle(corners[(0+quadSubAngleNdx) % 4], corners[(2+quadSubAngleNdx) % 4], corners[(3+quadSubAngleNdx) % 4], Vec4(0.5f, 0.5f, 0.5f, 1.0f)); } unicoloredRegions.push_back(QuadCorners(corners[0], corners[1], corners[2], corners[3])); } else DE_ASSERT(false); // Read pixels and check unicolored regions. readImage(renderedImg); tcu::clear(errorImg.getAccess(), Vec4(0.0f, 1.0f, 0.0f, 1.0f)); log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG); bool errorsDetected = false; for (int i = 0; i < (int)unicoloredRegions.size(); i++) { const QuadCorners& region = unicoloredRegions[i]; IVec2 p0Win = ((region.p0+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt(); IVec2 p1Win = ((region.p1+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt(); IVec2 p2Win = ((region.p2+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt(); IVec2 p3Win = ((region.p3+1.0f) * 0.5f * (float)(m_viewportSize-1) + 0.5f).asInt(); bool errorsInCurrentRegion = !isPixelRegionUnicolored(renderedImg, p0Win, p1Win, p2Win, p3Win); if (errorsInCurrentRegion) drawUnicolorTestErrors(renderedImg, errorImg.getAccess(), p0Win, p1Win, p2Win, p3Win); errorsDetected = errorsDetected || errorsInCurrentRegion; } m_currentIteration++; if (errorsDetected) { log << TestLog::Message << "Failure: Not all quad interiors seem unicolored - common-edge artifacts?" << TestLog::EndMessage; log << TestLog::Message << "Erroneous pixels are drawn red in the following image" << TestLog::EndMessage; log << TestLog::Image("RenderedImageWithErrors", "Rendered image with errors marked", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG); log << TestLog::Image("ErrorsOnly", "Image with error pixels only", errorImg, QP_IMAGE_COMPRESSION_MODE_PNG); m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } else if (m_currentIteration < m_numIterations) { log << TestLog::Message << "Quads seem OK - moving on to next pattern" << TestLog::EndMessage; return CONTINUE; } else { log << TestLog::Message << "Success: All quad interiors seem unicolored (no common-edge artifacts)" << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed"); return STOP; } } /*--------------------------------------------------------------------*//*! * \brief Test that depth values are per-sample. * * Draws intersecting, differently-colored polygons and checks that there * are at least sample count+1 distinct colors present, due to some of the * samples at the intersection line belonging to one and some to another * polygon. *//*--------------------------------------------------------------------*/ class SampleDepthCase : public NumSamplesCase { public: SampleDepthCase (Context& context, const char* name, const char* description, int numFboSamples = 0); ~SampleDepthCase (void) {} void init (void); protected: void renderPattern (void) const; }; SampleDepthCase::SampleDepthCase (Context& context, const char* name, const char* description, int numFboSamples) : NumSamplesCase (context, name, description, numFboSamples >= 0 ? FboParams(numFboSamples, true, false) : FboParams()) { } void SampleDepthCase::init (void) { TestLog& log = m_testCtx.getLog(); if (m_context.getRenderTarget().getDepthBits() == 0) TCU_THROW(NotSupportedError, "Test requires depth buffer"); MultisampleCase::init(); GLU_CHECK_CALL(glEnable(GL_DEPTH_TEST)); GLU_CHECK_CALL(glDepthFunc(GL_LESS)); log << TestLog::Message << "Depth test enabled, depth func is GL_LESS" << TestLog::EndMessage; log << TestLog::Message << "Drawing several bigger-than-viewport black or white polygons intersecting each other" << TestLog::EndMessage; } void SampleDepthCase::renderPattern (void) const { GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); GLU_CHECK_CALL(glClearDepthf(1.0f)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); { const int numPolygons = 50; for (int i = 0; i < numPolygons; i++) { Vec4 color = i % 2 == 0 ? Vec4(1.0f, 1.0f, 1.0f, 1.0f) : Vec4(0.0f, 0.0f, 0.0f, 1.0f); float angle = 2.0f * DE_PI * (float)i / (float)numPolygons + 0.001f*(float)m_currentIteration; Vec3 pt0 (3.0f*deFloatCos(angle + 2.0f*DE_PI*0.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*0.0f/3.0f), 1.0f); Vec3 pt1 (3.0f*deFloatCos(angle + 2.0f*DE_PI*1.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*1.0f/3.0f), 0.0f); Vec3 pt2 (3.0f*deFloatCos(angle + 2.0f*DE_PI*2.0f/3.0f), 3.0f*deFloatSin(angle + 2.0f*DE_PI*2.0f/3.0f), 0.0f); renderTriangle(pt0, pt1, pt2, color); } } } /*--------------------------------------------------------------------*//*! * \brief Test that stencil buffer values are per-sample. * * Draws a unicolored pattern and marks drawn samples in stencil buffer; * then clears and draws a viewport-size quad with that color and with * proper stencil test such that the resulting image should be exactly the * same as after the pattern was first drawn. *//*--------------------------------------------------------------------*/ class SampleStencilCase : public MultisampleCase { public: SampleStencilCase (Context& context, const char* name, const char* description, int numFboSamples = 0); ~SampleStencilCase (void) {} void init (void); IterateResult iterate (void); }; SampleStencilCase::SampleStencilCase (Context& context, const char* name, const char* description, int numFboSamples) : MultisampleCase (context, name, description, 256, numFboSamples >= 0 ? FboParams(numFboSamples, false, true) : FboParams()) { } void SampleStencilCase::init (void) { if (m_context.getRenderTarget().getStencilBits() == 0) TCU_THROW(NotSupportedError, "Test requires stencil buffer"); MultisampleCase::init(); } SampleStencilCase::IterateResult SampleStencilCase::iterate (void) { TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImgFirst (m_viewportSize, m_viewportSize); tcu::Surface renderedImgSecond (m_viewportSize, m_viewportSize); randomizeViewport(); GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f)); GLU_CHECK_CALL(glClearStencil(0)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)); GLU_CHECK_CALL(glEnable(GL_STENCIL_TEST)); GLU_CHECK_CALL(glStencilFunc(GL_ALWAYS, 1, 1)); GLU_CHECK_CALL(glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)); log << TestLog::Message << "Drawing a pattern with glStencilFunc(GL_ALWAYS, 1, 1) and glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)" << TestLog::EndMessage; { const int numTriangles = 25; for (int i = 0; i < numTriangles; i++) { float angle0 = 2.0f*DE_PI * (float)i / (float)numTriangles; float angle1 = 2.0f*DE_PI * ((float)i + 0.5f) / (float)numTriangles; renderTriangle(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f), Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f), Vec4(1.0f)); } } readImage(renderedImgFirst); log << TestLog::Image("RenderedImgFirst", "First image rendered", renderedImgFirst, QP_IMAGE_COMPRESSION_MODE_PNG); log << TestLog::Message << "Clearing color buffer to black" << TestLog::EndMessage; GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); GLU_CHECK_CALL(glStencilFunc(GL_EQUAL, 1, 1)); GLU_CHECK_CALL(glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)); { log << TestLog::Message << "Checking that color buffer was actually cleared to black" << TestLog::EndMessage; tcu::Surface clearedImg(m_viewportSize, m_viewportSize); readImage(clearedImg); for (int y = 0; y < clearedImg.getHeight(); y++) for (int x = 0; x < clearedImg.getWidth(); x++) { const tcu::RGBA& clr = clearedImg.getPixel(x, y); if (clr != tcu::RGBA::black()) { log << TestLog::Message << "Failure: first non-black pixel, color " << clr << ", detected at coordinates (" << x << ", " << y << ")" << TestLog::EndMessage; log << TestLog::Image("ClearedImg", "Image after clearing, erroneously non-black", clearedImg); m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } } } log << TestLog::Message << "Drawing a viewport-sized quad with glStencilFunc(GL_EQUAL, 1, 1) and glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) - should result in same image as the first" << TestLog::EndMessage; renderQuad(Vec2(-1.0f, -1.0f), Vec2( 1.0f, -1.0f), Vec2(-1.0f, 1.0f), Vec2( 1.0f, 1.0f), Vec4(1.0f)); readImage(renderedImgSecond); log << TestLog::Image("RenderedImgSecond", "Second image rendered", renderedImgSecond, QP_IMAGE_COMPRESSION_MODE_PNG); bool passed = tcu::pixelThresholdCompare(log, "ImageCompare", "Image comparison", renderedImgFirst, renderedImgSecond, tcu::RGBA(0), tcu::COMPARE_LOG_ON_ERROR); if (passed) log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage; m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, passed ? "Passed" : "Failed"); return STOP; } /*--------------------------------------------------------------------*//*! * \brief Tests coverage mask generation proportionality property. * * Tests that the number of coverage bits in a coverage mask created by * GL_SAMPLE_ALPHA_TO_COVERAGE or GL_SAMPLE_COVERAGE is, on average, * proportional to the alpha or coverage value, respectively. Draws * multiple frames, each time increasing the alpha or coverage value used, * and checks that the average color is changing appropriately. *//*--------------------------------------------------------------------*/ class MaskProportionalityCase : public MultisampleCase { public: enum CaseType { CASETYPE_ALPHA_TO_COVERAGE = 0, CASETYPE_SAMPLE_COVERAGE, CASETYPE_SAMPLE_COVERAGE_INVERTED, CASETYPE_LAST }; MaskProportionalityCase (Context& context, const char* name, const char* description, CaseType type, int numFboSamples = 0); ~MaskProportionalityCase (void) {} void init (void); IterateResult iterate (void); private: const CaseType m_type; int m_numIterations; int m_currentIteration; deInt32 m_previousIterationColorSum; }; MaskProportionalityCase::MaskProportionalityCase (Context& context, const char* name, const char* description, CaseType type, int numFboSamples) : MultisampleCase (context, name, description, 32, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams()) , m_type (type) , m_currentIteration (0) , m_previousIterationColorSum (-1) { } void MaskProportionalityCase::init (void) { TestLog& log = m_testCtx.getLog(); MultisampleCase::init(); if (m_type == CASETYPE_ALPHA_TO_COVERAGE) { GLU_CHECK_CALL(glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE)); log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage; } else { DE_ASSERT(m_type == CASETYPE_SAMPLE_COVERAGE || m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED); GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE)); log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage; } m_numIterations = de::max(2, getIterationCount(m_testCtx, m_numSamples * 5)); randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate. } MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate (void) { TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); deInt32 numPixels = (deInt32)renderedImg.getWidth()*(deInt32)renderedImg.getHeight(); log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage; GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); if (m_type == CASETYPE_ALPHA_TO_COVERAGE) { GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE)); log << TestLog::Message << "Using color mask TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage; } // Draw quad. { const Vec2 pt0 (-1.0f, -1.0f); const Vec2 pt1 ( 1.0f, -1.0f); const Vec2 pt2 (-1.0f, 1.0f); const Vec2 pt3 ( 1.0f, 1.0f); Vec4 quadColor (1.0f, 0.0f, 0.0f, 1.0f); float alphaOrCoverageValue = (float)m_currentIteration / (float)(m_numIterations-1); if (m_type == CASETYPE_ALPHA_TO_COVERAGE) { log << TestLog::Message << "Drawing a red quad using alpha value " + de::floatToString(alphaOrCoverageValue, 2) << TestLog::EndMessage; quadColor.w() = alphaOrCoverageValue; } else { DE_ASSERT(m_type == CASETYPE_SAMPLE_COVERAGE || m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED); bool isInverted = m_type == CASETYPE_SAMPLE_COVERAGE_INVERTED; float coverageValue = isInverted ? 1.0f - alphaOrCoverageValue : alphaOrCoverageValue; log << TestLog::Message << "Drawing a red quad using sample coverage value " + de::floatToString(coverageValue, 2) << (isInverted ? " (inverted)" : "") << TestLog::EndMessage; GLU_CHECK_CALL(glSampleCoverage(coverageValue, isInverted ? GL_TRUE : GL_FALSE)); } renderQuad(pt0, pt1, pt2, pt3, quadColor); } // Read ang log image. readImage(renderedImg); log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG); // Compute average red component in rendered image. deInt32 sumRed = 0; for (int y = 0; y < renderedImg.getHeight(); y++) for (int x = 0; x < renderedImg.getWidth(); x++) sumRed += renderedImg.getPixel(x, y).getRed(); log << TestLog::Message << "Average red color component: " << de::floatToString((float)sumRed / 255.0f / (float)numPixels, 2) << TestLog::EndMessage; // Check if average color has decreased from previous frame's color. if (sumRed < m_previousIterationColorSum) { log << TestLog::Message << "Failure: Current average red color component is lower than previous" << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } // Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted). if (m_currentIteration == 0 && sumRed != 0) { log << TestLog::Message << "Failure: Image should be completely black" << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } if (m_currentIteration == m_numIterations-1 && sumRed != 0xff*numPixels) { log << TestLog::Message << "Failure: Image should be completely red" << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } m_previousIterationColorSum = sumRed; m_currentIteration++; if (m_currentIteration >= m_numIterations) { log << TestLog::Message << "Success: Number of coverage mask bits set appears to be, on average, proportional to " << (m_type == CASETYPE_ALPHA_TO_COVERAGE ? "alpha" : m_type == CASETYPE_SAMPLE_COVERAGE ? "sample coverage value" : "inverted sample coverage value") << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed"); return STOP; } else return CONTINUE; } /*--------------------------------------------------------------------*//*! * \brief Tests coverage mask generation constancy property. * * Tests that the coverage mask created by GL_SAMPLE_ALPHA_TO_COVERAGE or * GL_SAMPLE_COVERAGE is constant at given pixel coordinates, with a given * alpha component or coverage value, respectively. Draws two quads, with * the second one fully overlapping the first one such that at any given * pixel, both quads have the same alpha or coverage value. This way, if * the constancy property is fulfilled, only the second quad should be * visible. *//*--------------------------------------------------------------------*/ class MaskConstancyCase : public MultisampleCase { public: enum CaseType { CASETYPE_ALPHA_TO_COVERAGE = 0, //!< Use only alpha-to-coverage. CASETYPE_SAMPLE_COVERAGE, //!< Use only sample coverage. CASETYPE_SAMPLE_COVERAGE_INVERTED, //!< Use only inverted sample coverage. CASETYPE_BOTH, //!< Use both alpha-to-coverage and sample coverage. CASETYPE_BOTH_INVERTED, //!< Use both alpha-to-coverage and inverted sample coverage. CASETYPE_LAST }; MaskConstancyCase (Context& context, const char* name, const char* description, CaseType type, int numFboSamples = 0); ~MaskConstancyCase (void) {} IterateResult iterate (void); private: const bool m_isAlphaToCoverageCase; const bool m_isSampleCoverageCase; const bool m_isInvertedSampleCoverageCase; }; MaskConstancyCase::MaskConstancyCase (Context& context, const char* name, const char* description, CaseType type, int numFboSamples) : MultisampleCase (context, name, description, 256, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams()) , m_isAlphaToCoverageCase (type == CASETYPE_ALPHA_TO_COVERAGE || type == CASETYPE_BOTH || type == CASETYPE_BOTH_INVERTED) , m_isSampleCoverageCase (type == CASETYPE_SAMPLE_COVERAGE || type == CASETYPE_SAMPLE_COVERAGE_INVERTED || type == CASETYPE_BOTH || type == CASETYPE_BOTH_INVERTED) , m_isInvertedSampleCoverageCase (type == CASETYPE_SAMPLE_COVERAGE_INVERTED || type == CASETYPE_BOTH_INVERTED) { } MaskConstancyCase::IterateResult MaskConstancyCase::iterate (void) { TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); randomizeViewport(); log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage; GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 1.0f)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); if (m_isAlphaToCoverageCase) { GLU_CHECK_CALL(glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE)); GLU_CHECK_CALL(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE)); log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage; log << TestLog::Message << "Color mask is TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage; } if (m_isSampleCoverageCase) { GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE)); log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage; } log << TestLog::Message << "Drawing several green quads, each fully overlapped by a red quad with the same " << (m_isAlphaToCoverageCase ? "alpha" : "") << (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "") << (m_isInvertedSampleCoverageCase ? "inverted " : "") << (m_isSampleCoverageCase ? "sample coverage" : "") << " values" << TestLog::EndMessage; const int numQuadRowsCols = m_numSamples*4; for (int row = 0; row < numQuadRowsCols; row++) { for (int col = 0; col < numQuadRowsCols; col++) { float x0 = (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f; float x1 = (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f; float y0 = (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f; float y1 = (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f; const Vec4 baseGreen (0.0f, 1.0f, 0.0f, 0.0f); const Vec4 baseRed (1.0f, 0.0f, 0.0f, 0.0f); Vec4 alpha0 (0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)col / (float)(numQuadRowsCols-1) : 1.0f); Vec4 alpha1 (0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)row / (float)(numQuadRowsCols-1) : 1.0f); if (m_isSampleCoverageCase) { float value = (float)(row*numQuadRowsCols + col) / (float)(numQuadRowsCols*numQuadRowsCols-1); GLU_CHECK_CALL(glSampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value, m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE)); } renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen + alpha0, baseGreen + alpha1, baseGreen + alpha0, baseGreen + alpha1); renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed + alpha0, baseRed + alpha1, baseRed + alpha0, baseRed + alpha1); } } readImage(renderedImg); log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG); for (int y = 0; y < renderedImg.getHeight(); y++) for (int x = 0; x < renderedImg.getWidth(); x++) { if (renderedImg.getPixel(x, y).getGreen() > 0) { log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad" << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed"); return STOP; } } log << TestLog::Message << "Success: Coverage mask appears to be constant at a given pixel coordinate with a given " << (m_isAlphaToCoverageCase ? "alpha" : "") << (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "") << (m_isSampleCoverageCase ? "coverage value" : "") << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed"); return STOP; } /*--------------------------------------------------------------------*//*! * \brief Tests coverage mask inversion validity. * * Tests that the coverage masks obtained by glSampleCoverage(..., GL_TRUE) * and glSampleCoverage(..., GL_FALSE) are indeed each others' inverses. * This is done by drawing a pattern, with varying coverage values, * overlapped by a pattern that has inverted masks and is otherwise * identical. The resulting image is compared to one obtained by drawing * the same pattern but with all-ones coverage masks. *//*--------------------------------------------------------------------*/ class CoverageMaskInvertCase : public MultisampleCase { public: CoverageMaskInvertCase (Context& context, const char* name, const char* description, int numFboSamples = 0); ~CoverageMaskInvertCase (void) {} IterateResult iterate (void); private: void drawPattern (bool invertSampleCoverage) const; }; CoverageMaskInvertCase::CoverageMaskInvertCase (Context& context, const char* name, const char* description, int numFboSamples) : MultisampleCase (context, name, description, 256, numFboSamples >= 0 ? FboParams(numFboSamples, false, false) : FboParams()) { } void CoverageMaskInvertCase::drawPattern (bool invertSampleCoverage) const { const int numTriangles = 25; for (int i = 0; i < numTriangles; i++) { GLU_CHECK_CALL(glSampleCoverage((float)i / (float)(numTriangles-1), invertSampleCoverage ? GL_TRUE : GL_FALSE)); float angle0 = 2.0f*DE_PI * (float)i / (float)numTriangles; float angle1 = 2.0f*DE_PI * ((float)i + 0.5f) / (float)numTriangles; renderTriangle(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f), Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f), Vec4(0.4f + (float)i/(float)numTriangles*0.6f, 0.5f + (float)i/(float)numTriangles*0.3f, 0.6f - (float)i/(float)numTriangles*0.5f, 0.7f - (float)i/(float)numTriangles*0.7f)); } } CoverageMaskInvertCase::IterateResult CoverageMaskInvertCase::iterate (void) { TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImgNoSampleCoverage (m_viewportSize, m_viewportSize); tcu::Surface renderedImgSampleCoverage (m_viewportSize, m_viewportSize); randomizeViewport(); GLU_CHECK_CALL(glEnable(GL_BLEND)); GLU_CHECK_CALL(glBlendEquation(GL_FUNC_ADD)); GLU_CHECK_CALL(glBlendFunc(GL_ONE, GL_ONE)); log << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage; log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage; GLU_CHECK_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE disabled" << TestLog::EndMessage; drawPattern(false); readImage(renderedImgNoSampleCoverage); log << TestLog::Image("RenderedImageNoSampleCoverage", "Rendered image with GL_SAMPLE_COVERAGE disabled", renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG); log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage; GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT)); GLU_CHECK_CALL(glEnable(GL_SAMPLE_COVERAGE)); log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE enabled, using non-inverted masks" << TestLog::EndMessage; drawPattern(false); log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_COVERAGE enabled, using same sample coverage values but inverted masks" << TestLog::EndMessage; drawPattern(true); readImage(renderedImgSampleCoverage); log << TestLog::Image("RenderedImageSampleCoverage", "Rendered image with GL_SAMPLE_COVERAGE enabled", renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG); bool passed = tcu::pixelThresholdCompare(log, "CoverageVsNoCoverage", "Comparison of same pattern with GL_SAMPLE_COVERAGE disabled and enabled", renderedImgNoSampleCoverage, renderedImgSampleCoverage, tcu::RGBA(0), tcu::COMPARE_LOG_ON_ERROR); if (passed) log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage; m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, passed ? "Passed" : "Failed"); return STOP; } MultisampleTests::MultisampleTests (Context& context) : TestCaseGroup(context, "multisample", "Multisampling tests") { } MultisampleTests::~MultisampleTests (void) { } void MultisampleTests::init (void) { enum CaseType { CASETYPE_DEFAULT_FRAMEBUFFER = 0, CASETYPE_FBO_4_SAMPLES, CASETYPE_FBO_8_SAMPLES, CASETYPE_FBO_MAX_SAMPLES, CASETYPE_LAST }; for (int caseTypeI = 0; caseTypeI < (int)CASETYPE_LAST; caseTypeI++) { CaseType caseType = (CaseType)caseTypeI; int numFboSamples = caseType == CASETYPE_DEFAULT_FRAMEBUFFER ? -1 : caseType == CASETYPE_FBO_4_SAMPLES ? 4 : caseType == CASETYPE_FBO_8_SAMPLES ? 8 : caseType == CASETYPE_FBO_MAX_SAMPLES ? 0 : -2; TestCaseGroup* group = new TestCaseGroup(m_context, caseType == CASETYPE_DEFAULT_FRAMEBUFFER ? "default_framebuffer" : caseType == CASETYPE_FBO_4_SAMPLES ? "fbo_4_samples" : caseType == CASETYPE_FBO_8_SAMPLES ? "fbo_8_samples" : caseType == CASETYPE_FBO_MAX_SAMPLES ? "fbo_max_samples" : DE_NULL, caseType == CASETYPE_DEFAULT_FRAMEBUFFER ? "Render into default framebuffer" : caseType == CASETYPE_FBO_4_SAMPLES ? "Render into a framebuffer object with 4 samples" : caseType == CASETYPE_FBO_8_SAMPLES ? "Render into a framebuffer object with 8 samples" : caseType == CASETYPE_FBO_MAX_SAMPLES ? "Render into a framebuffer object with the maximum number of samples" : DE_NULL); DE_ASSERT(group->getName() != DE_NULL); DE_ASSERT(group->getDescription() != DE_NULL); DE_ASSERT(numFboSamples >= -1); addChild(group); group->addChild(new PolygonNumSamplesCase (m_context, "num_samples_polygon", "Test sanity of the sample count, with polygons", numFboSamples)); group->addChild(new LineNumSamplesCase (m_context, "num_samples_line", "Test sanity of the sample count, with lines", numFboSamples)); group->addChild(new CommonEdgeCase (m_context, "common_edge_small_quads", "Test polygons' common edges with small quads", CommonEdgeCase::CASETYPE_SMALL_QUADS, numFboSamples)); group->addChild(new CommonEdgeCase (m_context, "common_edge_big_quad", "Test polygons' common edges with bigger-than-viewport quads", CommonEdgeCase::CASETYPE_BIGGER_THAN_VIEWPORT_QUAD, numFboSamples)); group->addChild(new CommonEdgeCase (m_context, "common_edge_viewport_quad", "Test polygons' common edges with exactly viewport-sized quads", CommonEdgeCase::CASETYPE_FIT_VIEWPORT_QUAD, numFboSamples)); group->addChild(new SampleDepthCase (m_context, "depth", "Test that depth values are per-sample", numFboSamples)); group->addChild(new SampleStencilCase (m_context, "stencil", "Test that stencil values are per-sample", numFboSamples)); group->addChild(new CoverageMaskInvertCase (m_context, "sample_coverage_invert", "Test that non-inverted and inverted sample coverage masks are each other's negations", numFboSamples)); group->addChild(new MaskProportionalityCase (m_context, "proportionality_alpha_to_coverage", "Test the proportionality property of GL_SAMPLE_ALPHA_TO_COVERAGE", MaskProportionalityCase::CASETYPE_ALPHA_TO_COVERAGE, numFboSamples)); group->addChild(new MaskProportionalityCase (m_context, "proportionality_sample_coverage", "Test the proportionality property of GL_SAMPLE_COVERAGE", MaskProportionalityCase::CASETYPE_SAMPLE_COVERAGE, numFboSamples)); group->addChild(new MaskProportionalityCase (m_context, "proportionality_sample_coverage_inverted", "Test the proportionality property of inverted-mask GL_SAMPLE_COVERAGE", MaskProportionalityCase::CASETYPE_SAMPLE_COVERAGE_INVERTED, numFboSamples)); group->addChild(new MaskConstancyCase (m_context, "constancy_alpha_to_coverage", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE", MaskConstancyCase::CASETYPE_ALPHA_TO_COVERAGE, numFboSamples)); group->addChild(new MaskConstancyCase (m_context, "constancy_sample_coverage", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE", MaskConstancyCase::CASETYPE_SAMPLE_COVERAGE, numFboSamples)); group->addChild(new MaskConstancyCase (m_context, "constancy_sample_coverage_inverted", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using inverted-mask GL_SAMPLE_COVERAGE", MaskConstancyCase::CASETYPE_SAMPLE_COVERAGE_INVERTED, numFboSamples)); group->addChild(new MaskConstancyCase (m_context, "constancy_both", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_COVERAGE", MaskConstancyCase::CASETYPE_BOTH, numFboSamples)); group->addChild(new MaskConstancyCase (m_context, "constancy_both_inverted", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and inverted-mask GL_SAMPLE_COVERAGE", MaskConstancyCase::CASETYPE_BOTH_INVERTED, numFboSamples)); } } } // Functional } // gles3 } // deqp