/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES 3.1 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 Multisample tests *//*--------------------------------------------------------------------*/ #include "es31fMultisampleTests.hpp" #include "tcuRenderTarget.hpp" #include "tcuVector.hpp" #include "tcuSurface.hpp" #include "tcuImageCompare.hpp" #include "tcuStringTemplate.hpp" #include "gluPixelTransfer.hpp" #include "gluRenderContext.hpp" #include "gluCallLogWrapper.hpp" #include "gluObjectWrapper.hpp" #include "gluShaderProgram.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "deRandom.hpp" #include "deStringUtil.hpp" #include "deString.h" #include "deMath.h" using namespace glw; using tcu::TestLog; using tcu::Vec2; using tcu::Vec3; using tcu::Vec4; namespace deqp { namespace gles31 { namespace Functional { namespace { using std::map; using std::string; static std::string sampleMaskToString (const std::vector<deUint32>& bitfield, int numBits) { std::string result(numBits, '0'); // move from back to front and set chars to 1 for (int wordNdx = 0; wordNdx < (int)bitfield.size(); ++wordNdx) { for (int bit = 0; bit < 32; ++bit) { const int targetCharNdx = numBits - (wordNdx*32+bit) - 1; // beginning of the string reached if (targetCharNdx < 0) return result; if ((bitfield[wordNdx] >> bit) & 0x01) result[targetCharNdx] = '1'; } } return result; } /*--------------------------------------------------------------------*//*! * \brief Returns the number of words needed to represent mask of given length *//*--------------------------------------------------------------------*/ static int getEffectiveSampleMaskWordCount (int highestBitNdx) { const int wordSize = 32; const int maskLen = highestBitNdx + 1; return ((maskLen - 1) / wordSize) + 1; // round_up(mask_len / wordSize) } /*--------------------------------------------------------------------*//*! * \brief Creates sample mask with all less significant bits than nthBit set *//*--------------------------------------------------------------------*/ static std::vector<deUint32> genAllSetToNthBitSampleMask (int nthBit) { const int wordSize = 32; const int numWords = getEffectiveSampleMaskWordCount(nthBit - 1); const deUint32 topWordBits = (deUint32)(nthBit - (numWords - 1) * wordSize); std::vector<deUint32> mask (numWords); for (int ndx = 0; ndx < numWords - 1; ++ndx) mask[ndx] = 0xFFFFFFFF; mask[numWords - 1] = deBitMask32(0, (int)topWordBits); return mask; } class SamplePosQueryCase : public TestCase { public: SamplePosQueryCase (Context& context, const char* name, const char* desc); private: void init (void); IterateResult iterate (void); }; SamplePosQueryCase::SamplePosQueryCase (Context& context, const char* name, const char* desc) : TestCase(context, name, desc) { } void SamplePosQueryCase::init (void) { if (m_context.getRenderTarget().getNumSamples() == 0) throw tcu::NotSupportedError("No multisample buffers"); } SamplePosQueryCase::IterateResult SamplePosQueryCase::iterate (void) { glu::CallLogWrapper gl (m_context.getRenderContext().getFunctions(), m_testCtx.getLog()); bool error = false; gl.enableLogging(true); for (int ndx = 0; ndx < m_context.getRenderTarget().getNumSamples(); ++ndx) { tcu::Vec2 samplePos = tcu::Vec2(-1, -1); gl.glGetMultisamplefv(GL_SAMPLE_POSITION, ndx, samplePos.getPtr()); GLU_EXPECT_NO_ERROR(gl.glGetError(), "getMultisamplefv"); // check value range if (samplePos.x() < 0.0f || samplePos.x() > 1.0f || samplePos.y() < 0.0f || samplePos.y() > 1.0f) { m_testCtx.getLog() << tcu::TestLog::Message << "Sample " << ndx << " is not in valid range [0,1], got " << samplePos << tcu::TestLog::EndMessage; error = true; } } if (!error) m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); else m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid sample pos"); return STOP; } /*--------------------------------------------------------------------*//*! * \brief Abstract base class handling common stuff for default fbo multisample cases. *//*--------------------------------------------------------------------*/ class DefaultFBOMultisampleCase : public TestCase { public: DefaultFBOMultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize); virtual ~DefaultFBOMultisampleCase (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 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 randomizeViewport (void); void readImage (tcu::Surface& dst) const; int m_numSamples; int m_viewportSize; private: DefaultFBOMultisampleCase (const DefaultFBOMultisampleCase& other); DefaultFBOMultisampleCase& operator= (const DefaultFBOMultisampleCase& other); const int m_desiredViewportSize; glu::ShaderProgram* m_program; int m_attrPositionLoc; int m_attrColorLoc; int m_viewportX; int m_viewportY; de::Random m_rnd; bool m_initCalled; }; DefaultFBOMultisampleCase::DefaultFBOMultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize) : TestCase (context, name, desc) , m_numSamples (0) , m_viewportSize (0) , m_desiredViewportSize (desiredViewportSize) , m_program (DE_NULL) , m_attrPositionLoc (-1) , m_attrColorLoc (-1) , m_viewportX (0) , m_viewportY (0) , m_rnd (deStringHash(name)) , m_initCalled (false) { } DefaultFBOMultisampleCase::~DefaultFBOMultisampleCase (void) { DefaultFBOMultisampleCase::deinit(); } void DefaultFBOMultisampleCase::init (void) { const bool isES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)); map<string, string> args; args["GLSL_VERSION_DECL"] = isES32 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) : getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES); static const char* vertShaderSource = "${GLSL_VERSION_DECL}\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 = "${GLSL_VERSION_DECL}\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(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (m_context.getRenderTarget().getNumSamples() <= 1) throw tcu::NotSupportedError("No multisample buffers"); m_initCalled = true; // Query and log number of samples per pixel. gl.getIntegerv(GL_SAMPLES, &m_numSamples); GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(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::ProgramSources() << glu::VertexSource(tcu::StringTemplate(vertShaderSource).specialize(args)) << glu::FragmentSource(tcu::StringTemplate(fragShaderSource).specialize(args))); if (!m_program->isOk()) throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__); m_attrPositionLoc = gl.getAttribLocation(m_program->getProgram(), "a_position"); m_attrColorLoc = gl.getAttribLocation(m_program->getProgram(), "a_color"); GLU_EXPECT_NO_ERROR(gl.getError(), "getAttribLocation"); if (m_attrPositionLoc < 0 || m_attrColorLoc < 0) { delete m_program; throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__); } // Get suitable viewport size. m_viewportSize = de::min<int>(m_desiredViewportSize, de::min(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())); randomizeViewport(); } void DefaultFBOMultisampleCase::deinit (void) { // Do not try to call GL functions during case list creation if (!m_initCalled) return; delete m_program; m_program = DE_NULL; } void DefaultFBOMultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const { 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 }; const 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(), }; const glw::Functions& gl = m_context.getRenderContext().getFunctions(); glu::Buffer vtxBuf (m_context.getRenderContext()); glu::Buffer colBuf (m_context.getRenderContext()); glu::VertexArray vao (m_context.getRenderContext()); gl.bindVertexArray(*vao); GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray"); gl.bindBuffer(GL_ARRAY_BUFFER, *vtxBuf); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), &vertexPositions[0], GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "vtx buf"); gl.enableVertexAttribArray(m_attrPositionLoc); gl.vertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, DE_NULL); GLU_EXPECT_NO_ERROR(gl.getError(), "vtx vertexAttribPointer"); gl.bindBuffer(GL_ARRAY_BUFFER, *colBuf); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexColors), &vertexColors[0], GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "col buf"); gl.enableVertexAttribArray(m_attrColorLoc); gl.vertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, DE_NULL); GLU_EXPECT_NO_ERROR(gl.getError(), "col vertexAttribPointer"); gl.useProgram(m_program->getProgram()); gl.drawArrays(GL_TRIANGLES, 0, 3); GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays"); } void DefaultFBOMultisampleCase::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 DefaultFBOMultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const { renderTriangle(p0, p1, p2, color, color, color); } void DefaultFBOMultisampleCase::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 DefaultFBOMultisampleCase::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 DefaultFBOMultisampleCase::randomizeViewport (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); m_viewportX = m_rnd.getInt(0, m_context.getRenderTarget().getWidth() - m_viewportSize); m_viewportY = m_rnd.getInt(0, m_context.getRenderTarget().getHeight() - m_viewportSize); gl.viewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize); GLU_EXPECT_NO_ERROR(gl.getError(), "viewport"); } void DefaultFBOMultisampleCase::readImage (tcu::Surface& dst) const { glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess()); } /*--------------------------------------------------------------------*//*! * \brief Tests coverage mask inversion validity. * * Tests that the coverage masks obtained by masks set with glSampleMaski(mask) * and glSampleMaski(~mask) 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 MaskInvertCase : public DefaultFBOMultisampleCase { public: MaskInvertCase (Context& context, const char* name, const char* description); ~MaskInvertCase (void) {} void init (void); IterateResult iterate (void); private: void drawPattern (bool invert) const; }; MaskInvertCase::MaskInvertCase (Context& context, const char* name, const char* description) : DefaultFBOMultisampleCase (context, name, description, 256) { } void MaskInvertCase::init (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); // check the test is even possible GLint maxSampleMaskWords = 0; gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords); if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords) throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS"); // normal init DefaultFBOMultisampleCase::init(); } MaskInvertCase::IterateResult MaskInvertCase::iterate (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImgNoSampleCoverage (m_viewportSize, m_viewportSize); tcu::Surface renderedImgSampleCoverage (m_viewportSize, m_viewportSize); randomizeViewport(); gl.enable(GL_BLEND); gl.blendEquation(GL_FUNC_ADD); gl.blendFunc(GL_ONE, GL_ONE); GLU_EXPECT_NO_ERROR(gl.getError(), "set blend"); 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; gl.clearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK disabled" << TestLog::EndMessage; drawPattern(false); readImage(renderedImgNoSampleCoverage); log << TestLog::Image("RenderedImageNoSampleMask", "Rendered image with GL_SAMPLE_MASK disabled", renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG); log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage; gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); gl.enable(GL_SAMPLE_MASK); GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)"); log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using non-inverted sample masks" << TestLog::EndMessage; drawPattern(false); log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using inverted sample masks" << TestLog::EndMessage; drawPattern(true); readImage(renderedImgSampleCoverage); log << TestLog::Image("RenderedImageSampleMask", "Rendered image with GL_SAMPLE_MASK enabled", renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG); bool passed = tcu::pixelThresholdCompare(log, "CoverageVsNoCoverage", "Comparison of same pattern with GL_SAMPLE_MASK 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; } void MaskInvertCase::drawPattern (bool invert) const { const int numTriangles = 25; const glw::Functions& gl = m_context.getRenderContext().getFunctions(); for (int triNdx = 0; triNdx < numTriangles; triNdx++) { const float angle0 = 2.0f*DE_PI * (float)triNdx / (float)numTriangles; const float angle1 = 2.0f*DE_PI * ((float)triNdx + 0.5f) / (float)numTriangles; const Vec4 color = Vec4(0.4f + (float)triNdx/(float)numTriangles*0.6f, 0.5f + (float)triNdx/(float)numTriangles*0.3f, 0.6f - (float)triNdx/(float)numTriangles*0.5f, 0.7f - (float)triNdx/(float)numTriangles*0.7f); const int wordCount = getEffectiveSampleMaskWordCount(m_numSamples - 1); const GLbitfield finalWordBits = m_numSamples - 32 * ((m_numSamples-1) / 32); const GLbitfield finalWordMask = (GLbitfield)deBitMask32(0, (int)finalWordBits); for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx) { const GLbitfield rawMask = (GLbitfield)deUint32Hash(wordNdx * 32 + triNdx); const GLbitfield mask = (invert) ? (~rawMask) : (rawMask); const bool isFinalWord = (wordNdx + 1) == wordCount; const GLbitfield maskMask = (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count gl.sampleMaski(wordNdx, mask & maskMask); } GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski"); renderTriangle(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f), Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f), color); } } /*--------------------------------------------------------------------*//*! * \brief Tests coverage mask generation proportionality property. * * Tests that the number of coverage bits in a coverage mask set with * glSampleMaski is, on average, proportional to the number of set bits. * Draws multiple frames, each time increasing the number of mask bits set * and checks that the average color is changing appropriately. *//*--------------------------------------------------------------------*/ class MaskProportionalityCase : public DefaultFBOMultisampleCase { public: MaskProportionalityCase (Context& context, const char* name, const char* description); ~MaskProportionalityCase (void) {} void init (void); IterateResult iterate (void); private: int m_numIterations; int m_currentIteration; deInt32 m_previousIterationColorSum; }; MaskProportionalityCase::MaskProportionalityCase (Context& context, const char* name, const char* description) : DefaultFBOMultisampleCase (context, name, description, 32) , m_numIterations (-1) , m_currentIteration (0) , m_previousIterationColorSum (-1) { } void MaskProportionalityCase::init (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); TestLog& log = m_testCtx.getLog(); // check the test is even possible GLint maxSampleMaskWords = 0; gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords); if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords) throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS"); DefaultFBOMultisampleCase::init(); // set state gl.enable(GL_SAMPLE_MASK); GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)"); log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage; m_numIterations = m_numSamples + 1; randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate. } MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); deInt32 numPixels = (deInt32)renderedImg.getWidth()*(deInt32)renderedImg.getHeight(); DE_ASSERT(m_numIterations >= 0); log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage; gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); // 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); const std::vector<deUint32> sampleMask = genAllSetToNthBitSampleMask(m_currentIteration); DE_ASSERT(m_currentIteration <= m_numSamples + 1); log << TestLog::Message << "Drawing a red quad using sample mask 0b" << sampleMaskToString(sampleMask, m_numSamples) << TestLog::EndMessage; for (int wordNdx = 0; wordNdx < getEffectiveSampleMaskWordCount(m_numSamples - 1); ++wordNdx) { const GLbitfield mask = (wordNdx < (int)sampleMask.size()) ? ((GLbitfield)(sampleMask[wordNdx])) : (0); gl.sampleMaski(wordNdx, mask); GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski"); } 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 the number of set sample mask bits" << 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_MASK is constant at * given pixel coordinates. Draws two quads, with the second one fully * overlapping the first one such that at any given pixel, both quads have * the same coverage mask value. This way, if the constancy property is * fulfilled, only the second quad should be visible. *//*--------------------------------------------------------------------*/ class MaskConstancyCase : public DefaultFBOMultisampleCase { public: enum CaseBits { CASEBIT_ALPHA_TO_COVERAGE = 1, //!< Use alpha-to-coverage. CASEBIT_SAMPLE_COVERAGE = 2, //!< Use sample coverage. CASEBIT_SAMPLE_COVERAGE_INVERTED = 4, //!< Inverted sample coverage. CASEBIT_SAMPLE_MASK = 8, //!< Use sample mask. }; MaskConstancyCase (Context& context, const char* name, const char* description, deUint32 typeBits); ~MaskConstancyCase (void) {} void init (void); IterateResult iterate (void); private: const bool m_isAlphaToCoverageCase; const bool m_isSampleCoverageCase; const bool m_isInvertedSampleCoverageCase; const bool m_isSampleMaskCase; }; MaskConstancyCase::MaskConstancyCase (Context& context, const char* name, const char* description, deUint32 typeBits) : DefaultFBOMultisampleCase (context, name, description, 256) , m_isAlphaToCoverageCase (0 != (typeBits & CASEBIT_ALPHA_TO_COVERAGE)) , m_isSampleCoverageCase (0 != (typeBits & CASEBIT_SAMPLE_COVERAGE)) , m_isInvertedSampleCoverageCase (0 != (typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED)) , m_isSampleMaskCase (0 != (typeBits & CASEBIT_SAMPLE_MASK)) { // CASEBIT_SAMPLE_COVERAGE_INVERT => CASEBIT_SAMPLE_COVERAGE DE_ASSERT((typeBits & CASEBIT_SAMPLE_COVERAGE) || ~(typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED)); DE_ASSERT(m_isSampleMaskCase); // no point testing non-sample-mask cases, they are checked already in gles3 } void MaskConstancyCase::init (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); // check the test is even possible if (m_isSampleMaskCase) { GLint maxSampleMaskWords = 0; gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords); if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords) throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS"); } // normal init DefaultFBOMultisampleCase::init(); } MaskConstancyCase::IterateResult MaskConstancyCase::iterate (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); randomizeViewport(); log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage; gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); if (m_isAlphaToCoverageCase) { gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE); gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_ALPHA_TO_COVERAGE"); 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) { gl.enable(GL_SAMPLE_COVERAGE); GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_COVERAGE"); log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage; } if (m_isSampleMaskCase) { gl.enable(GL_SAMPLE_MASK); GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK"); log << TestLog::Message << "GL_SAMPLE_MASK 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 || m_isSampleMaskCase) ? " and " : "") << (m_isInvertedSampleCoverageCase ? "inverted " : "") << (m_isSampleCoverageCase ? "sample coverage" : "") << (m_isSampleCoverageCase && m_isSampleMaskCase ? " and " : "") << (m_isSampleMaskCase ? "sample mask" : "") << " 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); gl.sampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value, m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE); GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleCoverage"); } if (m_isSampleMaskCase) { const int wordCount = getEffectiveSampleMaskWordCount(m_numSamples - 1); const GLbitfield finalWordBits = m_numSamples - 32 * ((m_numSamples-1) / 32); const GLbitfield finalWordMask = (GLbitfield)deBitMask32(0, (int)finalWordBits); for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx) { const GLbitfield mask = (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row); const bool isFinalWord = (wordNdx + 1) == wordCount; const GLbitfield maskMask = (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count gl.sampleMaski(wordNdx, mask & maskMask); GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski"); } } 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 that unused bits of a sample mask have no effect * * Tests that the bits in the sample mask with positions higher than * the number of samples do not have effect. In multisample fragment * operations the sample mask is ANDed with the fragment coverage value. * The coverage value cannot have the corresponding bits set. * * This is done by drawing a quads with varying sample masks and then * redrawing the quads with identical masks but with the mask's high bits * having different values. Only the latter quad pattern should be visible. *//*--------------------------------------------------------------------*/ class SampleMaskHighBitsCase : public DefaultFBOMultisampleCase { public: SampleMaskHighBitsCase (Context& context, const char* name, const char* description); ~SampleMaskHighBitsCase (void) {} void init (void); IterateResult iterate (void); }; SampleMaskHighBitsCase::SampleMaskHighBitsCase (Context& context, const char* name, const char* description) : DefaultFBOMultisampleCase(context, name, description, 256) { } void SampleMaskHighBitsCase::init (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); GLint maxSampleMaskWords = 0; // check the test is even possible gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords); if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords) throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS"); // normal init DefaultFBOMultisampleCase::init(); } SampleMaskHighBitsCase::IterateResult SampleMaskHighBitsCase::iterate (void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); TestLog& log = m_testCtx.getLog(); tcu::Surface renderedImg (m_viewportSize, m_viewportSize); de::Random rnd (12345); if (m_numSamples % 32 == 0) { log << TestLog::Message << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping." << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Skipped"); return STOP; } randomizeViewport(); log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage; gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); gl.enable(GL_SAMPLE_MASK); GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK"); log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage; log << TestLog::Message << "Drawing several green quads, each fully overlapped by a red quad with the same effective sample mask 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, 1.0f); const Vec4 baseRed (1.0f, 0.0f, 0.0f, 1.0f); const int wordCount = getEffectiveSampleMaskWordCount(m_numSamples - 1); const GLbitfield finalWordBits = m_numSamples - 32 * ((m_numSamples-1) / 32); const GLbitfield finalWordMask = (GLbitfield)deBitMask32(0, (int)finalWordBits); for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx) { const GLbitfield mask = (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row); const bool isFinalWord = (wordNdx + 1) == wordCount; const GLbitfield maskMask = (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count const GLbitfield highBits = rnd.getUint32(); gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask)); GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski"); } renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen, baseGreen, baseGreen, baseGreen); for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx) { const GLbitfield mask = (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row); const bool isFinalWord = (wordNdx + 1) == wordCount; const GLbitfield maskMask = (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count const GLbitfield highBits = rnd.getUint32(); gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask)); GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski"); } renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed, baseRed, baseRed, baseRed); } } 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. Mask unused bits have effect." << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits modified mask"); return STOP; } } log << TestLog::Message << "Success: Coverage mask high bits appear to have no effect." << TestLog::EndMessage; m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed"); return STOP; } } // anonymous MultisampleTests::MultisampleTests (Context& context) : TestCaseGroup(context, "multisample", "Multisample tests") { } MultisampleTests::~MultisampleTests (void) { } void MultisampleTests::init (void) { tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "default_framebuffer", "Test with default framebuffer"); addChild(group); // .default_framebuffer { // sample positions group->addChild(new SamplePosQueryCase (m_context, "sample_position", "test SAMPLE_POSITION")); // sample mask group->addChild(new MaskInvertCase (m_context, "sample_mask_sum_of_inverses", "Test that mask and its negation's sum equal the fully set mask")); group->addChild(new MaskProportionalityCase (m_context, "proportionality_sample_mask", "Test the proportionality property of GL_SAMPLE_MASK")); group->addChild(new MaskConstancyCase (m_context, "constancy_sample_mask", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_MASK", MaskConstancyCase::CASEBIT_SAMPLE_MASK)); group->addChild(new MaskConstancyCase (m_context, "constancy_alpha_to_coverage_sample_mask", "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_MASK", MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK)); group->addChild(new MaskConstancyCase (m_context, "constancy_sample_coverage_sample_mask", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK", MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK)); group->addChild(new MaskConstancyCase (m_context, "constancy_alpha_to_coverage_sample_coverage_sample_mask", "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE, GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK", MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK)); group->addChild(new SampleMaskHighBitsCase (m_context, "sample_mask_non_effective_bits", "Test that values of unused bits of a sample mask (bit index > sample count) have no effect")); } } } // Functional } // gles31 } // deqp