| /*------------------------------------------------------------------------ |
| * Vulkan Conformance Tests |
| * ------------------------ |
| * |
| * Copyright (c) 2014 The Android Open Source Project |
| * Copyright (c) 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 Tessellation Invariance Tests |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "vktTessellationInvarianceTests.hpp" |
| #include "vktTestCaseUtil.hpp" |
| #include "vktTessellationUtil.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuVectorUtil.hpp" |
| |
| #include "vkDefs.hpp" |
| #include "vkBarrierUtil.hpp" |
| #include "vkQueryUtil.hpp" |
| #include "vkBuilderUtil.hpp" |
| #include "vkImageUtil.hpp" |
| #include "vkTypeUtil.hpp" |
| #include "vkCmdUtil.hpp" |
| #include "vkObjUtil.hpp" |
| |
| #include "deUniquePtr.hpp" |
| #include "deStringUtil.hpp" |
| #include "deRandom.hpp" |
| |
| #include <string> |
| #include <vector> |
| #include <set> |
| |
| namespace vkt |
| { |
| namespace tessellation |
| { |
| |
| using namespace vk; |
| |
| namespace |
| { |
| |
| enum Constants |
| { |
| NUM_EXTRA_TESS_GEOM_INVOCATIONS = |
| 4, // Need to set this value properly to allocate enough memory to store vertices data |
| NUM_TESS_LEVELS = 6, // two inner and four outer levels |
| }; |
| |
| enum WindingUsage |
| { |
| WINDING_USAGE_CCW = 0, |
| WINDING_USAGE_CW, |
| WINDING_USAGE_VARY, |
| |
| WINDING_USAGE_LAST, |
| }; |
| |
| inline WindingUsage getWindingUsage(const Winding winding) |
| { |
| const WindingUsage usage = winding == WINDING_CCW ? WINDING_USAGE_CCW : |
| winding == WINDING_CW ? WINDING_USAGE_CW : |
| WINDING_USAGE_LAST; |
| DE_ASSERT(usage != WINDING_USAGE_LAST); |
| return usage; |
| } |
| |
| std::vector<Winding> getWindingCases(const WindingUsage windingUsage) |
| { |
| std::vector<Winding> cases; |
| switch (windingUsage) |
| { |
| case WINDING_USAGE_CCW: |
| cases.push_back(WINDING_CCW); |
| break; |
| case WINDING_USAGE_CW: |
| cases.push_back(WINDING_CW); |
| break; |
| case WINDING_USAGE_VARY: |
| cases.push_back(WINDING_CCW); |
| cases.push_back(WINDING_CW); |
| break; |
| default: |
| DE_ASSERT(false); |
| break; |
| } |
| return cases; |
| } |
| |
| enum PointModeUsage |
| { |
| POINT_MODE_USAGE_DONT_USE = 0, |
| POINT_MODE_USAGE_USE, |
| POINT_MODE_USAGE_VARY, |
| |
| POINT_MODE_USAGE_LAST, |
| }; |
| |
| inline PointModeUsage getPointModeUsage(const bool usePointMode) |
| { |
| return usePointMode ? POINT_MODE_USAGE_USE : POINT_MODE_USAGE_DONT_USE; |
| } |
| |
| std::vector<bool> getUsePointModeCases(const PointModeUsage pointModeUsage) |
| { |
| std::vector<bool> cases; |
| switch (pointModeUsage) |
| { |
| case POINT_MODE_USAGE_DONT_USE: |
| cases.push_back(false); |
| break; |
| case POINT_MODE_USAGE_USE: |
| cases.push_back(true); |
| break; |
| case POINT_MODE_USAGE_VARY: |
| cases.push_back(false); |
| cases.push_back(true); |
| break; |
| default: |
| DE_ASSERT(false); |
| break; |
| } |
| return cases; |
| } |
| |
| //! Data captured in the shader per output primitive (in geometry stage). |
| struct PerPrimitive |
| { |
| int32_t patchPrimitiveID; //!< gl_PrimitiveID in tessellation evaluation shader |
| int32_t primitiveID; //!< ID of an output primitive in geometry shader (user-defined) |
| |
| int32_t unused_padding[2]; |
| |
| tcu::Vec4 tessCoord[3]; //!< 3 coords for triangles/quads, 2 for isolines, 1 for point mode. Vec4 due to alignment. |
| }; |
| |
| typedef std::vector<PerPrimitive> PerPrimitiveVec; |
| |
| inline bool byPatchPrimitiveID(const PerPrimitive &a, const PerPrimitive &b) |
| { |
| return a.patchPrimitiveID < b.patchPrimitiveID; |
| } |
| |
| inline std::string getProgramName(const std::string &baseName, const Winding winding, const bool usePointMode) |
| { |
| std::ostringstream str; |
| str << baseName << "_" << getWindingShaderName(winding) << (usePointMode ? "_point_mode" : ""); |
| return str.str(); |
| } |
| |
| inline std::string getProgramName(const std::string &baseName, const bool usePointMode) |
| { |
| std::ostringstream str; |
| str << baseName << (usePointMode ? "_point_mode" : ""); |
| return str.str(); |
| } |
| |
| inline std::string getProgramDescription(const Winding winding, const bool usePointMode) |
| { |
| std::ostringstream str; |
| str << "winding mode " << getWindingShaderName(winding) << ", " << (usePointMode ? "" : "don't ") |
| << "use point mode"; |
| return str.str(); |
| } |
| |
| template <typename T, int N> |
| std::vector<T> arrayToVector(const T (&arr)[N]) |
| { |
| return std::vector<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); |
| } |
| |
| template <typename T, int N> |
| T arrayMax(const T (&arr)[N]) |
| { |
| return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); |
| } |
| |
| template <int Size> |
| inline tcu::Vector<bool, Size> singleTrueMask(int index) |
| { |
| DE_ASSERT(de::inBounds(index, 0, Size)); |
| tcu::Vector<bool, Size> result; |
| result[index] = true; |
| return result; |
| } |
| |
| template <typename ContainerT, typename T> |
| inline bool contains(const ContainerT &c, const T &key) |
| { |
| return c.find(key) != c.end(); |
| } |
| |
| template <typename SeqT, int Size, typename Pred> |
| class LexCompare |
| { |
| public: |
| LexCompare(void) : m_pred(Pred()) |
| { |
| } |
| |
| bool operator()(const SeqT &a, const SeqT &b) const |
| { |
| for (int i = 0; i < Size; ++i) |
| { |
| if (m_pred(a[i], b[i])) |
| return true; |
| if (m_pred(b[i], a[i])) |
| return false; |
| } |
| return false; |
| } |
| |
| private: |
| Pred m_pred; |
| }; |
| |
| template <int Size> |
| class VecLexLessThan : public LexCompare<tcu::Vector<float, Size>, Size, std::less<float>> |
| { |
| }; |
| |
| //! Add default programs for invariance tests. |
| //! Creates multiple shader programs for combinations of winding and point mode. |
| //! mirrorCoords - special mode where some tessellation coordinates are mirrored in tessellation evaluation shader. |
| //! This is used by symmetric outer edge test. |
| void addDefaultPrograms(vk::SourceCollections &programCollection, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const WindingUsage windingUsage, |
| const PointModeUsage pointModeUsage, const bool mirrorCoords = false) |
| { |
| // Vertex shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "\n" |
| << "layout(location = 0) in highp float in_v_attr;\n" |
| << "layout(location = 0) out highp float in_tc_attr;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " in_tc_attr = in_v_attr;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("vert") << glu::VertexSource(src.str()); |
| } |
| |
| // Tessellation control shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "\n" |
| << "layout(vertices = 1) out;\n" |
| << "\n" |
| << "layout(location = 0) in highp float in_tc_attr[];\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| << " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| << "\n" |
| << " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| << " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| << " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| << " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str()); |
| } |
| |
| const std::string perVertexInterfaceBlock = "VertexData {\n" // no in/out qualifier |
| " vec4 in_gs_tessCoord;\n" // w component is used by mirroring test |
| " int in_gs_primitiveID;\n" |
| "}"; // no newline nor semicolon |
| |
| // Alternative tess coordinates handling code |
| std::ostringstream tessEvalCoordSrc; |
| if (mirrorCoords) |
| switch (primitiveType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: |
| tessEvalCoordSrc |
| << " float x = gl_TessCoord.x;\n" |
| << " float y = gl_TessCoord.y;\n" |
| << " float z = gl_TessCoord.z;\n" |
| << "\n" |
| << " // Mirror one half of each outer edge onto the other half, except the endpoints (because they " |
| "belong to two edges)\n" |
| << " ib_out.in_gs_tessCoord = z == 0.0 && x > 0.5 && x != 1.0 ? vec4(1.0-x, 1.0-y, 0.0, 1.0)\n" |
| << " : y == 0.0 && z > 0.5 && z != 1.0 ? vec4(1.0-x, 0.0, 1.0-z, 1.0)\n" |
| << " : x == 0.0 && y > 0.5 && y != 1.0 ? vec4( 0.0, 1.0-y, 1.0-z, 1.0)\n" |
| << " : vec4(x, y, z, 0.0);\n"; |
| break; |
| case TESSPRIMITIVETYPE_QUADS: |
| tessEvalCoordSrc << " float x = gl_TessCoord.x;\n" |
| << " float y = gl_TessCoord.y;\n" |
| << "\n" |
| << " // Mirror one half of each outer edge onto the other half, except the endpoints " |
| "(because they belong to two edges)\n" |
| << " ib_out.in_gs_tessCoord = (x == 0.0 || x == 1.0) && y > 0.5 && y != 1.0 ? vec4( " |
| " x, 1.0-y, 0.0, 1.0)\n" |
| << " : (y == 0.0 || y == 1.0) && x > 0.5 && x != 1.0 ? " |
| "vec4(1.0-x, y, 0.0, 1.0)\n" |
| << " : vec4(x, y, 0.0, 0.0);\n"; |
| break; |
| case TESSPRIMITIVETYPE_ISOLINES: |
| tessEvalCoordSrc |
| << " float x = gl_TessCoord.x;\n" |
| << " float y = gl_TessCoord.y;\n" |
| << "\n" |
| << " // Mirror one half of each outer edge onto the other half\n" |
| << " ib_out.in_gs_tessCoord = (x == 0.0 || x == 1.0) && y > 0.5 ? vec4(x, 1.0-y, 0.0, 1.0)\n" |
| << " : vec4(x, y, 0.0, 0.0);\n"; |
| break; |
| default: |
| DE_ASSERT(false); |
| return; |
| } |
| else |
| tessEvalCoordSrc << " ib_out.in_gs_tessCoord = vec4(gl_TessCoord, 0.0);\n"; |
| |
| const std::vector<Winding> windingCases = getWindingCases(windingUsage); |
| const std::vector<bool> usePointModeCases = getUsePointModeCases(pointModeUsage); |
| |
| for (std::vector<Winding>::const_iterator windingIter = windingCases.begin(); windingIter != windingCases.end(); |
| ++windingIter) |
| for (std::vector<bool>::const_iterator usePointModeIter = usePointModeCases.begin(); |
| usePointModeIter != usePointModeCases.end(); ++usePointModeIter) |
| { |
| // Tessellation evaluation shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "\n" |
| << "layout(" << getTessPrimitiveTypeShaderName(primitiveType) << ", " |
| << getSpacingModeShaderName(spacingMode) << ", " << getWindingShaderName(*windingIter) |
| << (*usePointModeIter ? ", point_mode" : "") << ") in;\n" |
| << "\n" |
| << "layout(location = 0) out " << perVertexInterfaceBlock << " ib_out;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << tessEvalCoordSrc.str() << " ib_out.in_gs_primitiveID = gl_PrimitiveID;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add(getProgramName("tese", *windingIter, *usePointModeIter)) |
| << glu::TessellationEvaluationSource(src.str()); |
| } |
| } // for windingNdx, usePointModeNdx |
| |
| // Geometry shader: data is captured here. |
| { |
| for (std::vector<bool>::const_iterator usePointModeIter = usePointModeCases.begin(); |
| usePointModeIter != usePointModeCases.end(); ++usePointModeIter) |
| { |
| const int numVertices = numVerticesPerPrimitive( |
| primitiveType, *usePointModeIter); // Primitives that the tessellated patch comprises of. |
| |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_geometry_shader : require\n" |
| << "\n" |
| << "layout(" << getGeometryShaderInputPrimitiveTypeShaderName(primitiveType, *usePointModeIter) |
| << ") in;\n" |
| << "layout(" << getGeometryShaderOutputPrimitiveTypeShaderName(primitiveType, *usePointModeIter) |
| << ", max_vertices = " << numVertices << ") out;\n" |
| << "\n" |
| << "layout(location = 0) in " << perVertexInterfaceBlock << " ib_in[];\n" |
| << "\n" |
| << "struct PerPrimitive {\n" |
| << " int patchPrimitiveID;\n" |
| << " int primitiveID;\n" |
| << " vec4 tessCoord[3];\n" |
| << "};\n" |
| << "\n" |
| << "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n" |
| << " int numPrimitives;\n" |
| << " PerPrimitive primitive[];\n" |
| << "} sb_out;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " int index = atomicAdd(sb_out.numPrimitives, 1);\n" |
| << " sb_out.primitive[index].patchPrimitiveID = ib_in[0].in_gs_primitiveID;\n" |
| << " sb_out.primitive[index].primitiveID = index;\n"; |
| for (int i = 0; i < numVertices; ++i) |
| src << " sb_out.primitive[index].tessCoord[" << i << "] = ib_in[" << i << "].in_gs_tessCoord;\n"; |
| for (int i = 0; i < numVertices; ++i) |
| src << "\n" |
| << " gl_Position = vec4(0.0);\n" |
| << " EmitVertex();\n"; |
| src << "}\n"; |
| |
| programCollection.glslSources.add(getProgramName("geom", *usePointModeIter)) |
| << glu::GeometrySource(src.str()); |
| } |
| } |
| } |
| |
| //! A description of an outer edge of a triangle, quad or isolines. |
| //! An outer edge can be described by the index of a u/v/w coordinate |
| //! and the coordinate's value along that edge. |
| struct OuterEdgeDescription |
| { |
| int constantCoordinateIndex; |
| float constantCoordinateValueChoices[2]; |
| int numConstantCoordinateValueChoices; |
| |
| OuterEdgeDescription(const int i, const float c0) : constantCoordinateIndex(i), numConstantCoordinateValueChoices(1) |
| { |
| constantCoordinateValueChoices[0] = c0; |
| } |
| OuterEdgeDescription(const int i, const float c0, const float c1) |
| : constantCoordinateIndex(i) |
| , numConstantCoordinateValueChoices(2) |
| { |
| constantCoordinateValueChoices[0] = c0; |
| constantCoordinateValueChoices[1] = c1; |
| } |
| |
| std::string description(void) const |
| { |
| static const char *const coordinateNames[] = {"u", "v", "w"}; |
| std::string result; |
| for (int i = 0; i < numConstantCoordinateValueChoices; ++i) |
| result += std::string() + (i > 0 ? " or " : "") + coordinateNames[constantCoordinateIndex] + "=" + |
| de::toString(constantCoordinateValueChoices[i]); |
| return result; |
| } |
| |
| bool contains(const tcu::Vec3 &v) const |
| { |
| for (int i = 0; i < numConstantCoordinateValueChoices; ++i) |
| if (v[constantCoordinateIndex] == constantCoordinateValueChoices[i]) |
| return true; |
| return false; |
| } |
| }; |
| |
| std::vector<OuterEdgeDescription> outerEdgeDescriptions(const TessPrimitiveType primType) |
| { |
| static const OuterEdgeDescription triangleOuterEdgeDescriptions[3] = { |
| OuterEdgeDescription(0, 0.0f), OuterEdgeDescription(1, 0.0f), OuterEdgeDescription(2, 0.0f)}; |
| |
| static const OuterEdgeDescription quadOuterEdgeDescriptions[4] = { |
| OuterEdgeDescription(0, 0.0f), OuterEdgeDescription(1, 0.0f), OuterEdgeDescription(0, 1.0f), |
| OuterEdgeDescription(1, 1.0f)}; |
| |
| static const OuterEdgeDescription isolinesOuterEdgeDescriptions[1] = { |
| OuterEdgeDescription(0, 0.0f, 1.0f), |
| }; |
| |
| switch (primType) |
| { |
| case TESSPRIMITIVETYPE_TRIANGLES: |
| return arrayToVector(triangleOuterEdgeDescriptions); |
| case TESSPRIMITIVETYPE_QUADS: |
| return arrayToVector(quadOuterEdgeDescriptions); |
| case TESSPRIMITIVETYPE_ISOLINES: |
| return arrayToVector(isolinesOuterEdgeDescriptions); |
| |
| default: |
| DE_ASSERT(false); |
| return std::vector<OuterEdgeDescription>(); |
| } |
| } |
| |
| namespace InvariantOuterEdge |
| { |
| |
| struct CaseDefinition |
| { |
| TessPrimitiveType primitiveType; |
| SpacingMode spacingMode; |
| Winding winding; |
| bool usePointMode; |
| }; |
| |
| typedef std::set<tcu::Vec3, VecLexLessThan<3>> Vec3Set; |
| |
| std::vector<float> generateRandomPatchTessLevels(const int numPatches, const int constantOuterLevelIndex, |
| const float constantOuterLevel, de::Random &rnd) |
| { |
| std::vector<float> tessLevels(numPatches * NUM_TESS_LEVELS); |
| |
| for (int patchNdx = 0; patchNdx < numPatches; ++patchNdx) |
| { |
| float *const inner = &tessLevels[patchNdx * NUM_TESS_LEVELS + 0]; |
| float *const outer = &tessLevels[patchNdx * NUM_TESS_LEVELS + 2]; |
| |
| for (int j = 0; j < 2; ++j) |
| inner[j] = rnd.getFloat(1.0f, 62.0f); |
| for (int j = 0; j < 4; ++j) |
| outer[j] = j == constantOuterLevelIndex ? constantOuterLevel : rnd.getFloat(1.0f, 62.0f); |
| } |
| |
| return tessLevels; |
| } |
| |
| std::vector<float> generatePatchTessLevels(const int numPatches, const int constantOuterLevelIndex, |
| const float constantOuterLevel) |
| { |
| de::Random rnd(123); |
| return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd); |
| } |
| |
| int multiplePatchReferencePrimitiveCount(const TessPrimitiveType primitiveType, const SpacingMode spacingMode, |
| const bool usePointMode, const float *levels, int numPatches) |
| { |
| int result = 0; |
| for (int patchNdx = 0; patchNdx < numPatches; ++patchNdx) |
| result += |
| referencePrimitiveCount(primitiveType, spacingMode, usePointMode, &levels[NUM_TESS_LEVELS * patchNdx + 0], |
| &levels[NUM_TESS_LEVELS * patchNdx + 2]); |
| return result; |
| } |
| |
| template <std::size_t N> |
| int computeMaxPrimitiveCount(const int numPatchesToDraw, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const bool usePointMode, |
| const float (&singleOuterEdgeLevels)[N]) |
| { |
| const int outerEdgeIndex = 0; // outer-edge index doesn't affect vertex count |
| const std::vector<float> patchTessLevels = |
| generatePatchTessLevels(numPatchesToDraw, outerEdgeIndex, arrayMax(singleOuterEdgeLevels)); |
| return multiplePatchReferencePrimitiveCount(primitiveType, spacingMode, usePointMode, &patchTessLevels[0], |
| numPatchesToDraw); |
| } |
| |
| void logOuterTessellationLevel(tcu::TestLog &log, const float tessLevel, const OuterEdgeDescription &edgeDesc) |
| { |
| log << tcu::TestLog::Message << "Testing with outer tessellation level " << tessLevel << " for the " |
| << edgeDesc.description() << " edge, and with various levels for other edges, and with all programs" |
| << tcu::TestLog::EndMessage; |
| } |
| |
| void logPrimitiveCountError(tcu::TestLog &log, const int numPatchesToDraw, int numPrimitives, |
| const int refNumPrimitives, const std::vector<float> &patchTessLevels) |
| { |
| log << tcu::TestLog::Message << "Failure: the number of generated primitives is " << numPrimitives |
| << ", expected at least " << refNumPrimitives << tcu::TestLog::EndMessage; |
| |
| if (numPatchesToDraw == 1) |
| log << tcu::TestLog::Message |
| << "Note: rendered one patch; tessellation levels are (in order [inner0, inner1, outer0, outer1, outer2, " |
| "outer3]):\n" |
| << containerStr(patchTessLevels, NUM_TESS_LEVELS) << tcu::TestLog::EndMessage; |
| else |
| log << tcu::TestLog::Message << "Note: rendered " << numPatchesToDraw << " patches in one draw call; " |
| << "tessellation levels for each patch are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n" |
| << containerStr(patchTessLevels, NUM_TESS_LEVELS) << tcu::TestLog::EndMessage; |
| } |
| |
| class BaseTestInstance : public TestInstance |
| { |
| public: |
| struct DrawResult |
| { |
| bool success; |
| int refNumPrimitives; |
| int numPrimitiveVertices; |
| int32_t numPrimitives; |
| PerPrimitiveVec primitives; |
| }; |
| |
| BaseTestInstance(Context &context, const CaseDefinition caseDef, const int numPatchesToDraw); |
| DrawResult draw(const uint32_t vertexCount, const std::vector<float> &patchTessLevels, const Winding winding, |
| const bool usePointMode); |
| void uploadVertexAttributes(const std::vector<float> &vertexData); |
| |
| protected: |
| static const float m_singleOuterEdgeLevels[]; |
| |
| const CaseDefinition m_caseDef; |
| const int m_numPatchesToDraw; |
| const VkFormat m_vertexFormat; |
| const uint32_t m_vertexStride; |
| const std::vector<OuterEdgeDescription> m_edgeDescriptions; |
| const int m_maxNumPrimitivesInDrawCall; |
| const VkDeviceSize m_vertexDataSizeBytes; |
| const Buffer m_vertexBuffer; |
| const int m_resultBufferPrimitiveDataOffset; |
| const VkDeviceSize m_resultBufferSizeBytes; |
| const Buffer m_resultBuffer; |
| Unique<VkDescriptorSetLayout> m_descriptorSetLayout; |
| Unique<VkDescriptorPool> m_descriptorPool; |
| Unique<VkDescriptorSet> m_descriptorSet; |
| Unique<VkRenderPass> m_renderPass; |
| Unique<VkFramebuffer> m_framebuffer; |
| Unique<VkPipelineLayout> m_pipelineLayout; |
| Unique<VkCommandPool> m_cmdPool; |
| Unique<VkCommandBuffer> m_cmdBuffer; |
| }; |
| |
| const float BaseTestInstance::m_singleOuterEdgeLevels[] = {1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, |
| 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f}; |
| |
| BaseTestInstance::BaseTestInstance(Context &context, const CaseDefinition caseDef, const int numPatchesToDraw) |
| : TestInstance(context) |
| , m_caseDef(caseDef) |
| , m_numPatchesToDraw(numPatchesToDraw) |
| , m_vertexFormat(VK_FORMAT_R32_SFLOAT) |
| , m_vertexStride(tcu::getPixelSize(mapVkFormat(m_vertexFormat))) |
| , m_edgeDescriptions(outerEdgeDescriptions(m_caseDef.primitiveType)) |
| , m_maxNumPrimitivesInDrawCall(NUM_EXTRA_TESS_GEOM_INVOCATIONS * |
| computeMaxPrimitiveCount(m_numPatchesToDraw, caseDef.primitiveType, |
| caseDef.spacingMode, caseDef.usePointMode, |
| m_singleOuterEdgeLevels)) |
| , m_vertexDataSizeBytes(NUM_TESS_LEVELS * m_numPatchesToDraw * m_vertexStride) |
| , m_vertexBuffer(m_context.getDeviceInterface(), m_context.getDevice(), m_context.getDefaultAllocator(), |
| makeBufferCreateInfo(m_vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT), |
| MemoryRequirement::HostVisible) |
| , m_resultBufferPrimitiveDataOffset((int)sizeof(int32_t) * 4) |
| , m_resultBufferSizeBytes(m_resultBufferPrimitiveDataOffset + m_maxNumPrimitivesInDrawCall * sizeof(PerPrimitive)) |
| , m_resultBuffer(m_context.getDeviceInterface(), m_context.getDevice(), m_context.getDefaultAllocator(), |
| makeBufferCreateInfo(m_resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), |
| MemoryRequirement::HostVisible) |
| , m_descriptorSetLayout(DescriptorSetLayoutBuilder() |
| .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT) |
| .build(m_context.getDeviceInterface(), m_context.getDevice())) |
| , m_descriptorPool(DescriptorPoolBuilder() |
| .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) |
| .build(m_context.getDeviceInterface(), m_context.getDevice(), |
| VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)) |
| , m_descriptorSet(makeDescriptorSet(m_context.getDeviceInterface(), m_context.getDevice(), *m_descriptorPool, |
| *m_descriptorSetLayout)) |
| , m_renderPass(makeRenderPassWithoutAttachments(m_context.getDeviceInterface(), m_context.getDevice())) |
| , m_framebuffer( |
| makeFramebuffer(m_context.getDeviceInterface(), m_context.getDevice(), *m_renderPass, 0u, DE_NULL, 1u, 1u)) |
| , m_pipelineLayout( |
| makePipelineLayout(m_context.getDeviceInterface(), m_context.getDevice(), *m_descriptorSetLayout)) |
| , m_cmdPool(makeCommandPool(m_context.getDeviceInterface(), m_context.getDevice(), |
| m_context.getUniversalQueueFamilyIndex())) |
| , m_cmdBuffer(allocateCommandBuffer(m_context.getDeviceInterface(), m_context.getDevice(), *m_cmdPool, |
| VK_COMMAND_BUFFER_LEVEL_PRIMARY)) |
| { |
| requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(), |
| FEATURE_TESSELLATION_SHADER | FEATURE_GEOMETRY_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS); |
| |
| const VkDescriptorBufferInfo resultBufferInfo = |
| makeDescriptorBufferInfo(m_resultBuffer.get(), 0ull, m_resultBufferSizeBytes); |
| |
| DescriptorSetUpdateBuilder() |
| .writeSingle(*m_descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo) |
| .update(m_context.getDeviceInterface(), m_context.getDevice()); |
| } |
| |
| //! patchTessLevels are tessellation levels for all drawn patches. |
| BaseTestInstance::DrawResult BaseTestInstance::draw(const uint32_t vertexCount, |
| const std::vector<float> &patchTessLevels, const Winding winding, |
| const bool usePointMode) |
| { |
| const DeviceInterface &vk = m_context.getDeviceInterface(); |
| const VkDevice device = m_context.getDevice(); |
| const VkQueue queue = m_context.getUniversalQueue(); |
| |
| const Unique<VkPipeline> pipeline( |
| GraphicsPipelineBuilder() |
| .setPatchControlPoints(NUM_TESS_LEVELS) |
| .setVertexInputSingleAttribute(m_vertexFormat, m_vertexStride) |
| .setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, |
| m_context.getBinaryCollection().get("tesc"), DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, |
| m_context.getBinaryCollection().get(getProgramName("tese", winding, usePointMode)), DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_GEOMETRY_BIT, |
| m_context.getBinaryCollection().get(getProgramName("geom", usePointMode)), DE_NULL) |
| .build(vk, device, *m_pipelineLayout, *m_renderPass)); |
| |
| { |
| const Allocation &alloc = m_resultBuffer.getAllocation(); |
| |
| deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(m_resultBufferSizeBytes)); |
| flushAlloc(vk, device, alloc); |
| } |
| |
| beginCommandBuffer(vk, *m_cmdBuffer); |
| beginRenderPassWithRasterizationDisabled(vk, *m_cmdBuffer, *m_renderPass, *m_framebuffer); |
| |
| vk.cmdBindPipeline(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| vk.cmdBindDescriptorSets(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipelineLayout, 0u, 1u, |
| &m_descriptorSet.get(), 0u, DE_NULL); |
| { |
| const VkDeviceSize vertexBufferOffset = 0ull; |
| vk.cmdBindVertexBuffers(*m_cmdBuffer, 0u, 1u, &m_vertexBuffer.get(), &vertexBufferOffset); |
| } |
| |
| vk.cmdDraw(*m_cmdBuffer, vertexCount, 1u, 0u, 0u); |
| endRenderPass(vk, *m_cmdBuffer); |
| |
| { |
| const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier( |
| VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *m_resultBuffer, 0ull, m_resultBufferSizeBytes); |
| |
| vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u, |
| DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL); |
| } |
| |
| endCommandBuffer(vk, *m_cmdBuffer); |
| submitCommandsAndWait(vk, device, queue, *m_cmdBuffer); |
| |
| // Read back and check results |
| |
| const Allocation &resultAlloc = m_resultBuffer.getAllocation(); |
| |
| invalidateAlloc(vk, device, resultAlloc); |
| |
| DrawResult result; |
| result.success = true; |
| result.refNumPrimitives = multiplePatchReferencePrimitiveCount( |
| m_caseDef.primitiveType, m_caseDef.spacingMode, usePointMode, &patchTessLevels[0], m_numPatchesToDraw); |
| result.numPrimitiveVertices = numVerticesPerPrimitive(m_caseDef.primitiveType, usePointMode); |
| result.numPrimitives = *static_cast<int32_t *>(resultAlloc.getHostPtr()); |
| result.primitives = |
| sorted(readInterleavedData<PerPrimitive>(result.numPrimitives, resultAlloc.getHostPtr(), |
| m_resultBufferPrimitiveDataOffset, sizeof(PerPrimitive)), |
| byPatchPrimitiveID); |
| |
| // If this fails then we didn't read all vertices from shader and test must be changed to allow more. |
| DE_ASSERT(result.numPrimitives <= m_maxNumPrimitivesInDrawCall); |
| |
| tcu::TestLog &log = m_context.getTestContext().getLog(); |
| if (result.numPrimitives < result.refNumPrimitives) |
| { |
| logPrimitiveCountError(log, m_numPatchesToDraw, result.numPrimitives, result.refNumPrimitives, patchTessLevels); |
| result.success = false; |
| } |
| return result; |
| } |
| |
| void BaseTestInstance::uploadVertexAttributes(const std::vector<float> &vertexData) |
| { |
| const DeviceInterface &vk = m_context.getDeviceInterface(); |
| const VkDevice device = m_context.getDevice(); |
| |
| const Allocation &alloc = m_vertexBuffer.getAllocation(); |
| |
| deMemcpy(alloc.getHostPtr(), &vertexData[0], sizeInBytes(vertexData)); |
| flushAlloc(vk, device, alloc); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #2 |
| * |
| * Test that the set of vertices along an outer edge of a quad or triangle |
| * only depends on that edge's tessellation level, and spacing. |
| * |
| * For each (outer) edge in the quad or triangle, draw multiple patches |
| * with identical tessellation levels for that outer edge but with |
| * different values for the other outer edges; compare, among the |
| * primitives, the vertices generated for that outer edge. Repeat with |
| * different programs, using different winding etc. settings. Compare |
| * the edge's vertices between different programs. |
| *//*--------------------------------------------------------------------*/ |
| class OuterEdgeDivisionTestInstance : public BaseTestInstance |
| { |
| public: |
| OuterEdgeDivisionTestInstance(Context &context, const CaseDefinition caseDef) |
| : BaseTestInstance(context, caseDef, 10) |
| { |
| } |
| tcu::TestStatus iterate(void); |
| }; |
| |
| tcu::TestStatus OuterEdgeDivisionTestInstance::iterate(void) |
| { |
| for (int outerEdgeIndex = 0; outerEdgeIndex < static_cast<int>(m_edgeDescriptions.size()); ++outerEdgeIndex) |
| for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(m_singleOuterEdgeLevels); |
| ++outerEdgeLevelCaseNdx) |
| { |
| const OuterEdgeDescription &edgeDesc = m_edgeDescriptions[outerEdgeIndex]; |
| const std::vector<float> patchTessLevels = generatePatchTessLevels( |
| m_numPatchesToDraw, outerEdgeIndex, m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| |
| Vec3Set |
| firstOuterEdgeVertices; // Vertices of the outer edge of the first patch of the first program's draw call; used for comparison with other patches. |
| |
| uploadVertexAttributes(patchTessLevels); |
| logOuterTessellationLevel(m_context.getTestContext().getLog(), |
| m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx], edgeDesc); |
| |
| for (int windingNdx = 0; windingNdx < WINDING_LAST; ++windingNdx) |
| for (int usePointModeNdx = 0; usePointModeNdx <= 1; ++usePointModeNdx) |
| { |
| const Winding winding = static_cast<Winding>(windingNdx); |
| const bool usePointMode = (usePointModeNdx != 0); |
| const bool isFirstProgram = (windingNdx == 0 && usePointModeNdx == 0); |
| |
| const DrawResult result = |
| draw(static_cast<uint32_t>(patchTessLevels.size()), patchTessLevels, winding, usePointMode); |
| |
| if (!result.success) |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| |
| // Check the vertices of each patch. |
| |
| int primitiveNdx = 0; |
| for (int patchNdx = 0; patchNdx < m_numPatchesToDraw; ++patchNdx) |
| { |
| const float *const innerLevels = &patchTessLevels[NUM_TESS_LEVELS * patchNdx + 0]; |
| const float *const outerLevels = &patchTessLevels[NUM_TESS_LEVELS * patchNdx + 2]; |
| |
| Vec3Set outerEdgeVertices; |
| |
| // We're interested in just the vertices on the current outer edge. |
| for (; primitiveNdx < result.numPrimitives && |
| result.primitives[primitiveNdx].patchPrimitiveID == patchNdx; |
| ++primitiveNdx) |
| for (int i = 0; i < result.numPrimitiveVertices; ++i) |
| { |
| const tcu::Vec3 &coord = result.primitives[primitiveNdx].tessCoord[i].swizzle(0, 1, 2); |
| if (edgeDesc.contains(coord)) |
| outerEdgeVertices.insert(coord); |
| } |
| |
| // Compare the vertices to those of the first patch (unless this is the first patch). |
| |
| if (isFirstProgram && patchNdx == 0) |
| firstOuterEdgeVertices = outerEdgeVertices; |
| else if (firstOuterEdgeVertices != outerEdgeVertices) |
| { |
| tcu::TestLog &log = m_context.getTestContext().getLog(); |
| |
| log << tcu::TestLog::Message |
| << "Failure: vertices generated for the edge differ between the following cases:\n" |
| << " - case A: " << getProgramDescription((Winding)0, (bool)0) |
| << ", tessellation levels: " |
| << getTessellationLevelsString(&patchTessLevels[0], &patchTessLevels[2]) << "\n" |
| << " - case B: " << getProgramDescription(winding, usePointMode) |
| << ", tessellation levels: " << getTessellationLevelsString(innerLevels, outerLevels) |
| << tcu::TestLog::EndMessage; |
| |
| log << tcu::TestLog::Message |
| << "Note: resulting vertices for the edge for the cases were:\n" |
| << " - case A: " << containerStr(firstOuterEdgeVertices, 5, 14) << "\n" |
| << " - case B: " << containerStr(outerEdgeVertices, 5, 14) << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| } |
| } |
| DE_ASSERT(primitiveNdx == result.numPrimitives); |
| } // for windingNdx, usePointModeNdx |
| } // for outerEdgeIndex, outerEdgeLevelCaseNdx |
| |
| return tcu::TestStatus::pass("OK"); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #4 |
| * |
| * Test that the vertices on an outer edge don't depend on which of the |
| * edges it is, other than with respect to component order. |
| *//*--------------------------------------------------------------------*/ |
| class OuterEdgeIndexIndependenceTestInstance : public BaseTestInstance |
| { |
| public: |
| OuterEdgeIndexIndependenceTestInstance(Context &context, const CaseDefinition caseDef) |
| : BaseTestInstance(context, caseDef, 1) |
| { |
| } |
| tcu::TestStatus iterate(void); |
| }; |
| |
| tcu::TestStatus OuterEdgeIndexIndependenceTestInstance::iterate(void) |
| { |
| for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(m_singleOuterEdgeLevels); |
| ++outerEdgeLevelCaseNdx) |
| { |
| Vec3Set firstEdgeVertices; |
| |
| for (int outerEdgeIndex = 0; outerEdgeIndex < static_cast<int>(m_edgeDescriptions.size()); ++outerEdgeIndex) |
| { |
| const OuterEdgeDescription &edgeDesc = m_edgeDescriptions[outerEdgeIndex]; |
| const std::vector<float> patchTessLevels = generatePatchTessLevels( |
| m_numPatchesToDraw, outerEdgeIndex, m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| |
| uploadVertexAttributes(patchTessLevels); |
| logOuterTessellationLevel(m_context.getTestContext().getLog(), |
| m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx], edgeDesc); |
| const DrawResult result = draw(static_cast<uint32_t>(patchTessLevels.size()), patchTessLevels, |
| m_caseDef.winding, m_caseDef.usePointMode); |
| |
| // Verify case result |
| |
| if (!result.success) |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| |
| Vec3Set currentEdgeVertices; |
| |
| // Get the vertices on the current outer edge. |
| for (int primitiveNdx = 0; primitiveNdx < result.numPrimitives; ++primitiveNdx) |
| for (int i = 0; i < result.numPrimitiveVertices; ++i) |
| { |
| const tcu::Vec3 &coord = result.primitives[primitiveNdx].tessCoord[i].swizzle(0, 1, 2); |
| if (edgeDesc.contains(coord)) |
| { |
| // Swizzle components to match the order of the first edge. |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| currentEdgeVertices.insert(outerEdgeIndex == 0 ? coord : |
| outerEdgeIndex == 1 ? coord.swizzle(1, 0, 2) : |
| outerEdgeIndex == 2 ? coord.swizzle(2, 1, 0) : |
| tcu::Vec3(-1.0f)); |
| else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS) |
| currentEdgeVertices.insert(tcu::Vec3(outerEdgeIndex == 0 ? coord.y() : |
| outerEdgeIndex == 1 ? coord.x() : |
| outerEdgeIndex == 2 ? coord.y() : |
| outerEdgeIndex == 3 ? coord.x() : |
| -1.0f, |
| 0.0f, 0.0f)); |
| else |
| DE_ASSERT(false); |
| } |
| } |
| |
| if (outerEdgeIndex == 0) |
| firstEdgeVertices = currentEdgeVertices; |
| else |
| { |
| // Compare vertices of this edge to those of the first edge. |
| if (currentEdgeVertices != firstEdgeVertices) |
| { |
| const char *const swizzleDesc = |
| m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? (outerEdgeIndex == 1 ? "(y, x, z)" : |
| outerEdgeIndex == 2 ? "(z, y, x)" : |
| DE_NULL) : |
| m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS ? (outerEdgeIndex == 1 ? "(x, 0)" : |
| outerEdgeIndex == 2 ? "(y, 0)" : |
| outerEdgeIndex == 3 ? "(x, 0)" : |
| DE_NULL) : |
| DE_NULL; |
| |
| tcu::TestLog &log = m_context.getTestContext().getLog(); |
| log << tcu::TestLog::Message << "Failure: the set of vertices on the " << edgeDesc.description() |
| << " edge" |
| << " doesn't match the set of vertices on the " << m_edgeDescriptions[0].description() |
| << " edge" << tcu::TestLog::EndMessage; |
| |
| log << tcu::TestLog::Message << "Note: set of vertices on " << edgeDesc.description() |
| << " edge, components swizzled like " << swizzleDesc |
| << " to match component order on first edge:\n" |
| << containerStr(currentEdgeVertices, 5) << "\non " << m_edgeDescriptions[0].description() |
| << " edge:\n" |
| << containerStr(firstEdgeVertices, 5) << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| } |
| } |
| } |
| } |
| return tcu::TestStatus::pass("OK"); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #3 |
| * |
| * Test that the vertices along an outer edge are placed symmetrically. |
| * |
| * Draw multiple patches with different tessellation levels and different |
| * point_mode, winding etc. Before outputting tesscoords from shader, mirror |
| * the vertices in the TES such that every vertex on an outer edge - |
| * except the possible middle vertex - should be duplicated in the output. |
| * Check that appropriate duplicates exist. |
| *//*--------------------------------------------------------------------*/ |
| class SymmetricOuterEdgeTestInstance : public BaseTestInstance |
| { |
| public: |
| SymmetricOuterEdgeTestInstance(Context &context, const CaseDefinition caseDef) |
| : BaseTestInstance(context, caseDef, 1) |
| { |
| } |
| tcu::TestStatus iterate(void); |
| }; |
| |
| tcu::TestStatus SymmetricOuterEdgeTestInstance::iterate(void) |
| { |
| for (int outerEdgeIndex = 0; outerEdgeIndex < static_cast<int>(m_edgeDescriptions.size()); ++outerEdgeIndex) |
| for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(m_singleOuterEdgeLevels); |
| ++outerEdgeLevelCaseNdx) |
| { |
| const OuterEdgeDescription &edgeDesc = m_edgeDescriptions[outerEdgeIndex]; |
| const std::vector<float> patchTessLevels = generatePatchTessLevels( |
| m_numPatchesToDraw, outerEdgeIndex, m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx]); |
| |
| uploadVertexAttributes(patchTessLevels); |
| logOuterTessellationLevel(m_context.getTestContext().getLog(), |
| m_singleOuterEdgeLevels[outerEdgeLevelCaseNdx], edgeDesc); |
| const DrawResult result = draw(static_cast<uint32_t>(patchTessLevels.size()), patchTessLevels, |
| m_caseDef.winding, m_caseDef.usePointMode); |
| |
| // Verify case result |
| |
| if (!result.success) |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| |
| Vec3Set nonMirroredEdgeVertices; |
| Vec3Set mirroredEdgeVertices; |
| |
| // Get the vertices on the current outer edge. |
| for (int primitiveNdx = 0; primitiveNdx < result.numPrimitives; ++primitiveNdx) |
| for (int i = 0; i < result.numPrimitiveVertices; ++i) |
| { |
| const tcu::Vec3 &coord = result.primitives[primitiveNdx].tessCoord[i].swizzle(0, 1, 2); |
| if (edgeDesc.contains(coord)) |
| { |
| // Ignore the middle vertex of the outer edge, as it's exactly at the mirroring point; |
| // for isolines, also ignore (0, 0) and (1, 0) because there's no mirrored counterpart for them. |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES && |
| coord == tcu::select(tcu::Vec3(0.0f), tcu::Vec3(0.5f), |
| singleTrueMask<3>(edgeDesc.constantCoordinateIndex))) |
| continue; |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS && |
| coord.swizzle(0, 1) == tcu::select(tcu::Vec2(edgeDesc.constantCoordinateValueChoices[0]), |
| tcu::Vec2(0.5f), |
| singleTrueMask<2>(edgeDesc.constantCoordinateIndex))) |
| continue; |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_ISOLINES && |
| (coord == tcu::Vec3(0.0f, 0.5f, 0.0f) || coord == tcu::Vec3(1.0f, 0.5f, 0.0f) || |
| coord == tcu::Vec3(0.0f, 0.0f, 0.0f) || coord == tcu::Vec3(1.0f, 0.0f, 0.0f))) |
| continue; |
| |
| const bool isMirrored = result.primitives[primitiveNdx].tessCoord[i].w() > 0.5f; |
| if (isMirrored) |
| mirroredEdgeVertices.insert(coord); |
| else |
| nonMirroredEdgeVertices.insert(coord); |
| } |
| } |
| |
| if (m_caseDef.primitiveType != TESSPRIMITIVETYPE_ISOLINES) |
| { |
| // Check that both endpoints are present. Note that endpoints aren't mirrored by the shader, since they belong to more than one edge. |
| |
| tcu::Vec3 endpointA; |
| tcu::Vec3 endpointB; |
| |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| endpointA = tcu::select(tcu::Vec3(1.0f), tcu::Vec3(0.0f), |
| singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 1) % 3)); |
| endpointB = tcu::select(tcu::Vec3(1.0f), tcu::Vec3(0.0f), |
| singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 2) % 3)); |
| } |
| else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| endpointA.xy() = tcu::select(tcu::Vec2(edgeDesc.constantCoordinateValueChoices[0]), tcu::Vec2(0.0f), |
| singleTrueMask<2>(edgeDesc.constantCoordinateIndex)); |
| endpointB.xy() = tcu::select(tcu::Vec2(edgeDesc.constantCoordinateValueChoices[0]), tcu::Vec2(1.0f), |
| singleTrueMask<2>(edgeDesc.constantCoordinateIndex)); |
| } |
| else |
| DE_ASSERT(false); |
| |
| if (!contains(nonMirroredEdgeVertices, endpointA) || !contains(nonMirroredEdgeVertices, endpointB)) |
| { |
| m_context.getTestContext().getLog() |
| << tcu::TestLog::Message << "Failure: edge doesn't contain both endpoints, " << endpointA |
| << " and " << endpointB << tcu::TestLog::EndMessage << tcu::TestLog::Message |
| << "Note: non-mirrored vertices:\n" |
| << containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n" |
| << containerStr(mirroredEdgeVertices, 5) << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| } |
| nonMirroredEdgeVertices.erase(endpointA); |
| nonMirroredEdgeVertices.erase(endpointB); |
| } |
| |
| if (nonMirroredEdgeVertices != mirroredEdgeVertices) |
| { |
| m_context.getTestContext().getLog() |
| << tcu::TestLog::Message |
| << "Failure: the set of mirrored edges isn't equal to the set of non-mirrored edges (ignoring " |
| "endpoints and possible middle)" |
| << tcu::TestLog::EndMessage << tcu::TestLog::Message << "Note: non-mirrored vertices:\n" |
| << containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n" |
| << containerStr(mirroredEdgeVertices, 5) << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of vertices"); |
| } |
| } |
| return tcu::TestStatus::pass("OK"); |
| } |
| |
| class OuterEdgeDivisionTest : public TestCase |
| { |
| public: |
| OuterEdgeDivisionTest(tcu::TestContext &testCtx, const std::string &name, const std::string &description, |
| const CaseDefinition caseDef) |
| : TestCase(testCtx, name, description) |
| , m_caseDef(caseDef) |
| { |
| } |
| |
| void initPrograms(vk::SourceCollections &programCollection) const |
| { |
| addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode, WINDING_USAGE_VARY, |
| POINT_MODE_USAGE_VARY); |
| } |
| |
| TestInstance *createInstance(Context &context) const |
| { |
| return new OuterEdgeDivisionTestInstance(context, m_caseDef); |
| } |
| |
| void checkSupport(Context &context) const |
| { |
| if (const vk::VkPhysicalDevicePortabilitySubsetFeaturesKHR *const features = getPortability(context)) |
| checkPointMode(*features); |
| } |
| |
| private: |
| const CaseDefinition m_caseDef; |
| }; |
| |
| class OuterEdgeIndexIndependenceTest : public TestCase |
| { |
| public: |
| OuterEdgeIndexIndependenceTest(tcu::TestContext &testCtx, const std::string &name, const std::string &description, |
| const CaseDefinition caseDef) |
| : TestCase(testCtx, name, description) |
| , m_caseDef(caseDef) |
| { |
| DE_ASSERT(m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES || |
| m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS); |
| } |
| |
| void initPrograms(vk::SourceCollections &programCollection) const |
| { |
| addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode, |
| getWindingUsage(m_caseDef.winding), getPointModeUsage(m_caseDef.usePointMode)); |
| } |
| |
| TestInstance *createInstance(Context &context) const |
| { |
| return new OuterEdgeIndexIndependenceTestInstance(context, m_caseDef); |
| } |
| |
| void checkSupport(Context &context) const |
| { |
| checkSupportCase(context, m_caseDef); |
| } |
| |
| private: |
| const CaseDefinition m_caseDef; |
| }; |
| |
| class SymmetricOuterEdgeTest : public TestCase |
| { |
| public: |
| SymmetricOuterEdgeTest(tcu::TestContext &testCtx, const std::string &name, const std::string &description, |
| const CaseDefinition caseDef) |
| : TestCase(testCtx, name, description) |
| , m_caseDef(caseDef) |
| { |
| } |
| |
| void initPrograms(vk::SourceCollections &programCollection) const |
| { |
| const bool mirrorCoords = true; |
| addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode, |
| getWindingUsage(m_caseDef.winding), getPointModeUsage(m_caseDef.usePointMode), mirrorCoords); |
| } |
| |
| TestInstance *createInstance(Context &context) const |
| { |
| return new SymmetricOuterEdgeTestInstance(context, m_caseDef); |
| } |
| |
| void checkSupport(Context &context) const |
| { |
| checkSupportCase(context, m_caseDef); |
| } |
| |
| private: |
| const CaseDefinition m_caseDef; |
| }; |
| |
| tcu::TestCase *makeOuterEdgeDivisionTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode) |
| { |
| const CaseDefinition caseDef = {primitiveType, spacingMode, WINDING_LAST, false}; // winding is ignored by this test |
| return new OuterEdgeDivisionTest(testCtx, name, description, caseDef); |
| } |
| |
| tcu::TestCase *makeOuterEdgeIndexIndependenceTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const Winding winding, |
| const bool usePointMode) |
| { |
| const CaseDefinition caseDef = {primitiveType, spacingMode, winding, usePointMode}; |
| return new OuterEdgeIndexIndependenceTest(testCtx, name, description, caseDef); |
| } |
| |
| tcu::TestCase *makeSymmetricOuterEdgeTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const Winding winding, const bool usePointMode) |
| { |
| const CaseDefinition caseDef = {primitiveType, spacingMode, winding, usePointMode}; |
| return new SymmetricOuterEdgeTest(testCtx, name, description, caseDef); |
| } |
| |
| } // namespace InvariantOuterEdge |
| |
| namespace PrimitiveSetInvariance |
| { |
| |
| enum CaseType |
| { |
| CASETYPE_INVARIANT_PRIMITIVE_SET, |
| CASETYPE_INVARIANT_TRIANGLE_SET, |
| CASETYPE_INVARIANT_OUTER_TRIANGLE_SET, |
| CASETYPE_INVARIANT_INNER_TRIANGLE_SET, |
| }; |
| |
| struct CaseDefinition |
| { |
| CaseType caseType; |
| TessPrimitiveType primitiveType; |
| SpacingMode spacingMode; |
| WindingUsage windingUsage; |
| bool usePointMode; |
| }; |
| |
| struct LevelCase |
| { |
| std::vector<TessLevels> levels; |
| int mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed. |
| |
| LevelCase(const TessLevels &lev) : levels(std::vector<TessLevels>(1, lev)), mem(0) |
| { |
| } |
| LevelCase(void) : mem(0) |
| { |
| } |
| }; |
| |
| typedef tcu::Vector<tcu::Vec3, 3> Triangle; |
| |
| inline Triangle makeTriangle(const PerPrimitive &primitive) |
| { |
| return Triangle(primitive.tessCoord[0].swizzle(0, 1, 2), primitive.tessCoord[1].swizzle(0, 1, 2), |
| primitive.tessCoord[2].swizzle(0, 1, 2)); |
| } |
| |
| //! Compare triangle sets, ignoring triangle order and vertex order within triangle, and possibly exclude some triangles too. |
| template <typename IsTriangleRelevantT> |
| bool compareTriangleSets(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, tcu::TestLog &log, |
| const IsTriangleRelevantT &isTriangleRelevant, |
| const char *ignoredTriangleDescription = DE_NULL) |
| { |
| typedef LexCompare<Triangle, 3, VecLexLessThan<3>> TriangleLexLessThan; |
| typedef std::set<Triangle, TriangleLexLessThan> TriangleSet; |
| |
| const int numTrianglesA = static_cast<int>(primitivesA.size()); |
| const int numTrianglesB = static_cast<int>(primitivesB.size()); |
| TriangleSet trianglesA; |
| TriangleSet trianglesB; |
| |
| for (int aOrB = 0; aOrB < 2; ++aOrB) |
| { |
| const PerPrimitiveVec &primitives = aOrB == 0 ? primitivesA : primitivesB; |
| const int numTriangles = aOrB == 0 ? numTrianglesA : numTrianglesB; |
| TriangleSet &triangles = aOrB == 0 ? trianglesA : trianglesB; |
| |
| for (int triNdx = 0; triNdx < numTriangles; ++triNdx) |
| { |
| Triangle triangle = makeTriangle(primitives[triNdx]); |
| |
| if (isTriangleRelevant(triangle.getPtr())) |
| { |
| std::sort(triangle.getPtr(), triangle.getPtr() + 3, VecLexLessThan<3>()); |
| triangles.insert(triangle); |
| } |
| } |
| } |
| { |
| TriangleSet::const_iterator aIt = trianglesA.begin(); |
| TriangleSet::const_iterator bIt = trianglesB.begin(); |
| |
| while (aIt != trianglesA.end() || bIt != trianglesB.end()) |
| { |
| const bool aEnd = aIt == trianglesA.end(); |
| const bool bEnd = bIt == trianglesB.end(); |
| |
| if (aEnd || bEnd || *aIt != *bIt) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: triangle sets in two cases are not equal (when ignoring triangle and vertex order" |
| << (ignoredTriangleDescription == DE_NULL ? "" : |
| std::string() + ", and " + ignoredTriangleDescription) |
| << ")" << tcu::TestLog::EndMessage; |
| |
| if (!aEnd && (bEnd || TriangleLexLessThan()(*aIt, *bIt))) |
| log << tcu::TestLog::Message << "Note: e.g. triangle " << *aIt |
| << " exists for first case but not for second" << tcu::TestLog::EndMessage; |
| else |
| log << tcu::TestLog::Message << "Note: e.g. triangle " << *bIt |
| << " exists for second case but not for first" << tcu::TestLog::EndMessage; |
| |
| return false; |
| } |
| |
| ++aIt; |
| ++bIt; |
| } |
| |
| return true; |
| } |
| } |
| |
| template <typename ArgT, bool res> |
| struct ConstantUnaryPredicate |
| { |
| bool operator()(const ArgT &) const |
| { |
| return res; |
| } |
| }; |
| |
| bool compareTriangleSets(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, tcu::TestLog &log) |
| { |
| return compareTriangleSets(primitivesA, primitivesB, log, ConstantUnaryPredicate<const tcu::Vec3 *, true>()); |
| } |
| |
| //! Compare two sets of primitives. Order of primitives in each set is undefined, but within each primitive |
| //! vertex order and coordinates are expected to match exactly. |
| bool comparePrimitivesExact(const PerPrimitive *const primitivesA, const PerPrimitive *const primitivesB, |
| const int numPrimitivesPerPatch) |
| { |
| int ndxB = 0; |
| for (int ndxA = 0; ndxA < numPrimitivesPerPatch; ++ndxA) |
| { |
| const tcu::Vec4(&coordsA)[3] = primitivesA[ndxA].tessCoord; |
| bool match = false; |
| |
| // Actually both sets are usually somewhat sorted, so don't reset ndxB after each match. Instead, continue from the next index. |
| for (int i = 0; i < numPrimitivesPerPatch; ++i) |
| { |
| const tcu::Vec4(&coordsB)[3] = primitivesB[ndxB].tessCoord; |
| ndxB = (ndxB + 1) % numPrimitivesPerPatch; |
| |
| if (coordsA[0] == coordsB[0] && coordsA[1] == coordsB[1] && coordsA[2] == coordsB[2]) |
| { |
| match = true; |
| break; |
| } |
| } |
| |
| if (!match) |
| return false; |
| } |
| return true; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Base class for testing invariance of entire primitive set |
| * |
| * Draws two patches with identical tessellation levels and compares the |
| * results. Repeats the same with other programs that are only different |
| * in irrelevant ways; compares the results between these two programs. |
| * Also potentially compares to results produced by different tessellation |
| * levels (see e.g. invariance rule #6). |
| * Furthermore, repeats the above with multiple different tessellation |
| * value sets. |
| * |
| * The manner of primitive set comparison is defined by subclass. E.g. |
| * case for invariance rule #1 tests that same vertices come out, in same |
| * order; rule #5 only requires that the same triangles are output, but |
| * not necessarily in the same order. |
| *//*--------------------------------------------------------------------*/ |
| class InvarianceTestCase : public TestCase |
| { |
| public: |
| InvarianceTestCase(tcu::TestContext &context, const std::string &name, const std::string &description, |
| const CaseDefinition &caseDef) |
| : TestCase(context, name, description) |
| , m_caseDef(caseDef) |
| { |
| } |
| |
| virtual ~InvarianceTestCase(void) |
| { |
| } |
| |
| void initPrograms(SourceCollections &programCollection) const; |
| void checkSupport(Context &context) const; |
| TestInstance *createInstance(Context &context) const; |
| |
| private: |
| const CaseDefinition m_caseDef; |
| }; |
| |
| void InvarianceTestCase::initPrograms(SourceCollections &programCollection) const |
| { |
| addDefaultPrograms(programCollection, m_caseDef.primitiveType, m_caseDef.spacingMode, m_caseDef.windingUsage, |
| getPointModeUsage(m_caseDef.usePointMode)); |
| } |
| |
| void InvarianceTestCase::checkSupport(Context &context) const |
| { |
| checkSupportCase(context, m_caseDef); |
| } |
| |
| class InvarianceTestInstance : public TestInstance |
| { |
| public: |
| InvarianceTestInstance(Context &context, const CaseDefinition &caseDef) : TestInstance(context), m_caseDef(caseDef) |
| { |
| } |
| virtual ~InvarianceTestInstance(void) |
| { |
| } |
| |
| tcu::TestStatus iterate(void); |
| |
| protected: |
| virtual std::vector<LevelCase> genTessLevelCases(void) const; |
| virtual bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, |
| const int levelCaseMem) const = 0; |
| |
| const CaseDefinition m_caseDef; |
| }; |
| |
| std::vector<LevelCase> InvarianceTestInstance::genTessLevelCases(void) const |
| { |
| static const TessLevels basicTessLevelCases[] = { |
| {{1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}}, {{63.0f, 24.0f}, {15.0f, 42.0f, 10.0f, 12.0f}}, |
| {{3.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{4.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, |
| {{2.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{5.0f, 6.0f}, {1.0f, 1.0f, 1.0f, 1.0f}}, |
| {{1.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, {{5.0f, 1.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, |
| {{5.2f, 1.6f}, {2.9f, 3.4f, 1.5f, 4.1f}}}; |
| |
| std::vector<LevelCase> result; |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(basicTessLevelCases); ++i) |
| result.push_back(LevelCase(basicTessLevelCases[i])); |
| |
| { |
| de::Random rnd(123); |
| for (int i = 0; i < 10; ++i) |
| { |
| TessLevels levels; |
| for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.inner); ++j) |
| levels.inner[j] = rnd.getFloat(1.0f, 16.0f); |
| for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.outer); ++j) |
| levels.outer[j] = rnd.getFloat(1.0f, 16.0f); |
| result.push_back(LevelCase(levels)); |
| } |
| } |
| |
| return result; |
| } |
| |
| tcu::TestStatus InvarianceTestInstance::iterate(void) |
| { |
| requireFeatures(m_context.getInstanceInterface(), m_context.getPhysicalDevice(), |
| FEATURE_TESSELLATION_SHADER | FEATURE_GEOMETRY_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS); |
| |
| const DeviceInterface &vk = m_context.getDeviceInterface(); |
| const VkDevice device = m_context.getDevice(); |
| const VkQueue queue = m_context.getUniversalQueue(); |
| const uint32_t queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); |
| Allocator &allocator = m_context.getDefaultAllocator(); |
| |
| const std::vector<LevelCase> tessLevelCases = genTessLevelCases(); |
| const int numPatchesPerDrawCall = 2; |
| int maxNumPrimitivesPerPatch = 0; // computed below |
| std::vector<std::vector<int>> primitiveCounts; |
| |
| for (int caseNdx = 0; caseNdx < static_cast<int>(tessLevelCases.size()); ++caseNdx) |
| { |
| primitiveCounts.push_back(std::vector<int>()); |
| for (int levelNdx = 0; levelNdx < static_cast<int>(tessLevelCases[caseNdx].levels.size()); ++levelNdx) |
| { |
| const int primitiveCount = referencePrimitiveCount( |
| m_caseDef.primitiveType, m_caseDef.spacingMode, m_caseDef.usePointMode, |
| &tessLevelCases[caseNdx].levels[levelNdx].inner[0], &tessLevelCases[caseNdx].levels[levelNdx].outer[0]); |
| primitiveCounts.back().push_back(primitiveCount); |
| maxNumPrimitivesPerPatch = de::max(maxNumPrimitivesPerPatch, primitiveCount); |
| } |
| } |
| |
| // Allow for more primitievs in case tessellation/geometry has extra invocations |
| maxNumPrimitivesPerPatch *= NUM_EXTRA_TESS_GEOM_INVOCATIONS; |
| |
| // Vertex input attributes buffer: to pass tessellation levels |
| |
| const VkFormat vertexFormat = VK_FORMAT_R32_SFLOAT; |
| const uint32_t vertexStride = tcu::getPixelSize(mapVkFormat(vertexFormat)); |
| const VkDeviceSize vertexDataSizeBytes = NUM_TESS_LEVELS * numPatchesPerDrawCall * vertexStride; |
| const Buffer vertexBuffer(vk, device, allocator, |
| makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT), |
| MemoryRequirement::HostVisible); |
| |
| // Output buffer: number of primitives and an array of PerPrimitive structures |
| |
| const int resultBufferMaxVertices = numPatchesPerDrawCall * maxNumPrimitivesPerPatch * |
| numVerticesPerPrimitive(m_caseDef.primitiveType, m_caseDef.usePointMode); |
| const int resultBufferTessCoordsOffset = (int)sizeof(int32_t) * 4; |
| const VkDeviceSize resultBufferSizeBytes = |
| resultBufferTessCoordsOffset + resultBufferMaxVertices * sizeof(PerPrimitive); |
| const Buffer resultBuffer(vk, device, allocator, |
| makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), |
| MemoryRequirement::HostVisible); |
| |
| // Descriptors |
| |
| const Unique<VkDescriptorSetLayout> descriptorSetLayout( |
| DescriptorSetLayoutBuilder() |
| .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT) |
| .build(vk, device)); |
| |
| const Unique<VkDescriptorPool> descriptorPool( |
| DescriptorPoolBuilder() |
| .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) |
| .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); |
| |
| const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout)); |
| const VkDescriptorBufferInfo resultBufferInfo = |
| makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes); |
| |
| DescriptorSetUpdateBuilder() |
| .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo) |
| .update(vk, device); |
| |
| const Unique<VkRenderPass> renderPass(makeRenderPassWithoutAttachments(vk, device)); |
| const Unique<VkFramebuffer> framebuffer(makeFramebuffer(vk, device, *renderPass, 0u, DE_NULL, 1u, 1u)); |
| const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout)); |
| const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex)); |
| const Unique<VkCommandBuffer> cmdBuffer( |
| allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < static_cast<int>(tessLevelCases.size()); ++tessLevelCaseNdx) |
| { |
| const LevelCase &levelCase = tessLevelCases[tessLevelCaseNdx]; |
| PerPrimitiveVec firstPrim; |
| |
| { |
| tcu::TestLog &log = m_context.getTestContext().getLog(); |
| std::ostringstream tessLevelsStr; |
| |
| for (int i = 0; i < static_cast<int>(levelCase.levels.size()); ++i) |
| tessLevelsStr << (levelCase.levels.size() > 1u ? "\n" : "") |
| << getTessellationLevelsString(levelCase.levels[i], m_caseDef.primitiveType); |
| |
| log << tcu::TestLog::Message << "Tessellation level sets: " << tessLevelsStr.str() |
| << tcu::TestLog::EndMessage; |
| } |
| |
| for (int subTessLevelCaseNdx = 0; subTessLevelCaseNdx < static_cast<int>(levelCase.levels.size()); |
| ++subTessLevelCaseNdx) |
| { |
| const TessLevels &tessLevels = levelCase.levels[subTessLevelCaseNdx]; |
| { |
| TessLevels data[2]; |
| data[0] = tessLevels; |
| data[1] = tessLevels; |
| |
| const Allocation &alloc = vertexBuffer.getAllocation(); |
| |
| deMemcpy(alloc.getHostPtr(), data, sizeof(data)); |
| flushAlloc(vk, device, alloc); |
| } |
| |
| int programNdx = 0; |
| const std::vector<Winding> windingCases = getWindingCases(m_caseDef.windingUsage); |
| for (std::vector<Winding>::const_iterator windingIter = windingCases.begin(); |
| windingIter != windingCases.end(); ++windingIter) |
| { |
| const Unique<VkPipeline> pipeline( |
| GraphicsPipelineBuilder() |
| .setPatchControlPoints(NUM_TESS_LEVELS) |
| .setVertexInputSingleAttribute(vertexFormat, vertexStride) |
| .setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), |
| DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, |
| m_context.getBinaryCollection().get("tesc"), DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, |
| m_context.getBinaryCollection().get( |
| getProgramName("tese", *windingIter, m_caseDef.usePointMode)), |
| DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_GEOMETRY_BIT, |
| m_context.getBinaryCollection().get(getProgramName("geom", m_caseDef.usePointMode)), |
| DE_NULL) |
| .build(vk, device, *pipelineLayout, *renderPass)); |
| |
| { |
| const Allocation &alloc = resultBuffer.getAllocation(); |
| |
| deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes)); |
| flushAlloc(vk, device, alloc); |
| } |
| |
| beginCommandBuffer(vk, *cmdBuffer); |
| beginRenderPassWithRasterizationDisabled(vk, *cmdBuffer, *renderPass, *framebuffer); |
| |
| vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u, |
| &descriptorSet.get(), 0u, DE_NULL); |
| { |
| const VkDeviceSize vertexBufferOffset = 0ull; |
| vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset); |
| } |
| |
| vk.cmdDraw(*cmdBuffer, numPatchesPerDrawCall * NUM_TESS_LEVELS, 1u, 0u, 0u); |
| endRenderPass(vk, *cmdBuffer); |
| |
| { |
| const VkBufferMemoryBarrier shaderWriteBarrier = |
| makeBufferMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, |
| 0ull, resultBufferSizeBytes); |
| |
| vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, |
| 0u, 0u, DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL); |
| } |
| |
| endCommandBuffer(vk, *cmdBuffer); |
| submitCommandsAndWait(vk, device, queue, *cmdBuffer); |
| |
| // Verify case result |
| { |
| const Allocation &resultAlloc = resultBuffer.getAllocation(); |
| |
| invalidateAlloc(vk, device, resultAlloc); |
| |
| const int refNumPrimitives = |
| numPatchesPerDrawCall * primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx]; |
| const int numPrimitiveVertices = |
| numVerticesPerPrimitive(m_caseDef.primitiveType, m_caseDef.usePointMode); |
| const int32_t numPrimitives = *static_cast<int32_t *>(resultAlloc.getHostPtr()); |
| const PerPrimitiveVec primitives = |
| sorted(readInterleavedData<PerPrimitive>(numPrimitives, resultAlloc.getHostPtr(), |
| resultBufferTessCoordsOffset, sizeof(PerPrimitive)), |
| byPatchPrimitiveID); |
| |
| // If this fails then we didn't read all vertices from shader and test must be changed to allow more. |
| DE_ASSERT(numPrimitiveVertices * numPrimitives <= resultBufferMaxVertices); |
| DE_UNREF(numPrimitiveVertices); |
| |
| tcu::TestLog &log = m_context.getTestContext().getLog(); |
| |
| if (numPrimitives < refNumPrimitives) |
| { |
| log << tcu::TestLog::Message << "Failure: got " << numPrimitives |
| << " primitives, but expected at least" << refNumPrimitives << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of primitives"); |
| } |
| |
| const int half = static_cast<int>(primitives.size() / 2); |
| const PerPrimitiveVec prim0 = PerPrimitiveVec(primitives.begin(), primitives.begin() + half); |
| const PerPrimitive *const prim1 = &primitives[half]; |
| |
| if (!comparePrimitivesExact(&prim0[0], prim1, half)) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: tessellation coordinates differ between two primitives drawn in one draw call" |
| << tcu::TestLog::EndMessage << tcu::TestLog::Message |
| << "Note: tessellation levels for both primitives were: " |
| << getTessellationLevelsString(tessLevels, m_caseDef.primitiveType) |
| << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of primitives"); |
| } |
| |
| if (programNdx == 0 && subTessLevelCaseNdx == 0) |
| firstPrim = prim0; |
| else |
| { |
| const bool compareOk = compare(firstPrim, prim0, levelCase.mem); |
| if (!compareOk) |
| { |
| log << tcu::TestLog::Message |
| << "Note: comparison of tessellation coordinates failed; comparison was made between " |
| "following cases:\n" |
| << " - case A: program 0, tessellation levels: " |
| << getTessellationLevelsString(tessLevelCases[tessLevelCaseNdx].levels[0], |
| m_caseDef.primitiveType) |
| << "\n" |
| << " - case B: program " << programNdx << ", tessellation levels: " |
| << getTessellationLevelsString(tessLevels, m_caseDef.primitiveType) |
| << tcu::TestLog::EndMessage; |
| |
| return tcu::TestStatus::fail("Invalid set of primitives"); |
| } |
| } |
| } |
| ++programNdx; |
| } |
| } |
| } |
| return tcu::TestStatus::pass("OK"); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #1 |
| * |
| * Test that the sequence of primitives input to the TES only depends on |
| * the tessellation levels, tessellation mode, spacing mode, winding, and |
| * point mode. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantPrimitiveSetTestInstance : public InvarianceTestInstance |
| { |
| public: |
| InvariantPrimitiveSetTestInstance(Context &context, const CaseDefinition &caseDef) |
| : InvarianceTestInstance(context, caseDef) |
| { |
| } |
| |
| protected: |
| bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int) const |
| { |
| if (!comparePrimitivesExact(&primitivesA[0], &primitivesB[0], static_cast<int>(primitivesA.size()))) |
| { |
| m_context.getTestContext().getLog() |
| << tcu::TestLog::Message << "Failure: tessellation coordinates differ between two programs" |
| << tcu::TestLog::EndMessage; |
| |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #5 |
| * |
| * Test that the set of triangles input to the TES only depends on the |
| * tessellation levels, tessellation mode and spacing mode. Specifically, |
| * winding doesn't change the set of triangles, though it can change the |
| * order in which they are input to TES, and can (and will) change the |
| * vertex order within a triangle. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantTriangleSetTestInstance : public InvarianceTestInstance |
| { |
| public: |
| InvariantTriangleSetTestInstance(Context &context, const CaseDefinition &caseDef) |
| : InvarianceTestInstance(context, caseDef) |
| { |
| } |
| |
| protected: |
| bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int) const |
| { |
| return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog()); |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #6 |
| * |
| * Test that the set of inner triangles input to the TES only depends on |
| * the inner tessellation levels, tessellation mode and spacing mode. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantInnerTriangleSetTestInstance : public InvarianceTestInstance |
| { |
| public: |
| InvariantInnerTriangleSetTestInstance(Context &context, const CaseDefinition &caseDef) |
| : InvarianceTestInstance(context, caseDef) |
| { |
| } |
| |
| protected: |
| std::vector<LevelCase> genTessLevelCases(void) const |
| { |
| const int numSubCases = 4; |
| const std::vector<LevelCase> baseResults = InvarianceTestInstance::genTessLevelCases(); |
| std::vector<LevelCase> result; |
| de::Random rnd(123); |
| |
| // Generate variants with different values for irrelevant levels. |
| for (int baseNdx = 0; baseNdx < static_cast<int>(baseResults.size()); ++baseNdx) |
| { |
| const TessLevels &base = baseResults[baseNdx].levels[0]; |
| TessLevels levels = base; |
| LevelCase levelCase; |
| |
| for (int subNdx = 0; subNdx < numSubCases; ++subNdx) |
| { |
| levelCase.levels.push_back(levels); |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); ++i) |
| levels.outer[i] = rnd.getFloat(2.0f, 16.0f); |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| levels.inner[1] = rnd.getFloat(2.0f, 16.0f); |
| } |
| |
| result.push_back(levelCase); |
| } |
| |
| return result; |
| } |
| |
| struct IsInnerTriangleTriangle |
| { |
| bool operator()(const tcu::Vec3 *vertices) const |
| { |
| for (int v = 0; v < 3; ++v) |
| for (int c = 0; c < 3; ++c) |
| if (vertices[v][c] == 0.0f) |
| return false; |
| return true; |
| } |
| }; |
| |
| struct IsInnerQuadTriangle |
| { |
| bool operator()(const tcu::Vec3 *vertices) const |
| { |
| for (int v = 0; v < 3; ++v) |
| for (int c = 0; c < 2; ++c) |
| if (vertices[v][c] == 0.0f || vertices[v][c] == 1.0f) |
| return false; |
| return true; |
| } |
| }; |
| |
| bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int) const |
| { |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(), |
| IsInnerTriangleTriangle(), "outer triangles"); |
| else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS) |
| return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(), |
| IsInnerQuadTriangle(), "outer triangles"); |
| else |
| { |
| DE_ASSERT(false); |
| return false; |
| } |
| } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test invariance rule #7 |
| * |
| * Test that the set of outer triangles input to the TES only depends on |
| * tessellation mode, spacing mode and the inner and outer tessellation |
| * levels corresponding to the inner and outer edges relevant to that |
| * triangle. |
| *//*--------------------------------------------------------------------*/ |
| class InvariantOuterTriangleSetTestInstance : public InvarianceTestInstance |
| { |
| public: |
| InvariantOuterTriangleSetTestInstance(Context &context, const CaseDefinition &caseDef) |
| : InvarianceTestInstance(context, caseDef) |
| { |
| } |
| |
| protected: |
| std::vector<LevelCase> genTessLevelCases(void) const |
| { |
| const int numSubCasesPerEdge = 4; |
| const int numEdges = m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : |
| m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : |
| 0; |
| const std::vector<LevelCase> baseResult = InvarianceTestInstance::genTessLevelCases(); |
| std::vector<LevelCase> result; |
| de::Random rnd(123); |
| |
| // Generate variants with different values for irrelevant levels. |
| for (int baseNdx = 0; baseNdx < static_cast<int>(baseResult.size()); ++baseNdx) |
| { |
| const TessLevels &base = baseResult[baseNdx].levels[0]; |
| if (base.inner[0] == 1.0f || (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS && base.inner[1] == 1.0f)) |
| continue; |
| |
| for (int edgeNdx = 0; edgeNdx < numEdges; ++edgeNdx) |
| { |
| TessLevels levels = base; |
| LevelCase levelCase; |
| levelCase.mem = edgeNdx; |
| |
| for (int subCaseNdx = 0; subCaseNdx < numSubCasesPerEdge; ++subCaseNdx) |
| { |
| levelCase.levels.push_back(levels); |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); ++i) |
| { |
| if (i != edgeNdx) |
| levels.outer[i] = rnd.getFloat(2.0f, 16.0f); |
| } |
| |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| levels.inner[1] = rnd.getFloat(2.0f, 16.0f); |
| } |
| |
| result.push_back(levelCase); |
| } |
| } |
| |
| return result; |
| } |
| |
| class IsTriangleTriangleOnOuterEdge |
| { |
| public: |
| IsTriangleTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx) |
| { |
| } |
| bool operator()(const tcu::Vec3 *vertices) const |
| { |
| bool touchesAppropriateEdge = false; |
| for (int v = 0; v < 3; ++v) |
| if (vertices[v][m_edgeNdx] == 0.0f) |
| touchesAppropriateEdge = true; |
| |
| if (touchesAppropriateEdge) |
| { |
| const tcu::Vec3 avg = (vertices[0] + vertices[1] + vertices[2]) / 3.0f; |
| return avg[m_edgeNdx] < avg[(m_edgeNdx + 1) % 3] && avg[m_edgeNdx] < avg[(m_edgeNdx + 2) % 3]; |
| } |
| return false; |
| } |
| |
| private: |
| const int m_edgeNdx; |
| }; |
| |
| class IsQuadTriangleOnOuterEdge |
| { |
| public: |
| IsQuadTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx) |
| { |
| } |
| |
| bool onEdge(const tcu::Vec3 &v) const |
| { |
| return v[m_edgeNdx % 2] == (m_edgeNdx <= 1 ? 0.0f : 1.0f); |
| } |
| |
| static inline bool onAnyEdge(const tcu::Vec3 &v) |
| { |
| return v[0] == 0.0f || v[0] == 1.0f || v[1] == 0.0f || v[1] == 1.0f; |
| } |
| |
| bool operator()(const tcu::Vec3 *vertices) const |
| { |
| for (int v = 0; v < 3; ++v) |
| { |
| const tcu::Vec3 &a = vertices[v]; |
| const tcu::Vec3 &b = vertices[(v + 1) % 3]; |
| const tcu::Vec3 &c = vertices[(v + 2) % 3]; |
| if (onEdge(a) && onEdge(b)) |
| return true; |
| if (onEdge(c) && !onAnyEdge(a) && !onAnyEdge(b) && a[m_edgeNdx % 2] == b[m_edgeNdx % 2]) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private: |
| const int m_edgeNdx; |
| }; |
| |
| bool compare(const PerPrimitiveVec &primitivesA, const PerPrimitiveVec &primitivesB, const int outerEdgeNdx) const |
| { |
| if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES) |
| { |
| return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(), |
| IsTriangleTriangleOnOuterEdge(outerEdgeNdx), |
| ("inner triangles, and outer triangles corresponding to other edge than edge " + |
| outerEdgeDescriptions(m_caseDef.primitiveType)[outerEdgeNdx].description()) |
| .c_str()); |
| } |
| else if (m_caseDef.primitiveType == TESSPRIMITIVETYPE_QUADS) |
| { |
| return compareTriangleSets(primitivesA, primitivesB, m_context.getTestContext().getLog(), |
| IsQuadTriangleOnOuterEdge(outerEdgeNdx), |
| ("inner triangles, and outer triangles corresponding to other edge than edge " + |
| outerEdgeDescriptions(m_caseDef.primitiveType)[outerEdgeNdx].description()) |
| .c_str()); |
| } |
| else |
| DE_ASSERT(false); |
| |
| return true; |
| } |
| }; |
| |
| TestInstance *InvarianceTestCase::createInstance(Context &context) const |
| { |
| switch (m_caseDef.caseType) |
| { |
| case CASETYPE_INVARIANT_PRIMITIVE_SET: |
| return new InvariantPrimitiveSetTestInstance(context, m_caseDef); |
| case CASETYPE_INVARIANT_TRIANGLE_SET: |
| return new InvariantTriangleSetTestInstance(context, m_caseDef); |
| case CASETYPE_INVARIANT_OUTER_TRIANGLE_SET: |
| return new InvariantOuterTriangleSetTestInstance(context, m_caseDef); |
| case CASETYPE_INVARIANT_INNER_TRIANGLE_SET: |
| return new InvariantInnerTriangleSetTestInstance(context, m_caseDef); |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| TestCase *makeInvariantPrimitiveSetTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const Winding winding, const bool usePointMode) |
| { |
| const CaseDefinition caseDef = {CASETYPE_INVARIANT_PRIMITIVE_SET, primitiveType, spacingMode, |
| getWindingUsage(winding), usePointMode}; |
| return new InvarianceTestCase(testCtx, name, description, caseDef); |
| } |
| |
| TestCase *makeInvariantTriangleSetTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode) |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS); |
| const CaseDefinition caseDef = {CASETYPE_INVARIANT_TRIANGLE_SET, primitiveType, spacingMode, WINDING_USAGE_VARY, |
| false}; |
| return new InvarianceTestCase(testCtx, name, description, caseDef); |
| } |
| |
| TestCase *makeInvariantInnerTriangleSetTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode) |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS); |
| const CaseDefinition caseDef = {CASETYPE_INVARIANT_INNER_TRIANGLE_SET, primitiveType, spacingMode, |
| WINDING_USAGE_VARY, false}; |
| return new InvarianceTestCase(testCtx, name, description, caseDef); |
| } |
| |
| TestCase *makeInvariantOuterTriangleSetTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode) |
| { |
| DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS); |
| const CaseDefinition caseDef = {CASETYPE_INVARIANT_OUTER_TRIANGLE_SET, primitiveType, spacingMode, |
| WINDING_USAGE_VARY, false}; |
| return new InvarianceTestCase(testCtx, name, description, caseDef); |
| } |
| |
| } // namespace PrimitiveSetInvariance |
| |
| namespace TessCoordComponent |
| { |
| |
| enum CaseType |
| { |
| CASETYPE_TESS_COORD_RANGE = 0, //!< Test that all (relevant) components of tess coord are in [0,1]. |
| CASETYPE_ONE_MINUS_TESS_COORD, //!< Test that for every (relevant) component c of a tess coord, 1.0-c is exact. |
| |
| CASETYPE_LAST |
| }; |
| |
| struct CaseDefinition |
| { |
| CaseType caseType; |
| TessPrimitiveType primitiveType; |
| SpacingMode spacingMode; |
| Winding winding; |
| bool usePointMode; |
| }; |
| |
| std::vector<TessLevels> genTessLevelCases(const int numCases) |
| { |
| de::Random rnd(123); |
| std::vector<TessLevels> result; |
| |
| for (int i = 0; i < numCases; ++i) |
| { |
| TessLevels levels; |
| levels.inner[0] = rnd.getFloat(1.0f, 63.0f); |
| levels.inner[1] = rnd.getFloat(1.0f, 63.0f); |
| levels.outer[0] = rnd.getFloat(1.0f, 63.0f); |
| levels.outer[1] = rnd.getFloat(1.0f, 63.0f); |
| levels.outer[2] = rnd.getFloat(1.0f, 63.0f); |
| levels.outer[3] = rnd.getFloat(1.0f, 63.0f); |
| result.push_back(levels); |
| } |
| |
| return result; |
| } |
| |
| typedef bool (*CompareFunc)(tcu::TestLog &log, const float value); |
| |
| bool compareTessCoordRange(tcu::TestLog &log, const float value) |
| { |
| if (!de::inRange(value, 0.0f, 1.0f)) |
| { |
| log << tcu::TestLog::Message << "Failure: tess coord component isn't in range [0,1]" |
| << tcu::TestLog::EndMessage; |
| return false; |
| } |
| return true; |
| } |
| |
| bool compareOneMinusTessCoord(tcu::TestLog &log, const float value) |
| { |
| if (value != 1.0f) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: comp + (1.0-comp) doesn't equal 1.0 for some component of tessellation coordinate" |
| << tcu::TestLog::EndMessage; |
| return false; |
| } |
| return true; |
| } |
| |
| void initPrograms(vk::SourceCollections &programCollection, const CaseDefinition caseDef) |
| { |
| // Vertex shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "\n" |
| << "layout(location = 0) in highp float in_v_attr;\n" |
| << "layout(location = 0) out highp float in_tc_attr;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " in_tc_attr = in_v_attr;\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("vert") << glu::VertexSource(src.str()); |
| } |
| |
| // Tessellation control shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "\n" |
| << "layout(vertices = 1) out;\n" |
| << "\n" |
| << "layout(location = 0) in highp float in_tc_attr[];\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| << " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| << "\n" |
| << " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| << " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| << " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| << " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str()); |
| } |
| |
| // Tessellation evaluation shader |
| { |
| std::ostringstream tessCoordSrc; |
| |
| if (caseDef.caseType == CASETYPE_TESS_COORD_RANGE) |
| tessCoordSrc << " sb_out.tessCoord[index] = gl_TessCoord;\n"; |
| else if (caseDef.caseType == CASETYPE_ONE_MINUS_TESS_COORD) |
| { |
| const char *components[] = {"x", "y", "z"}; |
| const int numComponents = (caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2); |
| |
| for (int i = 0; i < numComponents; ++i) |
| tessCoordSrc << " {\n" |
| << " float oneMinusComp = 1.0 - gl_TessCoord." << components[i] << ";\n" |
| << " sb_out.tessCoord[index]." << components[i] << " = gl_TessCoord." |
| << components[i] << " + oneMinusComp;\n" |
| << " }\n"; |
| } |
| else |
| { |
| DE_ASSERT(false); |
| return; |
| } |
| |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n" |
| << "\n" |
| << "layout(" << getTessPrimitiveTypeShaderName(caseDef.primitiveType) << ", " |
| << getSpacingModeShaderName(caseDef.spacingMode) << ", " << getWindingShaderName(caseDef.winding) |
| << (caseDef.usePointMode ? ", point_mode" : "") << ") in;\n" |
| << "\n" |
| << "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n" |
| << " int numInvocations;\n" |
| << " vec3 tessCoord[];\n" |
| << "} sb_out;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " int index = atomicAdd(sb_out.numInvocations, 1);\n" |
| << tessCoordSrc.str() << "}\n"; |
| |
| programCollection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str()); |
| } |
| } |
| |
| tcu::TestStatus test(Context &context, const CaseDefinition caseDef) |
| { |
| requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(), |
| FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS); |
| |
| const DeviceInterface &vk = context.getDeviceInterface(); |
| const VkDevice device = context.getDevice(); |
| const VkQueue queue = context.getUniversalQueue(); |
| const uint32_t queueFamilyIndex = context.getUniversalQueueFamilyIndex(); |
| Allocator &allocator = context.getDefaultAllocator(); |
| |
| const int numTessLevelCases = 32; |
| const std::vector<TessLevels> tessLevelCases = genTessLevelCases(numTessLevelCases); |
| |
| int maxNumVerticesInDrawCall = 0; |
| for (int i = 0; i < numTessLevelCases; ++i) |
| maxNumVerticesInDrawCall = |
| de::max(maxNumVerticesInDrawCall, |
| referenceVertexCount(caseDef.primitiveType, caseDef.spacingMode, caseDef.usePointMode, |
| &tessLevelCases[i].inner[0], &tessLevelCases[i].outer[0])); |
| |
| // We may get more invocations than expected, so add some more space (arbitrary number). |
| maxNumVerticesInDrawCall += 4; |
| |
| // Vertex input attributes buffer: to pass tessellation levels |
| |
| const VkFormat vertexFormat = VK_FORMAT_R32_SFLOAT; |
| const uint32_t vertexStride = tcu::getPixelSize(mapVkFormat(vertexFormat)); |
| const VkDeviceSize vertexDataSizeBytes = NUM_TESS_LEVELS * vertexStride; |
| const Buffer vertexBuffer(vk, device, allocator, |
| makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT), |
| MemoryRequirement::HostVisible); |
| |
| DE_ASSERT(vertexDataSizeBytes == sizeof(TessLevels)); |
| |
| // Output buffer: number of invocations and array of tess coords |
| |
| const int resultBufferTessCoordsOffset = (int)sizeof(int32_t) * 4; |
| const VkDeviceSize resultBufferSizeBytes = |
| resultBufferTessCoordsOffset + maxNumVerticesInDrawCall * sizeof(tcu::Vec4); |
| const Buffer resultBuffer(vk, device, allocator, |
| makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), |
| MemoryRequirement::HostVisible); |
| |
| // Descriptors |
| |
| const Unique<VkDescriptorSetLayout> descriptorSetLayout( |
| DescriptorSetLayoutBuilder() |
| .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) |
| .build(vk, device)); |
| |
| const Unique<VkDescriptorPool> descriptorPool( |
| DescriptorPoolBuilder() |
| .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) |
| .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); |
| |
| const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout)); |
| const VkDescriptorBufferInfo resultBufferInfo = |
| makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes); |
| |
| DescriptorSetUpdateBuilder() |
| .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), |
| VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo) |
| .update(vk, device); |
| |
| const Unique<VkRenderPass> renderPass(makeRenderPassWithoutAttachments(vk, device)); |
| const Unique<VkFramebuffer> framebuffer(makeFramebuffer(vk, device, *renderPass, 0u, DE_NULL, 1u, 1u)); |
| const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout)); |
| const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex)); |
| const Unique<VkCommandBuffer> cmdBuffer( |
| allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); |
| |
| const Unique<VkPipeline> pipeline( |
| GraphicsPipelineBuilder() |
| .setPatchControlPoints(NUM_TESS_LEVELS) |
| .setVertexInputSingleAttribute(vertexFormat, vertexStride) |
| .setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, context.getBinaryCollection().get("vert"), DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, context.getBinaryCollection().get("tesc"), |
| DE_NULL) |
| .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, |
| context.getBinaryCollection().get("tese"), DE_NULL) |
| .build(vk, device, *pipelineLayout, *renderPass)); |
| |
| for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < numTessLevelCases; ++tessLevelCaseNdx) |
| { |
| context.getTestContext().getLog() |
| << tcu::TestLog::Message << "Testing with tessellation levels: " |
| << getTessellationLevelsString(tessLevelCases[tessLevelCaseNdx], caseDef.primitiveType) |
| << tcu::TestLog::EndMessage; |
| |
| { |
| const Allocation &alloc = vertexBuffer.getAllocation(); |
| |
| deMemcpy(alloc.getHostPtr(), &tessLevelCases[tessLevelCaseNdx], sizeof(TessLevels)); |
| flushAlloc(vk, device, alloc); |
| } |
| { |
| const Allocation &alloc = resultBuffer.getAllocation(); |
| |
| deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes)); |
| flushAlloc(vk, device, alloc); |
| } |
| |
| beginCommandBuffer(vk, *cmdBuffer); |
| beginRenderPassWithRasterizationDisabled(vk, *cmdBuffer, *renderPass, *framebuffer); |
| |
| vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u, |
| &descriptorSet.get(), 0u, DE_NULL); |
| { |
| const VkDeviceSize vertexBufferOffset = 0ull; |
| vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset); |
| } |
| |
| vk.cmdDraw(*cmdBuffer, NUM_TESS_LEVELS, 1u, 0u, 0u); |
| endRenderPass(vk, *cmdBuffer); |
| |
| { |
| const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier( |
| VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, 0ull, resultBufferSizeBytes); |
| |
| vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u, |
| DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL); |
| } |
| |
| endCommandBuffer(vk, *cmdBuffer); |
| submitCommandsAndWait(vk, device, queue, *cmdBuffer); |
| |
| // Verify case result |
| { |
| const Allocation &resultAlloc = resultBuffer.getAllocation(); |
| |
| invalidateAlloc(vk, device, resultAlloc); |
| |
| const int32_t numVertices = *static_cast<int32_t *>(resultAlloc.getHostPtr()); |
| const std::vector<tcu::Vec3> vertices = readInterleavedData<tcu::Vec3>( |
| numVertices, resultAlloc.getHostPtr(), resultBufferTessCoordsOffset, sizeof(tcu::Vec4)); |
| |
| // If this fails then we didn't read all vertices from shader and test must be changed to allow more. |
| DE_ASSERT(numVertices <= maxNumVerticesInDrawCall); |
| |
| tcu::TestLog &log = context.getTestContext().getLog(); |
| const int numComponents = (caseDef.primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2); |
| |
| CompareFunc compare = (caseDef.caseType == CASETYPE_TESS_COORD_RANGE ? compareTessCoordRange : |
| caseDef.caseType == CASETYPE_ONE_MINUS_TESS_COORD ? compareOneMinusTessCoord : |
| DE_NULL); |
| |
| DE_ASSERT(compare != DE_NULL); |
| |
| for (std::vector<tcu::Vec3>::const_iterator vertexIter = vertices.begin(); vertexIter != vertices.end(); |
| ++vertexIter) |
| for (int i = 0; i < numComponents; ++i) |
| if (!compare(log, (*vertexIter)[i])) |
| { |
| log << tcu::TestLog::Message << "Note: got a wrong tessellation coordinate " |
| << (numComponents == 3 ? de::toString(*vertexIter) : |
| de::toString(vertexIter->swizzle(0, 1))) |
| << tcu::TestLog::EndMessage; |
| |
| tcu::TestStatus::fail("Invalid tessellation coordinate component"); |
| } |
| } |
| } |
| return tcu::TestStatus::pass("OK"); |
| } |
| |
| tcu::TestCase *makeTessCoordRangeTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const Winding winding, const bool usePointMode) |
| { |
| const CaseDefinition caseDef = {CASETYPE_TESS_COORD_RANGE, primitiveType, spacingMode, winding, usePointMode}; |
| return createFunctionCaseWithPrograms(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, description, checkSupportCase, |
| initPrograms, test, caseDef); |
| } |
| |
| tcu::TestCase *makeOneMinusTessCoordTest(tcu::TestContext &testCtx, const std::string &name, |
| const std::string &description, const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, const Winding winding, const bool usePointMode) |
| { |
| const CaseDefinition caseDef = {CASETYPE_ONE_MINUS_TESS_COORD, primitiveType, spacingMode, winding, usePointMode}; |
| return createFunctionCaseWithPrograms(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, description, checkSupportCase, |
| initPrograms, test, caseDef); |
| } |
| |
| } // namespace TessCoordComponent |
| |
| } // namespace |
| |
| //! These tests correspond to dEQP-GLES31.functional.tessellation.invariance.* |
| //! Original OpenGL ES tests used transform feedback to get vertices in primitive order. To emulate this behavior we have to use geometry shader, |
| //! which allows us to intercept verticess of final output primitives. This can't be done with tessellation shaders alone as number and order of |
| //! invocation is undefined. |
| tcu::TestCaseGroup *createInvarianceTests(tcu::TestContext &testCtx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> group( |
| new tcu::TestCaseGroup(testCtx, "invariance", "Test tessellation invariance rules")); |
| |
| de::MovePtr<tcu::TestCaseGroup> invariantPrimitiveSetGroup( |
| new tcu::TestCaseGroup(testCtx, "primitive_set", "Test invariance rule #1")); |
| de::MovePtr<tcu::TestCaseGroup> invariantOuterEdgeGroup( |
| new tcu::TestCaseGroup(testCtx, "outer_edge_division", "Test invariance rule #2")); |
| de::MovePtr<tcu::TestCaseGroup> symmetricOuterEdgeGroup( |
| new tcu::TestCaseGroup(testCtx, "outer_edge_symmetry", "Test invariance rule #3")); |
| de::MovePtr<tcu::TestCaseGroup> outerEdgeVertexSetIndexIndependenceGroup( |
| new tcu::TestCaseGroup(testCtx, "outer_edge_index_independence", "Test invariance rule #4")); |
| de::MovePtr<tcu::TestCaseGroup> invariantTriangleSetGroup( |
| new tcu::TestCaseGroup(testCtx, "triangle_set", "Test invariance rule #5")); |
| de::MovePtr<tcu::TestCaseGroup> invariantInnerTriangleSetGroup( |
| new tcu::TestCaseGroup(testCtx, "inner_triangle_set", "Test invariance rule #6")); |
| de::MovePtr<tcu::TestCaseGroup> invariantOuterTriangleSetGroup( |
| new tcu::TestCaseGroup(testCtx, "outer_triangle_set", "Test invariance rule #7")); |
| de::MovePtr<tcu::TestCaseGroup> tessCoordComponentRangeGroup( |
| new tcu::TestCaseGroup(testCtx, "tess_coord_component_range", "Test invariance rule #8, first part")); |
| de::MovePtr<tcu::TestCaseGroup> oneMinusTessCoordComponentGroup( |
| new tcu::TestCaseGroup(testCtx, "one_minus_tess_coord_component", "Test invariance rule #8, second part")); |
| |
| for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; ++primitiveTypeNdx) |
| for (int spacingModeNdx = 0; spacingModeNdx < SPACINGMODE_LAST; ++spacingModeNdx) |
| { |
| const TessPrimitiveType primitiveType = static_cast<TessPrimitiveType>(primitiveTypeNdx); |
| const SpacingMode spacingMode = static_cast<SpacingMode>(spacingModeNdx); |
| const bool triOrQuad = |
| primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS; |
| const std::string primName = getTessPrimitiveTypeShaderName(primitiveType); |
| const std::string primSpacName = primName + "_" + getSpacingModeShaderName(spacingMode); |
| |
| if (triOrQuad) |
| { |
| invariantOuterEdgeGroup->addChild(InvariantOuterEdge::makeOuterEdgeDivisionTest( |
| testCtx, primSpacName, "", primitiveType, spacingMode)); |
| invariantTriangleSetGroup->addChild(PrimitiveSetInvariance::makeInvariantTriangleSetTest( |
| testCtx, primSpacName, "", primitiveType, spacingMode)); |
| invariantInnerTriangleSetGroup->addChild(PrimitiveSetInvariance::makeInvariantInnerTriangleSetTest( |
| testCtx, primSpacName, "", primitiveType, spacingMode)); |
| invariantOuterTriangleSetGroup->addChild(PrimitiveSetInvariance::makeInvariantOuterTriangleSetTest( |
| testCtx, primSpacName, "", primitiveType, spacingMode)); |
| } |
| |
| for (int windingNdx = 0; windingNdx < WINDING_LAST; ++windingNdx) |
| for (int usePointModeNdx = 0; usePointModeNdx <= 1; ++usePointModeNdx) |
| { |
| const Winding winding = static_cast<Winding>(windingNdx); |
| const bool usePointMode = (usePointModeNdx != 0); |
| const std::string primSpacWindPointName = |
| primSpacName + "_" + getWindingShaderName(winding) + (usePointMode ? "_point_mode" : ""); |
| |
| invariantPrimitiveSetGroup->addChild(PrimitiveSetInvariance::makeInvariantPrimitiveSetTest( |
| testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode)); |
| tessCoordComponentRangeGroup->addChild(TessCoordComponent::makeTessCoordRangeTest( |
| testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode)); |
| oneMinusTessCoordComponentGroup->addChild(TessCoordComponent::makeOneMinusTessCoordTest( |
| testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode)); |
| symmetricOuterEdgeGroup->addChild(InvariantOuterEdge::makeSymmetricOuterEdgeTest( |
| testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode)); |
| |
| if (triOrQuad) |
| outerEdgeVertexSetIndexIndependenceGroup->addChild( |
| InvariantOuterEdge::makeOuterEdgeIndexIndependenceTest( |
| testCtx, primSpacWindPointName, "", primitiveType, spacingMode, winding, usePointMode)); |
| } |
| } |
| |
| group->addChild(invariantPrimitiveSetGroup.release()); |
| group->addChild(invariantOuterEdgeGroup.release()); |
| group->addChild(symmetricOuterEdgeGroup.release()); |
| group->addChild(outerEdgeVertexSetIndexIndependenceGroup.release()); |
| group->addChild(invariantTriangleSetGroup.release()); |
| group->addChild(invariantInnerTriangleSetGroup.release()); |
| group->addChild(invariantOuterTriangleSetGroup.release()); |
| group->addChild(tessCoordComponentRangeGroup.release()); |
| group->addChild(oneMinusTessCoordComponentGroup.release()); |
| |
| return group.release(); |
| } |
| |
| } // namespace tessellation |
| } // namespace vkt |