/*------------------------------------------------------------------------ * 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 #include #include 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 getWindingCases (const WindingUsage windingUsage) { std::vector 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 getUsePointModeCases (const PointModeUsage pointModeUsage) { std::vector 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 { deInt32 patchPrimitiveID; //!< gl_PrimitiveID in tessellation evaluation shader deInt32 primitiveID; //!< ID of an output primitive in geometry shader (user-defined) deInt32 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 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 std::vector arrayToVector (const T (&arr)[N]) { return std::vector(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); } template T arrayMax (const T (&arr)[N]) { return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr)); } template inline tcu::Vector singleTrueMask (int index) { DE_ASSERT(de::inBounds(index, 0, Size)); tcu::Vector result; result[index] = true; return result; } template inline bool contains (const ContainerT& c, const T& key) { return c.find(key) != c.end(); } template 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 class VecLexLessThan : public LexCompare, Size, std::less > { }; //! 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 windingCases = getWindingCases(windingUsage); const std::vector usePointModeCases = getUsePointModeCases(pointModeUsage); for (std::vector::const_iterator windingIter = windingCases.begin(); windingIter != windingCases.end(); ++windingIter) for (std::vector::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::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 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(); } } namespace InvariantOuterEdge { struct CaseDefinition { TessPrimitiveType primitiveType; SpacingMode spacingMode; Winding winding; bool usePointMode; }; typedef std::set > Vec3Set; std::vector generateRandomPatchTessLevels (const int numPatches, const int constantOuterLevelIndex, const float constantOuterLevel, de::Random& rnd) { std::vector 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 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 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 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& 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; deInt32 numPrimitives; PerPrimitiveVec primitives; }; BaseTestInstance (Context& context, const CaseDefinition caseDef, const int numPatchesToDraw); DrawResult draw (const deUint32 vertexCount, const std::vector& patchTessLevels, const Winding winding, const bool usePointMode); void uploadVertexAttributes (const std::vector& vertexData); protected: static const float m_singleOuterEdgeLevels[]; const CaseDefinition m_caseDef; const int m_numPatchesToDraw; const VkFormat m_vertexFormat; const deUint32 m_vertexStride; const std::vector 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 m_descriptorSetLayout; Unique m_descriptorPool; Unique m_descriptorSet; Unique m_renderPass; Unique m_framebuffer; Unique m_pipelineLayout; Unique m_cmdPool; Unique 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(deInt32) * 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 deUint32 vertexCount, const std::vector& 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 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(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(resultAlloc.getHostPtr()); result.primitives = sorted(readInterleavedData(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& 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(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 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(windingNdx); const bool usePointMode = (usePointModeNdx != 0); const bool isFirstProgram = (windingNdx == 0 && usePointModeNdx == 0); const DrawResult result = draw(static_cast(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(m_edgeDescriptions.size()); ++outerEdgeIndex) { const OuterEdgeDescription& edgeDesc = m_edgeDescriptions[outerEdgeIndex]; const std::vector 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(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(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 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(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); } } // InvariantOuterEdge ns 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 levels; int mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed. LevelCase (const TessLevels& lev) : levels(std::vector(1, lev)), mem(0) {} LevelCase (void) : mem(0) {} }; typedef tcu::Vector 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 bool compareTriangleSets (const PerPrimitiveVec& primitivesA, const PerPrimitiveVec& primitivesB, tcu::TestLog& log, const IsTriangleRelevantT& isTriangleRelevant, const char* ignoredTriangleDescription = DE_NULL) { typedef LexCompare > TriangleLexLessThan; typedef std::set TriangleSet; const int numTrianglesA = static_cast(primitivesA.size()); const int numTrianglesB = static_cast(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 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()); } //! 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 genTessLevelCases (void) const; virtual bool compare (const PerPrimitiveVec& primitivesA, const PerPrimitiveVec& primitivesB, const int levelCaseMem) const = 0; const CaseDefinition m_caseDef; }; std::vector 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 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 deUint32 queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); Allocator& allocator = m_context.getDefaultAllocator(); const std::vector tessLevelCases = genTessLevelCases(); const int numPatchesPerDrawCall = 2; int maxNumPrimitivesPerPatch = 0; // computed below std::vector > primitiveCounts; for (int caseNdx = 0; caseNdx < static_cast(tessLevelCases.size()); ++caseNdx) { primitiveCounts.push_back(std::vector()); for (int levelNdx = 0; levelNdx < static_cast(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 deUint32 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(deInt32) * 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 descriptorSetLayout(DescriptorSetLayoutBuilder() .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT) .build(vk, device)); const Unique descriptorPool(DescriptorPoolBuilder() .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); const Unique 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 renderPass (makeRenderPassWithoutAttachments (vk, device)); const Unique framebuffer (makeFramebuffer (vk, device, *renderPass, 0u, DE_NULL, 1u, 1u)); const Unique pipelineLayout (makePipelineLayout (vk, device, *descriptorSetLayout)); const Unique cmdPool (makeCommandPool (vk, device, queueFamilyIndex)); const Unique cmdBuffer (allocateCommandBuffer (vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < static_cast(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(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(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 windingCases = getWindingCases(m_caseDef.windingUsage); for (std::vector::const_iterator windingIter = windingCases.begin(); windingIter != windingCases.end(); ++windingIter) { const Unique 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(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 deInt32 numPrimitives = *static_cast(resultAlloc.getHostPtr()); const PerPrimitiveVec primitives = sorted(readInterleavedData(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(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(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 genTessLevelCases (void) const { const int numSubCases = 4; const std::vector baseResults = InvarianceTestInstance::genTessLevelCases(); std::vector result; de::Random rnd (123); // Generate variants with different values for irrelevant levels. for (int baseNdx = 0; baseNdx < static_cast(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 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 baseResult = InvarianceTestInstance::genTessLevelCases(); std::vector result; de::Random rnd (123); // Generate variants with different values for irrelevant levels. for (int baseNdx = 0; baseNdx < static_cast(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); } } // PrimitiveSetInvariance ns 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 genTessLevelCases (const int numCases) { de::Random rnd(123); std::vector 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 deUint32 queueFamilyIndex = context.getUniversalQueueFamilyIndex(); Allocator& allocator = context.getDefaultAllocator(); const int numTessLevelCases = 32; const std::vector 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 deUint32 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(deInt32) * 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 descriptorSetLayout(DescriptorSetLayoutBuilder() .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) .build(vk, device)); const Unique descriptorPool(DescriptorPoolBuilder() .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); const Unique 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 renderPass (makeRenderPassWithoutAttachments (vk, device)); const Unique framebuffer (makeFramebuffer (vk, device, *renderPass, 0u, DE_NULL, 1u, 1u)); const Unique pipelineLayout (makePipelineLayout (vk, device, *descriptorSetLayout)); const Unique cmdPool (makeCommandPool (vk, device, queueFamilyIndex)); const Unique cmdBuffer (allocateCommandBuffer (vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); const Unique 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(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 deInt32 numVertices = *static_cast(resultAlloc.getHostPtr()); const std::vector vertices = readInterleavedData(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::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); } } // TessCoordComponent ns } // anonymous //! 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 group (new tcu::TestCaseGroup(testCtx, "invariance", "Test tessellation invariance rules")); de::MovePtr invariantPrimitiveSetGroup (new tcu::TestCaseGroup(testCtx, "primitive_set", "Test invariance rule #1")); de::MovePtr invariantOuterEdgeGroup (new tcu::TestCaseGroup(testCtx, "outer_edge_division", "Test invariance rule #2")); de::MovePtr symmetricOuterEdgeGroup (new tcu::TestCaseGroup(testCtx, "outer_edge_symmetry", "Test invariance rule #3")); de::MovePtr outerEdgeVertexSetIndexIndependenceGroup(new tcu::TestCaseGroup(testCtx, "outer_edge_index_independence", "Test invariance rule #4")); de::MovePtr invariantTriangleSetGroup (new tcu::TestCaseGroup(testCtx, "triangle_set", "Test invariance rule #5")); de::MovePtr invariantInnerTriangleSetGroup (new tcu::TestCaseGroup(testCtx, "inner_triangle_set", "Test invariance rule #6")); de::MovePtr invariantOuterTriangleSetGroup (new tcu::TestCaseGroup(testCtx, "outer_triangle_set", "Test invariance rule #7")); de::MovePtr tessCoordComponentRangeGroup (new tcu::TestCaseGroup(testCtx, "tess_coord_component_range", "Test invariance rule #8, first part")); de::MovePtr 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(primitiveTypeNdx); const SpacingMode spacingMode = static_cast(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(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(); } } // tessellation } // vkt