| /*------------------------------------------------------------------------- |
| * OpenGL Conformance Test Suite |
| * ----------------------------- |
| * |
| * Copyright (c) 2017 The Khronos Group Inc. |
| * |
| * 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 glcSeparableProgramXFBTests.cpp |
| * \brief |
| */ /*-------------------------------------------------------------------*/ |
| |
| #include "glcSeparableProgramsTransformFeedbackTests.hpp" |
| #include "glcViewportArrayTests.hpp" |
| #include "gluContextInfo.hpp" |
| #include "gluDefs.hpp" |
| #include "glwEnums.hpp" |
| #include "glwFunctions.hpp" |
| #include "tcuCommandLine.hpp" |
| #include "tcuStringTemplate.hpp" |
| #include "tcuTestLog.hpp" |
| |
| using namespace tcu; |
| using namespace glu; |
| using namespace glw; |
| using namespace glcts::ViewportArray; |
| |
| namespace glcts |
| { |
| |
| /** |
| * @brief The StageIndex enum. Stages order coresponds to order |
| * in which shader sources are specified in Utils::program::build. |
| */ |
| enum StageIndex |
| { |
| FRAGMENT_STAGE_INDEX = 0, |
| GEOMETRY_STAGE_INDEX, |
| TESSELLATION_CONTROL_STAGE, |
| TESSELLATION_EVALUATION_STAGE, |
| VERTEX_STAGE, |
| STAGES_COUNT |
| }; |
| |
| /** |
| * @brief The StageTokens array. Stages order coresponds to order |
| * in which shader sources are specified in Utils::program::build. |
| */ |
| static const GLenum StageTokens[STAGES_COUNT] = { GL_FRAGMENT_SHADER_BIT, GL_GEOMETRY_SHADER_BIT, |
| GL_TESS_CONTROL_SHADER_BIT, GL_TESS_EVALUATION_SHADER_BIT, |
| GL_VERTEX_SHADER_BIT }; |
| |
| /** |
| * @brief The StageData structure. |
| */ |
| struct StageData |
| { |
| const GLchar* source; |
| const GLchar* const* tfVaryings; |
| const GLuint tfVaryingsCount; |
| }; |
| |
| /** |
| * @brief The PerStageData structure containimg shader data per all stages. |
| */ |
| struct PerStageData |
| { |
| StageData stage[STAGES_COUNT]; |
| }; |
| |
| static const GLchar* vs_code = "${VERSION}\n" |
| "flat out highp int o_vert;\n" |
| "${PERVERTEX_BLOCK}\n" |
| "void main()\n" |
| "{\n" |
| " o_vert = 1;\n" |
| " gl_Position = vec4(1, 0, 0, 1);\n" |
| "}\n"; |
| |
| static const GLchar* vs_tf_varyings[] = { "o_vert" }; |
| |
| static const GLchar* tcs_code = "${VERSION}\n" |
| "layout(vertices = 1) out;\n" |
| "flat in highp int o_vert[];\n" |
| "${PERVERTEX_BLOCK}\n" |
| "void main()\n" |
| "{\n" |
| " gl_TessLevelInner[0] = 1.0;\n" |
| " gl_TessLevelInner[1] = 1.0;\n" |
| " gl_TessLevelOuter[0] = 1.0;\n" |
| " gl_TessLevelOuter[1] = 1.0;\n" |
| " gl_TessLevelOuter[2] = 1.0;\n" |
| " gl_TessLevelOuter[3] = 1.0;\n" |
| "}\n"; |
| |
| static const GLchar* tes_code = "${VERSION}\n" |
| "layout (triangles, point_mode) in;\n" |
| "flat out highp int o_tess;\n" |
| "${PERVERTEX_BLOCK}\n" |
| "void main()\n" |
| "{\n" |
| " o_tess = 2;\n" |
| " gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 0.0, 1.0);\n" |
| "}\n"; |
| |
| static const GLchar* tes_tf_varyings[] = { "o_tess" }; |
| |
| static const GLchar* gs_code = "${VERSION}\n" |
| "layout (points) in;\n" |
| "layout (points, max_vertices = 3) out;\n" |
| "${PERVERTEX_BLOCK}\n" |
| "flat in highp int ${IN_VARYING_NAME}[];\n" |
| "flat out highp int o_geom;\n" |
| "void main()\n" |
| "{\n" |
| " o_geom = 3;\n" |
| " gl_Position = vec4(-1, -1, 0, 1);\n" |
| " EmitVertex();\n" |
| " o_geom = 3;\n" |
| " gl_Position = vec4(-1, 1, 0, 1);\n" |
| " EmitVertex();\n" |
| " o_geom = 3;\n" |
| " gl_Position = vec4(1, -1, 0, 1);\n" |
| " EmitVertex();\n" |
| "}\n"; |
| |
| static const GLchar* gs_tf_varyings[] = { "o_geom" }; |
| |
| static const GLchar* fs_code = "${VERSION}\n" |
| "flat in highp int ${IN_VARYING_NAME};" |
| "out highp vec4 o_color;\n" |
| "void main()\n" |
| "{\n" |
| " o_color = vec4(1.0);\n" |
| "}\n"; |
| |
| class SeparableProgramTFTestCase : public deqp::TestCase |
| { |
| public: |
| /* Public methods */ |
| SeparableProgramTFTestCase(deqp::Context& context, const char* name, PerStageData shaderData, GLint expectedValue); |
| |
| tcu::TestNode::IterateResult iterate(void); |
| |
| protected: |
| /* Protected attributes */ |
| PerStageData m_shaderData; |
| GLint m_expectedValue; |
| }; |
| |
| /** Constructor. |
| * |
| * @param context Rendering context |
| * @param name Test name |
| * @param description Test description |
| */ |
| SeparableProgramTFTestCase::SeparableProgramTFTestCase(deqp::Context& context, const char* name, |
| PerStageData shaderData, GLint expectedValue) |
| : deqp::TestCase(context, name, ""), m_shaderData(shaderData), m_expectedValue(expectedValue) |
| { |
| } |
| |
| tcu::TestNode::IterateResult SeparableProgramTFTestCase::iterate(void) |
| { |
| const Functions& gl = m_context.getRenderContext().getFunctions(); |
| ContextType contextType = m_context.getRenderContext().getType(); |
| GLSLVersion glslVersion = getContextTypeGLSLVersion(contextType); |
| |
| /* For core GL gl_PerVertex interface block is combined from two parts. |
| * First part contains definition and the second part name, which is |
| * only specified for tess control stage (arrays are used here to avoid |
| * three branches in a loop). For ES both parts are empty string */ |
| const char* blockName[STAGES_COUNT] = { "", ";\n", " gl_out[];\n", ";\n", ";\n" }; |
| const char* blockEmptyName[STAGES_COUNT] = { "", "", "", "", "" }; |
| std::string vertexBlock(""); |
| const char** vertexBlockPostfix = blockEmptyName; |
| if (isContextTypeGLCore(contextType)) |
| { |
| vertexBlock = "out gl_PerVertex" |
| "{\n" |
| " vec4 gl_Position;\n" |
| "}"; |
| vertexBlockPostfix = blockName; |
| } |
| |
| /* Construct specialization map - some specializations differ per stage */ |
| std::map<std::string, std::string> specializationMap; |
| specializationMap["VERSION"] = glu::getGLSLVersionDeclaration(glslVersion); |
| |
| /* Create separate programs - start from vertex stage to catch varying names */ |
| std::vector<Utils::program> programs(STAGES_COUNT, Utils::program(m_context)); |
| const char* code[STAGES_COUNT] = { 0, 0, 0, 0, 0 }; |
| for (int stageIndex = VERTEX_STAGE; stageIndex > -1; --stageIndex) |
| { |
| StageData* stageData = m_shaderData.stage + stageIndex; |
| std::string source = stageData->source; |
| if (source.empty()) |
| continue; |
| specializationMap["PERVERTEX_BLOCK"] = vertexBlock + vertexBlockPostfix[stageIndex]; |
| std::string specializedShader = StringTemplate(source).specialize(specializationMap); |
| |
| code[stageIndex] = specializedShader.c_str(); |
| programs[stageIndex].build(0, code[0], code[1], code[2], code[3], code[4], stageData->tfVaryings, |
| stageData->tfVaryingsCount, true); |
| code[stageIndex] = 0; |
| |
| /* Use varying name from current stage to specialize next stage */ |
| if (stageData->tfVaryings) |
| specializationMap["IN_VARYING_NAME"] = stageData->tfVaryings[0]; |
| } |
| |
| /* Create program pipeline */ |
| GLuint pipelineId; |
| gl.genProgramPipelines(1, &pipelineId); |
| gl.bindProgramPipeline(pipelineId); |
| for (int stageIndex = 0; stageIndex < STAGES_COUNT; ++stageIndex) |
| { |
| if (!programs[stageIndex].m_program_object_id) |
| continue; |
| gl.useProgramStages(pipelineId, StageTokens[stageIndex], programs[stageIndex].m_program_object_id); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgramStages() call failed."); |
| } |
| |
| /* Validate the pipeline */ |
| GLint validateStatus = GL_FALSE; |
| gl.validateProgramPipeline(pipelineId); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glValidateProgramPipeline() call failed."); |
| gl.getProgramPipelineiv(pipelineId, GL_VALIDATE_STATUS, &validateStatus); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramPipelineiv() call failed."); |
| if (validateStatus != GL_TRUE) |
| { |
| GLint logLength; |
| gl.getProgramPipelineiv(pipelineId, GL_INFO_LOG_LENGTH, &logLength); |
| if (logLength) |
| { |
| std::vector<GLchar> logBuffer(logLength + 1); |
| gl.getProgramPipelineInfoLog(pipelineId, logLength + 1, NULL, &logBuffer[0]); |
| m_context.getTestContext().getLog() << tcu::TestLog::Message << &logBuffer[0] << tcu::TestLog::EndMessage; |
| } |
| TCU_FAIL("Program pipeline has not been validated successfully."); |
| } |
| |
| /* Generate buffer object to hold result XFB data */ |
| Utils::buffer tfb(m_context); |
| GLsizeiptr tfbSize = 100; |
| tfb.generate(GL_TRANSFORM_FEEDBACK_BUFFER); |
| tfb.update(tfbSize, 0 /* data */, GL_DYNAMIC_COPY); |
| tfb.bindRange(0, 0, tfbSize); |
| |
| /* Generate VAO to use for the draw calls */ |
| Utils::vertexArray vao(m_context); |
| vao.generate(); |
| vao.bind(); |
| |
| /* Generate query object */ |
| GLuint queryId; |
| gl.genQueries(1, &queryId); |
| |
| /* Check if tessellation stage is active */ |
| GLenum drawMode = GL_POINTS; |
| if (strlen(m_shaderData.stage[TESSELLATION_CONTROL_STAGE].source) > 0) |
| drawMode = GL_PATCHES; |
| |
| /* Draw and capture data */ |
| gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, queryId); |
| gl.beginTransformFeedback(GL_POINTS); |
| gl.patchParameteri(GL_PATCH_VERTICES, 1); |
| gl.drawArrays(drawMode, 0 /* first */, 1 /* count */); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawArrays() call failed."); |
| gl.endTransformFeedback(); |
| gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); |
| |
| /* Get TF results */ |
| GLuint writtenPrimitives = 0; |
| gl.getQueryObjectuiv(queryId, GL_QUERY_RESULT, &writtenPrimitives); |
| GLint* feedbackData = (GLint*)gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfbSize, GL_MAP_READ_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBuffer"); |
| |
| /* Verify if only values from upstream shader were captured */ |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| if (writtenPrimitives != 0) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| for (GLuint dataIndex = 0; dataIndex < writtenPrimitives; ++dataIndex) |
| { |
| if (feedbackData[dataIndex] == m_expectedValue) |
| continue; |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); |
| break; |
| } |
| } |
| |
| /* Cleanup */ |
| gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); |
| gl.deleteQueries(1, &queryId); |
| gl.bindProgramPipeline(0); |
| gl.deleteProgramPipelines(1, &pipelineId); |
| |
| return STOP; |
| } |
| |
| /** Constructor. |
| * |
| * @param context Rendering context. |
| */ |
| SeparableProgramsTransformFeedbackTests::SeparableProgramsTransformFeedbackTests(deqp::Context& context) |
| : deqp::TestCaseGroup(context, "separable_programs_tf", "") |
| { |
| } |
| |
| /** Initializes the test group contents. */ |
| void SeparableProgramsTransformFeedbackTests::init(void) |
| { |
| PerStageData tessellation_active = { { |
| { fs_code, NULL, 0 }, // fragment stage |
| { "", NULL, 0 }, // geometry stage |
| { tcs_code, NULL, 0 }, // tesselation control stage |
| { tes_code, tes_tf_varyings, 1 }, // tesselation evaluation stage |
| { vs_code, vs_tf_varyings, 1 } // vertex_stage |
| } }; |
| PerStageData geometry_active = { { |
| { fs_code, NULL, 0 }, // fragment stage |
| { gs_code, gs_tf_varyings, 1 }, // geometry stage |
| { tcs_code, NULL, 0 }, // tesselation control stage |
| { tes_code, tes_tf_varyings, 1 }, // tesselation evaluation stage |
| { vs_code, vs_tf_varyings, 1 } // vertex_stage |
| } }; |
| |
| addChild(new SeparableProgramTFTestCase(m_context, "tessellation_active", tessellation_active, 2)); |
| addChild(new SeparableProgramTFTestCase(m_context, "geometry_active", geometry_active, 3)); |
| } |
| |
| } /* glcts namespace */ |