/*------------------------------------------------------------------------- * 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 "esextcTessellationShaderVertexSpacing.hpp" #include "esextcTessellationShaderUtils.hpp" #include "gluContextInfo.hpp" #include "gluDefs.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include "tcuTestLog.hpp" #include /* Precision, with which the test should be executed. */ const float epsilon = 1e-3f; namespace glcts { /** Compares two barycentric/cartesian coordinates, using test-wide epsilon. * * @param in Coordinate to compare current instance against. * * @return true if the coordinates are equal, false otherwise. **/ bool TessellationShaderVertexSpacing::_tess_coordinate::operator==( const TessellationShaderVertexSpacing::_tess_coordinate& in) const { if (de::abs(this->u - in.u) < epsilon && de::abs(this->v - in.v) < epsilon && de::abs(this->w - in.w) < epsilon) { return true; } else { return false; } } /** Compares two Cartesian coordinates, using test-wide epsilon. * * @param in Coordinate to compare current instance against. * * @return true if the coordinates are equal, false otherwise. **/ bool TessellationShaderVertexSpacing::_tess_coordinate_cartesian::operator==( const TessellationShaderVertexSpacing::_tess_coordinate_cartesian& in) const { if (de::abs(this->x - in.x) < epsilon && de::abs(this->y - in.y) < epsilon) { return true; } else { return false; } } /** Constructor * * @param context Test context **/ TessellationShaderVertexSpacing::TessellationShaderVertexSpacing(Context& context, const ExtParameters& extParams) : TestCaseBase(context, extParams, "vertex_spacing", "Verifies vertex spacing qualifier behaves as specified") , m_gl_max_tess_gen_level_value(0) , m_vao_id(0) , m_utils(DE_NULL) { /* Left blank on purpose */ } /** Comparator function, used to compare two _tess_coordinate_cartesian * instances by their X components. * * @param a First coordinate to use for comparison; * @param b Second coordinate to use for comparison. * * @return true if X component of @param a is lower than b's; * false otherwise. **/ bool TessellationShaderVertexSpacing::compareEdgeByX(_tess_coordinate_cartesian a, _tess_coordinate_cartesian b) { return a.x < b.x; } /** Comparator function, used to compare two _tess_coordinate_cartesian * instances by their Y components. * * @param a First coordinate to use for comparison; * @param b Second coordinate to use for comparison. * * @return true if Y component of @param a is lower than b's; * false otherwise. **/ bool TessellationShaderVertexSpacing::compareEdgeByY(_tess_coordinate_cartesian a, _tess_coordinate_cartesian b) { return a.y < b.y; } /** Deinitializes ES objects created for the test. */ void TessellationShaderVertexSpacing::deinit() { /* Call base class' deinit() */ TestCaseBase::deinit(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); /* Unbind vertex array object */ gl.bindVertexArray(0); /* Delete vertex array object */ if (m_vao_id != 0) { gl.deleteVertexArrays(1, &m_vao_id); m_vao_id = 0; } /* Deinitialize utils instance */ if (m_utils != DE_NULL) { delete m_utils; m_utils = DE_NULL; } } /** Takes data generated by tessellator for a specific run configuration and * converts it into a set of edges that correspond to subsequent isolines. * This function asserts that the run uses a 'isolines' primitive mode. * * @param run Test run properties. * * @return A vector storing found edges. **/ TessellationShaderVertexSpacing::_tess_edges TessellationShaderVertexSpacing::getEdgesForIsolinesTessellation( const _run& run) { _tess_edges result; /* First convert the array data to a vector of edges, where each edge * is a vector of points with the same V component. After this is done, * points for each edge need to be sorted by U component. */ for (unsigned int n_vertex = 0; n_vertex < run.n_vertices; ++n_vertex) { /* Isolines are simple - we only need to create a new edge per each unique height */ const float* coordinate = (const float*)(&run.data[0]) + 3 /* components */ * n_vertex; _tess_coordinate_cartesian new_item; new_item.x = coordinate[0]; new_item.y = coordinate[1]; /* Is V recognized? */ _tess_edges_iterator edges_iterator; for (edges_iterator = result.begin(); edges_iterator != result.end(); edges_iterator++) { _tess_edge& edge = *edges_iterator; /* Each edge uses the same Y component, so we only need to check the first entry */ if (de::abs(edge.points[0].y - coordinate[1]) < epsilon) { /* Add the new point to the vector */ edge.points.push_back(new_item); break; } } /* for (all edges) */ if (edges_iterator == result.end()) { /* New edge starts at this point. * * Note that outermost tessellation level does not apply to this * primitive mode. **/ _tess_edge new_edge(run.outer[1], run.outer[1], 1.0f); new_edge.points.push_back(new_item); result.push_back(new_edge); } } /* for (all vertices) */ /* For each edge, sort the points by U coordinate */ for (_tess_edges_iterator edges_iterator = result.begin(); edges_iterator != result.end(); ++edges_iterator) { _tess_edge_points& edge_points = edges_iterator->points; std::sort(edge_points.begin(), edge_points.end(), compareEdgeByX); } /* Done */ return result; } /** Takes data generated by tessellator for a specific run configuration and * converts it into a set of edges that define the outer and inner quad. * This function asserts that the run uses a 'quads' primitive mode. * * @param run Test run properties. * * @return A vector storing found edges. **/ TessellationShaderVertexSpacing::_tess_edges TessellationShaderVertexSpacing::getEdgesForQuadsTessellation( const _run& run) { _tess_edges result; /* First, convert the raw coordinate array into a vector of cartesian coordinates. * For this method, we will need to take out vertices in no specific order. */ std::vector<_tess_coordinate_cartesian> coordinates; for (unsigned int n_vertex = 0; n_vertex < run.n_vertices; ++n_vertex) { _tess_coordinate_cartesian new_coordinate; const float* vertex_data = (const float*)(&run.data[0]) + 3 /* components */ * n_vertex; new_coordinate.x = vertex_data[0]; new_coordinate.y = vertex_data[1]; coordinates.push_back(new_coordinate); } /* Data set is expected to describe an outer and inner rectangles. We will execute the quad extraction * process in two iterations: * * - first iteration will determine all coordinates that are part of the outer quad; * - second iteration will focus on the inner quad. * * Each iteration will start from identifying corner vertices: * * - top-left corner (x delta from (0.5, 0.5) should be negative, y delta from (0.5, 0.5) should be positive); * - top-right corner (x delta from (0.5, 0.5) should be positive, y delta from (0.5, 0.5) should be positive); * - bottom-left corner (x delta from (0.5, 0.5) should be negative, y delta from (0.5, 0.5) should be negative); * - bottom-right corner (x delta from (0.5, 0.5) should be positive, y delta from (0.5, 0.5) should be negative); * * Once we know where the corner vertices are, we will remove them from the data set and iterate again over the * data set to identify all points that are part of edges connecting the edge corners. After the loop is done, * these vertices will have been associated with relevant edges and removed from the data sets. * * Once two iterations are complete, we're done. */ const unsigned int n_iterations = (run.inner[0] > 1) ? 2 : 1; for (unsigned int n_iteration = 0; n_iteration < n_iterations; ++n_iteration) { _tess_coordinate_cartesian current_tl_point; float current_tl_point_delta = 0.0f; _tess_coordinate_cartesian current_tr_point; float current_tr_point_delta = 0.0f; _tess_coordinate_cartesian current_bl_point; float current_bl_point_delta = 0.0f; _tess_coordinate_cartesian current_br_point; float current_br_point_delta = 0.0f; /* Iterate over all points */ for (std::vector<_tess_coordinate_cartesian>::const_iterator coordinate_iterator = coordinates.begin(); coordinate_iterator != coordinates.end(); coordinate_iterator++) { const _tess_coordinate_cartesian& coordinate = *coordinate_iterator; float delta_x = coordinate.x - 0.5f; float delta_y = coordinate.y - 0.5f; float delta = deFloatSqrt(delta_x * delta_x + delta_y * delta_y); /* top-left corner (x delta from (0.5, 0.5) should be negative, y delta from (0.5, 0.5) should be positive); */ if (delta_x <= 0.0f && delta_y >= 0.0f) { if (delta > current_tl_point_delta) { current_tl_point = coordinate; current_tl_point_delta = delta; } } /* top-right corner (x delta from (0.5, 0.5) should be positive, y delta from (0.5, 0.5) should be positive); */ if (delta_x >= 0.0f && delta_y >= 0.0f) { if (delta > current_tr_point_delta) { current_tr_point = coordinate; current_tr_point_delta = delta; } } /* bottom-left corner (x delta from (0.5, 0.5) should be negative, y delta from (0.5, 0.5) should be negative); */ if (delta_x <= 0.0f && delta_y <= 0.0f) { if (delta > current_bl_point_delta) { current_bl_point = coordinate; current_bl_point_delta = delta; } } /* bottom-right corner (x delta from (0.5, 0.5) should be positive, y delta from (0.5, 0.5) should be negative); */ if (delta_x >= 0.0f && delta_y <= 0.0f) { if (delta > current_br_point_delta) { current_br_point = coordinate; current_br_point_delta = delta; } } } /* for (all coordinates) */ /* Note: If any of the outer tessellation level is 1, at least * two "current" points will refer to the same point. * * Now that we know where the corner vertices are, remove them from the data set */ const _tess_coordinate_cartesian* corner_coordinate_ptrs[] = { ¤t_tl_point, ¤t_tr_point, ¤t_bl_point, ¤t_br_point }; const unsigned int n_corner_coordinate_ptrs = sizeof(corner_coordinate_ptrs) / sizeof(corner_coordinate_ptrs[0]); for (unsigned int n_corner_coordinate = 0; n_corner_coordinate < n_corner_coordinate_ptrs; ++n_corner_coordinate) { const _tess_coordinate_cartesian& corner_coordinate = *(corner_coordinate_ptrs[n_corner_coordinate]); std::vector<_tess_coordinate_cartesian>::iterator iterator = std::find(coordinates.begin(), coordinates.end(), corner_coordinate); /* Iterator can be invalid at this point of any of the corner coordinates * referred more than once to the same point. */ if (iterator != coordinates.end()) { coordinates.erase(iterator); } } /* for (all corner coordinates) */ /* Proceed with identification of coordinates describing the edges. * * Note: for inner quad, we need to subtract 2 segments connecting outer and inner coordinates */ float tl_tr_delta = deFloatSqrt((current_tl_point.x - current_tr_point.x) * (current_tl_point.x - current_tr_point.x) + (current_tl_point.y - current_tr_point.y) * (current_tl_point.y - current_tr_point.y)); float tr_br_delta = deFloatSqrt((current_tr_point.x - current_br_point.x) * (current_tr_point.x - current_br_point.x) + (current_tr_point.y - current_br_point.y) * (current_tr_point.y - current_br_point.y)); float br_bl_delta = deFloatSqrt((current_br_point.x - current_bl_point.x) * (current_br_point.x - current_bl_point.x) + (current_br_point.y - current_bl_point.y) * (current_br_point.y - current_bl_point.y)); float bl_tl_delta = deFloatSqrt((current_bl_point.x - current_tl_point.x) * (current_bl_point.x - current_tl_point.x) + (current_bl_point.y - current_tl_point.y) * (current_bl_point.y - current_tl_point.y)); _tess_edge tl_tr_edge(0, 0, tl_tr_delta); _tess_edge tr_br_edge(0, 0, tr_br_delta); _tess_edge br_bl_edge(0, 0, br_bl_delta); _tess_edge bl_tl_edge(0, 0, bl_tl_delta); tl_tr_edge.outermost_tess_level = run.outer[3]; tr_br_edge.outermost_tess_level = run.outer[2]; br_bl_edge.outermost_tess_level = run.outer[1]; bl_tl_edge.outermost_tess_level = run.outer[0]; if (n_iteration == 0) { tl_tr_edge.tess_level = run.outer[3]; tr_br_edge.tess_level = run.outer[2]; br_bl_edge.tess_level = run.outer[1]; bl_tl_edge.tess_level = run.outer[0]; } else { tl_tr_edge.tess_level = run.inner[0] - 2 /* segments between inner and outer edges */; br_bl_edge.tess_level = run.inner[0] - 2 /* segments between inner and outer edges */; tr_br_edge.tess_level = run.inner[1] - 2 /* segments between inner and outer edges */; bl_tl_edge.tess_level = run.inner[1] - 2 /* segments between inner and outer edges */; } /* Add corner points to edge descriptors. Do *NOT* add the same point twice, as * that will confuse verifyEdges() and cause incorrect failures. */ tl_tr_edge.points.push_back(current_tl_point); if (!(current_tl_point == current_tr_point)) { tl_tr_edge.points.push_back(current_tr_point); } tr_br_edge.points.push_back(current_tr_point); if (!(current_tr_point == current_br_point)) { tr_br_edge.points.push_back(current_br_point); } br_bl_edge.points.push_back(current_br_point); if (!(current_br_point == current_bl_point)) { br_bl_edge.points.push_back(current_bl_point); } bl_tl_edge.points.push_back(current_bl_point); if (!(current_bl_point == current_tl_point)) { bl_tl_edge.points.push_back(current_tl_point); } /* Identify points that lie on any of the edges considered */ _tess_edge* edge_ptrs[] = { &tl_tr_edge, &tr_br_edge, &br_bl_edge, &bl_tl_edge }; const unsigned int n_edge_ptrs = sizeof(edge_ptrs) / sizeof(edge_ptrs[0]); for (unsigned int n_edge = 0; n_edge < n_edge_ptrs; ++n_edge) { _tess_edge& edge = *(edge_ptrs[n_edge]); /* Degenerate edges will only consist of one point, for which the following * code needs not be executed */ if (edge.points.size() > 1) { /* Retrieve edge's start & end points */ _tess_coordinate_cartesian edge_start_point = edge.points[0]; _tess_coordinate_cartesian edge_end_point = edge.points[1]; /* Iterate over the data set */ for (std::vector<_tess_coordinate_cartesian>::const_iterator iterator = coordinates.begin(); iterator != coordinates.end(); iterator++) { const _tess_coordinate_cartesian& coordinate = *iterator; if (isPointOnLine(edge_start_point, edge_end_point, coordinate) && !(edge_start_point == coordinate) && !(edge_end_point == coordinate)) { /* Make sure the point has not already been added. If this happens, * it is very likely there is a bug in the way the implementation's * support of point mode */ if (std::find_if(edge.points.begin(), edge.points.end(), _comparator_exact_tess_coordinate_match(coordinate)) == edge.points.end()) { edge.points.push_back(coordinate); } else { std::string primitive_mode_string = TessellationShaderUtils::getESTokenForPrimitiveMode(run.primitive_mode); std::string vertex_spacing_string = TessellationShaderUtils::getESTokenForVertexSpacingMode(run.vertex_spacing); m_testCtx.getLog() << tcu::TestLog::Message << "A duplicate vertex" << " (" << coordinate.x << ", " << coordinate.y << ") was found in set of coordinates generated for the following configuration:" << " inner tessellation levels:(" << run.inner[0] << ", " << run.inner[1] << ") outer tessellation levels:(" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ") primitive mode:" << primitive_mode_string << " vertex spacing mode:" << vertex_spacing_string << " point mode:yes" << tcu::TestLog::EndMessage; TCU_FAIL("A duplicate vertex was found in generated tessellation coordinate set " "when point mode was used"); } } } /* for (all coordinates in the data set) */ /* Sort all points in the edge relative to the start point */ std::sort(edge.points.begin(), edge.points.end(), _comparator_relative_to_base_point(edge_start_point)); } } /* for (all edges) */ /* Remove all coordinates associated to edges from the data set */ for (unsigned int n_edge = 0; n_edge < n_edge_ptrs; ++n_edge) { _tess_edge& edge = *(edge_ptrs[n_edge]); for (std::vector<_tess_coordinate_cartesian>::const_iterator iterator = edge.points.begin(); iterator != edge.points.end(); iterator++) { const _tess_coordinate_cartesian& coordinate = *iterator; std::vector<_tess_coordinate_cartesian>::iterator dataset_iterator = std::find(coordinates.begin(), coordinates.end(), coordinate); if (dataset_iterator != coordinates.end()) { coordinates.erase(dataset_iterator); } } /* for (all edge points) */ } /* for (all edges) */ /* Store the edges */ for (unsigned int n_edge = 0; n_edge < n_edge_ptrs; ++n_edge) { _tess_edge& edge = *(edge_ptrs[n_edge]); result.push_back(edge); } } /* for (both iterations) */ return result; } /** Takes data generated by tessellator for a specific run configuration and * converts it into a set of edges that correspond to subsequent triangles. * This function asserts that the run uses a 'triangles' primitive mode. * * This function throws a TestError, should an error occur or the data is found * to be incorrect. * * @param run Test run properties. * * @return A vector storing found edges. **/ TessellationShaderVertexSpacing::_tess_edges TessellationShaderVertexSpacing::getEdgesForTrianglesTessellation( const _run& run) { DE_ASSERT(run.data_cartesian != DE_NULL); /* Before we proceed, convert the raw arrayed data into a vector. We'll need to take items out * in an undefined order */ std::vector<_tess_coordinate_cartesian> coordinates; for (unsigned int n_vertex = 0; n_vertex < run.n_vertices; ++n_vertex) { const float* vertex_data_cartesian = (const float*)run.data_cartesian + n_vertex * 2; /* components */ _tess_coordinate_cartesian cartesian; cartesian.x = vertex_data_cartesian[0]; cartesian.y = vertex_data_cartesian[1]; coordinates.push_back(cartesian); } /* The function iterates over the data set generated by the tessellator and: * * 1) Finds three corner vertices. These coordinates are considered to correspond to corner vertices * of the outermost triangle. The coordinates are removed from the data set. Note that it is an * error if less than three coordinates are available in the data set at this point. * 2) Iterates over remaining points in the data set and associates them with AB, BC and CA edges. * Each point found to be a part of any of the edges considered is removed from the data set. * 3) If more than 0 coordinates are still available in the data set at this point, implementation * returns to step 1) * * After the implementation runs out of tessellation coordinates, a few quick checks are executed * and the function returns to the caller. */ float base_tess_level = 0.0f; unsigned int tess_level_delta = 0; _tess_edges result; /* Make sure to follow the cited extension spec language: * * If the inner tessellation level is one and any of the outer tessellation * levels is greater than one, the inner tessellation level is treated as * though it were originally specified as 1+epsilon and will be rounded up to * result in a two- or three-segment subdivision according to the * tessellation spacing. * */ float inner0_round_clamped_value = 0; TessellationShaderUtils::getTessellationLevelAfterVertexSpacing( run.vertex_spacing, run.inner[0], m_gl_max_tess_gen_level_value, DE_NULL, /* out_clamped */ &inner0_round_clamped_value); if (inner0_round_clamped_value == 1.0f && (run.outer[0] > 1.0f || run.outer[1] > 1.0f || run.outer[2] > 1.0f)) { TessellationShaderUtils::getTessellationLevelAfterVertexSpacing( run.vertex_spacing, inner0_round_clamped_value + 1.0f /* epsilon */, m_gl_max_tess_gen_level_value, DE_NULL, /* out_clamped */ &base_tess_level); } else { TessellationShaderUtils::getTessellationLevelAfterVertexSpacing( run.vertex_spacing, run.inner[0], m_gl_max_tess_gen_level_value, DE_NULL, /* out_clamped */ &base_tess_level); } while (coordinates.size() > 0) { /* If we're using an odd tessellation level, it is an error if less than three coordinates are * available at this point. * If it's an even tessellation level, we must have at least three coordinates at hand OR * one, in which case the only coordinate left is the degenerate one. Should that happen, * it's time to leave. */ if ((((int)base_tess_level) % 2 == 0) && (coordinates.size() == 1)) { /* We're left with the degenerate vertex. Leave the loop */ break; } if (coordinates.size() < 3) { TCU_FAIL("Could not extract three corner vertices that would make up a triangle"); } /* Iterate over all vertices left and identify three corner vertices. For the outermost triangle, * these will be represented by (1, 0, 0), (0, 1, 0) and (0, 0, 1) barycentric coordinates, which * correspond to the following coordinates in Euclidean space: (0.5, 0), (1, 1), (0, 1) (as defined * in TessellationShaderUtils::convertCartesianCoordinatesToBarycentric() ). * * The idea here is to identify vertices that are the closest to the ideal coordinates, not necessarily * to find a perfect match. */ unsigned int curr_index = 0; float delta_v1 = 0.0f; /* barycentric:(1, 0, 0) -> Euclidean:(0.5, 0.0) */ float delta_v2 = 0.0f; /* barycentric:(0, 1, 0) -> Euclidean:(1.0, 1.0) */ float delta_v3 = 0.0f; /* barycentric:(0, 0, 1) -> Euclidean:(0.0, 1.0) */ bool is_first_iteration = true; unsigned int selected_v1_index = 0; unsigned int selected_v2_index = 0; unsigned int selected_v3_index = 0; for (std::vector<_tess_coordinate_cartesian>::const_iterator iterator = coordinates.begin(); iterator != coordinates.end(); iterator++, curr_index++) { const _tess_coordinate_cartesian& cartesian_coordinate = *iterator; float curr_delta_v1 = deFloatSqrt((cartesian_coordinate.x - 0.5f) * (cartesian_coordinate.x - 0.5f) + (cartesian_coordinate.y - 0.0f) * (cartesian_coordinate.y - 0.0f)); float curr_delta_v2 = deFloatSqrt((cartesian_coordinate.x - 1.0f) * (cartesian_coordinate.x - 1.0f) + (cartesian_coordinate.y - 1.0f) * (cartesian_coordinate.y - 1.0f)); float curr_delta_v3 = deFloatSqrt((cartesian_coordinate.x - 0.0f) * (cartesian_coordinate.x - 0.0f) + (cartesian_coordinate.y - 1.0f) * (cartesian_coordinate.y - 1.0f)); if (is_first_iteration) { delta_v1 = curr_delta_v1; delta_v2 = curr_delta_v2; delta_v3 = curr_delta_v3; /* No need to update selected vertex indices, since this is the very first iteration */ is_first_iteration = false; } else { if (curr_delta_v1 < delta_v1) { delta_v1 = curr_delta_v1; selected_v1_index = curr_index; } if (curr_delta_v2 < delta_v2) { delta_v2 = curr_delta_v2; selected_v2_index = curr_index; } if (curr_delta_v3 < delta_v3) { delta_v3 = curr_delta_v3; selected_v3_index = curr_index; } } } /* for (all remaining coordinates) */ /* Extract the vertices out of the data set */ _tess_coordinate_cartesian corner_vertices[] = { *(coordinates.begin() + selected_v1_index), *(coordinates.begin() + selected_v2_index), *(coordinates.begin() + selected_v3_index) }; const unsigned int n_corner_vertices = sizeof(corner_vertices) / sizeof(corner_vertices[0]); /* Remove the vertices from the data set */ for (unsigned int n_corner_vertex = 0; n_corner_vertex < n_corner_vertices; ++n_corner_vertex) { const _tess_coordinate_cartesian& vertex = corner_vertices[n_corner_vertex]; std::vector<_tess_coordinate_cartesian>::iterator iterator = std::find(coordinates.begin(), coordinates.end(), vertex); DE_ASSERT(iterator != coordinates.end()); if (iterator != coordinates.end()) { coordinates.erase(iterator); } } /* for (all corner vertices) */ /* Now that we know where the corner vertices are, identify all points that lie * on edges defined by these vertices */ std::vector<_tess_coordinate_cartesian> edge_v1_v2_vertices; std::vector<_tess_coordinate_cartesian> edge_v2_v3_vertices; std::vector<_tess_coordinate_cartesian> edge_v3_v1_vertices; const _tess_coordinate_cartesian& v1 = corner_vertices[0]; const _tess_coordinate_cartesian& v2 = corner_vertices[1]; const _tess_coordinate_cartesian& v3 = corner_vertices[2]; for (std::vector<_tess_coordinate_cartesian>::const_iterator iterator = coordinates.begin(); iterator != coordinates.end(); iterator++, curr_index++) { const _tess_coordinate_cartesian& cartesian_coordinate = *iterator; if (isPointOnLine(v1, v2, cartesian_coordinate)) { edge_v1_v2_vertices.push_back(*iterator); } if (isPointOnLine(v2, v3, cartesian_coordinate)) { edge_v2_v3_vertices.push_back(*iterator); } if (isPointOnLine(v3, v1, cartesian_coordinate)) { edge_v3_v1_vertices.push_back(*iterator); } } /* for (all coordinates in data set) */ /* Now that edge vertices have been identified, remove them from the data set */ const std::vector<_tess_coordinate_cartesian>* edge_ptrs[] = { &edge_v1_v2_vertices, &edge_v2_v3_vertices, &edge_v3_v1_vertices }; const unsigned int n_edge_ptrs = sizeof(edge_ptrs) / sizeof(edge_ptrs[0]); for (unsigned int n_edge_ptr = 0; n_edge_ptr < n_edge_ptrs; ++n_edge_ptr) { const std::vector<_tess_coordinate_cartesian>& edge = *edge_ptrs[n_edge_ptr]; const unsigned int n_edge_vertices = (unsigned int)edge.size(); for (unsigned int n_edge_vertex = 0; n_edge_vertex < n_edge_vertices; ++n_edge_vertex) { const _tess_coordinate_cartesian& coordinate = edge[n_edge_vertex]; std::vector<_tess_coordinate_cartesian>::iterator iterator = std::find(coordinates.begin(), coordinates.end(), coordinate); if (iterator != coordinates.end()) { coordinates.erase(iterator); } } /* for (all edge vertices) */ } /* for (all edges) */ /* Add corner coordinates to our vectors, but only if they are not * already there. */ if (std::find(edge_v1_v2_vertices.begin(), edge_v1_v2_vertices.end(), v1) == edge_v1_v2_vertices.end()) { edge_v1_v2_vertices.push_back(v1); } if (std::find(edge_v1_v2_vertices.begin(), edge_v1_v2_vertices.end(), v2) == edge_v1_v2_vertices.end()) { edge_v1_v2_vertices.push_back(v2); } if (std::find(edge_v2_v3_vertices.begin(), edge_v2_v3_vertices.end(), v2) == edge_v2_v3_vertices.end()) { edge_v2_v3_vertices.push_back(v2); } if (std::find(edge_v2_v3_vertices.begin(), edge_v2_v3_vertices.end(), v3) == edge_v2_v3_vertices.end()) { edge_v2_v3_vertices.push_back(v3); } if (std::find(edge_v3_v1_vertices.begin(), edge_v3_v1_vertices.end(), v3) == edge_v3_v1_vertices.end()) { edge_v3_v1_vertices.push_back(v3); } if (std::find(edge_v3_v1_vertices.begin(), edge_v3_v1_vertices.end(), v1) == edge_v3_v1_vertices.end()) { edge_v3_v1_vertices.push_back(v1); } /* Sort all points relative to corner point */ std::sort(edge_v1_v2_vertices.begin(), edge_v1_v2_vertices.end(), _comparator_relative_to_base_point(v1)); std::sort(edge_v2_v3_vertices.begin(), edge_v2_v3_vertices.end(), _comparator_relative_to_base_point(v2)); std::sort(edge_v3_v1_vertices.begin(), edge_v3_v1_vertices.end(), _comparator_relative_to_base_point(v3)); /* We now have all the data to update the result vector with new edge data */ for (unsigned int n_edge_ptr = 0; n_edge_ptr < n_edge_ptrs; ++n_edge_ptr) { /* Compute tessellation level values for the edge */ glw::GLfloat curr_tess_level = 0.0f; glw::GLfloat outermost_tess_level = 0.0f; if (tess_level_delta == 0) { switch (n_edge_ptr) { /* Assuming: * * v1 = (1, 0, 0) * v2 = (0, 1, 0) * v3 = (0, 0, 1) */ case 0: /* v1->v2 */ { curr_tess_level = run.outer[2]; outermost_tess_level = run.outer[2]; break; } case 1: /* v2->v3 */ { curr_tess_level = run.outer[0]; outermost_tess_level = run.outer[0]; break; } case 2: /* v3->v1 */ { curr_tess_level = run.outer[1]; outermost_tess_level = run.outer[1]; break; } default: { DE_FATAL("Invalid edge index"); } } /* switch (n_edge_ptr) */ } else { curr_tess_level = base_tess_level - (float)tess_level_delta; outermost_tess_level = base_tess_level; } /* Convert internal representation to _tess_edge */ const std::vector<_tess_coordinate_cartesian>& edge = *edge_ptrs[n_edge_ptr]; const _tess_coordinate_cartesian& edge_start_point = *edge.begin(); const _tess_coordinate_cartesian& edge_end_point = *(edge.begin() + (edge.size() - 1)); float edge_length = deFloatSqrt((edge_end_point.x - edge_start_point.x) * (edge_end_point.x - edge_start_point.x) + (edge_end_point.y - edge_start_point.y) * (edge_end_point.y - edge_start_point.y)); _tess_edge result_edge(curr_tess_level, outermost_tess_level, edge_length); for (std::vector<_tess_coordinate_cartesian>::const_iterator edge_point_iterator = edge.begin(); edge_point_iterator != edge.end(); edge_point_iterator++) { const _tess_coordinate_cartesian& edge_point = *edge_point_iterator; result_edge.points.push_back(edge_point); } /* Good to store the edge now */ result.push_back(result_edge); } /* for (all edges) */ /* Moving on with next inner triangle. As per spec, reduce tessellation level by 2 */ tess_level_delta += 2; } /* while (run.n_vertices > 0) */ return result; } /** Tells whether given two-dimensional point is located on a two-dimensional line defined * by two points. * * @param line_v1 First vertex defining the line; * @param line_v2 Second vertex defining the line; * @param point Point to check. * * @return true if the point was determned to be a part of the line, * false otherwise. **/ bool TessellationShaderVertexSpacing::isPointOnLine(const _tess_coordinate_cartesian& line_v1, const _tess_coordinate_cartesian& line_v2, const _tess_coordinate_cartesian& point) { bool result = false; /* Calculate distance from a point to a line passing through two points */ float Dx = line_v1.x - line_v2.x; float Dy = line_v1.y - line_v2.y; float denominator = deFloatSqrt(Dx * Dx + Dy * Dy); float d = de::abs(Dy * point.x - Dx * point.y + line_v1.x * line_v2.y - line_v2.x * line_v1.y) / denominator; if (de::abs(d) < epsilon) { result = true; } return result; } /** Initializes ES objects necessary to run the test. */ void TessellationShaderVertexSpacing::initTest() { /* Skip if required extensions are not supported. */ if (!m_is_tessellation_shader_supported) { throw tcu::NotSupportedError(TESSELLATION_SHADER_EXTENSION_NOT_SUPPORTED); } /* Initialize Utils instance */ const glw::Functions& gl = m_context.getRenderContext().getFunctions(); m_utils = new TessellationShaderUtils(gl, this); /* Initialize vertex array object */ gl.genVertexArrays(1, &m_vao_id); GLU_EXPECT_NO_ERROR(gl.getError(), "Could not generate vertex array object"); gl.bindVertexArray(m_vao_id); GLU_EXPECT_NO_ERROR(gl.getError(), "Error binding vertex array object!"); /* Retrieve GL_MAX_TESS_GEN_LEVEL_EXT value */ gl.getIntegerv(m_glExtTokens.MAX_TESS_GEN_LEVEL, &m_gl_max_tess_gen_level_value); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv() call failed for GL_MAX_TESS_GEN_LEVEL_EXT pname"); const _tessellation_shader_vertex_spacing vs_modes[] = { TESSELLATION_SHADER_VERTEX_SPACING_EQUAL, TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_EVEN, TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_ODD, TESSELLATION_SHADER_VERTEX_SPACING_DEFAULT }; const unsigned int n_vs_modes = sizeof(vs_modes) / sizeof(vs_modes[0]); const _tessellation_primitive_mode primitive_modes[] = { TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES, TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES, TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS }; const unsigned int n_primitive_modes = sizeof(primitive_modes) / sizeof(primitive_modes[0]); /* Iterate through all primitive modes */ for (unsigned int n_primitive_mode = 0; n_primitive_mode < n_primitive_modes; ++n_primitive_mode) { _tessellation_primitive_mode primitive_mode = primitive_modes[n_primitive_mode]; /* Generate tessellation level set for current primitive mode */ _tessellation_levels_set tess_levels_set; tess_levels_set = TessellationShaderUtils::getTessellationLevelSetForPrimitiveMode( primitive_mode, m_gl_max_tess_gen_level_value, TESSELLATION_LEVEL_SET_FILTER_INNER_AND_OUTER_LEVELS_USE_DIFFERENT_VALUES); /* Iterate through all vertex spacing modes */ for (unsigned int n_vs_mode = 0; n_vs_mode < n_vs_modes; ++n_vs_mode) { _tessellation_shader_vertex_spacing vs_mode = vs_modes[n_vs_mode]; /* Iterate through all tessellation level combinations */ for (_tessellation_levels_set_const_iterator tess_levels_set_iterator = tess_levels_set.begin(); tess_levels_set_iterator != tess_levels_set.end(); tess_levels_set_iterator++) { const _tessellation_levels& tess_levels = *tess_levels_set_iterator; _run run; /* Skip border cases that this test cannot handle */ if (primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS && vs_mode == TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_ODD && (tess_levels.inner[0] <= 1 || tess_levels.inner[1] <= 1)) { continue; } /* Fill run descriptor */ memcpy(run.inner, tess_levels.inner, sizeof(run.inner)); memcpy(run.outer, tess_levels.outer, sizeof(run.outer)); run.primitive_mode = primitive_mode; run.vertex_spacing = vs_mode; /* Retrieve vertex data for both passes */ run.n_vertices = m_utils->getAmountOfVerticesGeneratedByTessellator( run.primitive_mode, run.inner, run.outer, run.vertex_spacing, true); /* is_point_mode_enabled */ if (run.n_vertices == 0) { std::string primitive_mode_string = TessellationShaderUtils::getESTokenForPrimitiveMode(primitive_mode); std::string vs_mode_string = TessellationShaderUtils::getESTokenForVertexSpacingMode(vs_mode); m_testCtx.getLog() << tcu::TestLog::Message << "No vertices were generated by tessellator for: " "inner tess levels:" "[" << run.inner[0] << ", " << run.inner[1] << "]" ", outer tess levels:" "[" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << "]" ", primitive mode: " << primitive_mode_string << ", vertex spacing: " << vs_mode_string << tcu::TestLog::EndMessage; TCU_FAIL("Zero vertices were generated by tessellator"); } /* Retrieve the data buffers */ run.data = m_utils->getDataGeneratedByTessellator( run.inner, true, /* is_point_mode_enabled */ run.primitive_mode, TESSELLATION_SHADER_VERTEX_ORDERING_CCW, run.vertex_spacing, run.outer); /* 'triangles' tessellation data is expressed in barycentric coordinates. Before we can * continue, we need to convert the data to Euclidean space */ if (run.primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES) { run.data_cartesian = new float[run.n_vertices * 2 /* components */]; for (unsigned int n_vertex = 0; n_vertex < run.n_vertices; ++n_vertex) { const float* barycentric_vertex_data = (const float*)(&run.data[0]) + n_vertex * 3; /* components */ float* cartesian_vertex_data = (float*)run.data_cartesian + n_vertex * 2; /* components */ TessellationShaderUtils::convertBarycentricCoordinatesToCartesian(barycentric_vertex_data, cartesian_vertex_data); } } /* if (run.primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES) */ else { run.data_cartesian = DE_NULL; } /* Store the run data */ m_runs.push_back(run); } /* for (all tessellation level values ) */ } /* for (all primitive modes) */ } /* for (all vertex spacing modes) */ } /** Executes the test. * * Sets the test result to QP_TEST_RESULT_FAIL if the test failed, QP_TEST_RESULT_PASS otherwise. * * Note the function throws exception should an error occur! * * @return STOP if the test has finished, CONTINUE to indicate iterate() should be called once again. **/ tcu::TestNode::IterateResult TessellationShaderVertexSpacing::iterate(void) { /* Do not execute if required extensions are not supported. */ if (!m_is_tessellation_shader_supported) { throw tcu::NotSupportedError(TESSELLATION_SHADER_EXTENSION_NOT_SUPPORTED); } /* Initialize the test */ initTest(); /* Iterate through all runs */ for (_runs_const_iterator run_iterator = m_runs.begin(); run_iterator != m_runs.end(); run_iterator++) { _tess_edges edges; const _run& run = *run_iterator; switch (run.primitive_mode) { case TESSELLATION_SHADER_PRIMITIVE_MODE_ISOLINES: { edges = getEdgesForIsolinesTessellation(run); break; } case TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS: { edges = getEdgesForQuadsTessellation(run); break; } case TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES: { edges = getEdgesForTrianglesTessellation(run); break; } default: { TCU_FAIL("Unrecognized primitive mode"); } } /* switch (run.primitive_mode) */ verifyEdges(edges, run); } /* for (all runs) */ /* All done */ m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } /* Verifies that user-provided edges follow the vertex spacing convention, as * defined in the extension specification. * * This function throws a TestError, should an error occur or the data is found * to be incorrect. * * @param edges Data of all edges to run the check against. * @param run Test run properties. **/ void TessellationShaderVertexSpacing::verifyEdges(const _tess_edges& edges, const _run& run) { /* Cache strings that may be used by logging the routines */ const std::string primitive_mode_string = TessellationShaderUtils::getESTokenForPrimitiveMode(run.primitive_mode); const std::string vertex_spacing_string = TessellationShaderUtils::getESTokenForVertexSpacingMode(run.vertex_spacing); /* Iterate through all edges */ unsigned int n_edge = 0; for (_tess_edges_const_iterator edges_iterator = edges.begin(); edges_iterator != edges.end(); edges_iterator++, n_edge++) { const _tess_edge& edge = *edges_iterator; float edge_clamped_tess_level = 0.0f; float edge_clamped_rounded_tess_level = 0.0f; float outermost_edge_tess_level_clamped_rounded = 0.0f; _tess_coordinate_deltas segment_deltas; TessellationShaderUtils::getTessellationLevelAfterVertexSpacing( run.vertex_spacing, edge.outermost_tess_level, m_gl_max_tess_gen_level_value, DE_NULL, /* out_clamped */ &outermost_edge_tess_level_clamped_rounded); /* Retrieve amount of segments the edge should consist of */ TessellationShaderUtils::getTessellationLevelAfterVertexSpacing( run.vertex_spacing, edge.tess_level, m_gl_max_tess_gen_level_value, &edge_clamped_tess_level, &edge_clamped_rounded_tess_level); /* Take two subsequent points if they are available. Vertex spacing has no meaning * in a world of degenerate edges, so skip the check if we have just encountered one. */ const unsigned int n_points = (unsigned int)edge.points.size(); if (n_points < 2) { continue; } /* Compute segment deltas */ for (unsigned int n_base_point = 0; n_base_point < n_points - 1; n_base_point++) { const _tess_coordinate_cartesian& point_a = edge.points[n_base_point + 0]; const _tess_coordinate_cartesian& point_b = edge.points[n_base_point + 1]; /* Calculate the distance between the points */ float distance_nonsqrt = 0.0f; float distance = 0.0f; distance_nonsqrt = (point_a.x - point_b.x) * (point_a.x - point_b.x) + (point_a.y - point_b.y) * (point_a.y - point_b.y); distance = deFloatSqrt(distance_nonsqrt); /* Check if the distance is not already recognized. */ _tess_coordinate_deltas_iterator deltas_iterator; for (deltas_iterator = segment_deltas.begin(); deltas_iterator != segment_deltas.end(); deltas_iterator++) { if (de::abs(deltas_iterator->delta - distance) < epsilon) { /* Increment the counter and leave */ deltas_iterator->counter++; break; } } if (deltas_iterator == segment_deltas.end()) { /* This is the first time we're encountering a segment of this specific length. */ _tess_coordinate_delta new_item; new_item.counter = 1; new_item.delta = distance; segment_deltas.push_back(new_item); } } /* for (all base points) */ DE_ASSERT(segment_deltas.size() != 0); switch (run.vertex_spacing) { case TESSELLATION_SHADER_VERTEX_SPACING_DEFAULT: case TESSELLATION_SHADER_VERTEX_SPACING_EQUAL: { /* For equal vertex spacings, we should end up with a single _tess_coordinate_delta instance * of a predefined length, describing exactly edge_clamped_rounded_tess_level invocations */ float expected_delta = edge.edge_length / edge_clamped_rounded_tess_level; if (segment_deltas.size() != 1) { m_testCtx.getLog() << tcu::TestLog::Message << "More than one segment delta was generated for the following tessellation" " configuration: " "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Equal vertex spacing mode tessellation generated segment edges of varying lengths, " "whereas only one was expected."); } if (de::abs(segment_deltas[0].delta - expected_delta) > epsilon) { m_testCtx.getLog() << tcu::TestLog::Message << "Invalid segment delta (expected:" << expected_delta << ", found: " << segment_deltas[0].delta << ") was generated for the following tessellation configuration: " "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Invalid delta between segments generated by the tessellator configured to run " "in equal vertex spacing mode"); } if (segment_deltas[0].counter != (unsigned int)edge_clamped_rounded_tess_level) { m_testCtx.getLog() << tcu::TestLog::Message << "Invalid amount of segments (expected:" << (int)edge_clamped_rounded_tess_level << ", found: " << segment_deltas[0].counter << ") " "was generated for the following tessellation configuration: " "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Invalid amount of segments generated for equal vertex spacing mode"); } break; } /* default/equal vertex spacing */ case TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_EVEN: case TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_ODD: { /* For fractional vertex spacings, situation is more tricky. The extension specification * is very liberal when it comes to defining segment lengths. Hence the only thing we * should be testing here is that: * * a) No more than 2 deltas are generated for a single edge. * * b) If a single delta is generated, it should: * * 1. define exactly edge_clamped_rounded_tess_level-2 segments if * |edge_clamped_rounded_tess_level - edge_clamped_tess_level| == 2.0f, * * 2. define exactly edge_clamped_rounded_tess_level segments otherwise. * * c) If two deltas are generated, one of them should define 2 segments, and the other * one should define edge_clamped_rounded_tess_level-2 segments. */ if (segment_deltas.size() > 2) { m_testCtx.getLog() << tcu::TestLog::Message << "More than two segment deltas (" << segment_deltas.size() << ") were generated for the following tessellation configuration: " "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Fractional spacing mode tessellated edges to segments of more than two " "differentiable lengths"); } if (segment_deltas.size() == 1) { int expected_counter = 0; if (run.primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_TRIANGLES) { /* With each triangle level, 2 segments go out. Each triangle consists of 3 edges */ expected_counter = (int)outermost_edge_tess_level_clamped_rounded - 2 * (n_edge / 3); } else { expected_counter = (int)edge_clamped_rounded_tess_level; if (run.primitive_mode == TESSELLATION_SHADER_PRIMITIVE_MODE_QUADS && run.vertex_spacing == TESSELLATION_SHADER_VERTEX_SPACING_FRACTIONAL_ODD) { /* 8 edges expected in total; we should expect 2 segments less for the inner quad */ expected_counter = (int)edge_clamped_rounded_tess_level - 2 * (n_edge / 4); } /* For degenerate cases, always assume exactly one segment should be generated */ if (edge.tess_level <= 0.0f) { expected_counter = 1; } } if (segment_deltas[0].counter != (unsigned int)expected_counter) { m_testCtx.getLog() << tcu::TestLog::Message << "Invalid amount of segments (expected:" << expected_counter << ", found: " << segment_deltas[0].counter << ") " "was generated for the following tessellation configuration: " "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Invalid amount of segments generated for fractional vertex spacing mode"); } } else { DE_ASSERT(segment_deltas.size() == 2); if (!((segment_deltas[0].counter == 2 && segment_deltas[1].counter == ((unsigned int)edge_clamped_rounded_tess_level - 2)) || (segment_deltas[1].counter == 2 && segment_deltas[0].counter == ((unsigned int)edge_clamped_rounded_tess_level - 2)))) { m_testCtx.getLog() << tcu::TestLog::Message << "Invalid number of segments with different deltas (" << segment_deltas[0].delta << " and " << segment_deltas[1].delta << ") was generated. " "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Equal amount of segments was generated for segments of different deltas"); } if (segment_deltas[0].counter != 2 && segment_deltas[1].counter != 2) { m_testCtx.getLog() << tcu::TestLog::Message << "Neither of the segments generated by the tessellator was defined " "exactly twice." "primitive mode:" << primitive_mode_string << "vertex spacing mode:" << vertex_spacing_string << "inner tessellation levels: (" << run.inner[0] << ", " << run.inner[1] << ")" ", outer tessellation levels: (" << run.outer[0] << ", " << run.outer[1] << ", " << run.outer[2] << ", " << run.outer[3] << ")" << tcu::TestLog::EndMessage; TCU_FAIL("Neither of the generated segments was repeated exactly twice " "for fractional vertex spacing mode"); } } break; } /* fractional even/odd vertex spacing types */ default: { TCU_FAIL("Unrecognized vertex spacing mode"); } } /* switch (run.vertex_spacing) */ } /* for (all edges) */ } } /* namespace glcts */