| /*------------------------------------------------------------------------- |
| * 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 gl_HelperInvocation tests. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es31fShaderHelperInvocationTests.hpp" |
| |
| #include "gluObjectWrapper.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluDrawUtil.hpp" |
| #include "gluPixelTransfer.hpp" |
| |
| #include "glwFunctions.hpp" |
| #include "glwEnums.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuVector.hpp" |
| #include "tcuSurface.hpp" |
| |
| #include "deUniquePtr.hpp" |
| #include "deStringUtil.hpp" |
| #include "deRandom.hpp" |
| #include "deString.h" |
| |
| namespace deqp |
| { |
| namespace gles31 |
| { |
| namespace Functional |
| { |
| namespace |
| { |
| |
| using glu::ShaderProgram; |
| using tcu::TestLog; |
| using tcu::Vec2; |
| using tcu::IVec2; |
| using de::MovePtr; |
| using std::string; |
| using std::vector; |
| |
| enum PrimitiveType |
| { |
| PRIMITIVETYPE_TRIANGLE = 0, |
| PRIMITIVETYPE_LINE, |
| PRIMITIVETYPE_WIDE_LINE, |
| PRIMITIVETYPE_POINT, |
| PRIMITIVETYPE_WIDE_POINT, |
| |
| PRIMITIVETYPE_LAST |
| }; |
| |
| static int getNumVerticesPerPrimitive (PrimitiveType primType) |
| { |
| switch (primType) |
| { |
| case PRIMITIVETYPE_TRIANGLE: return 3; |
| case PRIMITIVETYPE_LINE: return 2; |
| case PRIMITIVETYPE_WIDE_LINE: return 2; |
| case PRIMITIVETYPE_POINT: return 1; |
| case PRIMITIVETYPE_WIDE_POINT: return 1; |
| default: |
| DE_ASSERT(false); |
| return 0; |
| } |
| } |
| |
| static glu::PrimitiveType getGluPrimitiveType (PrimitiveType primType) |
| { |
| switch (primType) |
| { |
| case PRIMITIVETYPE_TRIANGLE: return glu::PRIMITIVETYPE_TRIANGLES; |
| case PRIMITIVETYPE_LINE: return glu::PRIMITIVETYPE_LINES; |
| case PRIMITIVETYPE_WIDE_LINE: return glu::PRIMITIVETYPE_LINES; |
| case PRIMITIVETYPE_POINT: return glu::PRIMITIVETYPE_POINTS; |
| case PRIMITIVETYPE_WIDE_POINT: return glu::PRIMITIVETYPE_POINTS; |
| default: |
| DE_ASSERT(false); |
| return glu::PRIMITIVETYPE_LAST; |
| } |
| } |
| |
| static void genVertices (PrimitiveType primType, int numPrimitives, de::Random* rnd, vector<Vec2>* dst) |
| { |
| const bool isTri = primType == PRIMITIVETYPE_TRIANGLE; |
| const float minCoord = isTri ? -1.5f : -1.0f; |
| const float maxCoord = isTri ? +1.5f : +1.0f; |
| const int numVerticesPerPrimitive = getNumVerticesPerPrimitive(primType); |
| const int numVert = numVerticesPerPrimitive*numPrimitives; |
| |
| dst->resize(numVert); |
| |
| for (size_t ndx = 0; ndx < dst->size(); ndx++) |
| { |
| (*dst)[ndx][0] = rnd->getFloat(minCoord, maxCoord); |
| (*dst)[ndx][1] = rnd->getFloat(minCoord, maxCoord); |
| } |
| |
| // Don't produce completely or almost completely discardable primitives. |
| // \note: This doesn't guarantee that resulting primitives are visible or |
| // produce any fragments. This just removes trivially discardable |
| // primitives. |
| for (int primitiveNdx = 0; primitiveNdx < numPrimitives; ++primitiveNdx) |
| for (int component = 0; component < 2; ++component) |
| { |
| bool negativeClip = true; |
| bool positiveClip = true; |
| |
| for (int vertexNdx = 0; vertexNdx < numVerticesPerPrimitive; ++vertexNdx) |
| { |
| const float p = (*dst)[primitiveNdx * numVerticesPerPrimitive + vertexNdx][component]; |
| // \note 0.9 instead of 1.0 to avoid just barely visible primitives |
| if (p > -0.9f) |
| negativeClip = false; |
| if (p < +0.9f) |
| positiveClip = false; |
| } |
| |
| // if discardable, just mirror first vertex along center |
| if (negativeClip || positiveClip) |
| { |
| (*dst)[primitiveNdx * numVerticesPerPrimitive + 0][0] *= -1.0f; |
| (*dst)[primitiveNdx * numVerticesPerPrimitive + 0][1] *= -1.0f; |
| } |
| } |
| } |
| |
| static int getInteger (const glw::Functions& gl, deUint32 pname) |
| { |
| int v = 0; |
| gl.getIntegerv(pname, &v); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()"); |
| return v; |
| } |
| |
| static Vec2 getRange (const glw::Functions& gl, deUint32 pname) |
| { |
| Vec2 v(0.0f); |
| gl.getFloatv(pname, v.getPtr()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv()"); |
| return v; |
| } |
| |
| static void drawRandomPrimitives (const glu::RenderContext& renderCtx, deUint32 program, PrimitiveType primType, int numPrimitives, de::Random* rnd) |
| { |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| const float minPointSize = 16.0f; |
| const float maxPointSize = 32.0f; |
| const float minLineWidth = 16.0f; |
| const float maxLineWidth = 32.0f; |
| vector<Vec2> vertices; |
| vector<glu::VertexArrayBinding> vertexArrays; |
| |
| genVertices(primType, numPrimitives, rnd, &vertices); |
| |
| vertexArrays.push_back(glu::va::Float("a_position", 2, (int)vertices.size(), 0, (const float*)&vertices[0])); |
| |
| gl.useProgram(program); |
| |
| // Special state for certain primitives |
| if (primType == PRIMITIVETYPE_POINT || primType == PRIMITIVETYPE_WIDE_POINT) |
| { |
| const Vec2 range = getRange(gl, GL_ALIASED_POINT_SIZE_RANGE); |
| const bool isWidePoint = primType == PRIMITIVETYPE_WIDE_POINT; |
| const float pointSize = isWidePoint ? de::min(rnd->getFloat(minPointSize, maxPointSize), range.y()) : 1.0f; |
| const int pointSizeLoc = gl.getUniformLocation(program, "u_pointSize"); |
| |
| gl.uniform1f(pointSizeLoc, pointSize); |
| } |
| else if (primType == PRIMITIVETYPE_WIDE_LINE) |
| { |
| const Vec2 range = getRange(gl, GL_ALIASED_LINE_WIDTH_RANGE); |
| const float lineWidth = de::min(rnd->getFloat(minLineWidth, maxLineWidth), range.y()); |
| |
| gl.lineWidth(lineWidth); |
| } |
| |
| glu::draw(renderCtx, program, (int)vertexArrays.size(), &vertexArrays[0], |
| glu::PrimitiveList(getGluPrimitiveType(primType), (int)vertices.size())); |
| } |
| |
| class FboHelper |
| { |
| public: |
| FboHelper (const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples); |
| ~FboHelper (void); |
| |
| void bindForRendering (void); |
| void readPixels (int x, int y, const tcu::PixelBufferAccess& dst); |
| |
| private: |
| const glu::RenderContext& m_renderCtx; |
| const int m_numSamples; |
| const IVec2 m_size; |
| |
| glu::Renderbuffer m_colorbuffer; |
| glu::Framebuffer m_framebuffer; |
| glu::Renderbuffer m_resolveColorbuffer; |
| glu::Framebuffer m_resolveFramebuffer; |
| }; |
| |
| FboHelper::FboHelper (const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples) |
| : m_renderCtx (renderCtx) |
| , m_numSamples (numSamples) |
| , m_size (width, height) |
| , m_colorbuffer (renderCtx) |
| , m_framebuffer (renderCtx) |
| , m_resolveColorbuffer (renderCtx) |
| , m_resolveFramebuffer (renderCtx) |
| { |
| const glw::Functions& gl = m_renderCtx.getFunctions(); |
| const int maxSamples = getInteger(gl, GL_MAX_SAMPLES); |
| |
| gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorbuffer); |
| gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, format, width, height); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer); |
| gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_colorbuffer); |
| |
| if (m_numSamples > maxSamples && gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) |
| throw tcu::NotSupportedError("Sample count exceeds GL_MAX_SAMPLES"); |
| |
| TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); |
| |
| if (m_numSamples != 0) |
| { |
| gl.bindRenderbuffer(GL_RENDERBUFFER, *m_resolveColorbuffer); |
| gl.renderbufferStorage(GL_RENDERBUFFER, format, width, height); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, *m_resolveFramebuffer); |
| gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_resolveColorbuffer); |
| TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); |
| } |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create framebuffer"); |
| } |
| |
| FboHelper::~FboHelper (void) |
| { |
| } |
| |
| void FboHelper::bindForRendering (void) |
| { |
| const glw::Functions& gl = m_renderCtx.getFunctions(); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()"); |
| gl.viewport(0, 0, m_size.x(), m_size.y()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "viewport()"); |
| } |
| |
| void FboHelper::readPixels (int x, int y, const tcu::PixelBufferAccess& dst) |
| { |
| const glw::Functions& gl = m_renderCtx.getFunctions(); |
| const int width = dst.getWidth(); |
| const int height = dst.getHeight(); |
| |
| if (m_numSamples != 0) |
| { |
| gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, *m_resolveFramebuffer); |
| gl.blitFramebuffer(x, y, width, height, x, y, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| gl.bindFramebuffer(GL_READ_FRAMEBUFFER, *m_resolveFramebuffer); |
| } |
| |
| glu::readPixels(m_renderCtx, x, y, dst); |
| } |
| |
| enum |
| { |
| FRAMEBUFFER_WIDTH = 256, |
| FRAMEBUFFER_HEIGHT = 256, |
| FRAMEBUFFER_FORMAT = GL_RGBA8, |
| NUM_SAMPLES_MAX = -1 |
| }; |
| |
| //! Verifies that gl_HelperInvocation is false in all rendered pixels. |
| class HelperInvocationValueCase : public TestCase |
| { |
| public: |
| HelperInvocationValueCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples); |
| ~HelperInvocationValueCase (void); |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| const PrimitiveType m_primitiveType; |
| const int m_numSamples; |
| |
| const int m_numIters; |
| const int m_numPrimitivesPerIter; |
| |
| MovePtr<ShaderProgram> m_program; |
| MovePtr<FboHelper> m_fbo; |
| int m_iterNdx; |
| }; |
| |
| HelperInvocationValueCase::HelperInvocationValueCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples) |
| : TestCase (context, name, description) |
| , m_primitiveType (primType) |
| , m_numSamples (numSamples) |
| , m_numIters (5) |
| , m_numPrimitivesPerIter (10) |
| , m_iterNdx (0) |
| { |
| } |
| |
| HelperInvocationValueCase::~HelperInvocationValueCase (void) |
| { |
| deinit(); |
| } |
| |
| void HelperInvocationValueCase::init (void) |
| { |
| const glu::RenderContext& renderCtx = m_context.getRenderContext(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| const int maxSamples = getInteger(gl, GL_MAX_SAMPLES); |
| const int actualSamples = m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples; |
| |
| m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource( |
| "#version 310 es\n" |
| "in highp vec2 a_position;\n" |
| "uniform highp float u_pointSize;\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(a_position, 0.0, 1.0);\n" |
| " gl_PointSize = u_pointSize;\n" |
| "}\n") |
| << glu::FragmentSource( |
| "#version 310 es\n" |
| "out mediump vec4 o_color;\n" |
| "void main (void)\n" |
| "{\n" |
| " if (gl_HelperInvocation)\n" |
| " o_color = vec4(1.0, 0.0, 0.0, 1.0);\n" |
| " else\n" |
| " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n" |
| "}\n"))); |
| |
| m_testCtx.getLog() << *m_program; |
| |
| if (!m_program->isOk()) |
| { |
| m_program.clear(); |
| TCU_FAIL("Compile failed"); |
| } |
| |
| m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with " |
| << actualSamples << " samples" << TestLog::EndMessage; |
| |
| m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, |
| FRAMEBUFFER_FORMAT, actualSamples)); |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| } |
| |
| void HelperInvocationValueCase::deinit (void) |
| { |
| m_program.clear(); |
| m_fbo.clear(); |
| } |
| |
| static bool verifyHelperInvocationValue (TestLog& log, const tcu::Surface& result, bool isMultiSample) |
| { |
| const tcu::RGBA bgRef (0, 0, 0, 255); |
| const tcu::RGBA fgRef (0, 255, 0, 255); |
| const tcu::RGBA threshold (1, isMultiSample ? 254 : 1, 1, 1); |
| int numInvalidPixels = 0; |
| bool renderedSomething = false; |
| |
| for (int y = 0; y < result.getHeight(); ++y) |
| { |
| for (int x = 0; x < result.getWidth(); ++x) |
| { |
| const tcu::RGBA resPix = result.getPixel(x, y); |
| const bool isBg = tcu::compareThreshold(resPix, bgRef, threshold); |
| const bool isFg = tcu::compareThreshold(resPix, fgRef, threshold); |
| |
| if (!isBg && !isFg) |
| numInvalidPixels += 1; |
| |
| if (isFg) |
| renderedSomething = true; |
| } |
| } |
| |
| if (numInvalidPixels > 0) |
| { |
| log << TestLog::Image("Result", "Result image", result); |
| log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage; |
| return false; |
| } |
| else if (!renderedSomething) |
| { |
| log << TestLog::Image("Result", "Result image", result); |
| log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage; |
| return false; |
| } |
| else |
| { |
| log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage; |
| return true; |
| } |
| } |
| |
| HelperInvocationValueCase::IterateResult HelperInvocationValueCase::iterate (void) |
| { |
| const glu::RenderContext& renderCtx = m_context.getRenderContext(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| const string sectionName = string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters); |
| const tcu::ScopedLogSection section (m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName); |
| de::Random rnd (deStringHash(getName()) ^ deInt32Hash(m_iterNdx)); |
| tcu::Surface result (FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT); |
| |
| m_fbo->bindForRendering(); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, m_numPrimitivesPerIter, &rnd); |
| |
| m_fbo->readPixels(0, 0, result.getAccess()); |
| |
| if (!verifyHelperInvocationValue(m_testCtx.getLog(), result, m_numSamples != 0)) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found"); |
| |
| m_iterNdx += 1; |
| return (m_iterNdx < m_numIters) ? CONTINUE : STOP; |
| } |
| |
| //! Checks derivates when value depends on gl_HelperInvocation. |
| class HelperInvocationDerivateCase : public TestCase |
| { |
| public: |
| HelperInvocationDerivateCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc, bool checkAbsoluteValue); |
| ~HelperInvocationDerivateCase (void); |
| |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| private: |
| const PrimitiveType m_primitiveType; |
| const int m_numSamples; |
| const std::string m_derivateFunc; |
| const bool m_checkAbsoluteValue; |
| |
| const int m_numIters; |
| |
| MovePtr<ShaderProgram> m_program; |
| MovePtr<FboHelper> m_fbo; |
| int m_iterNdx; |
| }; |
| |
| HelperInvocationDerivateCase::HelperInvocationDerivateCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc, bool checkAbsoluteValue) |
| : TestCase (context, name, description) |
| , m_primitiveType (primType) |
| , m_numSamples (numSamples) |
| , m_derivateFunc (derivateFunc) |
| , m_checkAbsoluteValue (checkAbsoluteValue) |
| , m_numIters (16) |
| , m_iterNdx (0) |
| { |
| } |
| |
| HelperInvocationDerivateCase::~HelperInvocationDerivateCase (void) |
| { |
| deinit(); |
| } |
| |
| void HelperInvocationDerivateCase::init (void) |
| { |
| const glu::RenderContext& renderCtx = m_context.getRenderContext(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| const int maxSamples = getInteger(gl, GL_MAX_SAMPLES); |
| const int actualSamples = m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples; |
| const std::string funcSource = (m_checkAbsoluteValue) ? ("abs(" + m_derivateFunc + "(value))") : (m_derivateFunc + "(value)"); |
| |
| m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource( |
| "#version 310 es\n" |
| "in highp vec2 a_position;\n" |
| "uniform highp float u_pointSize;\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = vec4(a_position, 0.0, 1.0);\n" |
| " gl_PointSize = u_pointSize;\n" |
| "}\n") |
| << glu::FragmentSource(string( |
| "#version 310 es\n" |
| "out mediump vec4 o_color;\n" |
| "void main (void)\n" |
| "{\n" |
| " highp float value = gl_HelperInvocation ? 1.0 : 0.0;\n" |
| " highp float derivate = ") + funcSource + ";\n" |
| " if (gl_HelperInvocation)\n" |
| " o_color = vec4(1.0, 0.0, derivate, 1.0);\n" |
| " else\n" |
| " o_color = vec4(0.0, 1.0, derivate, 1.0);\n" |
| "}\n"))); |
| |
| m_testCtx.getLog() << *m_program; |
| |
| if (!m_program->isOk()) |
| { |
| m_program.clear(); |
| TCU_FAIL("Compile failed"); |
| } |
| |
| m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with " |
| << actualSamples << " samples" << TestLog::EndMessage; |
| |
| m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, |
| FRAMEBUFFER_FORMAT, actualSamples)); |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| } |
| |
| void HelperInvocationDerivateCase::deinit (void) |
| { |
| m_program.clear(); |
| m_fbo.clear(); |
| } |
| |
| static bool hasNeighborWithColor (const tcu::Surface& surface, int x, int y, tcu::RGBA color, tcu::RGBA threshold) |
| { |
| const int w = surface.getWidth(); |
| const int h = surface.getHeight(); |
| |
| for (int dx = -1; dx < 2; dx++) |
| for (int dy = -1; dy < 2; dy++) |
| { |
| const IVec2 pos = IVec2(x + dx, y + dy); |
| |
| if (dx == 0 && dy == 0) |
| continue; |
| |
| if (de::inBounds(pos.x(), 0, w) && de::inBounds(pos.y(), 0, h)) |
| { |
| const tcu::RGBA neighborColor = surface.getPixel(pos.x(), pos.y()); |
| |
| if (tcu::compareThreshold(color, neighborColor, threshold)) |
| return true; |
| } |
| else |
| return true; // Can't know for certain |
| } |
| |
| return false; |
| } |
| |
| static bool verifyHelperInvocationDerivate (TestLog& log, const tcu::Surface& result, bool isMultiSample) |
| { |
| const tcu::RGBA bgRef (0, 0, 0, 255); |
| const tcu::RGBA fgRef (0, 255, 0, 255); |
| const tcu::RGBA isBgThreshold (1, isMultiSample ? 254 : 1, 0, 1); |
| const tcu::RGBA isFgThreshold (1, isMultiSample ? 254 : 1, 255, 1); |
| int numInvalidPixels = 0; |
| int numNonZeroDeriv = 0; |
| bool renderedSomething = false; |
| |
| for (int y = 0; y < result.getHeight(); ++y) |
| { |
| for (int x = 0; x < result.getWidth(); ++x) |
| { |
| const tcu::RGBA resPix = result.getPixel(x, y); |
| const bool isBg = tcu::compareThreshold(resPix, bgRef, isBgThreshold); |
| const bool isFg = tcu::compareThreshold(resPix, fgRef, isFgThreshold); |
| const bool nonZeroDeriv = resPix.getBlue() > 0; |
| const bool neighborBg = nonZeroDeriv ? hasNeighborWithColor(result, x, y, bgRef, isBgThreshold) : false; |
| |
| if (nonZeroDeriv) |
| numNonZeroDeriv += 1; |
| |
| if ((!isBg && !isFg) || // Neither of valid colors (ignoring blue channel that has derivate) |
| (nonZeroDeriv && !neighborBg && !isFg)) // Has non-zero derivate, but sample not at primitive edge or inside primitive |
| numInvalidPixels += 1; |
| |
| if (isFg) |
| renderedSomething = true; |
| } |
| } |
| |
| log << TestLog::Message << "Found " << numNonZeroDeriv << " pixels with non-zero derivate (neighbor sample has gl_HelperInvocation = true)" << TestLog::EndMessage; |
| |
| if (numInvalidPixels > 0) |
| { |
| log << TestLog::Image("Result", "Result image", result); |
| log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage; |
| return false; |
| } |
| else if (!renderedSomething) |
| { |
| log << TestLog::Image("Result", "Result image", result); |
| log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage; |
| return false; |
| } |
| else |
| { |
| log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage; |
| return true; |
| } |
| } |
| |
| HelperInvocationDerivateCase::IterateResult HelperInvocationDerivateCase::iterate (void) |
| { |
| const glu::RenderContext& renderCtx = m_context.getRenderContext(); |
| const glw::Functions& gl = renderCtx.getFunctions(); |
| const string sectionName = string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters); |
| const tcu::ScopedLogSection section (m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName); |
| de::Random rnd (deStringHash(getName()) ^ deInt32Hash(m_iterNdx)); |
| tcu::Surface result (FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT); |
| |
| m_fbo->bindForRendering(); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| |
| drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, 1, &rnd); |
| |
| m_fbo->readPixels(0, 0, result.getAccess()); |
| |
| if (!verifyHelperInvocationDerivate(m_testCtx.getLog(), result, m_numSamples != 0)) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found"); |
| |
| m_iterNdx += 1; |
| return (m_iterNdx < m_numIters) ? CONTINUE : STOP; |
| } |
| |
| } // anonymous |
| |
| ShaderHelperInvocationTests::ShaderHelperInvocationTests (Context& context) |
| : TestCaseGroup(context, "helper_invocation", "gl_HelperInvocation tests") |
| { |
| } |
| |
| ShaderHelperInvocationTests::~ShaderHelperInvocationTests (void) |
| { |
| } |
| |
| void ShaderHelperInvocationTests::init (void) |
| { |
| static const struct |
| { |
| const char* caseName; |
| PrimitiveType primType; |
| } s_primTypes[] = |
| { |
| { "triangles", PRIMITIVETYPE_TRIANGLE }, |
| { "lines", PRIMITIVETYPE_LINE }, |
| { "wide_lines", PRIMITIVETYPE_WIDE_LINE }, |
| { "points", PRIMITIVETYPE_POINT }, |
| { "wide_points", PRIMITIVETYPE_WIDE_POINT } |
| }; |
| |
| static const struct |
| { |
| const char* suffix; |
| int numSamples; |
| } s_sampleCounts[] = |
| { |
| { "", 0 }, |
| { "_4_samples", 4 }, |
| { "_8_samples", 8 }, |
| { "_max_samples", NUM_SAMPLES_MAX } |
| }; |
| |
| // value |
| { |
| tcu::TestCaseGroup* const valueGroup = new tcu::TestCaseGroup(m_testCtx, "value", "gl_HelperInvocation value in rendered pixels"); |
| addChild(valueGroup); |
| |
| for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++) |
| { |
| for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++) |
| { |
| const string name = string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix; |
| const PrimitiveType primType = s_primTypes[primTypeNdx].primType; |
| const int numSamples = s_sampleCounts[sampleCountNdx].numSamples; |
| |
| valueGroup->addChild(new HelperInvocationValueCase(m_context, name.c_str(), "", primType, numSamples)); |
| } |
| } |
| } |
| |
| // derivate |
| { |
| tcu::TestCaseGroup* const derivateGroup = new tcu::TestCaseGroup(m_testCtx, "derivate", "Derivate of gl_HelperInvocation-dependent value"); |
| addChild(derivateGroup); |
| |
| for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++) |
| { |
| for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++) |
| { |
| const string name = string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix; |
| const PrimitiveType primType = s_primTypes[primTypeNdx].primType; |
| const int numSamples = s_sampleCounts[sampleCountNdx].numSamples; |
| |
| derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdx").c_str(), "", primType, numSamples, "dFdx", true)); |
| derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdy").c_str(), "", primType, numSamples, "dFdy", true)); |
| derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_fwidth").c_str(), "", primType, numSamples, "fwidth", false)); |
| } |
| } |
| } |
| } |
| |
| } // Functional |
| } // gles31 |
| } // deqp |