| /*------------------------------------------------------------------------- |
| * 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 Tessellation and geometry shader interaction tests. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es31fTessellationGeometryInteractionTests.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuRenderTarget.hpp" |
| #include "tcuSurface.hpp" |
| #include "tcuImageCompare.hpp" |
| #include "tcuVectorUtil.hpp" |
| #include "tcuTextureUtil.hpp" |
| #include "tcuStringTemplate.hpp" |
| #include "gluRenderContext.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluStrUtil.hpp" |
| #include "gluContextInfo.hpp" |
| #include "gluObjectWrapper.hpp" |
| #include "gluPixelTransfer.hpp" |
| #include "glwFunctions.hpp" |
| #include "glwEnums.hpp" |
| #include "deStringUtil.hpp" |
| #include "deUniquePtr.hpp" |
| |
| #include <sstream> |
| #include <algorithm> |
| #include <iterator> |
| |
| namespace deqp |
| { |
| namespace gles31 |
| { |
| namespace Functional |
| { |
| namespace |
| { |
| |
| static std::string specializeShader (const std::string& shaderSource, const glu::ContextType& contextType) |
| { |
| const bool supportsES32 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)); |
| std::map<std::string, std::string> shaderArgs; |
| |
| shaderArgs["VERSION_DECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType)); |
| shaderArgs["EXTENSION_GEOMETRY_SHADER"] = (supportsES32) ? ("") : ("#extension GL_EXT_geometry_shader : require\n"); |
| shaderArgs["EXTENSION_TESSELATION_SHADER"] = (supportsES32) ? ("") : ("#extension GL_EXT_tessellation_shader : require\n"); |
| |
| return tcu::StringTemplate(shaderSource).specialize(shaderArgs); |
| } |
| |
| static const char* const s_positionVertexShader = "${VERSION_DECL}\n" |
| "in highp vec4 a_position;\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = a_position;\n" |
| "}\n"; |
| static const char* const s_whiteOutputFragmentShader = "${VERSION_DECL}\n" |
| "layout(location = 0) out mediump vec4 fragColor;\n" |
| "void main (void)\n" |
| "{\n" |
| " fragColor = vec4(1.0);\n" |
| "}\n"; |
| |
| static bool isBlack (const tcu::RGBA& c) |
| { |
| return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0; |
| } |
| |
| class IdentityShaderCase : public TestCase |
| { |
| public: |
| IdentityShaderCase (Context& context, const char* name, const char* description); |
| |
| protected: |
| std::string getVertexSource (void) const; |
| std::string getFragmentSource (void) const; |
| }; |
| |
| IdentityShaderCase::IdentityShaderCase (Context& context, const char* name, const char* description) |
| : TestCase(context, name, description) |
| { |
| } |
| |
| std::string IdentityShaderCase::getVertexSource (void) const |
| { |
| std::string source = "${VERSION_DECL}\n" |
| "in highp vec4 a_position;\n" |
| "out highp vec4 v_vertex_color;\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_Position = a_position;\n" |
| " v_vertex_color = vec4(a_position.x * 0.5 + 0.5, a_position.y * 0.5 + 0.5, 1.0, 0.4);\n" |
| "}\n"; |
| |
| return specializeShader(source, m_context.getRenderContext().getType()); |
| } |
| |
| std::string IdentityShaderCase::getFragmentSource (void) const |
| { |
| std::string source = "${VERSION_DECL}\n" |
| "in mediump vec4 v_fragment_color;\n" |
| "layout(location = 0) out mediump vec4 fragColor;\n" |
| "void main (void)\n" |
| "{\n" |
| " fragColor = v_fragment_color;\n" |
| "}\n"; |
| |
| return specializeShader(source, m_context.getRenderContext().getType()); |
| } |
| |
| class IdentityGeometryShaderCase : public IdentityShaderCase |
| { |
| public: |
| enum CaseType |
| { |
| CASE_TRIANGLES = 0, |
| CASE_QUADS, |
| CASE_ISOLINES, |
| }; |
| |
| IdentityGeometryShaderCase (Context& context, const char* name, const char* description, CaseType caseType); |
| ~IdentityGeometryShaderCase (void); |
| |
| private: |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| std::string getTessellationControlSource (void) const; |
| std::string getTessellationEvaluationSource (bool geometryActive) const; |
| std::string getGeometrySource (void) const; |
| |
| enum |
| { |
| RENDER_SIZE = 128, |
| }; |
| |
| const CaseType m_case; |
| deUint32 m_patchBuffer; |
| }; |
| |
| IdentityGeometryShaderCase::IdentityGeometryShaderCase (Context& context, const char* name, const char* description, CaseType caseType) |
| : IdentityShaderCase (context, name, description) |
| , m_case (caseType) |
| , m_patchBuffer (0) |
| { |
| } |
| |
| IdentityGeometryShaderCase::~IdentityGeometryShaderCase (void) |
| { |
| deinit(); |
| } |
| |
| void IdentityGeometryShaderCase::init (void) |
| { |
| // Requirements |
| const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| |
| if (!supportsES32 && |
| (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") || |
| !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))) |
| throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions"); |
| |
| if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || |
| m_context.getRenderTarget().getHeight() < RENDER_SIZE) |
| throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target."); |
| |
| // Log |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Testing tessellating shader program output does not change when a passthrough geometry shader is attached.\n" |
| << "Rendering two images, first with and second without a geometry shader. Expecting similar results.\n" |
| << "Using additive blending to detect overlap.\n" |
| << tcu::TestLog::EndMessage; |
| |
| // Resources |
| |
| { |
| static const tcu::Vec4 patchBufferData[4] = |
| { |
| tcu::Vec4( -0.9f, -0.9f, 0.0f, 1.0f ), |
| tcu::Vec4( -0.9f, 0.9f, 0.0f, 1.0f ), |
| tcu::Vec4( 0.9f, -0.9f, 0.0f, 1.0f ), |
| tcu::Vec4( 0.9f, 0.9f, 0.0f, 1.0f ), |
| }; |
| |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| gl.genBuffers(1, &m_patchBuffer); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer); |
| gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer"); |
| } |
| } |
| |
| void IdentityGeometryShaderCase::deinit (void) |
| { |
| if (m_patchBuffer) |
| { |
| m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer); |
| m_patchBuffer = 0; |
| } |
| } |
| |
| IdentityGeometryShaderCase::IterateResult IdentityGeometryShaderCase::iterate (void) |
| { |
| const float innerTessellationLevel = 14.0f; |
| const float outerTessellationLevel = 14.0f; |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| tcu::Surface resultWithGeometry (RENDER_SIZE, RENDER_SIZE); |
| tcu::Surface resultWithoutGeometry (RENDER_SIZE, RENDER_SIZE); |
| |
| const struct |
| { |
| const char* name; |
| const char* description; |
| bool containsGeometryShader; |
| tcu::PixelBufferAccess surfaceAccess; |
| } renderTargets[] = |
| { |
| { "RenderWithGeometryShader", "Render with geometry shader", true, resultWithGeometry.getAccess() }, |
| { "RenderWithoutGeometryShader", "Render without geometry shader", false, resultWithoutGeometry.getAccess() }, |
| }; |
| |
| gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport"); |
| |
| gl.enable(GL_BLEND); |
| gl.blendFunc(GL_SRC_ALPHA, GL_ONE); |
| gl.blendEquation(GL_FUNC_ADD); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set blend"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Tessellation level: inner " << innerTessellationLevel << ", outer " << outerTessellationLevel << tcu::TestLog::EndMessage; |
| |
| // render with and without geometry shader |
| for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx) |
| { |
| const tcu::ScopedLogSection section (m_testCtx.getLog(), renderTargets[renderNdx].name, renderTargets[renderNdx].description); |
| glu::ProgramSources sources; |
| |
| sources << glu::VertexSource(getVertexSource()) |
| << glu::FragmentSource(getFragmentSource()) |
| << glu::TessellationControlSource(getTessellationControlSource()) |
| << glu::TessellationEvaluationSource(getTessellationEvaluationSource(renderTargets[renderNdx].containsGeometryShader)); |
| |
| if (renderTargets[renderNdx].containsGeometryShader) |
| sources << glu::GeometrySource(getGeometrySource()); |
| |
| { |
| const glu::ShaderProgram program (m_context.getRenderContext(), sources); |
| const glu::VertexArray vao (m_context.getRenderContext()); |
| const int posLocation = gl.getAttribLocation(program.getProgram(), "a_position"); |
| const int innerTessellationLoc = gl.getUniformLocation(program.getProgram(), "u_innerTessellationLevel"); |
| const int outerTessellationLoc = gl.getUniformLocation(program.getProgram(), "u_outerTessellationLevel"); |
| |
| m_testCtx.getLog() << program; |
| |
| if (!program.isOk()) |
| throw tcu::TestError("could not build program"); |
| if (posLocation == -1) |
| throw tcu::TestError("a_position location was -1"); |
| if (outerTessellationLoc == -1) |
| throw tcu::TestError("u_outerTessellationLevel location was -1"); |
| |
| gl.bindVertexArray(*vao); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer); |
| gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); |
| gl.enableVertexAttribArray(posLocation); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs"); |
| |
| gl.useProgram(program.getProgram()); |
| gl.uniform1f(outerTessellationLoc, outerTessellationLevel); |
| |
| if (innerTessellationLoc == -1) |
| gl.uniform1f(innerTessellationLoc, innerTessellationLevel); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "use program"); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, (m_case == CASE_TRIANGLES) ? (3): (4)); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param"); |
| |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); |
| |
| gl.drawArrays(GL_PATCHES, 0, 4); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches"); |
| |
| glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess); |
| } |
| } |
| |
| if (tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(), |
| "ImageCompare", |
| "Image comparison", |
| resultWithoutGeometry.getAccess(), |
| resultWithGeometry.getAccess(), |
| tcu::UVec4(8, 8, 8, 255), |
| tcu::IVec3(1, 1, 0), |
| true, |
| tcu::COMPARE_LOG_RESULT)) |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); |
| |
| return STOP; |
| } |
| |
| std::string IdentityGeometryShaderCase::getTessellationControlSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(vertices = 4) out;\n" |
| "\n" |
| "uniform highp float u_innerTessellationLevel;\n" |
| "uniform highp float u_outerTessellationLevel;\n" |
| "in highp vec4 v_vertex_color[];\n" |
| "out highp vec4 v_patch_color[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| " v_patch_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n" |
| "\n"; |
| |
| if (m_case == CASE_TRIANGLES) |
| buf << " gl_TessLevelOuter[0] = u_outerTessellationLevel;\n" |
| " gl_TessLevelOuter[1] = u_outerTessellationLevel;\n" |
| " gl_TessLevelOuter[2] = u_outerTessellationLevel;\n" |
| " gl_TessLevelInner[0] = u_innerTessellationLevel;\n"; |
| else if (m_case == CASE_QUADS) |
| buf << " gl_TessLevelOuter[0] = u_outerTessellationLevel;\n" |
| " gl_TessLevelOuter[1] = u_outerTessellationLevel;\n" |
| " gl_TessLevelOuter[2] = u_outerTessellationLevel;\n" |
| " gl_TessLevelOuter[3] = u_outerTessellationLevel;\n" |
| " gl_TessLevelInner[0] = u_innerTessellationLevel;\n" |
| " gl_TessLevelInner[1] = u_innerTessellationLevel;\n"; |
| else if (m_case == CASE_ISOLINES) |
| buf << " gl_TessLevelOuter[0] = u_outerTessellationLevel;\n" |
| " gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"; |
| else |
| DE_ASSERT(false); |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string IdentityGeometryShaderCase::getTessellationEvaluationSource (bool geometryActive) const |
| { |
| const char* const colorOutputName = ((geometryActive) ? ("v_evaluated_color") : ("v_fragment_color")); |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(" |
| << ((m_case == CASE_TRIANGLES) ? ("triangles") : (m_case == CASE_QUADS) ? ("quads") : ("isolines")) |
| << ") in;\n" |
| "\n" |
| "in highp vec4 v_patch_color[];\n" |
| "out highp vec4 " << colorOutputName << ";\n" |
| "\n" |
| "// note: No need to use precise gl_Position since we do not require gapless geometry\n" |
| "void main (void)\n" |
| "{\n"; |
| |
| if (m_case == CASE_TRIANGLES) |
| buf << " vec3 weights = vec3(pow(gl_TessCoord.x, 1.3), pow(gl_TessCoord.y, 1.3), pow(gl_TessCoord.z, 1.3));\n" |
| " vec3 cweights = gl_TessCoord;\n" |
| " gl_Position = vec4(weights.x * gl_in[0].gl_Position.xyz + weights.y * gl_in[1].gl_Position.xyz + weights.z * gl_in[2].gl_Position.xyz, 1.0);\n" |
| " " << colorOutputName << " = cweights.x * v_patch_color[0] + cweights.y * v_patch_color[1] + cweights.z * v_patch_color[2];\n"; |
| else if (m_case == CASE_QUADS || m_case == CASE_ISOLINES) |
| buf << " vec2 normalizedCoord = (gl_TessCoord.xy * 2.0 - vec2(1.0));\n" |
| " vec2 normalizedWeights = normalizedCoord * (vec2(1.0) - 0.3 * cos(normalizedCoord.yx * 1.57));\n" |
| " vec2 weights = normalizedWeights * 0.5 + vec2(0.5);\n" |
| " vec2 cweights = gl_TessCoord.xy;\n" |
| " gl_Position = mix(mix(gl_in[0].gl_Position, gl_in[1].gl_Position, weights.y), mix(gl_in[2].gl_Position, gl_in[3].gl_Position, weights.y), weights.x);\n" |
| " " << colorOutputName << " = mix(mix(v_patch_color[0], v_patch_color[1], cweights.y), mix(v_patch_color[2], v_patch_color[3], cweights.y), cweights.x);\n"; |
| else |
| DE_ASSERT(false); |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string IdentityGeometryShaderCase::getGeometrySource (void) const |
| { |
| const char* const geometryInputPrimitive = (m_case == CASE_ISOLINES) ? ("lines") : ("triangles"); |
| const char* const geometryOutputPrimitive = (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip"); |
| const int numEmitVertices = (m_case == CASE_ISOLINES) ? (2) : (3); |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_GEOMETRY_SHADER}" |
| "layout(" << geometryInputPrimitive << ") in;\n" |
| "layout(" << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices <<") out;\n" |
| "\n" |
| "in highp vec4 v_evaluated_color[];\n" |
| "out highp vec4 v_fragment_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n" |
| " {\n" |
| " gl_Position = gl_in[ndx].gl_Position;\n" |
| " v_fragment_color = v_evaluated_color[ndx];\n" |
| " EmitVertex();\n" |
| " }\n" |
| "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| class IdentityTessellationShaderCase : public IdentityShaderCase |
| { |
| public: |
| enum CaseType |
| { |
| CASE_TRIANGLES = 0, |
| CASE_ISOLINES, |
| }; |
| |
| IdentityTessellationShaderCase (Context& context, const char* name, const char* description, CaseType caseType); |
| ~IdentityTessellationShaderCase (void); |
| |
| private: |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| std::string getTessellationControlSource (void) const; |
| std::string getTessellationEvaluationSource (void) const; |
| std::string getGeometrySource (bool tessellationActive) const; |
| |
| enum |
| { |
| RENDER_SIZE = 256, |
| }; |
| |
| const CaseType m_case; |
| deUint32 m_dataBuffer; |
| }; |
| |
| IdentityTessellationShaderCase::IdentityTessellationShaderCase (Context& context, const char* name, const char* description, CaseType caseType) |
| : IdentityShaderCase (context, name, description) |
| , m_case (caseType) |
| , m_dataBuffer (0) |
| { |
| } |
| |
| IdentityTessellationShaderCase::~IdentityTessellationShaderCase (void) |
| { |
| deinit(); |
| } |
| |
| void IdentityTessellationShaderCase::init (void) |
| { |
| // Requirements |
| const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| |
| if (!supportsES32 && |
| (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") || |
| !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))) |
| throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions"); |
| |
| if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || |
| m_context.getRenderTarget().getHeight() < RENDER_SIZE) |
| throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target."); |
| |
| // Log |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Testing geometry shading shader program output does not change when a passthrough tessellation shader is attached.\n" |
| << "Rendering two images, first with and second without a tessellation shader. Expecting similar results.\n" |
| << "Using additive blending to detect overlap.\n" |
| << tcu::TestLog::EndMessage; |
| |
| // Resources |
| |
| { |
| static const tcu::Vec4 pointData[] = |
| { |
| tcu::Vec4( -0.4f, 0.4f, 0.0f, 1.0f ), |
| tcu::Vec4( 0.0f, -0.5f, 0.0f, 1.0f ), |
| tcu::Vec4( 0.4f, 0.4f, 0.0f, 1.0f ), |
| }; |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| gl.genBuffers(1, &m_dataBuffer); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer); |
| gl.bufferData(GL_ARRAY_BUFFER, sizeof(pointData), pointData, GL_STATIC_DRAW); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer"); |
| } |
| } |
| |
| void IdentityTessellationShaderCase::deinit (void) |
| { |
| if (m_dataBuffer) |
| { |
| m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBuffer); |
| m_dataBuffer = 0; |
| } |
| } |
| |
| IdentityTessellationShaderCase::IterateResult IdentityTessellationShaderCase::iterate (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| tcu::Surface resultWithTessellation (RENDER_SIZE, RENDER_SIZE); |
| tcu::Surface resultWithoutTessellation (RENDER_SIZE, RENDER_SIZE); |
| const int numPrimitiveVertices = (m_case == CASE_TRIANGLES) ? (3) : (2); |
| |
| const struct |
| { |
| const char* name; |
| const char* description; |
| bool containsTessellationShaders; |
| tcu::PixelBufferAccess surfaceAccess; |
| } renderTargets[] = |
| { |
| { "RenderWithTessellationShader", "Render with tessellation shader", true, resultWithTessellation.getAccess() }, |
| { "RenderWithoutTessellationShader", "Render without tessellation shader", false, resultWithoutTessellation.getAccess() }, |
| }; |
| |
| gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport"); |
| |
| gl.enable(GL_BLEND); |
| gl.blendFunc(GL_SRC_ALPHA, GL_ONE); |
| gl.blendEquation(GL_FUNC_ADD); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set blend"); |
| |
| // render with and without tessellation shader |
| for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx) |
| { |
| const tcu::ScopedLogSection section (m_testCtx.getLog(), renderTargets[renderNdx].name, renderTargets[renderNdx].description); |
| glu::ProgramSources sources; |
| |
| sources << glu::VertexSource(getVertexSource()) |
| << glu::FragmentSource(getFragmentSource()) |
| << glu::GeometrySource(getGeometrySource(renderTargets[renderNdx].containsTessellationShaders)); |
| |
| if (renderTargets[renderNdx].containsTessellationShaders) |
| sources << glu::TessellationControlSource(getTessellationControlSource()) |
| << glu::TessellationEvaluationSource(getTessellationEvaluationSource()); |
| |
| { |
| const glu::ShaderProgram program (m_context.getRenderContext(), sources); |
| const glu::VertexArray vao (m_context.getRenderContext()); |
| const int posLocation = gl.getAttribLocation(program.getProgram(), "a_position"); |
| |
| m_testCtx.getLog() << program; |
| |
| if (!program.isOk()) |
| throw tcu::TestError("could not build program"); |
| if (posLocation == -1) |
| throw tcu::TestError("a_position location was -1"); |
| |
| gl.bindVertexArray(*vao); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer); |
| gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); |
| gl.enableVertexAttribArray(posLocation); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs"); |
| |
| gl.useProgram(program.getProgram()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "use program"); |
| |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); |
| |
| if (renderTargets[renderNdx].containsTessellationShaders) |
| { |
| gl.patchParameteri(GL_PATCH_VERTICES, numPrimitiveVertices); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param"); |
| |
| gl.drawArrays(GL_PATCHES, 0, numPrimitiveVertices); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches"); |
| } |
| else |
| { |
| gl.drawArrays((m_case == CASE_TRIANGLES) ? (GL_TRIANGLES) : (GL_LINES), 0, numPrimitiveVertices); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw primitives"); |
| } |
| |
| glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess); |
| } |
| } |
| |
| // compare |
| { |
| bool imageOk; |
| |
| if (m_context.getRenderTarget().getNumSamples() > 1) |
| imageOk = tcu::fuzzyCompare(m_testCtx.getLog(), |
| "ImageCompare", |
| "Image comparison", |
| resultWithoutTessellation.getAccess(), |
| resultWithTessellation.getAccess(), |
| 0.03f, |
| tcu::COMPARE_LOG_RESULT); |
| else |
| imageOk = tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(), |
| "ImageCompare", |
| "Image comparison", |
| resultWithoutTessellation.getAccess(), |
| resultWithTessellation.getAccess(), |
| tcu::UVec4(8, 8, 8, 255), //!< threshold |
| tcu::IVec3(1, 1, 0), //!< 3x3 search kernel |
| true, //!< fragments may end up over the viewport, just ignore them |
| tcu::COMPARE_LOG_RESULT); |
| |
| if (imageOk) |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); |
| } |
| |
| return STOP; |
| } |
| |
| std::string IdentityTessellationShaderCase::getTessellationControlSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(vertices = " << ((m_case == CASE_TRIANGLES) ? (3) : (2)) << ") out;\n" |
| "\n" |
| "in highp vec4 v_vertex_color[];\n" |
| "out highp vec4 v_control_color[];\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| " v_control_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n" |
| "\n"; |
| |
| if (m_case == CASE_TRIANGLES) |
| buf << " gl_TessLevelOuter[0] = 1.0;\n" |
| " gl_TessLevelOuter[1] = 1.0;\n" |
| " gl_TessLevelOuter[2] = 1.0;\n" |
| " gl_TessLevelInner[0] = 1.0;\n"; |
| else if (m_case == CASE_ISOLINES) |
| buf << " gl_TessLevelOuter[0] = 1.0;\n" |
| " gl_TessLevelOuter[1] = 1.0;\n"; |
| else |
| DE_ASSERT(false); |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string IdentityTessellationShaderCase::getTessellationEvaluationSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(" |
| << ((m_case == CASE_TRIANGLES) ? ("triangles") : ("isolines")) |
| << ") in;\n" |
| "\n" |
| "in highp vec4 v_control_color[];\n" |
| "out highp vec4 v_evaluated_color;\n" |
| "\n" |
| "// note: No need to use precise gl_Position since we do not require gapless geometry\n" |
| "void main (void)\n" |
| "{\n"; |
| |
| if (m_case == CASE_TRIANGLES) |
| buf << " gl_Position = gl_TessCoord.x * gl_in[0].gl_Position + gl_TessCoord.y * gl_in[1].gl_Position + gl_TessCoord.z * gl_in[2].gl_Position;\n" |
| " v_evaluated_color = gl_TessCoord.x * v_control_color[0] + gl_TessCoord.y * v_control_color[1] + gl_TessCoord.z * v_control_color[2];\n"; |
| else if (m_case == CASE_ISOLINES) |
| buf << " gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);\n" |
| " v_evaluated_color = mix(v_control_color[0], v_control_color[1], gl_TessCoord.x);\n"; |
| else |
| DE_ASSERT(false); |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string IdentityTessellationShaderCase::getGeometrySource (bool tessellationActive) const |
| { |
| const char* const colorSourceName = (tessellationActive) ? ("v_evaluated_color") : ("v_vertex_color"); |
| const char* const geometryInputPrimitive = (m_case == CASE_ISOLINES) ? ("lines") : ("triangles"); |
| const char* const geometryOutputPrimitive = (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip"); |
| const int numEmitVertices = (m_case == CASE_ISOLINES) ? (11) : (8); |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_GEOMETRY_SHADER}" |
| "layout(" << geometryInputPrimitive << ") in;\n" |
| "layout(" << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices <<") out;\n" |
| "\n" |
| "in highp vec4 " << colorSourceName << "[];\n" |
| "out highp vec4 v_fragment_color;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n"; |
| |
| if (m_case == CASE_TRIANGLES) |
| { |
| buf << " vec4 centerPos = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3.0f;\n" |
| "\n" |
| " for (int ndx = 0; ndx < 4; ++ndx)\n" |
| " {\n" |
| " gl_Position = centerPos + (centerPos - gl_in[ndx % 3].gl_Position);\n" |
| " v_fragment_color = " << colorSourceName << "[ndx % 3];\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = centerPos + 0.7 * (centerPos - gl_in[ndx % 3].gl_Position);\n" |
| " v_fragment_color = " << colorSourceName << "[ndx % 3];\n" |
| " EmitVertex();\n" |
| " }\n"; |
| |
| } |
| else if (m_case == CASE_ISOLINES) |
| { |
| buf << " vec4 mdir = vec4(gl_in[0].gl_Position.y - gl_in[1].gl_Position.y, gl_in[1].gl_Position.x - gl_in[0].gl_Position.x, 0.0, 0.0);\n" |
| " for (int i = 0; i <= 10; ++i)\n" |
| " {\n" |
| " float xweight = cos(float(i) / 10.0 * 6.28) * 0.5 + 0.5;\n" |
| " float mweight = sin(float(i) / 10.0 * 6.28) * 0.1 + 0.1;\n" |
| " gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, xweight) + mweight * mdir;\n" |
| " v_fragment_color = mix(" << colorSourceName << "[0], " << colorSourceName << "[1], xweight);\n" |
| " EmitVertex();\n" |
| " }\n"; |
| } |
| else |
| DE_ASSERT(false); |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| class FeedbackPrimitiveTypeCase : public TestCase |
| { |
| public: |
| enum TessellationOutputType |
| { |
| TESSELLATION_OUT_TRIANGLES = 0, |
| TESSELLATION_OUT_QUADS, |
| TESSELLATION_OUT_ISOLINES, |
| |
| TESSELLATION_OUT_LAST |
| }; |
| enum TessellationPointMode |
| { |
| TESSELLATION_POINTMODE_OFF = 0, |
| TESSELLATION_POINTMODE_ON, |
| |
| TESSELLATION_POINTMODE_LAST |
| }; |
| enum GeometryOutputType |
| { |
| GEOMETRY_OUTPUT_POINTS = 0, |
| GEOMETRY_OUTPUT_LINES, |
| GEOMETRY_OUTPUT_TRIANGLES, |
| |
| GEOMETRY_OUTPUT_LAST |
| }; |
| |
| FeedbackPrimitiveTypeCase (Context& context, |
| const char* name, |
| const char* description, |
| TessellationOutputType tessellationOutput, |
| TessellationPointMode tessellationPointMode, |
| GeometryOutputType geometryOutputType); |
| ~FeedbackPrimitiveTypeCase (void); |
| |
| private: |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| void renderWithFeedback (tcu::Surface& dst); |
| void renderWithoutFeedback (tcu::Surface& dst); |
| void verifyFeedbackResults (const std::vector<tcu::Vec4>& feedbackResult); |
| void verifyRenderedImage (const tcu::Surface& image, const std::vector<tcu::Vec4>& vertices); |
| |
| void genTransformFeedback (void); |
| int getNumGeneratedElementsPerPrimitive (void) const; |
| int getNumGeneratedPrimitives (void) const; |
| int getNumTessellatedPrimitives (void) const; |
| int getGeometryAmplification (void) const; |
| |
| std::string getVertexSource (void) const; |
| std::string getFragmentSource (void) const; |
| std::string getTessellationControlSource (void) const; |
| std::string getTessellationEvaluationSource (void) const; |
| std::string getGeometrySource (void) const; |
| |
| static const char* getTessellationOutputDescription (TessellationOutputType tessellationOutput, |
| TessellationPointMode tessellationPointMode); |
| static const char* getGeometryInputDescription (TessellationOutputType tessellationOutput, |
| TessellationPointMode tessellationPointMode); |
| static const char* getGeometryOutputDescription (GeometryOutputType geometryOutput); |
| glw::GLenum getOutputPrimitiveGLType (void) const; |
| |
| enum |
| { |
| RENDER_SIZE = 128, |
| }; |
| |
| const TessellationOutputType m_tessellationOutput; |
| const TessellationPointMode m_tessellationPointMode; |
| const GeometryOutputType m_geometryOutputType; |
| |
| glu::ShaderProgram* m_feedbackProgram; |
| glu::ShaderProgram* m_nonFeedbackProgram; |
| deUint32 m_patchBuffer; |
| deUint32 m_feedbackID; |
| deUint32 m_feedbackBuffer; |
| }; |
| |
| FeedbackPrimitiveTypeCase::FeedbackPrimitiveTypeCase (Context& context, |
| const char* name, |
| const char* description, |
| TessellationOutputType tessellationOutput, |
| TessellationPointMode tessellationPointMode, |
| GeometryOutputType geometryOutputType) |
| : TestCase (context, name, description) |
| , m_tessellationOutput (tessellationOutput) |
| , m_tessellationPointMode (tessellationPointMode) |
| , m_geometryOutputType (geometryOutputType) |
| , m_feedbackProgram (DE_NULL) |
| , m_nonFeedbackProgram (DE_NULL) |
| , m_patchBuffer (0) |
| , m_feedbackID (0) |
| , m_feedbackBuffer (0) |
| { |
| DE_ASSERT(tessellationOutput < TESSELLATION_OUT_LAST); |
| DE_ASSERT(tessellationPointMode < TESSELLATION_POINTMODE_LAST); |
| DE_ASSERT(geometryOutputType < GEOMETRY_OUTPUT_LAST); |
| } |
| |
| FeedbackPrimitiveTypeCase::~FeedbackPrimitiveTypeCase (void) |
| { |
| deinit(); |
| } |
| |
| void FeedbackPrimitiveTypeCase::init (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| // Requirements |
| const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| |
| if (!supportsES32 && |
| (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") || |
| !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))) |
| throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions"); |
| |
| if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || |
| m_context.getRenderTarget().getHeight() < RENDER_SIZE) |
| throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target."); |
| |
| // Log |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Testing " |
| << getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) |
| << "->" |
| << getGeometryInputDescription(m_tessellationOutput, m_tessellationPointMode) |
| << " primitive conversion with and without transform feedback.\n" |
| << "Sending a patch of 4 vertices (2x2 uniform grid) to tessellation control shader.\n" |
| << "Control shader emits a patch of 9 vertices (3x3 uniform grid).\n" |
| << "Setting outer tessellation level = 3, inner = 3.\n" |
| << "Primitive generator emits " << getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) << "\n" |
| << "Geometry shader transforms emitted primitives to " << getGeometryOutputDescription(m_geometryOutputType) << "\n" |
| << "Reading back vertex positions of generated primitives using transform feedback.\n" |
| << "Verifying rendered image and feedback vertices are consistent.\n" |
| << "Rendering scene again with identical shader program, but without setting feedback varying. Expecting similar output image." |
| << tcu::TestLog::EndMessage; |
| |
| // Resources |
| |
| { |
| static const tcu::Vec4 patchBufferData[4] = |
| { |
| tcu::Vec4( -0.9f, -0.9f, 0.0f, 1.0f ), |
| tcu::Vec4( -0.9f, 0.9f, 0.0f, 1.0f ), |
| tcu::Vec4( 0.9f, -0.9f, 0.0f, 1.0f ), |
| tcu::Vec4( 0.9f, 0.9f, 0.0f, 1.0f ), |
| }; |
| |
| gl.genBuffers(1, &m_patchBuffer); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer); |
| gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer"); |
| } |
| |
| m_feedbackProgram = new glu::ShaderProgram(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(getVertexSource()) |
| << glu::FragmentSource(getFragmentSource()) |
| << glu::TessellationControlSource(getTessellationControlSource()) |
| << glu::TessellationEvaluationSource(getTessellationEvaluationSource()) |
| << glu::GeometrySource(getGeometrySource()) |
| << glu::TransformFeedbackVarying("tf_someVertexPosition") |
| << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)); |
| m_testCtx.getLog() << *m_feedbackProgram; |
| if (!m_feedbackProgram->isOk()) |
| throw tcu::TestError("failed to build program"); |
| |
| m_nonFeedbackProgram = new glu::ShaderProgram(m_context.getRenderContext(), |
| glu::ProgramSources() |
| << glu::VertexSource(getVertexSource()) |
| << glu::FragmentSource(getFragmentSource()) |
| << glu::TessellationControlSource(getTessellationControlSource()) |
| << glu::TessellationEvaluationSource(getTessellationEvaluationSource()) |
| << glu::GeometrySource(getGeometrySource())); |
| if (!m_nonFeedbackProgram->isOk()) |
| { |
| m_testCtx.getLog() << *m_nonFeedbackProgram; |
| throw tcu::TestError("failed to build program"); |
| } |
| |
| genTransformFeedback(); |
| } |
| |
| void FeedbackPrimitiveTypeCase::deinit (void) |
| { |
| if (m_patchBuffer) |
| { |
| m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer); |
| m_patchBuffer = 0; |
| } |
| |
| if (m_feedbackBuffer) |
| { |
| m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_feedbackBuffer); |
| m_feedbackBuffer = 0; |
| } |
| |
| if (m_feedbackID) |
| { |
| m_context.getRenderContext().getFunctions().deleteTransformFeedbacks(1, &m_feedbackID); |
| m_feedbackID = 0; |
| } |
| |
| if (m_feedbackProgram) |
| { |
| delete m_feedbackProgram; |
| m_feedbackProgram = DE_NULL; |
| } |
| |
| if (m_nonFeedbackProgram) |
| { |
| delete m_nonFeedbackProgram; |
| m_nonFeedbackProgram = DE_NULL; |
| } |
| } |
| |
| FeedbackPrimitiveTypeCase::IterateResult FeedbackPrimitiveTypeCase::iterate (void) |
| { |
| tcu::Surface feedbackResult (RENDER_SIZE, RENDER_SIZE); |
| tcu::Surface nonFeedbackResult (RENDER_SIZE, RENDER_SIZE); |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| |
| // render with and without XFB |
| renderWithFeedback(feedbackResult); |
| renderWithoutFeedback(nonFeedbackResult); |
| |
| // compare |
| { |
| bool imageOk; |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Comparing the image rendered with no transform feedback against the image rendered with enabled transform feedback." << tcu::TestLog::EndMessage; |
| |
| if (m_context.getRenderTarget().getNumSamples() > 1) |
| imageOk = tcu::fuzzyCompare(m_testCtx.getLog(), |
| "ImageCompare", |
| "Image comparison", |
| feedbackResult.getAccess(), |
| nonFeedbackResult.getAccess(), |
| 0.03f, |
| tcu::COMPARE_LOG_RESULT); |
| else |
| imageOk = tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(), |
| "ImageCompare", |
| "Image comparison", |
| feedbackResult.getAccess(), |
| nonFeedbackResult.getAccess(), |
| tcu::UVec4(8, 8, 8, 255), //!< threshold |
| tcu::IVec3(1, 1, 0), //!< 3x3 search kernel |
| true, //!< fragments may end up over the viewport, just ignore them |
| tcu::COMPARE_LOG_RESULT); |
| |
| if (!imageOk) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed"); |
| } |
| |
| return STOP; |
| } |
| |
| void FeedbackPrimitiveTypeCase::renderWithFeedback(tcu::Surface& dst) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| const glu::VertexArray vao (m_context.getRenderContext()); |
| const glu::Query primitivesGeneratedQuery (m_context.getRenderContext()); |
| const int posLocation = gl.getAttribLocation(m_feedbackProgram->getProgram(), "a_position"); |
| const glw::GLenum feedbackPrimitiveMode = getOutputPrimitiveGLType(); |
| |
| if (posLocation == -1) |
| throw tcu::TestError("a_position was -1"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Rendering with transform feedback" << tcu::TestLog::EndMessage; |
| |
| gl.viewport(0, 0, dst.getWidth(), dst.getHeight()); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); |
| |
| gl.bindVertexArray(*vao); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer); |
| gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); |
| gl.enableVertexAttribArray(posLocation); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs"); |
| |
| gl.useProgram(m_feedbackProgram->getProgram()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "use program"); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, 4); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param"); |
| |
| gl.beginQuery(GL_PRIMITIVES_GENERATED, *primitivesGeneratedQuery); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "begin GL_PRIMITIVES_GENERATED query"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Begin transform feedback with mode " << glu::getPrimitiveTypeStr(feedbackPrimitiveMode) << tcu::TestLog::EndMessage; |
| |
| gl.beginTransformFeedback(feedbackPrimitiveMode); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "begin xfb"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES" << tcu::TestLog::EndMessage; |
| |
| gl.drawArrays(GL_PATCHES, 0, 4); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches"); |
| |
| gl.endTransformFeedback(); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "end xfb"); |
| |
| gl.endQuery(GL_PRIMITIVES_GENERATED); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "end GL_PRIMITIVES_GENERATED query"); |
| |
| glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels"); |
| |
| // verify GL_PRIMITIVES_GENERATED |
| { |
| glw::GLuint primitivesGeneratedResult = 0; |
| gl.getQueryObjectuiv(*primitivesGeneratedQuery, GL_QUERY_RESULT, &primitivesGeneratedResult); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_PRIMITIVES_GENERATED value"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Verifying GL_PRIMITIVES_GENERATED, expecting " << getNumGeneratedPrimitives() << tcu::TestLog::EndMessage; |
| |
| if ((int)primitivesGeneratedResult != getNumGeneratedPrimitives()) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_PRIMITIVES_GENERATED was " << primitivesGeneratedResult << tcu::TestLog::EndMessage; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected GL_PRIMITIVES_GENERATED"); |
| } |
| else |
| m_testCtx.getLog() << tcu::TestLog::Message << "GL_PRIMITIVES_GENERATED valid." << tcu::TestLog::EndMessage; |
| } |
| |
| // feedback |
| { |
| std::vector<tcu::Vec4> feedbackResults (getNumGeneratedElementsPerPrimitive() * getNumGeneratedPrimitives()); |
| const void* mappedPtr = gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (glw::GLsizeiptr)(feedbackResults.size() * sizeof(tcu::Vec4)), GL_MAP_READ_BIT); |
| glw::GLboolean unmapResult; |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Reading transform feedback buffer." << tcu::TestLog::EndMessage; |
| if (!mappedPtr) |
| throw tcu::TestError("mapBufferRange returned null"); |
| |
| deMemcpy(feedbackResults[0].getPtr(), mappedPtr, (int)(feedbackResults.size() * sizeof(tcu::Vec4))); |
| |
| unmapResult = gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "unmapBuffer"); |
| |
| if (unmapResult != GL_TRUE) |
| throw tcu::TestError("unmapBuffer failed, did not return true"); |
| |
| // verify transform results |
| verifyFeedbackResults(feedbackResults); |
| |
| // verify feedback results are consistent with rendered image |
| verifyRenderedImage(dst, feedbackResults); |
| } |
| } |
| |
| void FeedbackPrimitiveTypeCase::renderWithoutFeedback (tcu::Surface& dst) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| const glu::VertexArray vao (m_context.getRenderContext()); |
| const int posLocation = gl.getAttribLocation(m_nonFeedbackProgram->getProgram(), "a_position"); |
| |
| if (posLocation == -1) |
| throw tcu::TestError("a_position was -1"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Rendering without transform feedback" << tcu::TestLog::EndMessage; |
| |
| gl.viewport(0, 0, dst.getWidth(), dst.getHeight()); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); |
| |
| gl.bindVertexArray(*vao); |
| gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer); |
| gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL); |
| gl.enableVertexAttribArray(posLocation); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs"); |
| |
| gl.useProgram(m_nonFeedbackProgram->getProgram()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "use program"); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, 4); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param"); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES" << tcu::TestLog::EndMessage; |
| |
| gl.drawArrays(GL_PATCHES, 0, 4); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches"); |
| |
| glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels"); |
| } |
| |
| void FeedbackPrimitiveTypeCase::verifyFeedbackResults (const std::vector<tcu::Vec4>& feedbackResult) |
| { |
| const int geometryAmplification = getGeometryAmplification(); |
| const int elementsPerPrimitive = getNumGeneratedElementsPerPrimitive(); |
| const int errorFloodThreshold = 8; |
| int readNdx = 0; |
| int numErrors = 0; |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Verifying feedback results." << tcu::TestLog::EndMessage; |
| |
| for (int tessellatedPrimitiveNdx = 0; tessellatedPrimitiveNdx < getNumTessellatedPrimitives(); ++tessellatedPrimitiveNdx) |
| { |
| const tcu::Vec4 primitiveVertex = feedbackResult[readNdx]; |
| |
| // check the generated vertices are in the proper range (range: -0.4 <-> 0.4) |
| { |
| const float equalThreshold = 1.0e-6f; |
| const bool centroidOk = (primitiveVertex.x() >= -0.4f - equalThreshold) && |
| (primitiveVertex.x() <= 0.4f + equalThreshold) && |
| (primitiveVertex.y() >= -0.4f - equalThreshold) && |
| (primitiveVertex.y() <= 0.4f + equalThreshold) && |
| (de::abs(primitiveVertex.z()) < equalThreshold) && |
| (de::abs(primitiveVertex.w() - 1.0f) < equalThreshold); |
| |
| if (!centroidOk && numErrors++ < errorFloodThreshold) |
| { |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Element at index " << (readNdx) << " (tessellation invocation " << tessellatedPrimitiveNdx << ")\n" |
| << "\texpected vertex in range: ( [-0.4, 0.4], [-0.4, 0.4], 0.0, 1.0 )\n" |
| << "\tgot: " << primitiveVertex |
| << tcu::TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid feedback output"); |
| |
| ++readNdx; |
| continue; |
| } |
| } |
| |
| // check all other primitives generated from this tessellated primitive have the same feedback value |
| for (int generatedPrimitiveNdx = 0; generatedPrimitiveNdx < geometryAmplification; ++generatedPrimitiveNdx) |
| for (int primitiveVertexNdx = 0; primitiveVertexNdx < elementsPerPrimitive; ++primitiveVertexNdx) |
| { |
| const tcu::Vec4 generatedElementVertex = feedbackResult[readNdx]; |
| const tcu::Vec4 equalThreshold (1.0e-6f); |
| |
| if (tcu::boolAny(tcu::greaterThan(tcu::abs(primitiveVertex - generatedElementVertex), equalThreshold))) |
| { |
| if (numErrors++ < errorFloodThreshold) |
| { |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Element at index " << (readNdx) << " (tessellation invocation " << tessellatedPrimitiveNdx << ", geometry primitive " << generatedPrimitiveNdx << ", emitted vertex " << primitiveVertexNdx << "):\n" |
| << "\tfeedback result was not contant over whole primitive.\n" |
| << "\tfirst emitted value: " << primitiveVertex << "\n" |
| << "\tcurrent emitted value:" << generatedElementVertex << "\n" |
| << tcu::TestLog::EndMessage; |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got multiple different feedback values for a single primitive"); |
| } |
| |
| readNdx++; |
| } |
| } |
| |
| if (numErrors > errorFloodThreshold) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (numErrors - errorFloodThreshold) << " error(s)." << tcu::TestLog::EndMessage; |
| } |
| |
| static bool feedbackResultCompare (const tcu::Vec4& a, const tcu::Vec4& b) |
| { |
| if (a.x() < b.x()) |
| return true; |
| if (a.x() > b.x()) |
| return false; |
| |
| return a.y() < b.y(); |
| } |
| |
| void FeedbackPrimitiveTypeCase::verifyRenderedImage (const tcu::Surface& image, const std::vector<tcu::Vec4>& tfVertices) |
| { |
| std::vector<tcu::Vec4> vertices; |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Comparing result image against feedback results." << tcu::TestLog::EndMessage; |
| |
| // Check only unique vertices |
| std::unique_copy(tfVertices.begin(), tfVertices.end(), std::back_insert_iterator<std::vector<tcu::Vec4> >(vertices)); |
| std::sort(vertices.begin(), vertices.end(), feedbackResultCompare); |
| vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end()); |
| |
| // Verifying vertices recorded with feedback actually ended up on the result image |
| for (int ndx = 0; ndx < (int)vertices.size(); ++ndx) |
| { |
| // Rasterization (of lines) may deviate by one pixel. In addition to that, allow minimal errors in rasterized position vs. feedback result. |
| // This minimal error could result in a difference in rounding => allow one additional pixel in deviation |
| |
| const int rasterDeviation = 2; |
| const tcu::IVec2 rasterPos ((int)deFloatRound((vertices[ndx].x() * 0.5f + 0.5f) * (float)image.getWidth()), (int)deFloatRound((vertices[ndx].y() * 0.5f + 0.5f) * (float)image.getHeight())); |
| |
| // Find produced rasterization results |
| bool found = false; |
| |
| for (int dy = -rasterDeviation; dy <= rasterDeviation && !found; ++dy) |
| for (int dx = -rasterDeviation; dx <= rasterDeviation && !found; ++dx) |
| { |
| // Raster result could end up outside the viewport |
| if (rasterPos.x() + dx < 0 || rasterPos.x() + dx >= image.getWidth() || |
| rasterPos.y() + dy < 0 || rasterPos.y() + dy >= image.getHeight()) |
| found = true; |
| else |
| { |
| const tcu::RGBA result = image.getPixel(rasterPos.x() + dx, rasterPos.y() + dy); |
| |
| if(!isBlack(result)) |
| found = true; |
| } |
| } |
| |
| if (!found) |
| { |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Vertex " << vertices[ndx] << "\n" |
| << "\tCould not find rasterization output for vertex.\n" |
| << "\tExpected non-black pixels near " << rasterPos |
| << tcu::TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid result image"); |
| } |
| } |
| } |
| |
| void FeedbackPrimitiveTypeCase::genTransformFeedback (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| const int elementsPerPrimitive = getNumGeneratedElementsPerPrimitive(); |
| const int feedbackPrimitives = getNumGeneratedPrimitives(); |
| const int feedbackElements = elementsPerPrimitive * feedbackPrimitives; |
| const std::vector<tcu::Vec4> initialBuffer (feedbackElements, tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f)); |
| |
| gl.genTransformFeedbacks(1, &m_feedbackID); |
| gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_feedbackID); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen transform feedback"); |
| |
| gl.genBuffers(1, &m_feedbackBuffer); |
| gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_feedbackBuffer); |
| gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(tcu::Vec4) * initialBuffer.size(), initialBuffer[0].getPtr(), GL_STATIC_COPY); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen feedback buffer"); |
| |
| gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_feedbackBuffer); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "bind feedback buffer"); |
| } |
| |
| static int getTriangleNumOutputPrimitives (int tessellationLevel) |
| { |
| if (tessellationLevel == 1) |
| return 1; |
| else if (tessellationLevel == 2) |
| return 6; |
| else |
| return 3 * (2 + 2 * (tessellationLevel - 2)) + getTriangleNumOutputPrimitives(tessellationLevel - 2); |
| } |
| |
| static int getTriangleNumOutputPrimitivesPoints (int tessellationLevel) |
| { |
| if (tessellationLevel == 0) |
| return 1; |
| else if (tessellationLevel == 1) |
| return 3; |
| else |
| return 3 + 3 * (tessellationLevel - 1) + getTriangleNumOutputPrimitivesPoints(tessellationLevel - 2); |
| } |
| |
| int FeedbackPrimitiveTypeCase::getNumGeneratedElementsPerPrimitive (void) const |
| { |
| if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES) |
| return 3; |
| else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) |
| return 2; |
| else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) |
| return 1; |
| else |
| { |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| int FeedbackPrimitiveTypeCase::getNumGeneratedPrimitives (void) const |
| { |
| return getNumTessellatedPrimitives() * getGeometryAmplification(); |
| } |
| |
| int FeedbackPrimitiveTypeCase::getNumTessellatedPrimitives (void) const |
| { |
| const int tessellationLevel = 3; |
| |
| if (m_tessellationPointMode == TESSELLATION_POINTMODE_OFF) |
| { |
| if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) |
| return getTriangleNumOutputPrimitives(tessellationLevel); |
| else if (m_tessellationOutput == TESSELLATION_OUT_QUADS) |
| return tessellationLevel * tessellationLevel * 2; // tessellated as triangles |
| else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) |
| return tessellationLevel * tessellationLevel; |
| } |
| else if (m_tessellationPointMode == TESSELLATION_POINTMODE_ON) |
| { |
| if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) |
| return getTriangleNumOutputPrimitivesPoints(tessellationLevel); |
| else if (m_tessellationOutput == TESSELLATION_OUT_QUADS) |
| return (tessellationLevel + 1) * (tessellationLevel + 1); |
| else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) |
| return tessellationLevel * (tessellationLevel + 1); |
| } |
| |
| DE_ASSERT(false); |
| return -1; |
| } |
| |
| int FeedbackPrimitiveTypeCase::getGeometryAmplification (void) const |
| { |
| const int outputAmplification = (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (2) : (1); |
| const int numInputVertices = (m_tessellationPointMode) ? (1) : (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) : (3); |
| |
| return outputAmplification * numInputVertices; |
| } |
| |
| glw::GLenum FeedbackPrimitiveTypeCase::getOutputPrimitiveGLType (void) const |
| { |
| if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES) |
| return GL_TRIANGLES; |
| else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) |
| return GL_LINES; |
| else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) |
| return GL_POINTS; |
| else |
| { |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| std::string FeedbackPrimitiveTypeCase::getVertexSource (void) const |
| { |
| return specializeShader(s_positionVertexShader, m_context.getRenderContext().getType()); |
| } |
| |
| std::string FeedbackPrimitiveTypeCase::getFragmentSource (void) const |
| { |
| return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType()); |
| } |
| |
| std::string FeedbackPrimitiveTypeCase::getTessellationControlSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(vertices = 9) out;\n" |
| "\n" |
| "uniform highp float u_innerTessellationLevel;\n" |
| "uniform highp float u_outerTessellationLevel;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " if (gl_PatchVerticesIn != 4)\n" |
| " return;\n" |
| "\n" |
| " // Convert input 2x2 grid to 3x3 grid\n" |
| " float xweight = float(gl_InvocationID % 3) / 2.0f;\n" |
| " float yweight = float(gl_InvocationID / 3) / 2.0f;\n" |
| "\n" |
| " vec4 y0 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, yweight);\n" |
| " vec4 y1 = mix(gl_in[2].gl_Position, gl_in[3].gl_Position, yweight);\n" |
| "\n" |
| " gl_out[gl_InvocationID].gl_Position = mix(y0, y1, xweight);\n" |
| "\n"; |
| |
| if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) |
| buf << " gl_TessLevelOuter[0] = 3.0;\n" |
| " gl_TessLevelOuter[1] = 3.0;\n" |
| " gl_TessLevelOuter[2] = 3.0;\n" |
| " gl_TessLevelInner[0] = 3.0;\n"; |
| else if (m_tessellationOutput == TESSELLATION_OUT_QUADS) |
| buf << " gl_TessLevelOuter[0] = 3.0;\n" |
| " gl_TessLevelOuter[1] = 3.0;\n" |
| " gl_TessLevelOuter[2] = 3.0;\n" |
| " gl_TessLevelOuter[3] = 3.0;\n" |
| " gl_TessLevelInner[0] = 3.0;\n" |
| " gl_TessLevelInner[1] = 3.0;\n"; |
| else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) |
| buf << " gl_TessLevelOuter[0] = 3.0;\n" |
| " gl_TessLevelOuter[1] = 3.0;\n"; |
| else |
| DE_ASSERT(false); |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string FeedbackPrimitiveTypeCase::getTessellationEvaluationSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(" |
| << ((m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) ? ("triangles") : (m_tessellationOutput == TESSELLATION_OUT_QUADS) ? ("quads") : ("isolines")) |
| << ((m_tessellationPointMode) ? (", point_mode") : ("")) |
| << ") in;\n" |
| "\n" |
| "out highp vec4 v_tessellationCoords;\n" |
| "\n" |
| "// note: No need to use precise gl_Position since we do not require gapless geometry\n" |
| "void main (void)\n" |
| "{\n" |
| " if (gl_PatchVerticesIn != 9)\n" |
| " return;\n" |
| "\n" |
| " vec4 patchCentroid = vec4(0.0);\n" |
| " for (int ndx = 0; ndx < gl_PatchVerticesIn; ++ndx)\n" |
| " patchCentroid += gl_in[ndx].gl_Position;\n" |
| " patchCentroid /= patchCentroid.w;\n" |
| "\n"; |
| |
| if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) |
| buf << " // map barycentric coords to 2d coords\n" |
| " const vec3 tessDirX = vec3( 0.4, 0.4, 0.0);\n" |
| " const vec3 tessDirY = vec3( 0.0, -0.4, 0.0);\n" |
| " const vec3 tessDirZ = vec3(-0.4, 0.4, 0.0);\n" |
| " gl_Position = patchCentroid + vec4(gl_TessCoord.x * tessDirX + gl_TessCoord.y * tessDirY + gl_TessCoord.z * tessDirZ, 0.0);\n"; |
| else if (m_tessellationOutput == TESSELLATION_OUT_QUADS || m_tessellationOutput == TESSELLATION_OUT_ISOLINES) |
| buf << " gl_Position = patchCentroid + vec4(gl_TessCoord.x * 0.8 - 0.4, gl_TessCoord.y * 0.8 - 0.4, 0.0, 0.0);\n"; |
| else |
| DE_ASSERT(false); |
| |
| buf << " v_tessellationCoords = vec4(gl_TessCoord, 0.0);\n" |
| "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string FeedbackPrimitiveTypeCase::getGeometrySource (void) const |
| { |
| const char* const geometryInputPrimitive = (m_tessellationPointMode) ? ("points") : (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? ("lines") : ("triangles"); |
| const char* const geometryOutputPrimitive = (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? ("points") : (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? ("line_strip") : ("triangle_strip"); |
| const int numInputVertices = (m_tessellationPointMode) ? (1) : (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) : (3); |
| const int numSingleVertexOutputVertices = (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? (1) : (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (4) : (3); |
| const int numEmitVertices = numInputVertices * numSingleVertexOutputVertices; |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_GEOMETRY_SHADER}" |
| "layout(" << geometryInputPrimitive << ") in;\n" |
| "layout(" << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices <<") out;\n" |
| "\n" |
| "in highp vec4 v_tessellationCoords[];\n" |
| "out highp vec4 tf_someVertexPosition;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " // Emit primitive\n" |
| " for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n" |
| " {\n"; |
| |
| switch (m_geometryOutputType) |
| { |
| case GEOMETRY_OUTPUT_POINTS: |
| buf << " // Draw point on vertex\n" |
| " gl_Position = gl_in[ndx].gl_Position;\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n"; |
| break; |
| |
| case GEOMETRY_OUTPUT_LINES: |
| buf << " // Draw cross on vertex\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4(-0.02, -0.02, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, 0.02, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " EndPrimitive();\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, -0.02, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4(-0.02, 0.02, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " EndPrimitive();\n"; |
| break; |
| |
| case GEOMETRY_OUTPUT_TRIANGLES: |
| buf << " // Draw triangle on vertex\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4( 0.00, -0.02, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, 0.00, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " gl_Position = gl_in[ndx].gl_Position + vec4( -0.02, 0.00, 0.0, 0.0);\n" |
| " tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n" |
| " EmitVertex();\n" |
| " EndPrimitive();\n"; |
| break; |
| |
| default: |
| DE_ASSERT(false); |
| return ""; |
| } |
| |
| buf << " }\n" |
| "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| const char* FeedbackPrimitiveTypeCase::getTessellationOutputDescription (TessellationOutputType tessellationOutput, TessellationPointMode pointMode) |
| { |
| switch (tessellationOutput) |
| { |
| case TESSELLATION_OUT_TRIANGLES: return (pointMode) ? ("points (triangles in point mode)") : ("triangles"); |
| case TESSELLATION_OUT_QUADS: return (pointMode) ? ("points (quads in point mode)") : ("quads"); |
| case TESSELLATION_OUT_ISOLINES: return (pointMode) ? ("points (isolines in point mode)") : ("isolines"); |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| const char* FeedbackPrimitiveTypeCase::getGeometryInputDescription (TessellationOutputType tessellationOutput, TessellationPointMode pointMode) |
| { |
| switch (tessellationOutput) |
| { |
| case TESSELLATION_OUT_TRIANGLES: return (pointMode) ? ("points") : ("triangles"); |
| case TESSELLATION_OUT_QUADS: return (pointMode) ? ("points") : ("triangles"); |
| case TESSELLATION_OUT_ISOLINES: return (pointMode) ? ("points") : ("lines"); |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| const char* FeedbackPrimitiveTypeCase::getGeometryOutputDescription (GeometryOutputType geometryOutput) |
| { |
| switch (geometryOutput) |
| { |
| case GEOMETRY_OUTPUT_POINTS: return "points"; |
| case GEOMETRY_OUTPUT_LINES: return "lines"; |
| case GEOMETRY_OUTPUT_TRIANGLES: return "triangles"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| class PointSizeCase : public TestCase |
| { |
| public: |
| enum Flags |
| { |
| FLAG_VERTEX_SET = 0x01, // !< set gl_PointSize in vertex shader |
| FLAG_TESSELLATION_CONTROL_SET = 0x02, // !< set gl_PointSize in tessellation evaluation shader |
| FLAG_TESSELLATION_EVALUATION_SET = 0x04, // !< set gl_PointSize in tessellation control shader |
| FLAG_TESSELLATION_ADD = 0x08, // !< read and add to gl_PointSize in tessellation shader pair |
| FLAG_TESSELLATION_DONT_SET = 0x10, // !< don't set gl_PointSize in tessellation shader |
| FLAG_GEOMETRY_SET = 0x20, // !< set gl_PointSize in geometry shader |
| FLAG_GEOMETRY_ADD = 0x40, // !< read and add to gl_PointSize in geometry shader |
| FLAG_GEOMETRY_DONT_SET = 0x80, // !< don't set gl_PointSize in geometry shader |
| }; |
| |
| PointSizeCase (Context& context, const char* name, const char* description, int flags); |
| ~PointSizeCase (void); |
| |
| static std::string genTestCaseName (int flags); |
| static std::string genTestCaseDescription (int flags); |
| |
| private: |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| void checkExtensions (void) const; |
| void checkPointSizeRequirements (void) const; |
| |
| void renderTo (tcu::Surface& dst); |
| bool verifyImage (const tcu::Surface& src); |
| int getExpectedPointSize (void) const; |
| |
| std::string genVertexSource (void) const; |
| std::string genFragmentSource (void) const; |
| std::string genTessellationControlSource (void) const; |
| std::string genTessellationEvaluationSource (void) const; |
| std::string genGeometrySource (void) const; |
| |
| enum |
| { |
| RENDER_SIZE = 32, |
| }; |
| |
| const int m_flags; |
| glu::ShaderProgram* m_program; |
| }; |
| |
| PointSizeCase::PointSizeCase (Context& context, const char* name, const char* description, int flags) |
| : TestCase (context, name, description) |
| , m_flags (flags) |
| , m_program (DE_NULL) |
| { |
| } |
| |
| PointSizeCase::~PointSizeCase (void) |
| { |
| deinit(); |
| } |
| |
| std::string PointSizeCase::genTestCaseName (int flags) |
| { |
| std::ostringstream buf; |
| |
| // join per-bit descriptions into a single string with '_' separator |
| if (flags & FLAG_VERTEX_SET) buf << "vertex_set"; |
| if (flags & FLAG_TESSELLATION_CONTROL_SET) buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET-1)) ? ("_") : ("")) << "control_set"; |
| if (flags & FLAG_TESSELLATION_EVALUATION_SET) buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET-1)) ? ("_") : ("")) << "evaluation_set"; |
| if (flags & FLAG_TESSELLATION_ADD) buf << ((flags & (FLAG_TESSELLATION_ADD-1)) ? ("_") : ("")) << "control_pass_eval_add"; |
| if (flags & FLAG_TESSELLATION_DONT_SET) buf << ((flags & (FLAG_TESSELLATION_DONT_SET-1)) ? ("_") : ("")) << "eval_default"; |
| if (flags & FLAG_GEOMETRY_SET) buf << ((flags & (FLAG_GEOMETRY_SET-1)) ? ("_") : ("")) << "geometry_set"; |
| if (flags & FLAG_GEOMETRY_ADD) buf << ((flags & (FLAG_GEOMETRY_ADD-1)) ? ("_") : ("")) << "geometry_add"; |
| if (flags & FLAG_GEOMETRY_DONT_SET) buf << ((flags & (FLAG_GEOMETRY_DONT_SET-1)) ? ("_") : ("")) << "geometry_default"; |
| |
| return buf.str(); |
| } |
| |
| std::string PointSizeCase::genTestCaseDescription (int flags) |
| { |
| std::ostringstream buf; |
| |
| // join per-bit descriptions into a single string with ", " separator |
| if (flags & FLAG_VERTEX_SET) buf << "set point size in vertex shader"; |
| if (flags & FLAG_TESSELLATION_CONTROL_SET) buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET-1)) ? (", ") : ("")) << "set point size in tessellation control shader"; |
| if (flags & FLAG_TESSELLATION_EVALUATION_SET) buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET-1)) ? (", ") : ("")) << "set point size in tessellation evaluation shader"; |
| if (flags & FLAG_TESSELLATION_ADD) buf << ((flags & (FLAG_TESSELLATION_ADD-1)) ? (", ") : ("")) << "add to point size in tessellation shader"; |
| if (flags & FLAG_TESSELLATION_DONT_SET) buf << ((flags & (FLAG_TESSELLATION_DONT_SET-1)) ? (", ") : ("")) << "don't set point size in tessellation evaluation shader"; |
| if (flags & FLAG_GEOMETRY_SET) buf << ((flags & (FLAG_GEOMETRY_SET-1)) ? (", ") : ("")) << "set point size in geometry shader"; |
| if (flags & FLAG_GEOMETRY_ADD) buf << ((flags & (FLAG_GEOMETRY_ADD-1)) ? (", ") : ("")) << "add to point size in geometry shader"; |
| if (flags & FLAG_GEOMETRY_DONT_SET) buf << ((flags & (FLAG_GEOMETRY_DONT_SET-1)) ? (", ") : ("")) << "don't set point size in geometry shader"; |
| |
| return buf.str(); |
| } |
| |
| void PointSizeCase::init (void) |
| { |
| checkExtensions(); |
| checkPointSizeRequirements(); |
| |
| // log |
| |
| if (m_flags & FLAG_VERTEX_SET) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in vertex shader to 2.0." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_TESSELLATION_CONTROL_SET) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in tessellation control shader to 4.0. (And ignoring it in evaluation)." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_TESSELLATION_EVALUATION_SET) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in tessellation evaluation shader to 4.0." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_TESSELLATION_ADD) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Reading point size in tessellation control shader and adding 2.0 to it in evaluation." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_TESSELLATION_DONT_SET) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Not setting point size in tessellation evaluation shader (resulting in the default point size)." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_GEOMETRY_SET) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in geometry shader to 6.0." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_GEOMETRY_ADD) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Reading point size in geometry shader and adding 2.0." << tcu::TestLog::EndMessage; |
| if (m_flags & FLAG_GEOMETRY_DONT_SET) |
| m_testCtx.getLog() << tcu::TestLog::Message << "Not setting point size in geometry shader (resulting in the default point size)." << tcu::TestLog::EndMessage; |
| |
| // program |
| |
| { |
| glu::ProgramSources sources; |
| sources << glu::VertexSource(genVertexSource()) |
| << glu::FragmentSource(genFragmentSource()); |
| |
| if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET)) |
| sources << glu::TessellationControlSource(genTessellationControlSource()) |
| << glu::TessellationEvaluationSource(genTessellationEvaluationSource()); |
| |
| if (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET)) |
| sources << glu::GeometrySource(genGeometrySource()); |
| |
| m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources); |
| |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| throw tcu::TestError("failed to build program"); |
| } |
| } |
| |
| void PointSizeCase::deinit (void) |
| { |
| delete m_program; |
| m_program = DE_NULL; |
| } |
| |
| PointSizeCase::IterateResult PointSizeCase::iterate (void) |
| { |
| tcu::Surface resultImage(RENDER_SIZE, RENDER_SIZE); |
| |
| renderTo(resultImage); |
| |
| if (verifyImage(resultImage)) |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| |
| return STOP; |
| } |
| |
| void PointSizeCase::checkExtensions (void) const |
| { |
| std::vector<std::string> requiredExtensions; |
| const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| bool allOk = true; |
| |
| if ((m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET)) && !supportsES32) |
| requiredExtensions.push_back("GL_EXT_tessellation_shader"); |
| |
| if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD)) |
| requiredExtensions.push_back("GL_EXT_tessellation_point_size"); |
| |
| if ((m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET))) && !supportsES32) |
| requiredExtensions.push_back("GL_EXT_geometry_shader"); |
| |
| if (m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD))) |
| requiredExtensions.push_back("GL_EXT_geometry_point_size"); |
| |
| for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx) |
| if (!m_context.getContextInfo().isExtensionSupported(requiredExtensions[ndx].c_str())) |
| allOk = false; |
| |
| if (!allOk) |
| { |
| std::ostringstream extensionList; |
| |
| for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx) |
| { |
| if (ndx != 0) |
| extensionList << ", "; |
| extensionList << requiredExtensions[ndx]; |
| } |
| |
| throw tcu::NotSupportedError("Test requires {" + extensionList.str() + "} extension(s)"); |
| } |
| } |
| |
| void PointSizeCase::checkPointSizeRequirements (void) const |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| float aliasedSizeRange[2] = { 0.0f, 0.0f }; |
| const int requiredSize = getExpectedPointSize(); |
| |
| gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, aliasedSizeRange); |
| |
| if (float(requiredSize) > aliasedSizeRange[1]) |
| throw tcu::NotSupportedError("Test requires point size " + de::toString(requiredSize)); |
| } |
| |
| void PointSizeCase::renderTo (tcu::Surface& dst) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| const bool tessellationActive = (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET)) != 0; |
| const int positionLocation = gl.getAttribLocation(m_program->getProgram(), "a_position"); |
| const glu::VertexArray vao (m_context.getRenderContext()); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Rendering single point." << tcu::TestLog::EndMessage; |
| |
| if (positionLocation == -1) |
| throw tcu::TestError("Attribute a_position location was -1"); |
| |
| gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "clear"); |
| |
| gl.bindVertexArray(*vao); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao"); |
| |
| gl.useProgram(m_program->getProgram()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "use program"); |
| |
| gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); |
| |
| if (tessellationActive) |
| { |
| gl.patchParameteri(GL_PATCH_VERTICES, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param"); |
| |
| gl.drawArrays(GL_PATCHES, 0, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches"); |
| } |
| else |
| { |
| gl.drawArrays(GL_POINTS, 0, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "draw points"); |
| } |
| |
| glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess()); |
| } |
| |
| bool PointSizeCase::verifyImage (const tcu::Surface& src) |
| { |
| const bool MSAATarget = (m_context.getRenderTarget().getNumSamples() > 1); |
| const int expectedSize = getExpectedPointSize(); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered point size. Expecting " << expectedSize << " pixels." << tcu::TestLog::EndMessage; |
| m_testCtx.getLog() << tcu::TestLog::Image("RenderImage", "Rendered image", src.getAccess()); |
| |
| { |
| bool resultAreaFound = false; |
| tcu::IVec4 resultArea; |
| |
| // Find rasterization output area |
| |
| for (int y = 0; y < src.getHeight(); ++y) |
| for (int x = 0; x < src.getWidth(); ++x) |
| { |
| if (!isBlack(src.getPixel(x, y))) |
| { |
| if (!resultAreaFound) |
| { |
| // first fragment |
| resultArea = tcu::IVec4(x, y, x + 1, y + 1); |
| resultAreaFound = true; |
| } |
| else |
| { |
| // union area |
| resultArea.x() = de::min(resultArea.x(), x); |
| resultArea.y() = de::min(resultArea.y(), y); |
| resultArea.z() = de::max(resultArea.z(), x+1); |
| resultArea.w() = de::max(resultArea.w(), y+1); |
| } |
| } |
| } |
| |
| if (!resultAreaFound) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Verification failed, could not find any point fragments." << tcu::TestLog::EndMessage; |
| return false; |
| } |
| |
| // verify area size |
| if (MSAATarget) |
| { |
| const tcu::IVec2 pointSize = resultArea.swizzle(2,3) - resultArea.swizzle(0, 1); |
| |
| // MSAA: edges may be a little fuzzy |
| if (de::abs(pointSize.x() - pointSize.y()) > 1) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Rasterized point is not a square. Detected point size was " << pointSize << tcu::TestLog::EndMessage; |
| return false; |
| } |
| |
| // MSAA may produce larger areas, allow one pixel larger |
| if (expectedSize != de::max(pointSize.x(), pointSize.y()) && (expectedSize+1) != de::max(pointSize.x(), pointSize.y())) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize << ", got " << de::max(pointSize.x(), pointSize.y()) << tcu::TestLog::EndMessage; |
| return false; |
| } |
| } |
| else |
| { |
| const tcu::IVec2 pointSize = resultArea.swizzle(2,3) - resultArea.swizzle(0, 1); |
| |
| if (pointSize.x() != pointSize.y()) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Rasterized point is not a square. Point size was " << pointSize << tcu::TestLog::EndMessage; |
| return false; |
| } |
| |
| if (pointSize.x() != expectedSize) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize << ", got " << pointSize.x() << tcu::TestLog::EndMessage; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| int PointSizeCase::getExpectedPointSize (void) const |
| { |
| int addition = 0; |
| |
| // geometry |
| if (m_flags & FLAG_GEOMETRY_DONT_SET) |
| return 1; |
| else if (m_flags & FLAG_GEOMETRY_SET) |
| return 6; |
| else if (m_flags & FLAG_GEOMETRY_ADD) |
| addition += 2; |
| |
| // tessellation |
| if (m_flags & FLAG_TESSELLATION_EVALUATION_SET) |
| return 4 + addition; |
| else if (m_flags & FLAG_TESSELLATION_ADD) |
| addition += 2; |
| else if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_DONT_SET)) |
| { |
| DE_ASSERT((m_flags & FLAG_GEOMETRY_ADD) == 0); // reading pointSize undefined |
| return 1; |
| } |
| |
| // vertex |
| if (m_flags & FLAG_VERTEX_SET) |
| return 2 + addition; |
| |
| // undefined |
| DE_ASSERT(false); |
| return -1; |
| } |
| |
| std::string PointSizeCase::genVertexSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| << "in highp vec4 a_position;\n" |
| << "void main ()\n" |
| << "{\n" |
| << " gl_Position = a_position;\n"; |
| |
| if (m_flags & FLAG_VERTEX_SET) |
| buf << " gl_PointSize = 2.0;\n"; |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string PointSizeCase::genFragmentSource (void) const |
| { |
| return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType()); |
| } |
| |
| std::string PointSizeCase::genTessellationControlSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| << "${EXTENSION_TESSELATION_SHADER}" |
| << ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("#extension GL_EXT_tessellation_point_size : require\n")) |
| << "layout(vertices = 1) out;\n" |
| << "void main ()\n" |
| << "{\n" |
| << " gl_TessLevelOuter[0] = 3.0;\n" |
| << " gl_TessLevelOuter[1] = 3.0;\n" |
| << " gl_TessLevelOuter[2] = 3.0;\n" |
| << " gl_TessLevelInner[0] = 3.0;\n" |
| << " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"; |
| |
| if (m_flags & FLAG_TESSELLATION_ADD) |
| buf << " // pass as is to eval\n" |
| << " gl_out[gl_InvocationID].gl_PointSize = gl_in[gl_InvocationID].gl_PointSize;\n"; |
| else if (m_flags & FLAG_TESSELLATION_CONTROL_SET) |
| buf << " // thrown away\n" |
| << " gl_out[gl_InvocationID].gl_PointSize = 4.0;\n"; |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string PointSizeCase::genTessellationEvaluationSource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| << "${EXTENSION_TESSELATION_SHADER}" |
| << ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("#extension GL_EXT_tessellation_point_size : require\n")) |
| << "layout(triangles, point_mode) in;\n" |
| << "void main ()\n" |
| << "{\n" |
| << " // hide all but one vertex\n" |
| << " if (gl_TessCoord.x < 0.99)\n" |
| << " gl_Position = vec4(-2.0, 0.0, 0.0, 1.0);\n" |
| << " else\n" |
| << " gl_Position = gl_in[0].gl_Position;\n"; |
| |
| if (m_flags & FLAG_TESSELLATION_ADD) |
| buf << "\n" |
| << " // add to point size\n" |
| << " gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n"; |
| else if (m_flags & FLAG_TESSELLATION_EVALUATION_SET) |
| buf << "\n" |
| << " // set point size\n" |
| << " gl_PointSize = 4.0;\n"; |
| |
| buf << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string PointSizeCase::genGeometrySource (void) const |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| << "${EXTENSION_GEOMETRY_SHADER}" |
| << ((m_flags & FLAG_GEOMETRY_DONT_SET) ? ("") : ("#extension GL_EXT_geometry_point_size : require\n")) |
| << "layout (points) in;\n" |
| << "layout (points, max_vertices=1) out;\n" |
| << "\n" |
| << "void main ()\n" |
| << "{\n"; |
| |
| if (m_flags & FLAG_GEOMETRY_SET) |
| buf << " gl_Position = gl_in[0].gl_Position;\n" |
| << " gl_PointSize = 6.0;\n"; |
| else if (m_flags & FLAG_GEOMETRY_ADD) |
| buf << " gl_Position = gl_in[0].gl_Position;\n" |
| << " gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n"; |
| else if (m_flags & FLAG_GEOMETRY_DONT_SET) |
| buf << " gl_Position = gl_in[0].gl_Position;\n"; |
| |
| buf << " EmitVertex();\n" |
| << "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| class AllowedRenderFailureException : public std::runtime_error |
| { |
| public: |
| AllowedRenderFailureException (const char* message) : std::runtime_error(message) { } |
| }; |
| |
| class GridRenderCase : public TestCase |
| { |
| public: |
| enum Flags |
| { |
| FLAG_TESSELLATION_MAX_SPEC = 0x0001, |
| FLAG_TESSELLATION_MAX_IMPLEMENTATION = 0x0002, |
| FLAG_GEOMETRY_MAX_SPEC = 0x0004, |
| FLAG_GEOMETRY_MAX_IMPLEMENTATION = 0x0008, |
| FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC = 0x0010, |
| FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION = 0x0020, |
| |
| FLAG_GEOMETRY_SCATTER_INSTANCES = 0x0040, |
| FLAG_GEOMETRY_SCATTER_PRIMITIVES = 0x0080, |
| FLAG_GEOMETRY_SEPARATE_PRIMITIVES = 0x0100, //!< if set, geometry shader outputs separate grid cells and not continuous slices |
| FLAG_GEOMETRY_SCATTER_LAYERS = 0x0200, |
| |
| FLAG_ALLOW_OUT_OF_MEMORY = 0x0400, //!< allow draw command to set GL_OUT_OF_MEMORY |
| }; |
| |
| GridRenderCase (Context& context, const char* name, const char* description, int flags); |
| ~GridRenderCase (void); |
| |
| private: |
| void init (void); |
| void deinit (void); |
| IterateResult iterate (void); |
| |
| void renderTo (std::vector<tcu::Surface>& dst); |
| bool verifyResultLayer (int layerNdx, const tcu::Surface& dst); |
| |
| std::string getVertexSource (void); |
| std::string getFragmentSource (void); |
| std::string getTessellationControlSource (int tessLevel); |
| std::string getTessellationEvaluationSource (int tessLevel); |
| std::string getGeometryShaderSource (int numPrimitives, int numInstances, int tessLevel); |
| |
| enum |
| { |
| RENDER_SIZE = 256 |
| }; |
| |
| const int m_flags; |
| |
| glu::ShaderProgram* m_program; |
| deUint32 m_texture; |
| int m_numLayers; |
| }; |
| |
| GridRenderCase::GridRenderCase (Context& context, const char* name, const char* description, int flags) |
| : TestCase (context, name, description) |
| , m_flags (flags) |
| , m_program (DE_NULL) |
| , m_texture (0) |
| , m_numLayers (1) |
| { |
| DE_ASSERT(((m_flags & FLAG_TESSELLATION_MAX_SPEC) == 0) || ((m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION) == 0)); |
| DE_ASSERT(((m_flags & FLAG_GEOMETRY_MAX_SPEC) == 0) || ((m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION) == 0)); |
| DE_ASSERT(((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) == 0) || ((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION) == 0)); |
| DE_ASSERT(((m_flags & (FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS)) != 0) == ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0)); |
| } |
| |
| GridRenderCase::~GridRenderCase (void) |
| { |
| deinit(); |
| } |
| |
| void GridRenderCase::init (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)); |
| |
| // Requirements |
| |
| if (!supportsES32 && |
| (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") || |
| !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))) |
| throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions"); |
| |
| if ((m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) == 0) |
| { |
| if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || |
| m_context.getRenderTarget().getHeight() < RENDER_SIZE) |
| throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" + de::toString<int>(RENDER_SIZE) + " or larger render target."); |
| } |
| |
| // Log |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Testing tessellation and geometry shaders that output a large number of primitives.\n" |
| << getDescription() |
| << tcu::TestLog::EndMessage; |
| |
| // Render target |
| if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) |
| { |
| // set limits |
| m_numLayers = 8; |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to 2d texture array, numLayers = " << m_numLayers << tcu::TestLog::EndMessage; |
| |
| gl.genTextures(1, &m_texture); |
| gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texture); |
| gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, RENDER_SIZE, RENDER_SIZE, m_numLayers); |
| |
| gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen texture"); |
| } |
| |
| // Gen program |
| { |
| glu::ProgramSources sources; |
| int tessGenLevel = -1; |
| |
| sources << glu::VertexSource(getVertexSource()) |
| << glu::FragmentSource(getFragmentSource()); |
| |
| // Tessellation limits |
| { |
| if (m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION) |
| { |
| gl.getIntegerv(GL_MAX_TESS_GEN_LEVEL, &tessGenLevel); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "query tessellation limits"); |
| } |
| else if (m_flags & FLAG_TESSELLATION_MAX_SPEC) |
| { |
| tessGenLevel = 64; |
| } |
| else |
| { |
| tessGenLevel = 5; |
| } |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Tessellation level: " << tessGenLevel << ", mode = quad.\n" |
| << "\tEach input patch produces " << (tessGenLevel*tessGenLevel) << " (" << (tessGenLevel*tessGenLevel*2) << " triangles)\n" |
| << tcu::TestLog::EndMessage; |
| |
| sources << glu::TessellationControlSource(getTessellationControlSource(tessGenLevel)) |
| << glu::TessellationEvaluationSource(getTessellationEvaluationSource(tessGenLevel)); |
| } |
| |
| // Geometry limits |
| { |
| int geometryOutputComponents = -1; |
| int geometryOutputVertices = -1; |
| int geometryTotalOutputComponents = -1; |
| int geometryShaderInvocations = -1; |
| bool logGeometryLimits = false; |
| bool logInvocationLimits = false; |
| |
| if (m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Using implementation maximum geometry shader output limits." << tcu::TestLog::EndMessage; |
| |
| gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, &geometryOutputComponents); |
| gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &geometryOutputVertices); |
| gl.getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &geometryTotalOutputComponents); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry limits"); |
| |
| logGeometryLimits = true; |
| } |
| else if (m_flags & FLAG_GEOMETRY_MAX_SPEC) |
| { |
| m_testCtx.getLog() << tcu::TestLog::Message << "Using geometry shader extension minimum maximum output limits." << tcu::TestLog::EndMessage; |
| |
| geometryOutputComponents = 128; |
| geometryOutputVertices = 256; |
| geometryTotalOutputComponents = 1024; |
| logGeometryLimits = true; |
| } |
| else |
| { |
| geometryOutputComponents = 128; |
| geometryOutputVertices = 16; |
| geometryTotalOutputComponents = 1024; |
| } |
| |
| if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION) |
| { |
| gl.getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS, &geometryShaderInvocations); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry invocation limits"); |
| |
| logInvocationLimits = true; |
| } |
| else if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) |
| { |
| geometryShaderInvocations = 32; |
| logInvocationLimits = true; |
| } |
| else |
| { |
| geometryShaderInvocations = 4; |
| } |
| |
| if (logGeometryLimits || logInvocationLimits) |
| { |
| tcu::MessageBuilder msg(&m_testCtx.getLog()); |
| |
| msg << "Geometry shader, targeting following limits:\n"; |
| |
| if (logGeometryLimits) |
| msg << "\tGL_MAX_GEOMETRY_OUTPUT_COMPONENTS = " << geometryOutputComponents << "\n" |
| << "\tGL_MAX_GEOMETRY_OUTPUT_VERTICES = " << geometryOutputVertices << "\n" |
| << "\tGL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << geometryTotalOutputComponents << "\n"; |
| |
| if (logInvocationLimits) |
| msg << "\tGL_MAX_GEOMETRY_SHADER_INVOCATIONS = " << geometryShaderInvocations; |
| |
| msg << tcu::TestLog::EndMessage; |
| } |
| |
| { |
| const bool separatePrimitives = (m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0; |
| const int numComponentsPerVertex = 8; // vec4 pos, vec4 color |
| int numVerticesPerInvocation; |
| int numPrimitivesPerInvocation; |
| int geometryVerticesPerPrimitive; |
| int geometryPrimitivesOutPerPrimitive; |
| |
| if (separatePrimitives) |
| { |
| const int numComponentLimit = geometryTotalOutputComponents / (4 * numComponentsPerVertex); |
| const int numOutputLimit = geometryOutputVertices / 4; |
| |
| numPrimitivesPerInvocation = de::min(numComponentLimit, numOutputLimit); |
| numVerticesPerInvocation = numPrimitivesPerInvocation * 4; |
| } |
| else |
| { |
| // If FLAG_GEOMETRY_SEPARATE_PRIMITIVES is not set, geometry shader fills a rectangle area in slices. |
| // Each slice is a triangle strip and is generated by a single shader invocation. |
| // One slice with 4 segment ends (nodes) and 3 segments: |
| // .__.__.__. |
| // |\ |\ |\ | |
| // |_\|_\|_\| |
| |
| const int numSliceNodesComponentLimit = geometryTotalOutputComponents / (2 * numComponentsPerVertex); // each node 2 vertices |
| const int numSliceNodesOutputLimit = geometryOutputVertices / 2; // each node 2 vertices |
| const int numSliceNodes = de::min(numSliceNodesComponentLimit, numSliceNodesOutputLimit); |
| |
| numVerticesPerInvocation = numSliceNodes * 2; |
| numPrimitivesPerInvocation = (numSliceNodes - 1) * 2; |
| } |
| |
| geometryVerticesPerPrimitive = numVerticesPerInvocation * geometryShaderInvocations; |
| geometryPrimitivesOutPerPrimitive = numPrimitivesPerInvocation * geometryShaderInvocations; |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Geometry shader:\n" |
| << "\tTotal output vertex count per invocation: " << (numVerticesPerInvocation) << "\n" |
| << "\tTotal output primitive count per invocation: " << (numPrimitivesPerInvocation) << "\n" |
| << "\tNumber of invocations per primitive: " << geometryShaderInvocations << "\n" |
| << "\tTotal output vertex count per input primitive: " << (geometryVerticesPerPrimitive) << "\n" |
| << "\tTotal output primitive count per input primitive: " << (geometryPrimitivesOutPerPrimitive) << "\n" |
| << tcu::TestLog::EndMessage; |
| |
| sources << glu::GeometrySource(getGeometryShaderSource(numPrimitivesPerInvocation, geometryShaderInvocations, tessGenLevel)); |
| |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Program:\n" |
| << "\tTotal program output vertices count per input patch: " << (tessGenLevel*tessGenLevel*2 * geometryVerticesPerPrimitive) << "\n" |
| << "\tTotal program output primitive count per input patch: " << (tessGenLevel*tessGenLevel*2 * geometryPrimitivesOutPerPrimitive) << "\n" |
| << tcu::TestLog::EndMessage; |
| } |
| } |
| |
| m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources); |
| m_testCtx.getLog() << *m_program; |
| if (!m_program->isOk()) |
| throw tcu::TestError("failed to build program"); |
| } |
| } |
| |
| void GridRenderCase::deinit (void) |
| { |
| delete m_program; |
| m_program = DE_NULL; |
| |
| if (m_texture) |
| { |
| m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture); |
| m_texture = 0; |
| } |
| } |
| |
| GridRenderCase::IterateResult GridRenderCase::iterate (void) |
| { |
| std::vector<tcu::Surface> renderedLayers (m_numLayers); |
| bool allLayersOk = true; |
| |
| for (int ndx = 0; ndx < m_numLayers; ++ndx) |
| renderedLayers[ndx].setSize(RENDER_SIZE, RENDER_SIZE); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Rendering single point at the origin. Expecting yellow and green colored grid-like image. (High-frequency grid may appear unicolored)." << tcu::TestLog::EndMessage; |
| |
| try |
| { |
| renderTo(renderedLayers); |
| } |
| catch (const AllowedRenderFailureException& ex) |
| { |
| // Got accepted failure |
| m_testCtx.getLog() |
| << tcu::TestLog::Message |
| << "Could not render, reason: " << ex.what() << "\n" |
| << "Failure is allowed." |
| << tcu::TestLog::EndMessage; |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| return STOP; |
| } |
| |
| for (int ndx = 0; ndx < m_numLayers; ++ndx) |
| allLayersOk &= verifyResultLayer(ndx, renderedLayers[ndx]); |
| |
| if (allLayersOk) |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| return STOP; |
| } |
| |
| void GridRenderCase::renderTo (std::vector<tcu::Surface>& dst) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| const int positionLocation = gl.getAttribLocation(m_program->getProgram(), "a_position"); |
| const glu::VertexArray vao (m_context.getRenderContext()); |
| de::MovePtr<glu::Framebuffer> fbo; |
| |
| if (positionLocation == -1) |
| throw tcu::TestError("Attribute a_position location was -1"); |
| |
| gl.viewport(0, 0, dst.front().getWidth(), dst.front().getHeight()); |
| gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "viewport"); |
| |
| gl.bindVertexArray(*vao); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao"); |
| |
| gl.useProgram(m_program->getProgram()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "use program"); |
| |
| gl.patchParameteri(GL_PATCH_VERTICES, 1); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param"); |
| |
| gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f); |
| |
| if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) |
| { |
| // clear texture contents |
| { |
| glu::Framebuffer clearFbo(m_context.getRenderContext()); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, *clearFbo); |
| |
| for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx) |
| { |
| gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| } |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "clear tex contents"); |
| } |
| |
| // create and bind layered fbo |
| |
| fbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext())); |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, **fbo); |
| gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo"); |
| } |
| else |
| { |
| // clear viewport |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| } |
| |
| // draw |
| { |
| glw::GLenum glerror; |
| |
| gl.drawArrays(GL_PATCHES, 0, 1); |
| |
| glerror = gl.getError(); |
| if (glerror == GL_OUT_OF_MEMORY && (m_flags & FLAG_ALLOW_OUT_OF_MEMORY)) |
| throw AllowedRenderFailureException("got GL_OUT_OF_MEMORY while drawing"); |
| |
| GLU_EXPECT_NO_ERROR(glerror, "draw patches"); |
| } |
| |
| // Read layers |
| |
| if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) |
| { |
| glu::Framebuffer readFbo(m_context.getRenderContext()); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, *readFbo); |
| |
| for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx) |
| { |
| gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx); |
| glu::readPixels(m_context.getRenderContext(), 0, 0, dst[layerNdx].getAccess()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels"); |
| } |
| } |
| else |
| { |
| glu::readPixels(m_context.getRenderContext(), 0, 0, dst.front().getAccess()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels"); |
| } |
| } |
| |
| bool GridRenderCase::verifyResultLayer (int layerNdx, const tcu::Surface& image) |
| { |
| tcu::Surface errorMask (image.getWidth(), image.getHeight()); |
| bool foundError = false; |
| |
| tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f)); |
| |
| m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output layer " << layerNdx << tcu::TestLog::EndMessage; |
| |
| for (int y = 0; y < image.getHeight(); ++y) |
| for (int x = 0; x < image.getWidth(); ++x) |
| { |
| const int threshold = 8; |
| const tcu::RGBA color = image.getPixel(x, y); |
| |
| // Color must be a linear combination of green and yellow |
| if (color.getGreen() < 255 - threshold || color.getBlue() > threshold) |
| { |
| errorMask.setPixel(x, y, tcu::RGBA::red()); |
| foundError = true; |
| } |
| } |
| |
| if (!foundError) |
| { |
| m_testCtx.getLog() |
| << tcu::TestLog::Message << "Image valid." << tcu::TestLog::EndMessage |
| << tcu::TestLog::ImageSet("ImageVerification", "Image verification") |
| << tcu::TestLog::Image("Result", "Rendered result", image.getAccess()) |
| << tcu::TestLog::EndImageSet; |
| return true; |
| } |
| else |
| { |
| m_testCtx.getLog() |
| << tcu::TestLog::Message << "Image verification failed, found invalid pixels." << tcu::TestLog::EndMessage |
| << tcu::TestLog::ImageSet("ImageVerification", "Image verification") |
| << tcu::TestLog::Image("Result", "Rendered result", image.getAccess()) |
| << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess()) |
| << tcu::TestLog::EndImageSet; |
| return false; |
| } |
| } |
| |
| std::string GridRenderCase::getVertexSource (void) |
| { |
| return specializeShader(s_positionVertexShader, m_context.getRenderContext().getType()); |
| } |
| |
| std::string GridRenderCase::getFragmentSource (void) |
| { |
| const char* source = "${VERSION_DECL}\n" |
| "flat in mediump vec4 v_color;\n" |
| "layout(location = 0) out mediump vec4 fragColor;\n" |
| "void main (void)\n" |
| "{\n" |
| " fragColor = v_color;\n" |
| "}\n"; |
| |
| return specializeShader(source, m_context.getRenderContext().getType()); |
| } |
| |
| std::string GridRenderCase::getTessellationControlSource (int tessLevel) |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(vertices=1) out;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| " gl_TessLevelOuter[0] = " << tessLevel << ".0;\n" |
| " gl_TessLevelOuter[1] = " << tessLevel << ".0;\n" |
| " gl_TessLevelOuter[2] = " << tessLevel << ".0;\n" |
| " gl_TessLevelOuter[3] = " << tessLevel << ".0;\n" |
| " gl_TessLevelInner[0] = " << tessLevel << ".0;\n" |
| " gl_TessLevelInner[1] = " << tessLevel << ".0;\n" |
| "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string GridRenderCase::getTessellationEvaluationSource (int tessLevel) |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_TESSELATION_SHADER}" |
| "layout(quads) in;\n" |
| "\n" |
| "out mediump ivec2 v_tessellationGridPosition;\n" |
| "\n" |
| "// note: No need to use precise gl_Position since position does not depend on order\n" |
| "void main (void)\n" |
| "{\n"; |
| |
| if (m_flags & (FLAG_GEOMETRY_SCATTER_INSTANCES | FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS)) |
| buf << " // Cover only a small area in a corner. The area will be expanded in geometry shader to cover whole viewport\n" |
| " gl_Position = vec4(gl_TessCoord.x * 0.3 - 1.0, gl_TessCoord.y * 0.3 - 1.0, 0.0, 1.0);\n"; |
| else |
| buf << " // Fill the whole viewport\n" |
| " gl_Position = vec4(gl_TessCoord.x * 2.0 - 1.0, gl_TessCoord.y * 2.0 - 1.0, 0.0, 1.0);\n"; |
| |
| buf << " // Calculate position in tessellation grid\n" |
| " v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float(" << tessLevel << ")));\n" |
| "}\n"; |
| |
| return specializeShader(buf.str(), m_context.getRenderContext().getType()); |
| } |
| |
| std::string GridRenderCase::getGeometryShaderSource (int numPrimitives, int numInstances, int tessLevel) |
| { |
| std::ostringstream buf; |
| |
| buf << "${VERSION_DECL}\n" |
| "${EXTENSION_GEOMETRY_SHADER}" |
| "layout(triangles, invocations=" << numInstances << ") in;\n" |
| "layout(triangle_strip, max_vertices=" << ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) ? (4 * numPrimitives) : (numPrimitives + 2)) << ") out;\n" |
| "\n" |
| "in mediump ivec2 v_tessellationGridPosition[];\n" |
| "flat out highp vec4 v_color;\n" |
| "\n" |
| "void main ()\n" |
| "{\n" |
| " const float equalThreshold = 0.001;\n" |
| " const float gapOffset = 0.0001; // subdivision performed by the geometry shader might produce gaps. Fill potential gaps by enlarging the output slice a little.\n" |
| "\n" |
| " // Input triangle is generated from an axis-aligned rectangle by splitting it in half\n" |
| " // Original rectangle can be found by finding the bounding AABB of the triangle\n" |
| " vec4 aabb = vec4(min(gl_in[0].gl_Position.x, min(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n" |
| " min(gl_in[0].gl_Position.y, min(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)),\n" |
| " max(gl_in[0].gl_Position.x, max(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n" |
| " max(gl_in[0].gl_Position.y, max(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)));\n" |
| "\n" |
| " // Location in tessellation grid\n" |
| " ivec2 gridPosition = ivec2(min(v_tessellationGridPosition[0], min(v_tessellationGridPosition[1], v_tessellationGridPosition[2])));\n" |
| "\n" |
| " // Which triangle of the two that split the grid cell\n" |
| " int numVerticesOnBottomEdge = 0;\n" |
| " for (int ndx = 0; ndx < 3; ++ndx)\n" |
| " if (abs(gl_in[ndx].gl_Position.y - aabb.w) < equalThreshold)\n" |
| " ++numVerticesOnBottomEdge;\n" |
| " bool isBottomTriangle = numVerticesOnBottomEdge == 2;\n" |
| "\n"; |
| |
| if (m_flags & FLAG_GEOMETRY_SCATTER_PRIMITIVES) |
| { |
| // scatter primitives |
| buf << " // Draw grid cells\n" |
| " int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n" |
| " for (int ndx = 0; ndx < " << numPrimitives << "; ++ndx)\n" |
| " {\n" |
| " ivec2 dstGridSize = ivec2(" << tessLevel << " * " << numPrimitives << ", 2 * " << tessLevel << " * " << numInstances << ");\n" |
| " ivec2 dstGridNdx = ivec2(" << tessLevel << " * ndx + gridPosition.x, " << tessLevel << " * inputTriangleNdx + 2 * gridPosition.y + ndx * 127) % dstGridSize;\n" |
| " vec4 dstArea;\n" |
| " dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n" |
| " dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n" |
| " dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n" |
| " dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n" |
| "\n" |
| " vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n" |
| " vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n" |
| " vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n" |
| "\n" |
| " gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " EmitVertex();\n" |
| " EndPrimitive();\n" |
| " }\n"; |
| } |
| else if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) |
| { |
| // Number of subrectangle instances = num layers |
| DE_ASSERT(m_numLayers == numInstances * 2); |
| |
| buf << " // Draw grid cells, send each primitive to a separate layer\n" |
| " int baseLayer = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n" |
| " for (int ndx = 0; ndx < " << numPrimitives << "; ++ndx)\n" |
| " {\n" |
| " ivec2 dstGridSize = ivec2(" << tessLevel << " * " << numPrimitives << ", " << tessLevel << ");\n" |
| " ivec2 dstGridNdx = ivec2((gridPosition.x * " << numPrimitives << " * 7 + ndx)*13, (gridPosition.y * 127 + ndx) * 19) % dstGridSize;\n" |
| " vec4 dstArea;\n" |
| " dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n" |
| " dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n" |
| " dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n" |
| " dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n" |
| "\n" |
| " vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n" |
| " vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n" |
| " vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n" |
| "\n" |
| " gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n" |
| " EmitVertex();\n" |
| "\n" |
| " gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n" |
| " v_color = outputColor;\n" |
| " gl_Layer = ((baseLayer + ndx) * 11) % " << m_numLayers << ";\n" |
| " EmitVertex();\n" |
| " EndPrimitive();\n" |
| " }\n"; |
| } |
| else |
| { |
| if (m_flags & FLAG_GEOMETRY_SCATTER_INSTANCES) |
| { |
| buf << " // Scatter slices\n" |
| " int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n" |
| " ivec2 srcSliceNdx = ivec2(gridPosition.x, gridPosition.y * " << (numInstances*2) << " + inputTriangleNdx);\n" |
| " ivec2 dstSliceNdx = ivec2(7 * srcSliceNdx.x, 127 * srcSliceNdx.y) % ivec2(" << tessLevel << ", " << tessLevel << " * " << (numInstances*2) << ");\n" |
| "\n" |
| " // Draw slice to the dstSlice slot\n" |
| " vec4 outputSliceArea;\n" |
| " outputSliceArea.x = float(dstSliceNdx.x) / float(" << tessLevel << ") * 2.0 - 1.0 - gapOffset;\n" |
| " outputSliceArea.y = float(dstSliceNdx.y) / float(" << (tessLevel * numInstances * 2) << ") * 2.0 - 1.0 - gapOffset;\n" |
| " outputSliceArea.z = float(dstSliceNdx.x+1) / float(" << tessLevel << ") * 2.0 - 1.0 + gapOffset;\n" |
| " outputSliceArea.w = float(dstSliceNdx.y+1) / float(" << (tessLevel * numInstances * 2) << ") * 2.0 - 1.0 + gapOffset;\n"; |
| } |
| else |
| { |
| buf << " // Fill the input area with slices\n" |
| " // Upper triangle produces slices only to the upper half of the quad and vice-versa\n" |
| " float triangleOffset = (isBottomTriangle) ? ((aabb.w + aabb.y) / 2.0) : (aabb.y);\n" |
| " // Each slice is a invocation\n" |
| " float sliceHeight = (aabb.w - aabb.y) / float(2 * " << numInstances << ");\n" |
|