| /*------------------------------------------------------------------------- |
| * 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 <string> |
| #include <vector> |
| |
| #include "glw.h" |
| |
| namespace deqp |
| { |
| namespace gles3 |
| { |
| namespace Functional |
| { |
| |
| using std::vector; |
| using tcu::IVec2; |
| using tcu::IVec4; |
| using tcu::TestLog; |
| using tcu::Vec2; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| |
| 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_) |
| { |
| } |
| }; |
| |
| } // namespace |
| |
| 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 <typename T> |
| static inline T min4(T a, T b, T c, T d) |
| { |
| return de::min(de::min(de::min(a, b), c), d); |
| } |
| |
| template <typename T> |
| 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; |
| uint32_t m_msColorRbo; |
| uint32_t m_msDepthStencilRbo; |
| uint32_t m_resolveColorRbo; |
| uint32_t m_msFbo; |
| uint32_t 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(uint32_t) == 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<int>(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<tcu::RGBA> 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<QuadCorners> 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*<actual size of quad>. |
| // 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 ®ion = 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; |
| |
| int32_t 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_numIterations(0) |
| , 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); |
| int32_t numPixels = (int32_t)renderedImg.getWidth() * (int32_t)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. |
| |
| int32_t 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(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)); |
| } |
| } |
| |
| } // namespace Functional |
| } // namespace gles3 |
| } // namespace deqp |