| /*------------------------------------------------------------------------- |
| * OpenGL Conformance Test Suite |
| * ----------------------------- |
| * |
| * Copyright (c) 2014-2016 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 |
| * \brief |
| */ /*-------------------------------------------------------------------*/ |
| |
| #include "esextcTessellationShaderUtils.hpp" |
| #include "deMath.h" |
| #include "glwEnums.hpp" |
| #include "glwFunctions.hpp" |
| #include "tcuTestLog.hpp" |
| #include <sstream> |
| |
| namespace glcts |
| { |
| |
| /** Constructor |
| * |
| * @param gl DEQP container for ES entry-points |
| * @param parentTest Pointer to owning test instance. |
| **/ |
| TessellationShaderUtils::TessellationShaderUtils(const glw::Functions& gl, glcts::TestCaseBase* parentTest) |
| : m_gl(gl), m_bo_id(0), m_fs_id(0), m_qo_pg_id(0), m_vs_id(0), m_parent_test(parentTest) |
| { |
| init(); |
| } |
| |
| /** Destructor */ |
| TessellationShaderUtils::~TessellationShaderUtils() |
| { |
| deinit(); |
| } |
| |
| /** Captures data generated by the tessellator when a geometry is drawn |
| * for user-provided vertex counter program. |
| * |
| * @param program Vertex counter program to use. |
| **/ |
| void TessellationShaderUtils::captureTessellationData(_tessellation_vertex_counter_program& program) |
| { |
| /* Cache current program object ID before we continue */ |
| glw::GLint current_po_id = 0; |
| |
| m_gl.getIntegerv(GL_CURRENT_PROGRAM, ¤t_po_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetIntegerv() failed for GL_CURRENT_PROGRAM pname"); |
| |
| /* Cache current GL_PATCH_VERTICES_EXT setting before continuing */ |
| glw::GLint current_patch_vertices = 0; |
| |
| m_gl.getIntegerv(GL_PATCH_VERTICES, ¤t_patch_vertices); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetIntegerv() failed for GL_PATCH_VERTICES_EXT pname"); |
| |
| /* Activate the program object and the query object */ |
| m_gl.useProgram(program.po_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() failed"); |
| |
| m_gl.beginQuery(m_parent_test->m_glExtTokens.PRIMITIVES_GENERATED, m_qo_pg_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBeginQuery() called for target GL_PRIMITIVES_GENERATED_EXT failed"); |
| |
| /* Disable rasterization, if it's enabled */ |
| glw::GLboolean is_rasterization_disabled = m_gl.isEnabled(GL_RASTERIZER_DISCARD); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glIsEnabled(GL_RASTERIZER_DISCARD) failed"); |
| |
| if (is_rasterization_disabled) |
| { |
| m_gl.enable(GL_RASTERIZER_DISCARD); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glEnable(GL_RASTERIZER_DISCARD) failed"); |
| } |
| |
| /* Update GL_PATCH_VERTICES_EXT */ |
| m_gl.patchParameteri(m_parent_test->m_glExtTokens.PATCH_VERTICES, program.n_patch_vertices); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glPatchParameteriEXT() failed"); |
| |
| /* Draw the test geometry */ |
| m_gl.drawArrays(m_parent_test->m_glExtTokens.PATCHES, 0, /* first */ |
| program.n_patch_vertices); /* count */ |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDrawArrays() failed"); |
| |
| /* End the query and retrieve the result */ |
| glw::GLuint queryValue = 0; |
| |
| m_gl.endQuery(m_parent_test->m_glExtTokens.PRIMITIVES_GENERATED); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glEndQuery(GL_PRIMITIVES_GENERATED_EXT) failed"); |
| |
| m_gl.getQueryObjectuiv(m_qo_pg_id, GL_QUERY_RESULT, &queryValue); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetQueryiv() failed"); |
| |
| /* Store amount of primitives under result */ |
| program.n_data_vertices = ((unsigned int)queryValue); |
| |
| if (!program.is_point_mode_enabled) |
| { |
| /* Quads get tessellated into triangles, meaning our primitives counter tells how |
| * many triangles were generated for both triangles and quads; isolines get |
| * tessellated into line segments. |
| */ |
| if (program.primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS || |
| program.primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES) |
| { |
| program.n_data_vertices *= 3; /* each triangle gets 3 vertices */ |
| } |
| else |
| { |
| program.n_data_vertices *= 2; /* each isoline gets 2 vertices */ |
| } |
| } /* if (!is_point_mode_enabled) */ |
| |
| if (program.n_data_vertices != 0) |
| { |
| /* Now that we now, how many vertices we need to allocate space for, set up TF */ |
| glw::GLint bo_size = static_cast<glw::GLint>(sizeof(float) * 3 /* components */ * program.n_data_vertices); |
| |
| m_gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_bo_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBindBuffer() failed"); |
| |
| m_gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0 /* index */, m_bo_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBindBufferBase() failed"); |
| |
| m_gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bo_size, DE_NULL, /* data */ |
| GL_STATIC_DRAW); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBufferData() failed"); |
| |
| /* Set up TF */ |
| glw::GLenum tf_mode = |
| TessellationShaderUtils::getTFModeForPrimitiveMode(program.primitive_mode, program.is_point_mode_enabled); |
| |
| m_gl.beginTransformFeedback(tf_mode); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glBeginTransformFeedback() failed"); |
| |
| m_gl.drawArrays(m_parent_test->m_glExtTokens.PATCHES, 0, /* first */ |
| program.n_patch_vertices); /* count */ |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDrawArrays() failed"); |
| |
| m_gl.endTransformFeedback(); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glEndTransformFeedback() failed"); |
| |
| /* Map the BO and copy the contents */ |
| const void* xfb_data = m_gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, /* offset */ |
| bo_size, GL_MAP_READ_BIT); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glMapBufferRange() failed"); |
| |
| program.m_data.resize(bo_size); |
| |
| memcpy(&program.m_data[0], xfb_data, bo_size); |
| |
| m_gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUnmapBuffer() failed"); |
| } /* if (program.n_data_vertices != 0) */ |
| |
| /* Bring the rasterization back up, if it was enabled prior to this call */ |
| if (!is_rasterization_disabled) |
| { |
| m_gl.disable(GL_RASTERIZER_DISCARD); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDisable(GL_RASTERIZER_DISCARD) failed"); |
| } |
| |
| /* Activate the pre-call program object*/ |
| m_gl.useProgram(current_po_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() failed"); |
| |
| /* Bring back pre-call GL_PATCH_VERTICES_EXT setting */ |
| m_gl.patchParameteri(m_parent_test->m_glExtTokens.PATCH_VERTICES, current_patch_vertices); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glPatchParameteriEXT() failed"); |
| } |
| |
| /** Compiles all requested shaders. Should any of the shaders not compile, |
| * TestError exception can be thrown if @param should_succeed is set to true. |
| * |
| * @param n_shaders Amount of shader IDs passed in @param shaders argument. |
| * @param shaders IDs of shader objects to compile. |
| * @param should_succeed True if the shaders are expected to compile, false if |
| * it's fine for them to not to compile successfully. |
| **/ |
| void TessellationShaderUtils::compileShaders(glw::GLint n_shaders, const glw::GLuint* shaders, bool should_succeed) |
| { |
| for (glw::GLint n_shader = 0; n_shader < n_shaders; ++n_shader) |
| { |
| glw::GLuint shader = shaders[n_shader]; |
| |
| if (shader != 0) |
| { |
| glw::GLint compile_status = GL_FALSE; |
| |
| m_gl.compileShader(shader); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCompileShader() failed"); |
| |
| m_gl.getShaderiv(shader, GL_COMPILE_STATUS, &compile_status); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGetShaderiv() failed"); |
| |
| if (should_succeed && compile_status != GL_TRUE) |
| { |
| std::string info_log = m_parent_test->getCompilationInfoLog(shader); |
| std::string shader_source = m_parent_test->getShaderSource(shader); |
| |
| m_parent_test->m_context.getTestContext().getLog() << tcu::TestLog::Message |
| << "Compilation failure:\n\n" |
| << info_log << "\n\n" |
| << "Source:\n\n" |
| << shader_source << "\n\n" |
| << tcu::TestLog::EndMessage; |
| TCU_FAIL("Shader compilation failed"); |
| } |
| else if (!should_succeed && compile_status == GL_TRUE) |
| { |
| std::string shader_source = m_parent_test->getShaderSource(shader); |
| m_parent_test->m_context.getTestContext().getLog() << tcu::TestLog::Message |
| << "Compilation failure expected.\nSource:\n\n" |
| << shader_source << "\n\n" |
| << tcu::TestLog::EndMessage; |
| TCU_FAIL("Shader compiled successfully, even though it was " |
| "expected to fail."); |
| } |
| } |
| } /* for (all shaders) */ |
| } |
| |
| /** Converts input barycentric coordinates to Cartesian coordinate system. The function assumes |
| * a triangle basis built of the following verticeS: (0.5, 0), (1, 1), (0, 1). |
| * |
| * @param barycentric_coordinates Three FP values storing barycentric coordinates of a point. Must |
| * NOT be NULL. |
| * @param out_cartesian_coordinates Deref will be used to store two result FP values. Must not be NULL. |
| **/ |
| void TessellationShaderUtils::convertBarycentricCoordinatesToCartesian(const float* barycentric_coordinates, |
| float* out_cartesian_coordinates) |
| { |
| /* Assume output triangle uses the following base: |
| * |
| * (0.5, 0) |
| * (1, 1) |
| * (0, 1) |
| */ |
| const float triangle_vertex1_cartesian[2] = { 0.5f, 0.0f }; |
| const float triangle_vertex2_cartesian[2] = { 1.0f, 1.0f }; |
| const float triangle_vertex3_cartesian[2] = { 0.0f, 1.0f }; |
| |
| out_cartesian_coordinates[0] = (float)((double)barycentric_coordinates[0] * (double)triangle_vertex1_cartesian[0] + |
| (double)barycentric_coordinates[1] * (double)triangle_vertex2_cartesian[0] + |
| (double)barycentric_coordinates[2] * (double)triangle_vertex3_cartesian[0]); |
| out_cartesian_coordinates[1] = barycentric_coordinates[0] * triangle_vertex1_cartesian[1] + |
| barycentric_coordinates[1] * triangle_vertex2_cartesian[1] + |
| barycentric_coordinates[2] * triangle_vertex3_cartesian[1]; |
| } |
| |
| /** Converts input Cartesian coordinates to barycentric coordinate system. The function assumes |
| * a triangle basis built of the following verticeS: (0.5, 0), (1, 1), (0, 1). |
| * |
| * @param cartesian_coordinates Two FP values storing Cartesian coordinates of a point. Must NOT |
| * be NULL. |
| * @param out_barycentric_coordinates Deref will be used to store three result FP values. Must NOT be NULL. |
| **/ |
| void TessellationShaderUtils::convertCartesianCoordinatesToBarycentric(const float* cartesian_coordinates, |
| float* out_barycentric_coordinates) |
| { |
| /* Assume input triangle uses the following base: |
| * |
| * (0.5, 0) |
| * (1, 1) |
| * (0, 1) |
| */ |
| const float x1 = 0.5f; |
| const float x2 = 1.0f; |
| const float x3 = 0.0f; |
| const float y1 = 0.0f; |
| const float y2 = 1.0f; |
| const float y3 = 1.0f; |
| |
| out_barycentric_coordinates[0] = |
| ((y2 - y3) * (cartesian_coordinates[0] - x3) + (x3 - x2) * (cartesian_coordinates[1] - y3)) / |
| ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3)); |
| out_barycentric_coordinates[1] = |
| ((y3 - y1) * (cartesian_coordinates[0] - x3) + (x1 - x3) * (cartesian_coordinates[1] - y3)) / |
| ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3)); |
| out_barycentric_coordinates[2] = 1.0f - out_barycentric_coordinates[0] - out_barycentric_coordinates[1]; |
| } |
| |
| /** Deinitializes ES objects created for TessellationShaderUtils |
| * instance. |
| **/ |
| void TessellationShaderUtils::deinit() |
| { |
| if (!m_parent_test->m_is_tessellation_shader_supported) |
| { |
| return; |
| } |
| |
| if (m_bo_id != 0) |
| { |
| m_gl.deleteBuffers(1, &m_bo_id); |
| |
| m_bo_id = 0; |
| } |
| |
| if (m_fs_id != 0) |
| { |
| m_gl.deleteShader(m_fs_id); |
| |
| m_fs_id = 0; |
| } |
| |
| if (m_qo_pg_id != 0) |
| { |
| m_gl.deleteQueries(1, &m_qo_pg_id); |
| |
| m_qo_pg_id = 0; |
| } |
| |
| if (m_vs_id != 0) |
| { |
| m_gl.deleteShader(m_vs_id); |
| |
| m_vs_id = 0; |
| } |
| |
| /* Revert TF buffer object bindings */ |
| m_gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0 /* buffer */); |
| m_gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0 /* index */, 0 /* buffer */); |
| |
| /* Disable GL_RASTERIZER_DISCARD mode */ |
| m_gl.disable(GL_RASTERIZER_DISCARD); |
| |
| /* Restore GL_PATCH_VERTICES_EXT value */ |
| m_gl.patchParameteri(m_parent_test->m_glExtTokens.PATCH_VERTICES, 3); |
| } |
| |
| /** Retrieves generic tessellation control shader source code. |
| * The shader users user-specified amount of output patch vertices and: |
| * |
| * - sets gl_Position to gl_in[0].gl_Position if second argument is set to false. |
| * - sets gl_Position to gl_in[gl_InvocationID].gl_Position otherwise. |
| * |
| * @param n_patch_vertices Amount of output patch vertices |
| * to use in the shader. |
| * @param should_use_glInvocationID_indexed_input See above. |
| * |
| * @return Requested string. |
| */ |
| std::string TessellationShaderUtils::getGenericTCCode(unsigned int n_patch_vertices, |
| bool should_use_glInvocationID_indexed_input) |
| { |
| std::string result; |
| const char* tc_body_false = "${VERSION}\n" |
| "\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| "layout (vertices = MAX_VERTICES) out;\n" |
| "\n" |
| "uniform vec2 inner_tess_level;\n" |
| "uniform vec4 outer_tess_level;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[0].gl_Position;\n" |
| "\n" |
| " gl_TessLevelInner[0] = inner_tess_level.x;\n" |
| " gl_TessLevelInner[1] = inner_tess_level.y;\n" |
| " gl_TessLevelOuter[0] = outer_tess_level.x;\n" |
| " gl_TessLevelOuter[1] = outer_tess_level.y;\n" |
| " gl_TessLevelOuter[2] = outer_tess_level.z;\n" |
| " gl_TessLevelOuter[3] = outer_tess_level.w;\n" |
| "}\n"; |
| |
| const char* tc_body_true = "${VERSION}\n" |
| "\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| "layout (vertices = MAX_VERTICES) out;\n" |
| "\n" |
| "uniform vec2 inner_tess_level;\n" |
| "uniform vec4 outer_tess_level;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n" |
| "\n" |
| " gl_TessLevelInner[0] = inner_tess_level.x;\n" |
| " gl_TessLevelInner[1] = inner_tess_level.y;\n" |
| " gl_TessLevelOuter[0] = outer_tess_level.x;\n" |
| " gl_TessLevelOuter[1] = outer_tess_level.y;\n" |
| " gl_TessLevelOuter[2] = outer_tess_level.z;\n" |
| " gl_TessLevelOuter[3] = outer_tess_level.w;\n" |
| "}\n"; |
| |
| const char* n_patch_vertices_raw_ptr = NULL; |
| std::stringstream n_patch_vertices_sstream; |
| std::string n_patch_vertices_string; |
| std::string token = "MAX_VERTICES"; |
| std::size_t token_index = std::string::npos; |
| |
| n_patch_vertices_sstream << n_patch_vertices; |
| n_patch_vertices_string = n_patch_vertices_sstream.str(); |
| n_patch_vertices_raw_ptr = n_patch_vertices_string.c_str(); |
| |
| result = (should_use_glInvocationID_indexed_input) ? tc_body_true : tc_body_false; |
| |
| while ((token_index = result.find(token)) != std::string::npos) |
| { |
| result = result.replace(token_index, token.length(), n_patch_vertices_raw_ptr); |
| |
| token_index = result.find(token); |
| } |
| |
| return result; |
| } |
| |
| /** Retrieves generic tessellation evaluation shader source code. |
| * The shader users user-specified tessellation properties. |
| * |
| * @param vertex_spacing Vertex spacing mode to use in the shader. |
| * @param primitive_mode Primitive mode to use in the shader. |
| * @param point_mode true to use point_mode in the shader, false |
| * to omit it. |
| * |
| * @return Requested string. |
| */ |
| std::string TessellationShaderUtils::getGenericTECode(_tessellation_shader_vertex_spacing vertex_spacing, |
| _tessellation_primitive_mode primitive_mode, |
| _tessellation_shader_vertex_ordering vertex_ordering, |
| bool point_mode) |
| { |
| std::string result; |
| const char* te_body = "${VERSION}\n" |
| "\n" |
| "${TESSELLATION_SHADER_REQUIRE}\n" |
| "\n" |
| "layout (TESSELLATOR_PRIMITIVE_MODE VERTEX_SPACING_MODE VERTEX_ORDERING POINT_MODE) in;\n" |
| "\n" |
| "out vec3 result_uvw;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = gl_in[0].gl_Position;\n" |
| " result_uvw = gl_TessCoord;\n" |
| "}\n"; |
| |
| const char* point_mode_token = "POINT_MODE"; |
| std::size_t point_mode_token_index = std::string::npos; |
| std::string primitive_mode_string = TessellationShaderUtils::getESTokenForPrimitiveMode(primitive_mode); |
| const char* primitive_mode_token = "TESSELLATOR_PRIMITIVE_MODE"; |
| std::size_t primitive_mode_token_index = std::string::npos; |
| std::string vertex_ordering_string; |
| const char* vertex_ordering_token = "VERTEX_ORDERING"; |
| std::size_t vertex_ordering_token_index = std::string::npos; |
| std::string vertex_spacing_mode_string; |
| const char* vertex_spacing_token = "VERTEX_SPACING_MODE"; |
| std::size_t vertex_spacing_token_index = std::string::npos; |
| |
| result = te_body; |
| |
| /* Prepare the vertex ordering token. We need to do this manually, because the default vertex spacing |
| * mode translates to empty string and the shader would fail to compile if we hadn't taken care of the |
| * comma |
| */ |
| if (vertex_ordering == TESSELLATION_SHADER_VERTEX_ORDERING_DEFAULT) |
| { |
| vertex_ordering_string = TessellationShaderUtils::getESTokenForVertexOrderingMode(vertex_ordering); |
| } |
| else |
| { |
| std::stringstream helper_sstream; |
| |
| helper_sstream << ", " << TessellationShaderUtils::getESTokenForVertexOrderingMode(vertex_ordering); |
| |
| vertex_ordering_string = helper_sstream.str(); |
| } |
| |
| /* Do the same for vertex spacing token */ |
| if (vertex_spacing == TESSELLATION_SHADER_VERTEX_SPACING_DEFAULT) |
| { |
| vertex_spacing_mode_string = TessellationShaderUtils::getESTokenForVertexSpacingMode(vertex_spacing); |
| } |
| else |
| { |
| std::stringstream helper_sstream; |
| |
| helper_sstream << ", " << TessellationShaderUtils::getESTokenForVertexSpacingMode(vertex_spacing); |
| |
| vertex_spacing_mode_string = helper_sstream.str(); |
| } |
| |
| /* Primitive mode */ |
| while ((primitive_mode_token_index = result.find(primitive_mode_token)) != std::string::npos) |
| { |
| result = result.replace(primitive_mode_token_index, strlen(primitive_mode_token), primitive_mode_string); |
| |
| primitive_mode_token_index = result.find(primitive_mode_token); |
| } |
| |
| /* Vertex ordering */ |
| while ((vertex_ordering_token_index = result.find(vertex_ordering_token)) != std::string::npos) |
| { |
| result = result.replace(vertex_ordering_token_index, strlen(vertex_ordering_token), vertex_ordering_string); |
| |
| vertex_ordering_token_index = result.find(vertex_ordering_token); |
| } |
| |
| /* Vertex spacing */ |
| while ((vertex_spacing_token_index = result.find(vertex_spacing_token)) != std::string::npos) |
| { |
| result = result.replace(vertex_spacing_token_index, strlen(vertex_spacing_token), vertex_spacing_mode_string); |
| |
| vertex_spacing_token_index = result.find(vertex_spacing_token); |
| } |
| |
| /* Point mode */ |
| while ((point_mode_token_index = result.find(point_mode_token)) != std::string::npos) |
| { |
| result = result.replace(point_mode_token_index, strlen(point_mode_token), (point_mode) ? ", point_mode" : ""); |
| |
| point_mode_token_index = result.find(point_mode_token); |
| } |
| |
| return result; |
| } |
| |
| /** Initializes ES objects that will be needed for non-static calls |
| * |
| * This function throws TestError exception if an error occurs. |
| * |
| **/ |
| void TessellationShaderUtils::init() |
| { |
| if (!m_parent_test->m_is_tessellation_shader_supported) |
| { |
| throw tcu::NotSupportedError(TESSELLATION_SHADER_EXTENSION_NOT_SUPPORTED); |
| } |
| |
| /* Create buffer object used to hold XFB data */ |
| m_gl.genBuffers(1, &m_bo_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGenBuffers() failed"); |
| |
| /* Create query object */ |
| m_gl.genQueries(1, &m_qo_pg_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glGenQueries() failed"); |
| |
| /* Initialize shader objects */ |
| m_fs_id = m_gl.createShader(GL_FRAGMENT_SHADER); |
| m_vs_id = m_gl.createShader(GL_VERTEX_SHADER); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glCreateShader() failed"); |
| |
| /* Initialize bodies of the shaders and try to compile them */ |
| const glw::GLuint shaders[] = { m_fs_id, m_vs_id }; |
| const unsigned int n_shaders = DE_LENGTH_OF_ARRAY(shaders); |
| |
| const char* fs_body = "${VERSION}\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| "}\n"; |
| const char* vs_body = "${VERSION}\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(1.0, 0.0, 0.0, 1.0);\n" |
| "}\n"; |
| |
| m_parent_test->shaderSourceSpecialized(m_fs_id, 1 /* count */, &fs_body); |
| m_parent_test->shaderSourceSpecialized(m_vs_id, 1 /* count */, &vs_body); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glShaderSource() failed"); |
| |
| compileShaders(n_shaders, shaders, true); |
| } |
| |
| /** Retrieves amount of vertices that will be generated for a draw call |
| * that uses a program object, which is built of (at least) one tessellation |
| * stage. |
| * |
| * NOTE: This function can temporarily unbind active program object. |
| * This function throws TestError exception if an error occurs. |
| * |
| * @param primitive_mode Primitive mode used for the tessellation. |
| * @param inner_tessellation_level Two FP values that define inner tessellation levels. |
| * Must NOT be NULL. |
| * @param outer_tessellation_level Four FP values that define outer tessellation levels. |
| * Must NOT be NULL. |
| * @param vertex_spacing Vertex spacing mode used for the tessellation. |
| * @param is_point_mode_enabled true if point_mode should be enabled for the query, |
| * false otherwise. |
| * |
| * This function REQUIRES GL_EXT_geometry_shader support. |
| * This function throws TestError exception, should an error occur. |
| * |
| * @return Amount of vertices that would be generated by the tessellator unit for |
| * a particular draw call. |
| **/ |
| unsigned int TessellationShaderUtils::getAmountOfVerticesGeneratedByTessellator( |
| _tessellation_primitive_mode primitive_mode, const float* inner_tessellation_level, |
| const float* outer_tessellation_level, _tessellation_shader_vertex_spacing vertex_spacing, |
| bool is_point_mode_enabled) |
| { |
| unsigned int result = 0; |
| |
| switch (primitive_mode) |
| { |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES: |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: |
| { |
| /* We refer to a counter program, TC and TE stages are configured as specified |
| * by the caller. Before we issue the draw call, we begin a query introduced in |
| * GL_EXT_geometry_shader that allows us to count how many primitives would have been generated. Doing so |
| * allows us to determine how many vertices were generated during the processed. |
| */ |
| const glw::GLint test_n_patch_vertices = 1; |
| _tessellation_vertex_counter_program test_program(m_gl); |
| |
| initTessellationVertexCounterProgram(inner_tessellation_level, outer_tessellation_level, test_n_patch_vertices, |
| vertex_spacing, primitive_mode, is_point_mode_enabled, test_program); |
| |
| result = test_program.n_data_vertices; |
| break; |
| } |
| |
| default: |
| { |
| TCU_FAIL("Unrecognized primitive mode"); |
| } |
| } /* switch (primitive_mode) */ |
| |
| return result; |
| } |
| |
| /** Retrieves data generated by a tessellator for a particular tessellation configuration. |
| * |
| * @param inner Two FP values defining inner tessellation values to be used for tessellation. |
| * Must not be NULL. |
| * @param point_mode true if point mode is to be used for tessellation, false otherwise. |
| * @param primitive_mode Primitive mode to be used for tessellation. |
| * @param vertex_ordering Vertex ordering to be used for tessellation. |
| * @param vertex_spacing Vertex spacing to be used for tessellation. |
| * @param outer Four FP values defining outer tessellation values to be used for tessellation. |
| * Must not be NULL. |
| * |
| * @return Pointer to buffer containing tessellated coordinates. |
| **/ |
| std::vector<char> TessellationShaderUtils::getDataGeneratedByTessellator( |
| const float* inner, bool point_mode, _tessellation_primitive_mode primitive_mode, |
| _tessellation_shader_vertex_ordering vertex_ordering, _tessellation_shader_vertex_spacing vertex_spacing, |
| const float* outer) |
| { |
| (void)vertex_ordering; |
| |
| glw::GLint test_n_patch_vertices = getPatchVerticesForPrimitiveMode(primitive_mode); |
| _tessellation_vertex_counter_program test_program(m_gl); |
| |
| initTessellationVertexCounterProgram(inner, outer, test_n_patch_vertices, vertex_spacing, primitive_mode, |
| point_mode, test_program); |
| return test_program.m_data; |
| } |
| |
| /** Retrieves ESSL token corresponding to particular primitive mode. |
| * Will throw TestError exception if @param primitive_mode is not |
| * valid. |
| * |
| * @param primitive_mode Primitive mode to consider. |
| * |
| * @return String telling how the primitive mode would be expressed in |
| * ES SL. |
| **/ |
| std::string TessellationShaderUtils::getESTokenForPrimitiveMode(_tessellation_primitive_mode primitive_mode) |
| { |
| std::string result = "?"; |
| |
| switch (primitive_mode) |
| { |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES: |
| { |
| result = "isolines"; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: |
| { |
| result = "quads"; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: |
| { |
| result = "triangles"; |
| |
| break; |
| } |
| |
| default: |
| { |
| TCU_FAIL("Unrecognized tessellation primitive mode"); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Retrieves ESSL token corresponding to particular vertex ordering. |
| * Will throw TestError exception if @param vertex_ordering is not |
| * valid. |
| * |
| * @param vertex_ordering Vertex ordering to consider. |
| * |
| * @return String telling how the vertex mode would be expressed in |
| * ES SL. |
| **/ |
| std::string TessellationShaderUtils::getESTokenForVertexOrderingMode( |
| _tessellation_shader_vertex_ordering vertex_ordering) |
| { |
| std::string result; |
| |
| switch (vertex_ordering) |
| { |
| case TESSELLATION_SHADER_VERTEX_ORDERING_CCW: |
| { |
| result = "ccw"; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_ORDERING_CW: |
| { |
| result = "cw"; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_ORDERING_DEFAULT: |
| { |
| /* Simply return an empty token */ |
| result = ""; |
| |
| break; |
| } |
| |
| default: |
| { |
| TCU_FAIL("Unrecognized tessellation shader vertex ordering"); |
| } |
| } /* switch (vertex_ordering) */ |
| |
| return result; |
| } |
| |
| /** Retrieves ESSL token corresponding to particular vertex spacing mode. |
| * Will throw TestError exception if @param vertex_spacing is not |
| * valid. |
| * |
| * @param vertex_spacing Vertex spacing mode to consider. |
| * |
| * @return String telling how the vertex spacing mode would be expressed in |
| * ES SL. |
| **/ |
| std::string TessellationShaderUtils::getESTokenForVertexSpacingMode(_tessellation_shader_vertex_spacing vertex_spacing) |
| { |
| std::string result; |
| |
| switch (vertex_spacing) |
| { |
| case TESSELLATION_SHADER_VERTEX_SPACING_DEFAULT: |
| { |
| result = ""; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_SPACING_EQUAL: |
| { |
| result = "equal_spacing"; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_EVEN: |
| { |
| result = "fractional_even_spacing"; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_ODD: |
| { |
| result = "fractional_odd_spacing"; |
| |
| break; |
| } |
| |
| default: |
| { |
| TCU_FAIL("Invalid vertex spacing mode requested"); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Tells how many vertices should be passed in a single patch for |
| * particular primitive mode to work for tessellation stage. |
| * |
| * Throws TestError exception if @param primitive_mode is invalid. |
| * |
| * @param primitive_mode Primitive mode to consider. |
| * |
| * @return Requested value. |
| **/ |
| glw::GLint TessellationShaderUtils::getPatchVerticesForPrimitiveMode(_tessellation_primitive_mode primitive_mode) |
| { |
| glw::GLint result = 0; |
| |
| switch (primitive_mode) |
| { |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES: |
| result = 4; |
| break; |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: |
| result = 4; |
| break; |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: |
| result = 3; |
| break; |
| |
| default: |
| { |
| TCU_FAIL("Unrecognized primitive mode"); |
| } |
| } /* switch (primitive_mode) */ |
| |
| return result; |
| } |
| |
| /** Retrieves tessellation level used by the tessellator, given |
| * vertex spacing setting. |
| * |
| * @param vertex_spacing Vertex spacing used for tessellation |
| * evaluation stage; |
| * @param level Tessellation level as defined in TC |
| * stage OR as configured with |
| * GL_PATCH_DEFAULT_*_LEVEL pnames. |
| * @param gl_max_tess_gen_level_value GL_MAX_TESS_GEN_LEVEL_EXT pname value, |
| * as reported by the implementation. |
| * @param out_clamped Deref will be used to store clamped (but |
| * not rounded) representation. Can be NULL. |
| * @param out_clamped_and_rounded Deref will be used to store clamped and |
| * rounded representation. Can be NULL. |
| **/ |
| void TessellationShaderUtils::getTessellationLevelAfterVertexSpacing(_tessellation_shader_vertex_spacing vertex_spacing, |
| float level, |
| glw::GLint gl_max_tess_gen_level_value, |
| float* out_clamped, float* out_clamped_and_rounded) |
| { |
| /* Behavior is as per EXT_tessellation_shader spec */ |
| switch (vertex_spacing) |
| { |
| case TESSELLATION_SHADER_VERTEX_SPACING_DEFAULT: |
| case TESSELLATION_SHADER_VERTEX_SPACING_EQUAL: |
| { |
| if (level < 1.0f) |
| { |
| level = 1.0f; |
| } |
| else if (level > (float)gl_max_tess_gen_level_value) |
| { |
| level = (float)gl_max_tess_gen_level_value; |
| } |
| |
| if (out_clamped != DE_NULL) |
| { |
| *out_clamped = level; |
| } |
| |
| /* Round *up* to nearest integer */ |
| level = (float)((int)(deFloatCeil(level) + 0.5f)); |
| |
| if (out_clamped_and_rounded != DE_NULL) |
| { |
| *out_clamped_and_rounded = level; |
| } |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_EVEN: |
| { |
| if (level < 2.0f) |
| { |
| level = 2.0f; |
| } |
| else if (level > (float)gl_max_tess_gen_level_value) |
| { |
| level = (float)gl_max_tess_gen_level_value; |
| } |
| |
| if (out_clamped != DE_NULL) |
| { |
| *out_clamped = level; |
| } |
| |
| /* Round *up* to nearest *even* integer */ |
| int level_temp = (int)(deFloatCeil(level) + 0.5f); |
| |
| if ((level_temp % 2) != 0) |
| { |
| level_temp++; |
| } |
| |
| if (out_clamped_and_rounded != DE_NULL) |
| { |
| *out_clamped_and_rounded = (float)level_temp; |
| } |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_ODD: |
| { |
| if (level < 1.0f) |
| { |
| level = 1.0f; |
| } |
| else if (level > (float)(gl_max_tess_gen_level_value - 1)) |
| { |
| level = (float)(gl_max_tess_gen_level_value - 1); |
| } |
| |
| if (out_clamped != DE_NULL) |
| { |
| *out_clamped = level; |
| } |
| |
| /* Round to *up* nearest *odd* integer */ |
| int level_temp = (int)(deFloatCeil(level) + 0.5f); |
| |
| if ((level_temp % 2) != 1) |
| { |
| level_temp++; |
| } |
| |
| if (out_clamped_and_rounded != DE_NULL) |
| { |
| *out_clamped_and_rounded = (float)level_temp; |
| } |
| |
| break; |
| } |
| |
| default: |
| { |
| TCU_FAIL("Unrecognized vertex spacing mode"); |
| } |
| } /* switch(vertex_spacing) */ |
| } |
| |
| /** Returns a vector of _tessellation_levels instances with different level values. |
| * |
| * @param primitive_mode Primitive mode to consider. |
| * @param gl_max_tess_gen_level_value Implementation-specific GL_MAX_TESS_GEN_LEVEL_EXT value. |
| * @param filter Condition which all generated tuples should meet in |
| * order to land in the result vector. |
| * |
| * @return _tessellation_levels_set instance storing described values. |
| **/ |
| _tessellation_levels_set TessellationShaderUtils::getTessellationLevelSetForPrimitiveMode( |
| _tessellation_primitive_mode primitive_mode, glw::GLint gl_max_tess_gen_level_value, |
| _tessellation_level_set_filter filter) |
| { |
| /* As a starter value, use a tessellation level that is different for each |
| * primitive modes, just to make sure the implementation can correctly |
| * handle various tessellation level values */ |
| glw::GLint n_min_patch_vertices = getPatchVerticesForPrimitiveMode(primitive_mode); |
| |
| glw::GLint n_half_max_patch_vertices_mul_min = gl_max_tess_gen_level_value / 2; |
| glw::GLint n_max_patch_vertices_mul_min = gl_max_tess_gen_level_value; |
| |
| if ((n_half_max_patch_vertices_mul_min % n_min_patch_vertices) != 0) |
| { |
| /* Round to nearest mul-of-min integer */ |
| n_half_max_patch_vertices_mul_min += |
| (n_min_patch_vertices - (gl_max_tess_gen_level_value / 2) % n_min_patch_vertices); |
| } |
| |
| if ((n_max_patch_vertices_mul_min % n_min_patch_vertices) != 0) |
| { |
| /* Round to previous nearest mul-of-min integer */ |
| n_max_patch_vertices_mul_min -= (gl_max_tess_gen_level_value % n_min_patch_vertices); |
| } |
| |
| /* Prepare the result vector items */ |
| _tessellation_levels_set result; |
| |
| if ((filter & TESSELLATION_LEVEL_SET_FILTER_ALL_LEVELS_USE_THE_SAME_VALUE) != 0) |
| { |
| /* Prepare the result vector items */ |
| _tessellation_levels item_1; |
| _tessellation_levels item_2; |
| _tessellation_levels item_3; |
| |
| item_1.inner[0] = float(n_min_patch_vertices); |
| item_1.inner[1] = float(n_min_patch_vertices + 1); |
| item_1.outer[0] = float(n_min_patch_vertices + 3); |
| item_1.outer[1] = float(n_min_patch_vertices + 2); |
| item_1.outer[2] = float(n_min_patch_vertices + 1); |
| item_1.outer[3] = float(n_min_patch_vertices); |
| |
| item_2.inner[0] = float(n_half_max_patch_vertices_mul_min); |
| item_2.inner[1] = float(n_half_max_patch_vertices_mul_min - 1); |
| item_2.outer[0] = float(n_half_max_patch_vertices_mul_min - 3); |
| item_2.outer[1] = float(n_half_max_patch_vertices_mul_min - 2); |
| item_2.outer[2] = float(n_half_max_patch_vertices_mul_min - 1); |
| item_2.outer[3] = float(n_half_max_patch_vertices_mul_min); |
| |
| item_3.inner[0] = float(n_max_patch_vertices_mul_min - 1); |
| item_3.inner[1] = float(n_max_patch_vertices_mul_min - 2); |
| item_3.outer[0] = float(n_max_patch_vertices_mul_min - 3); |
| item_3.outer[1] = float(n_max_patch_vertices_mul_min - 4); |
| item_3.outer[2] = float(n_max_patch_vertices_mul_min - 5); |
| item_3.outer[3] = float(n_max_patch_vertices_mul_min - 6); |
| |
| /* Push the items onto result vector. */ |
| result.push_back(item_1); |
| result.push_back(item_2); |
| result.push_back(item_3); |
| } |
| else |
| { |
| DE_ASSERT((filter & TESSELLATION_LEVEL_SET_FILTER_ALL_COMBINATIONS) != 0 || |
| (filter & TESSELLATION_LEVEL_SET_FILTER_INNER_AND_OUTER_LEVELS_USE_DIFFERENT_VALUES) != 0); |
| |
| const glw::GLint base_values[] = { -1, 1, n_half_max_patch_vertices_mul_min, n_max_patch_vertices_mul_min }; |
| const unsigned int n_base_values = DE_LENGTH_OF_ARRAY(base_values); |
| |
| const unsigned int n_relevant_inner_tess_levels = |
| (primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES) ? |
| 0 : |
| (primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS) ? 2 : 1; |
| |
| const unsigned int n_relevant_outer_tess_levels = |
| (primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES) ? |
| 2 : |
| (primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS) ? 4 : 3; |
| |
| for (unsigned int n_inner0_base_value = 0; |
| n_inner0_base_value < ((n_relevant_inner_tess_levels > 0) ? n_base_values : 1); ++n_inner0_base_value) |
| { |
| const glw::GLint inner0_value = base_values[n_inner0_base_value]; |
| |
| for (unsigned int n_inner1_base_value = 0; |
| n_inner1_base_value < ((n_relevant_inner_tess_levels > 1) ? n_base_values : 1); ++n_inner1_base_value) |
| { |
| const glw::GLint inner1_value = base_values[n_inner1_base_value]; |
| |
| for (unsigned int n_outer0_base_value = 0; |
| n_outer0_base_value < ((n_relevant_outer_tess_levels > 0) ? n_base_values : 1); |
| ++n_outer0_base_value) |
| { |
| const glw::GLint outer0_value = base_values[n_outer0_base_value]; |
| |
| for (unsigned int n_outer1_base_value = 0; |
| n_outer1_base_value < ((n_relevant_outer_tess_levels > 1) ? n_base_values : 1); |
| ++n_outer1_base_value) |
| { |
| const glw::GLint outer1_value = base_values[n_outer1_base_value]; |
| |
| for (unsigned int n_outer2_base_value = 0; |
| n_outer2_base_value < ((n_relevant_outer_tess_levels > 2) ? n_base_values : 1); |
| ++n_outer2_base_value) |
| { |
| const glw::GLint outer2_value = base_values[n_outer2_base_value]; |
| |
| for (unsigned int n_outer3_base_value = 0; |
| n_outer3_base_value < ((n_relevant_outer_tess_levels > 3) ? n_base_values : 1); |
| ++n_outer3_base_value) |
| { |
| const glw::GLint outer3_value = base_values[n_outer3_base_value]; |
| |
| /* Skip combinations where any of the relevant outer tessellation level values |
| * is negative. These would cause no tessellation coordinates to be generated |
| * by the tessellator. |
| */ |
| if ((n_relevant_outer_tess_levels > 0 && outer0_value < 0) || |
| (n_relevant_outer_tess_levels > 1 && outer1_value < 0) || |
| (n_relevant_outer_tess_levels > 2 && outer2_value < 0) || |
| (n_relevant_outer_tess_levels > 3 && outer3_value < 0)) |
| { |
| continue; |
| } |
| |
| /* If TESSELLATION_LEVEL_SET_FILTER_INNER_AND_OUTER_LEVELS_USE_DIFFERENT_VALUES |
| * filter was requested, make sure the values used separately for inner and outer |
| * tess levels are actually different. */ |
| if ((filter & |
| TESSELLATION_LEVEL_SET_FILTER_INNER_AND_OUTER_LEVELS_USE_DIFFERENT_VALUES) != 0) |
| { |
| DE_ASSERT(n_base_values >= 4 /* outer tess levels supported */); |
| |
| if ((n_relevant_inner_tess_levels > 1 && inner0_value == inner1_value) || |
| (n_relevant_outer_tess_levels > 1 && outer0_value == outer1_value) || |
| (n_relevant_outer_tess_levels > 2 && outer1_value == outer2_value) || |
| (n_relevant_outer_tess_levels > 3 && outer2_value == outer3_value)) |
| { |
| continue; |
| } |
| } /* if ((filter & TESSELLATION_LEVEL_SET_FILTER_INNER_AND_OUTER_LEVELS_USE_DIFFERENT_VALUES) != 0) */ |
| |
| /* If TESSELLATION_LEVEL_SET_FILTER_EXCLUDE_NEGATIVE_BASE_VALUE, make sure |
| * no inner/outer tessellation level we're about to use is negative. */ |
| if ((filter & TESSELLATION_LEVEL_SET_FILTER_EXCLUDE_NEGATIVE_BASE_VALUE) != 0) |
| { |
| if ((n_relevant_inner_tess_levels > 0 && inner0_value < 0) || |
| (n_relevant_inner_tess_levels > 1 && inner1_value < 0) || |
| (n_relevant_outer_tess_levels > 0 && outer0_value < 0) || |
| (n_relevant_outer_tess_levels > 1 && outer1_value < 0) || |
| (n_relevant_outer_tess_levels > 2 && outer2_value < 0) || |
| (n_relevant_outer_tess_levels > 3 && outer3_value < 0)) |
| { |
| continue; |
| } |
| } |
| |
| /* Construct the tess level combination */ |
| _tessellation_levels item; |
| |
| item.inner[0] = (glw::GLfloat)inner0_value; |
| item.inner[1] = (glw::GLfloat)inner1_value; |
| item.outer[0] = (glw::GLfloat)outer0_value; |
| item.outer[1] = (glw::GLfloat)outer1_value; |
| item.outer[2] = (glw::GLfloat)outer2_value; |
| item.outer[3] = (glw::GLfloat)outer3_value; |
| |
| /* Store it */ |
| result.push_back(item); |
| } /* for (all outer[3] base values) */ |
| } /* for (all outer[2] base values) */ |
| } /* for (all outer[1] base values) */ |
| } /* for (all outer[0] base values) */ |
| } /* for (all inner[1] base values) */ |
| } /* for (all inner[0] base values) */ |
| } |
| |
| return result; |
| } |
| |
| /** Retrieves transform feedback mode that should be used for glBeginTransformFeedback() |
| * call, if TF is to be active while a tessellated draw call is made. |
| * |
| * This function throws TestError exception if @param primitive_mode is invalid. |
| * |
| * @param primitive_mode Primitive mode to consider |
| * @param is_point_mode true if tessellation is run in point_mode mode, false otherwise. |
| * |
| * @return Corresponding ES enum. |
| **/ |
| glw::GLenum TessellationShaderUtils::getTFModeForPrimitiveMode(_tessellation_primitive_mode primitive_mode, |
| bool is_point_mode) |
| { |
| glw::GLenum result = GL_NONE; |
| |
| if (is_point_mode) |
| { |
| result = GL_POINTS; |
| } |
| else |
| { |
| switch (primitive_mode) |
| { |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES: |
| { |
| result = GL_LINES; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: |
| { |
| result = GL_TRIANGLES; |
| |
| break; |
| } |
| |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: |
| { |
| result = GL_TRIANGLES; |
| |
| break; |
| } |
| |
| default: |
| { |
| TCU_FAIL("Unrecognized primitive mode"); |
| } |
| } /* switch (primitive_mode) */ |
| } |
| |
| return result; |
| } |
| |
| /** Initializes a counter program. |
| * |
| * This function throws a TestError exception, should an error occur. |
| * |
| * @param inner_tess_level Two FP values to be used for inner tessellation levels. Must not be NULL. |
| * @param outer_tess_level Four FP values to be used for outer tessellation levels. Must not be NULL. |
| * @param n_patch_vertices Amount of TC stage output patch vertices. |
| * @param vertex_spacing Vertex spacing mode to be used for tessellation. |
| * @param primitive_mode Primitive mode to be used for tessellation. |
| * @param is_point_mode_enabled true if the point mode should be enabled for the program, false otherwise. |
| * @param result_descriptor Objects created during initialization will be stored in the referenced descriptor. |
| * |
| **/ |
| void TessellationShaderUtils::initTessellationVertexCounterProgram( |
| const float* inner_tess_level, const float* outer_tess_level, glw::GLint n_patch_vertices, |
| _tessellation_shader_vertex_spacing vertex_spacing, _tessellation_primitive_mode primitive_mode, |
| bool is_point_mode_enabled, _tessellation_vertex_counter_program& result_descriptor) |
| { |
| glw::GLint po_id = 0; |
| glw::GLint tc_id = 0; |
| glw::GLint te_id = 0; |
| |
| /* Generate the shader objects */ |
| tc_id = m_gl.createShader(m_parent_test->m_glExtTokens.TESS_CONTROL_SHADER); |
| te_id = m_gl.createShader(m_parent_test->m_glExtTokens.TESS_EVALUATION_SHADER); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "Could not create TC/TE shader objects"); |
| |
| /* Generate the program object */ |
| po_id = m_gl.createProgram(); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "Could not create a program object"); |
| |
| /* Initialize the shaders. |
| * |
| * Note: it's fine to use CCW ordering here, since it does not affect the amount |
| * of primitives generated by the tessellator. |
| **/ |
| std::string tc_code = getGenericTCCode(n_patch_vertices, false); |
| const char* tc_code_ptr = tc_code.c_str(); |
| std::string te_code = getGenericTECode(vertex_spacing, primitive_mode, TESSELLATION_SHADER_VERTEX_ORDERING_CCW, |
| is_point_mode_enabled); |
| const char* te_code_ptr = te_code.c_str(); |
| |
| /* Set up XFB */ |
| const char* varyings[] = { "result_uvw" }; |
| |
| m_gl.transformFeedbackVaryings(po_id, 1, /* count */ |
| varyings, GL_INTERLEAVED_ATTRIBS); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glTransformFeedbackVaryings() call failed"); |
| |
| /* Link the program and check that the linking has succeeded */ |
| bool build_success = m_parent_test->buildProgram(po_id, m_fs_id, 0 /* precompiled */, NULL, tc_id, 1, &tc_code_ptr, |
| te_id, 1, &te_code_ptr, m_vs_id, 0 /* precompiled */, NULL); |
| |
| if (!build_success) |
| { |
| TCU_FAIL("Compilation and/or linking failed"); |
| } |
| |
| /* Set up the inner/outer tess level uniforms */ |
| glw::GLint inner_tess_level_uniform_location = -1; |
| glw::GLint outer_tess_level_uniform_location = -1; |
| |
| inner_tess_level_uniform_location = m_gl.getUniformLocation(po_id, "inner_tess_level"); |
| outer_tess_level_uniform_location = m_gl.getUniformLocation(po_id, "outer_tess_level"); |
| |
| DE_ASSERT(inner_tess_level_uniform_location != -1); |
| DE_ASSERT(outer_tess_level_uniform_location != -1); |
| |
| m_gl.useProgram(po_id); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() call failed"); |
| |
| m_gl.uniform2fv(inner_tess_level_uniform_location, 1, /* count */ |
| inner_tess_level); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUniform2fv() call failed"); |
| |
| m_gl.uniform4fv(outer_tess_level_uniform_location, 1, /* count */ |
| outer_tess_level); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUniform4fv() call failed"); |
| |
| /* Initialize the test descriptor */ |
| memcpy(result_descriptor.inner_tess_level, inner_tess_level, sizeof(result_descriptor.inner_tess_level)); |
| memcpy(result_descriptor.outer_tess_level, outer_tess_level, sizeof(result_descriptor.outer_tess_level)); |
| |
| result_descriptor.is_point_mode_enabled = is_point_mode_enabled; |
| result_descriptor.n_patch_vertices = n_patch_vertices; |
| result_descriptor.po_id = po_id; |
| result_descriptor.primitive_mode = primitive_mode; |
| result_descriptor.tc_id = tc_id; |
| result_descriptor.te_id = te_id; |
| result_descriptor.tess_level_inner_uniform_location = inner_tess_level_uniform_location; |
| result_descriptor.tess_level_outer_uniform_location = outer_tess_level_uniform_location; |
| result_descriptor.vertex_spacing = vertex_spacing; |
| |
| captureTessellationData(result_descriptor); |
| } |
| |
| /** Tells whether user-provided vertex (expressed in tessellation space) generated |
| * during triangle tessellation is an outer edge vertex. |
| * |
| * @param primitive_mode Primitive mode, for which the tessellated vertex |
| * data was generated. |
| * @param tessellated_vertex_data Vertex data to check. Must define 3 floats. |
| * |
| * @return true if the vertex is a part of an outer edge, false otherwise. |
| **/ |
| bool TessellationShaderUtils::isOuterEdgeVertex(_tessellation_primitive_mode primitive_mode, |
| const float* tessellated_vertex_data) |
| { |
| const float epsilon = 1e-5f; |
| bool result = false; |
| |
| switch (primitive_mode) |
| { |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: |
| { |
| if (de::abs(tessellated_vertex_data[0]) < epsilon || de::abs(tessellated_vertex_data[0] - 1.0f) < epsilon || |
| de::abs(tessellated_vertex_data[1]) < epsilon || de::abs(tessellated_vertex_data[1] - 1.0f) < epsilon || |
| de::abs(tessellated_vertex_data[2]) < epsilon || de::abs(tessellated_vertex_data[2] - 1.0f) < epsilon) |
| { |
| /* Make sure vertex is inside the triangle */ |
| if (0.0f <= tessellated_vertex_data[0] && tessellated_vertex_data[0] <= 1.0f && |
| 0.0f <= tessellated_vertex_data[1] && tessellated_vertex_data[1] <= 1.0f && |
| 0.0f <= tessellated_vertex_data[2] && tessellated_vertex_data[2] <= 1.0f) |
| { |
| result = true; |
| } |
| } |
| |
| break; |
| } /* case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: */ |
| |
| case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: |
| { |
| if (de::abs(tessellated_vertex_data[0]) < epsilon || de::abs(tessellated_vertex_data[0] - 1.0f) < epsilon || |
| de::abs(tessellated_vertex_data[1]) < epsilon || de::abs(tessellated_vertex_data[1] - 1.0f) < epsilon) |
| { |
| result = true; |
| } |
| |
| break; |
| } /* case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: */ |
| |
| default: |
| { |
| DE_FATAL("Unrecognized primitive mode"); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Returns true if two triangles are the same. Takes potentially different |
| * vertex ordering into consideration. |
| * |
| * @param triangle_vertex_data Reference 9 FP values defining a triangle |
| * that the triangle defined by @param vertex_data |
| * will be compared against. |
| * @param vertex_data 9 FP values defining a triangle, perhaps in |
| * a different order, that @param triangle_vertex_data |
| * will be checked against. |
| * |
| * @return true if the triangles are the same; false otherwise. |
| * |
| **/ |
| bool TessellationShaderUtils::isTriangleDefined(const float* triangle_vertex_data, const float* vertex_data) |
| { |
| const float epsilon = 1e-5f; |
| bool has_triangle_vertex1_been_found = false; |
| bool has_triangle_vertex2_been_found = false; |
| bool has_triangle_vertex3_been_found = false; |
| bool result = false; |
| |
| if ((de::abs(triangle_vertex_data[0] - vertex_data[0]) < epsilon && |
| de::abs(triangle_vertex_data[1] - vertex_data[1]) < epsilon && |
| de::abs(triangle_vertex_data[2] - vertex_data[2]) < epsilon) || |
| (de::abs(triangle_vertex_data[3] - vertex_data[0]) < epsilon && |
| de::abs(triangle_vertex_data[4] - vertex_data[1]) < epsilon && |
| de::abs(triangle_vertex_data[5] - vertex_data[2]) < epsilon) || |
| (de::abs(triangle_vertex_data[6] - vertex_data[0]) < epsilon && |
| de::abs(triangle_vertex_data[7] - vertex_data[1]) < epsilon && |
| de::abs(triangle_vertex_data[8] - vertex_data[2]) < epsilon)) |
| { |
| has_triangle_vertex1_been_found = true; |
| } |
| |
| if ((de::abs(triangle_vertex_data[0] - vertex_data[3]) < epsilon && |
| de::abs(triangle_vertex_data[1] - vertex_data[4]) < epsilon && |
| de::abs(triangle_vertex_data[2] - vertex_data[5]) < epsilon) || |
| (de::abs(triangle_vertex_data[3] - vertex_data[3]) < epsilon && |
| de::abs(triangle_vertex_data[4] - vertex_data[4]) < epsilon && |
| de::abs(triangle_vertex_data[5] - vertex_data[5]) < epsilon) || |
| (de::abs(triangle_vertex_data[6] - vertex_data[3]) < epsilon && |
| de::abs(triangle_vertex_data[7] - vertex_data[4]) < epsilon && |
| de::abs(triangle_vertex_data[8] - vertex_data[5]) < epsilon)) |
| { |
| has_triangle_vertex2_been_found = true; |
| } |
| |
| if ((de::abs(triangle_vertex_data[0] - vertex_data[6]) < epsilon && |
| de::abs(triangle_vertex_data[1] - vertex_data[7]) < epsilon && |
| de::abs(triangle_vertex_data[2] - vertex_data[8]) < epsilon) || |
| (de::abs(triangle_vertex_data[3] - vertex_data[6]) < epsilon && |
| de::abs(triangle_vertex_data[4] - vertex_data[7]) < epsilon && |
| de::abs(triangle_vertex_data[5] - vertex_data[8]) < epsilon) || |
| (de::abs(triangle_vertex_data[6] - vertex_data[6]) < epsilon && |
| de::abs(triangle_vertex_data[7] - vertex_data[7]) < epsilon && |
| de::abs(triangle_vertex_data[8] - vertex_data[8]) < epsilon)) |
| { |
| has_triangle_vertex3_been_found = true; |
| } |
| |
| if (has_triangle_vertex1_been_found && has_triangle_vertex2_been_found && has_triangle_vertex3_been_found) |
| { |
| result = true; |
| } |
| |
| return result; |
| } |
| |
| } // namespace glcts |