1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2014 The Android Open Source Project
6  * Copyright (c) 2016 The Khronos Group Inc.
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  *//*!
21  * \file
22  * \brief Tessellation Coordinates Tests
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktTessellationCoordinatesTests.hpp"
26 #include "vktTestCaseUtil.hpp"
27 #include "vktTessellationUtil.hpp"
28 
29 #include "tcuTestLog.hpp"
30 #include "tcuRGBA.hpp"
31 #include "tcuSurface.hpp"
32 #include "tcuTextureUtil.hpp"
33 #include "tcuVectorUtil.hpp"
34 
35 #include "vkDefs.hpp"
36 #include "vkBarrierUtil.hpp"
37 #include "vkQueryUtil.hpp"
38 #include "vkBuilderUtil.hpp"
39 #include "vkTypeUtil.hpp"
40 #include "vkCmdUtil.hpp"
41 #include "vkObjUtil.hpp"
42 #include "vkBufferWithMemory.hpp"
43 #include "vkImageWithMemory.hpp"
44 
45 #include "deUniquePtr.hpp"
46 
47 #include <string>
48 #include <vector>
49 
50 namespace vkt
51 {
52 namespace tessellation
53 {
54 
55 using namespace vk;
56 
57 namespace
58 {
59 
60 template <typename T>
61 class SizeLessThan
62 {
63 public:
operator ()(const T & a,const T & b) const64     bool operator()(const T &a, const T &b) const
65     {
66         return a.size() < b.size();
67     }
68 };
69 
getCaseName(const TessPrimitiveType primitiveType,const SpacingMode spacingMode,bool executionModeInEvaluationShader)70 std::string getCaseName(const TessPrimitiveType primitiveType, const SpacingMode spacingMode,
71                         bool executionModeInEvaluationShader)
72 {
73     std::ostringstream str;
74     str << getTessPrimitiveTypeShaderName(primitiveType) << "_" << getSpacingModeShaderName(spacingMode);
75     if (!executionModeInEvaluationShader)
76         str << "_execution_mode_in_tesc";
77     return str.str();
78 }
79 
genTessLevelCases(const TessPrimitiveType primitiveType,const SpacingMode spacingMode)80 std::vector<TessLevels> genTessLevelCases(const TessPrimitiveType primitiveType, const SpacingMode spacingMode)
81 {
82     static const TessLevels rawTessLevelCases[] = {
83         {{1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}}, {{63.0f, 24.0f}, {15.0f, 42.0f, 10.0f, 12.0f}},
84         {{3.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{4.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
85         {{2.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{5.0f, 6.0f}, {1.0f, 1.0f, 1.0f, 1.0f}},
86         {{1.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, {{5.0f, 1.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
87         {{5.2f, 1.6f}, {2.9f, 3.4f, 1.5f, 4.1f}}};
88 
89     if (spacingMode == SPACINGMODE_EQUAL)
90         return std::vector<TessLevels>(DE_ARRAY_BEGIN(rawTessLevelCases), DE_ARRAY_END(rawTessLevelCases));
91     else
92     {
93         std::vector<TessLevels> result;
94         result.reserve(DE_LENGTH_OF_ARRAY(rawTessLevelCases));
95 
96         for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(rawTessLevelCases); ++tessLevelCaseNdx)
97         {
98             TessLevels curTessLevelCase = rawTessLevelCases[tessLevelCaseNdx];
99 
100             float *const inner = &curTessLevelCase.inner[0];
101             float *const outer = &curTessLevelCase.outer[0];
102 
103             for (int j = 0; j < 2; ++j)
104                 inner[j] = static_cast<float>(getClampedRoundedTessLevel(spacingMode, inner[j]));
105             for (int j = 0; j < 4; ++j)
106                 outer[j] = static_cast<float>(getClampedRoundedTessLevel(spacingMode, outer[j]));
107 
108             if (primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
109             {
110                 if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f)
111                 {
112                     if (inner[0] == 1.0f)
113                         inner[0] = static_cast<float>(getClampedRoundedTessLevel(spacingMode, inner[0] + 0.1f));
114                 }
115             }
116             else if (primitiveType == TESSPRIMITIVETYPE_QUADS)
117             {
118                 if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f || outer[3] > 1.0f)
119                 {
120                     if (inner[0] == 1.0f)
121                         inner[0] = static_cast<float>(getClampedRoundedTessLevel(spacingMode, inner[0] + 0.1f));
122                     if (inner[1] == 1.0f)
123                         inner[1] = static_cast<float>(getClampedRoundedTessLevel(spacingMode, inner[1] + 0.1f));
124                 }
125             }
126 
127             result.push_back(curTessLevelCase);
128         }
129 
130         DE_ASSERT(static_cast<int>(result.size()) == DE_LENGTH_OF_ARRAY(rawTessLevelCases));
131         return result;
132     }
133 }
134 
generateReferenceTessCoords(const TessPrimitiveType primitiveType,const SpacingMode spacingMode,const float * innerLevels,const float * outerLevels)135 std::vector<tcu::Vec3> generateReferenceTessCoords(const TessPrimitiveType primitiveType, const SpacingMode spacingMode,
136                                                    const float *innerLevels, const float *outerLevels)
137 {
138     if (isPatchDiscarded(primitiveType, outerLevels))
139         return std::vector<tcu::Vec3>();
140 
141     switch (primitiveType)
142     {
143     case TESSPRIMITIVETYPE_TRIANGLES:
144     {
145         int inner;
146         int outer[3];
147         getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
148 
149         if (spacingMode != SPACINGMODE_EQUAL)
150         {
151             // \note For fractional spacing modes, exact results are implementation-defined except in special cases.
152             DE_ASSERT(de::abs(innerLevels[0] - static_cast<float>(inner)) < 0.001f);
153             for (int i = 0; i < 3; ++i)
154                 DE_ASSERT(de::abs(outerLevels[i] - static_cast<float>(outer[i])) < 0.001f);
155             DE_ASSERT(inner > 1 || (outer[0] == 1 && outer[1] == 1 && outer[2] == 1));
156         }
157 
158         return generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]);
159     }
160 
161     case TESSPRIMITIVETYPE_QUADS:
162     {
163         int inner[2];
164         int outer[4];
165         getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
166 
167         if (spacingMode != SPACINGMODE_EQUAL)
168         {
169             // \note For fractional spacing modes, exact results are implementation-defined except in special cases.
170             for (int i = 0; i < 2; ++i)
171                 DE_ASSERT(de::abs(innerLevels[i] - static_cast<float>(inner[i])) < 0.001f);
172             for (int i = 0; i < 4; ++i)
173                 DE_ASSERT(de::abs(outerLevels[i] - static_cast<float>(outer[i])) < 0.001f);
174 
175             DE_ASSERT((inner[0] > 1 && inner[1] > 1) || (inner[0] == 1 && inner[1] == 1 && outer[0] == 1 &&
176                                                          outer[1] == 1 && outer[2] == 1 && outer[3] == 1));
177         }
178 
179         return generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]);
180     }
181 
182     case TESSPRIMITIVETYPE_ISOLINES:
183     {
184         int outer[2];
185         getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
186 
187         if (spacingMode != SPACINGMODE_EQUAL)
188         {
189             // \note For fractional spacing modes, exact results are implementation-defined except in special cases.
190             DE_ASSERT(de::abs(outerLevels[1] - static_cast<float>(outer[1])) < 0.001f);
191         }
192 
193         return generateReferenceIsolineTessCoords(outer[0], outer[1]);
194     }
195 
196     default:
197         DE_ASSERT(false);
198         return std::vector<tcu::Vec3>();
199     }
200 }
201 
drawPoint(tcu::Surface & dst,const int centerX,const int centerY,const tcu::RGBA & color,const int size)202 void drawPoint(tcu::Surface &dst, const int centerX, const int centerY, const tcu::RGBA &color, const int size)
203 {
204     const int width  = dst.getWidth();
205     const int height = dst.getHeight();
206     DE_ASSERT(de::inBounds(centerX, 0, width) && de::inBounds(centerY, 0, height));
207     DE_ASSERT(size > 0);
208 
209     for (int yOff = -((size - 1) / 2); yOff <= size / 2; ++yOff)
210         for (int xOff = -((size - 1) / 2); xOff <= size / 2; ++xOff)
211         {
212             const int pixX = centerX + xOff;
213             const int pixY = centerY + yOff;
214             if (de::inBounds(pixX, 0, width) && de::inBounds(pixY, 0, height))
215                 dst.setPixel(pixX, pixY, color);
216         }
217 }
218 
drawTessCoordPoint(tcu::Surface & dst,const TessPrimitiveType primitiveType,const tcu::Vec3 & pt,const tcu::RGBA & color,const int size)219 void drawTessCoordPoint(tcu::Surface &dst, const TessPrimitiveType primitiveType, const tcu::Vec3 &pt,
220                         const tcu::RGBA &color, const int size)
221 {
222     // \note These coordinates should match the description in the log message in TessCoordTestInstance::iterate.
223 
224     static const tcu::Vec2 triangleCorners[3] = {
225         tcu::Vec2(0.95f, 0.95f), tcu::Vec2(0.5f, 0.95f - 0.9f * deFloatSqrt(3.0f / 4.0f)), tcu::Vec2(0.05f, 0.95f)};
226 
227     static const float quadIsolineLDRU[4] = {0.1f, 0.9f, 0.9f, 0.1f};
228 
229     const tcu::Vec2 dstPos = primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
230                                  pt.x() * triangleCorners[0] + pt.y() * triangleCorners[1] + pt.z() * triangleCorners[2]
231 
232                              :
233                              primitiveType == TESSPRIMITIVETYPE_QUADS || primitiveType == TESSPRIMITIVETYPE_ISOLINES ?
234                                  tcu::Vec2((1.0f - pt.x()) * quadIsolineLDRU[0] + pt.x() * quadIsolineLDRU[2],
235                                            (1.0f - pt.y()) * quadIsolineLDRU[1] + pt.y() * quadIsolineLDRU[3])
236 
237                                  :
238                                  tcu::Vec2(-1.0f);
239 
240     drawPoint(dst, static_cast<int>(dstPos.x() * (float)dst.getWidth()),
241               static_cast<int>(dstPos.y() * (float)dst.getHeight()), color, size);
242 }
243 
drawTessCoordVisualization(tcu::Surface & dst,const TessPrimitiveType primitiveType,const std::vector<tcu::Vec3> & coords)244 void drawTessCoordVisualization(tcu::Surface &dst, const TessPrimitiveType primitiveType,
245                                 const std::vector<tcu::Vec3> &coords)
246 {
247     const int imageWidth  = 256;
248     const int imageHeight = 256;
249     dst.setSize(imageWidth, imageHeight);
250 
251     tcu::clear(dst.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
252 
253     for (int i = 0; i < static_cast<int>(coords.size()); ++i)
254         drawTessCoordPoint(dst, primitiveType, coords[i], tcu::RGBA::white(), 2);
255 }
256 
vec3XLessThan(const tcu::Vec3 & a,const tcu::Vec3 & b)257 inline bool vec3XLessThan(const tcu::Vec3 &a, const tcu::Vec3 &b)
258 {
259     return a.x() < b.x();
260 }
261 
binarySearchFirstVec3WithXAtLeast(const std::vector<tcu::Vec3> & sorted,float x)262 int binarySearchFirstVec3WithXAtLeast(const std::vector<tcu::Vec3> &sorted, float x)
263 {
264     const tcu::Vec3 ref(x, 0.0f, 0.0f);
265     const std::vector<tcu::Vec3>::const_iterator first =
266         std::lower_bound(sorted.begin(), sorted.end(), ref, vec3XLessThan);
267     if (first == sorted.end())
268         return -1;
269     return static_cast<int>(std::distance(sorted.begin(), first));
270 }
271 
272 // Check that all points in subset are (approximately) present also in superset.
oneWayComparePointSets(tcu::TestLog & log,tcu::Surface & errorDst,const TessPrimitiveType primitiveType,const std::vector<tcu::Vec3> & subset,const std::vector<tcu::Vec3> & superset,const char * subsetName,const char * supersetName,const tcu::RGBA & errorColor)273 bool oneWayComparePointSets(tcu::TestLog &log, tcu::Surface &errorDst, const TessPrimitiveType primitiveType,
274                             const std::vector<tcu::Vec3> &subset, const std::vector<tcu::Vec3> &superset,
275                             const char *subsetName, const char *supersetName, const tcu::RGBA &errorColor)
276 {
277     const std::vector<tcu::Vec3> supersetSorted = sorted(superset, vec3XLessThan);
278     const float epsilon                         = 0.01f;
279     const int maxNumFailurePrints               = 5;
280     int numFailuresDetected                     = 0;
281 
282     for (int subNdx = 0; subNdx < static_cast<int>(subset.size()); ++subNdx)
283     {
284         const tcu::Vec3 &subPt = subset[subNdx];
285 
286         bool matchFound = false;
287 
288         {
289             // Binary search the index of the first point in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
290             const tcu::Vec3 matchMin    = subPt - epsilon;
291             const tcu::Vec3 matchMax    = subPt + epsilon;
292             const int firstCandidateNdx = binarySearchFirstVec3WithXAtLeast(supersetSorted, matchMin.x());
293 
294             if (firstCandidateNdx >= 0)
295             {
296                 // Compare subPt to all points in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
297                 for (int superNdx = firstCandidateNdx;
298                      superNdx < static_cast<int>(supersetSorted.size()) && supersetSorted[superNdx].x() <= matchMax.x();
299                      ++superNdx)
300                 {
301                     const tcu::Vec3 &superPt = supersetSorted[superNdx];
302 
303                     if (tcu::boolAll(tcu::greaterThanEqual(superPt, matchMin)) &&
304                         tcu::boolAll(tcu::lessThanEqual(superPt, matchMax)))
305                     {
306                         matchFound = true;
307                         break;
308                     }
309                 }
310             }
311         }
312 
313         if (!matchFound)
314         {
315             ++numFailuresDetected;
316             if (numFailuresDetected < maxNumFailurePrints)
317                 log << tcu::TestLog::Message << "Failure: no matching " << supersetName << " point found for "
318                     << subsetName << " point " << subPt << tcu::TestLog::EndMessage;
319             else if (numFailuresDetected == maxNumFailurePrints)
320                 log << tcu::TestLog::Message << "Note: More errors follow" << tcu::TestLog::EndMessage;
321 
322             drawTessCoordPoint(errorDst, primitiveType, subPt, errorColor, 4);
323         }
324     }
325 
326     return numFailuresDetected == 0;
327 }
328 
329 //! Returns true on matching coordinate sets.
compareTessCoords(tcu::TestLog & log,TessPrimitiveType primitiveType,const std::vector<tcu::Vec3> & refCoords,const std::vector<tcu::Vec3> & resCoords)330 bool compareTessCoords(tcu::TestLog &log, TessPrimitiveType primitiveType, const std::vector<tcu::Vec3> &refCoords,
331                        const std::vector<tcu::Vec3> &resCoords)
332 {
333     tcu::Surface refVisual;
334     tcu::Surface resVisual;
335     bool success = true;
336 
337     drawTessCoordVisualization(refVisual, primitiveType, refCoords);
338     drawTessCoordVisualization(resVisual, primitiveType, resCoords);
339 
340     // Check that all points in reference also exist in result.
341     success = oneWayComparePointSets(log, refVisual, primitiveType, refCoords, resCoords, "reference", "result",
342                                      tcu::RGBA::blue()) &&
343               success;
344     // Check that all points in result also exist in reference.
345     success = oneWayComparePointSets(log, resVisual, primitiveType, resCoords, refCoords, "result", "reference",
346                                      tcu::RGBA::red()) &&
347               success;
348 
349     if (!success)
350     {
351         log << tcu::TestLog::Message
352             << "Note: in the following reference visualization, points that are missing in result point set are blue "
353                "(if any)"
354             << tcu::TestLog::EndMessage
355             << tcu::TestLog::Image("RefTessCoordVisualization", "Reference tessCoord visualization", refVisual)
356             << tcu::TestLog::Message
357             << "Note: in the following result visualization, points that are missing in reference point set are red "
358                "(if any)"
359             << tcu::TestLog::EndMessage;
360     }
361 
362     log << tcu::TestLog::Image("ResTessCoordVisualization", "Result tessCoord visualization", resVisual);
363 
364     return success;
365 }
366 
getTeseName(const bool writePointSize)367 inline std::string getTeseName(const bool writePointSize)
368 {
369     std::ostringstream str;
370     str << "tese" << (writePointSize ? "_point_size" : "");
371     return str.str();
372 }
373 
374 class TessCoordTest : public TestCase
375 {
376 public:
377     TessCoordTest(tcu::TestContext &testCtx, const TessPrimitiveType primitiveType, const SpacingMode spacingMode,
378                   const bool executionModeInEvaluationShader = true);
379 
380     void initPrograms(SourceCollections &programCollection) const;
381     void checkSupport(Context &context) const;
382     TestInstance *createInstance(Context &context) const;
383 
384 private:
385     const TessPrimitiveType m_primitiveType;
386     const SpacingMode m_spacingMode;
387     const bool m_executionModeInEvaluationShader;
388 };
389 
TessCoordTest(tcu::TestContext & testCtx,const TessPrimitiveType primitiveType,const SpacingMode spacingMode,const bool executionModeInEvaluationShader)390 TessCoordTest::TessCoordTest(tcu::TestContext &testCtx, const TessPrimitiveType primitiveType,
391                              const SpacingMode spacingMode, const bool executionModeInEvaluationShader)
392     : TestCase(testCtx, getCaseName(primitiveType, spacingMode, executionModeInEvaluationShader))
393     , m_primitiveType(primitiveType)
394     , m_spacingMode(spacingMode)
395     , m_executionModeInEvaluationShader(executionModeInEvaluationShader)
396 {
397 }
398 
checkSupport(Context & context) const399 void TessCoordTest::checkSupport(Context &context) const
400 {
401 #ifndef CTS_USES_VULKANSC
402     if (const vk::VkPhysicalDevicePortabilitySubsetFeaturesKHR *const features = getPortability(context))
403     {
404         checkPointMode(*features);
405         checkPrimitive(*features, m_primitiveType);
406     }
407 #else
408     DE_UNREF(context);
409 #endif // CTS_USES_VULKANSC
410 }
411 
initPrograms(SourceCollections & programCollection) const412 void TessCoordTest::initPrograms(SourceCollections &programCollection) const
413 {
414     if (m_executionModeInEvaluationShader)
415     {
416         // Vertex shader - no inputs
417         {
418             std::ostringstream src;
419             src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
420                 << "\n"
421                 << "void main (void)\n"
422                 << "{\n"
423                 << "}\n";
424 
425             programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
426         }
427 
428         // Tessellation control shader
429         {
430             std::ostringstream src;
431             src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
432                 << "#extension GL_EXT_tessellation_shader : require\n"
433                 << "\n"
434                 << "layout(vertices = 1) out;\n"
435                 << "\n"
436                 << "layout(set = 0, binding = 0, std430) readonly restrict buffer TessLevels {\n"
437                 << "    float inner0;\n"
438                 << "    float inner1;\n"
439                 << "    float outer0;\n"
440                 << "    float outer1;\n"
441                 << "    float outer2;\n"
442                 << "    float outer3;\n"
443                 << "} sb_levels;\n"
444                 << "\n"
445                 << "void main (void)\n"
446                 << "{\n"
447                 << "    gl_TessLevelInner[0] = sb_levels.inner0;\n"
448                 << "    gl_TessLevelInner[1] = sb_levels.inner1;\n"
449                 << "\n"
450                 << "    gl_TessLevelOuter[0] = sb_levels.outer0;\n"
451                 << "    gl_TessLevelOuter[1] = sb_levels.outer1;\n"
452                 << "    gl_TessLevelOuter[2] = sb_levels.outer2;\n"
453                 << "    gl_TessLevelOuter[3] = sb_levels.outer3;\n"
454                 << "}\n";
455 
456             programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
457         }
458 
459         // Tessellation evaluation shader
460         for (uint32_t i = 0; i < 2; ++i)
461         {
462             const bool writePointSize = i == 1;
463 
464             std::ostringstream src;
465             src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
466                 << "#extension GL_EXT_tessellation_shader : require\n";
467             if (writePointSize)
468                 src << "#extension GL_EXT_tessellation_point_size : require\n";
469             src << "\n"
470                 << "layout(" << getTessPrimitiveTypeShaderName(m_primitiveType) << ", "
471                 << getSpacingModeShaderName(m_spacingMode) << ", point_mode) in;\n"
472                 << "\n"
473                 << "layout(set = 0, binding = 1, std430) coherent restrict buffer Output {\n"
474                 << "    int  numInvocations;\n"
475                 << "    vec3 tessCoord[];\n" // alignment is 16 bytes, same as vec4
476                 << "} sb_out;\n"
477                 << "\n"
478                 << "void main (void)\n"
479                 << "{\n"
480                 << "    int index = atomicAdd(sb_out.numInvocations, 1);\n"
481                 << "    sb_out.tessCoord[index] = gl_TessCoord;\n";
482             if (writePointSize)
483                 src << "    gl_PointSize = 1.0f;\n";
484             src << "}\n";
485 
486             programCollection.glslSources.add(getTeseName(writePointSize))
487                 << glu::TessellationEvaluationSource(src.str());
488         }
489     }
490     else
491     {
492         // note: spirv code for all stages coresponds to glsl version above
493 
494         programCollection.spirvAsmSources.add("vert") << "OpCapability Shader\n"
495                                                          "%glsl_ext_inst = OpExtInstImport \"GLSL.std.450\"\n"
496                                                          "OpMemoryModel Logical GLSL450\n"
497                                                          "OpEntryPoint Vertex %main_fun \"main\"\n"
498                                                          "%type_void       = OpTypeVoid\n"
499                                                          "%type_void_f     = OpTypeFunction %type_void\n"
500                                                          "%main_fun        = OpFunction %type_void None %type_void_f\n"
501                                                          "%main_label      = OpLabel\n"
502                                                          "OpReturn\n"
503                                                          "OpFunctionEnd\n";
504 
505         // glsl requires primitive_mode, vertex_spacing, ordering and point_mode layout qualifiers to be defined in
506         // tessellation evaluation shader while spirv allows corresponding execution modes to be defined in TES and/or
507         // TCS; here we test using execution modes only in TCS as TES is tested with glsl version of tests
508 
509         const std::string executionMode = std::string("OpExecutionMode %main_fun ") +
510                                           getTessPrimitiveTypeShaderName(m_primitiveType, true) +
511                                           "\n"
512                                           "OpExecutionMode %main_fun " +
513                                           getSpacingModeShaderName(m_spacingMode, true) + "\n" +
514                                           "OpExecutionMode %main_fun PointMode\n"
515                                           "OpExecutionMode %main_fun VertexOrderCcw\n";
516 
517         std::string tescSrc =
518             "OpCapability Tessellation\n"
519             "%glsl_ext_inst = OpExtInstImport \"GLSL.std.450\"\n"
520             "OpMemoryModel Logical GLSL450\n"
521             "OpEntryPoint TessellationControl %main_fun \"main\" %var_tess_level_inner %var_tess_level_outer\n"
522             "OpExecutionMode %main_fun OutputVertices 1\n";
523         tescSrc +=
524             executionMode +
525             "OpDecorate %var_tess_level_inner Patch\n"
526             "OpDecorate %var_tess_level_inner BuiltIn TessLevelInner\n"
527             "OpMemberDecorate %type_struct_sb_levels 0 NonWritable\n"
528             "OpMemberDecorate %type_struct_sb_levels 0 Offset 0\n"
529             "OpMemberDecorate %type_struct_sb_levels 1 NonWritable\n"
530             "OpMemberDecorate %type_struct_sb_levels 1 Offset 4\n"
531             "OpMemberDecorate %type_struct_sb_levels 2 NonWritable\n"
532             "OpMemberDecorate %type_struct_sb_levels 2 Offset 8\n"
533             "OpMemberDecorate %type_struct_sb_levels 3 NonWritable\n"
534             "OpMemberDecorate %type_struct_sb_levels 3 Offset 12\n"
535             "OpMemberDecorate %type_struct_sb_levels 4 NonWritable\n"
536             "OpMemberDecorate %type_struct_sb_levels 4 Offset 16\n"
537             "OpMemberDecorate %type_struct_sb_levels 5 NonWritable\n"
538             "OpMemberDecorate %type_struct_sb_levels 5 Offset 20\n"
539             "OpDecorate %type_struct_sb_levels BufferBlock\n"
540             "OpDecorate %var_struct_sb_levels DescriptorSet 0\n"
541             "OpDecorate %var_struct_sb_levels Binding 0\n"
542             "OpDecorate %var_struct_sb_levels Restrict\n"
543             "OpDecorate %var_tess_level_outer Patch\n"
544             "OpDecorate %var_tess_level_outer BuiltIn TessLevelOuter\n"
545             "%type_void                 = OpTypeVoid\n"
546             "%type_void_f               = OpTypeFunction %type_void\n"
547             "%type_f32                  = OpTypeFloat 32\n"
548             "%type_u32                  = OpTypeInt 32 0\n"
549             "%c_u32_2                   = OpConstant %type_u32 2\n"
550             "%type_arr_f32_2            = OpTypeArray %type_f32 %c_u32_2\n"
551             "%type_arr_f32_2_ptr        = OpTypePointer Output %type_arr_f32_2\n"
552             "%type_i32                  = OpTypeInt 32 1\n"
553             "%type_struct_sb_levels     = OpTypeStruct %type_f32 %type_f32 %type_f32 %type_f32 %type_f32 %type_f32\n"
554             "%type_struct_sb_levels_ptr = OpTypePointer Uniform %type_struct_sb_levels\n"
555             "%var_struct_sb_levels      = OpVariable %type_struct_sb_levels_ptr Uniform\n"
556             "%type_uni_f32_ptr          = OpTypePointer Uniform %type_f32\n"
557             "%type_out_f32_ptr          = OpTypePointer Output %type_f32\n"
558             "%c_i32_0                   = OpConstant %type_i32 0\n"
559             "%c_i32_1                   = OpConstant %type_i32 1\n"
560             "%c_u32_4                   = OpConstant %type_u32 4\n"
561             "%c_i32_2                   = OpConstant %type_i32 2\n"
562             "%c_i32_3                   = OpConstant %type_i32 3\n"
563             "%c_i32_4                   = OpConstant %type_i32 4\n"
564             "%c_i32_5                   = OpConstant %type_i32 5\n"
565             "%type_arr_f32_4            = OpTypeArray %type_f32 %c_u32_4\n"
566             "%type_arr_f32_4_ptr        = OpTypePointer Output %type_arr_f32_4\n"
567             "%var_tess_level_inner      = OpVariable %type_arr_f32_2_ptr Output\n"
568             "%var_tess_level_outer      = OpVariable %type_arr_f32_4_ptr Output\n"
569             "%main_fun                  = OpFunction %type_void None %type_void_f\n"
570             "%main_label                = OpLabel\n"
571             "%tess_inner_0_ptr          = OpAccessChain %type_uni_f32_ptr %var_struct_sb_levels %c_i32_0\n"
572             "%tess_inner_0              = OpLoad %type_f32 %tess_inner_0_ptr\n"
573             "%gl_tess_inner_0           = OpAccessChain %type_out_f32_ptr %var_tess_level_inner %c_i32_0\n"
574             "                             OpStore %gl_tess_inner_0 %tess_inner_0\n"
575             "%tess_inner_1_ptr          = OpAccessChain %type_uni_f32_ptr %var_struct_sb_levels %c_i32_1\n"
576             "%tess_inner_1              = OpLoad %type_f32 %tess_inner_1_ptr\n"
577             "%gl_tess_inner_1           = OpAccessChain %type_out_f32_ptr %var_tess_level_inner %c_i32_1\n"
578             "                             OpStore %gl_tess_inner_1 %tess_inner_1\n"
579             "%tess_outer_0_ptr          = OpAccessChain %type_uni_f32_ptr %var_struct_sb_levels %c_i32_2\n"
580             "%tess_outer_0              = OpLoad %type_f32 %tess_outer_0_ptr\n"
581             "%gl_tess_outer_0           = OpAccessChain %type_out_f32_ptr %var_tess_level_outer %c_i32_0\n"
582             "                             OpStore %gl_tess_outer_0 %tess_outer_0\n"
583             "%tess_outer_1_ptr          = OpAccessChain %type_uni_f32_ptr %var_struct_sb_levels %c_i32_3\n"
584             "%tess_outer_1              = OpLoad %type_f32 %tess_outer_1_ptr\n"
585             "%gl_tess_outer_1           = OpAccessChain %type_out_f32_ptr %var_tess_level_outer %c_i32_1\n"
586             "                             OpStore %gl_tess_outer_1 %tess_outer_1\n"
587             "%tess_outer_2_ptr          = OpAccessChain %type_uni_f32_ptr %var_struct_sb_levels %c_i32_4\n"
588             "%tess_outer_2              = OpLoad %type_f32 %tess_outer_2_ptr\n"
589             "%gl_tess_outer_2           = OpAccessChain %type_out_f32_ptr %var_tess_level_outer %c_i32_2\n"
590             "                             OpStore %gl_tess_outer_2 %tess_outer_2\n"
591             "%tess_outer_3_ptr          = OpAccessChain %type_uni_f32_ptr %var_struct_sb_levels %c_i32_5\n"
592             "%tess_outer_3              = OpLoad %type_f32 %tess_outer_3_ptr\n"
593             "%gl_tess_outer_3           = OpAccessChain %type_out_f32_ptr %var_tess_level_outer %c_i32_3\n"
594             "                             OpStore %gl_tess_outer_3 %tess_outer_3\n"
595             "OpReturn\n"
596             "OpFunctionEnd\n";
597         programCollection.spirvAsmSources.add("tesc") << tescSrc;
598 
599         std::string teseSrc =
600             "OpCapability Tessellation\n"
601             "%glsl_ext_inst = OpExtInstImport \"GLSL.std.450\"\n"
602             "OpMemoryModel Logical GLSL450\n"
603             "OpEntryPoint TessellationEvaluation %main_fun \"main\" %var_gl_tess_coord\n"
604             "OpDecorate %type_run_arr_v3_f32 ArrayStride 16\n"
605             "OpMemberDecorate %type_struct 0 Coherent\n"
606             "OpMemberDecorate %type_struct 0 Offset 0\n"
607             "OpMemberDecorate %type_struct 1 Coherent\n"
608             "OpMemberDecorate %type_struct 1 Offset 16\n"
609             "OpDecorate %type_struct BufferBlock\n"
610             "OpDecorate %var_struct_ptr DescriptorSet 0\n"
611             "OpDecorate %var_struct_ptr Restrict\n"
612             "OpDecorate %var_struct_ptr Binding 1\n"
613             "OpDecorate %var_gl_tess_coord BuiltIn TessCoord\n"
614             "%type_void             = OpTypeVoid\n"
615             "%type_void_f           = OpTypeFunction %type_void\n"
616             "%type_i32              = OpTypeInt 32 1\n"
617             "%type_u32              = OpTypeInt 32 0\n"
618             "%type_i32_fp           = OpTypePointer Function %type_i32\n"
619             "%type_f32              = OpTypeFloat 32\n"
620             "%type_v3_f32           = OpTypeVector %type_f32 3\n"
621             "%type_run_arr_v3_f32   = OpTypeRuntimeArray %type_v3_f32\n"
622             "%type_struct           = OpTypeStruct %type_i32 %type_run_arr_v3_f32\n"
623             "%type_uni_struct_ptr   = OpTypePointer Uniform %type_struct\n"
624             "%type_uni_i32_ptr      = OpTypePointer Uniform %type_i32\n"
625             "%type_uni_v3_f32_ptr   = OpTypePointer Uniform %type_v3_f32\n"
626             "%type_in_v3_f32_ptr    = OpTypePointer Input %type_v3_f32\n"
627             "%c_i32_0               = OpConstant %type_i32 0\n"
628             "%c_i32_1               = OpConstant %type_i32 1\n"
629             "%c_u32_0               = OpConstant %type_u32 1\n"
630             "%c_u32_1               = OpConstant %type_u32 0\n"
631             "%var_struct_ptr        = OpVariable %type_uni_struct_ptr Uniform\n"
632             "%var_gl_tess_coord     = OpVariable %type_in_v3_f32_ptr Input\n"
633             "%main_fun              = OpFunction %type_void None %type_void_f\n"
634             "%main_label            = OpLabel\n"
635             "%var_i32_ptr           = OpVariable %type_i32_fp Function\n"
636             "%num_invocations       = OpAccessChain %type_uni_i32_ptr %var_struct_ptr %c_i32_0\n"
637             "%index_0               = OpAtomicIAdd %type_i32 %num_invocations %c_u32_0 %c_u32_1 %c_i32_1\n"
638             "                         OpStore %var_i32_ptr %index_0\n"
639             "%index_1               = OpLoad %type_i32 %var_i32_ptr\n"
640             "%gl_tess_coord         = OpLoad %type_v3_f32 %var_gl_tess_coord\n"
641             "%out_tess_coord        = OpAccessChain %type_uni_v3_f32_ptr %var_struct_ptr %c_i32_1 %index_1\n"
642             "                         OpStore %out_tess_coord %gl_tess_coord\n"
643             "OpReturn\n"
644             "OpFunctionEnd\n";
645         programCollection.spirvAsmSources.add(getTeseName(false)) << teseSrc;
646         programCollection.spirvAsmSources.add(getTeseName(true)) << teseSrc;
647     }
648 }
649 
650 class TessCoordTestInstance : public TestInstance
651 {
652 public:
653     TessCoordTestInstance(Context &context, const TessPrimitiveType primitiveType, const SpacingMode spacingMode);
654 
655     tcu::TestStatus iterate(void);
656 
657 private:
658     const TessPrimitiveType m_primitiveType;
659     const SpacingMode m_spacingMode;
660 };
661 
TessCoordTestInstance(Context & context,const TessPrimitiveType primitiveType,const SpacingMode spacingMode)662 TessCoordTestInstance::TessCoordTestInstance(Context &context, const TessPrimitiveType primitiveType,
663                                              const SpacingMode spacingMode)
664     : TestInstance(context)
665     , m_primitiveType(primitiveType)
666     , m_spacingMode(spacingMode)
667 {
668 }
669 
iterate(void)670 tcu::TestStatus TessCoordTestInstance::iterate(void)
671 {
672     const DeviceInterface &vk       = m_context.getDeviceInterface();
673     const VkDevice device           = m_context.getDevice();
674     const VkQueue queue             = m_context.getUniversalQueue();
675     const uint32_t queueFamilyIndex = m_context.getUniversalQueueFamilyIndex();
676     Allocator &allocator            = m_context.getDefaultAllocator();
677 
678     // Test data
679 
680     const std::vector<TessLevels> tessLevelCases = genTessLevelCases(m_primitiveType, m_spacingMode);
681     std::vector<std::vector<tcu::Vec3>> allReferenceTessCoords(tessLevelCases.size());
682 
683     for (uint32_t i = 0; i < tessLevelCases.size(); ++i)
684         allReferenceTessCoords[i] = generateReferenceTessCoords(
685             m_primitiveType, m_spacingMode, &tessLevelCases[i].inner[0], &tessLevelCases[i].outer[0]);
686 
687     const size_t maxNumVertices =
688         static_cast<int>(std::max_element(allReferenceTessCoords.begin(), allReferenceTessCoords.end(),
689                                           SizeLessThan<std::vector<tcu::Vec3>>())
690                              ->size());
691 
692     // Input buffer: tessellation levels. Data is filled in later.
693 
694     const BufferWithMemory tessLevelsBuffer(
695         vk, device, allocator, makeBufferCreateInfo(sizeof(TessLevels), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT),
696         MemoryRequirement::HostVisible);
697 
698     // Output buffer: number of invocations + padding + tessellation coordinates. Initialized later.
699 
700     const int resultBufferTessCoordsOffset = 4 * (int)sizeof(int32_t);
701     const int extraneousVertices =
702         16; // allow some room for extraneous vertices from duplicate shader invocations (number is arbitrary)
703     const VkDeviceSize resultBufferSizeBytes =
704         resultBufferTessCoordsOffset + (maxNumVertices + extraneousVertices) * sizeof(tcu::Vec4);
705     const BufferWithMemory resultBuffer(vk, device, allocator,
706                                         makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT),
707                                         MemoryRequirement::HostVisible);
708 
709     // Descriptors
710 
711     const Unique<VkDescriptorSetLayout> descriptorSetLayout(
712         DescriptorSetLayoutBuilder()
713             .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT)
714             .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)
715             .build(vk, device));
716 
717     const Unique<VkDescriptorPool> descriptorPool(
718         DescriptorPoolBuilder()
719             .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
720             .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
721             .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
722 
723     const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
724 
725     const VkDescriptorBufferInfo tessLevelsBufferInfo =
726         makeDescriptorBufferInfo(tessLevelsBuffer.get(), 0ull, sizeof(TessLevels));
727     const VkDescriptorBufferInfo resultBufferInfo =
728         makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes);
729 
730     DescriptorSetUpdateBuilder()
731         .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u),
732                      VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &tessLevelsBufferInfo)
733         .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(1u),
734                      VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo)
735         .update(vk, device);
736 
737     // Pipeline: set up vertex processing without rasterization
738 
739     const Unique<VkRenderPass> renderPass(makeRenderPassWithoutAttachments(vk, device));
740     const Unique<VkFramebuffer> framebuffer(makeFramebuffer(vk, device, *renderPass, 0u, DE_NULL, 1u, 1u));
741     const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout));
742     const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex));
743     const Unique<VkCommandBuffer> cmdBuffer(
744         allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
745 
746     const Unique<VkPipeline> pipeline(
747         GraphicsPipelineBuilder()
748             .setShader(vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL)
749             .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
750                        m_context.getBinaryCollection().get("tesc"), DE_NULL)
751             .setShader(vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
752                        m_context.getBinaryCollection().get(
753                            getTeseName(m_context.getDeviceFeatures().shaderTessellationAndGeometryPointSize)),
754                        DE_NULL)
755             .build(vk, device, *pipelineLayout, *renderPass));
756 
757     uint32_t numPassedCases = 0;
758 
759     // Repeat the test for all tessellation coords cases
760     for (uint32_t tessLevelCaseNdx = 0; tessLevelCaseNdx < tessLevelCases.size(); ++tessLevelCaseNdx)
761     {
762         m_context.getTestContext().getLog()
763             << tcu::TestLog::Message
764             << "Tessellation levels: " << getTessellationLevelsString(tessLevelCases[tessLevelCaseNdx], m_primitiveType)
765             << tcu::TestLog::EndMessage;
766 
767         // Upload tessellation levels data to the input buffer
768         {
769             const Allocation &alloc            = tessLevelsBuffer.getAllocation();
770             TessLevels *const bufferTessLevels = static_cast<TessLevels *>(alloc.getHostPtr());
771 
772             *bufferTessLevels = tessLevelCases[tessLevelCaseNdx];
773             flushAlloc(vk, device, alloc);
774         }
775 
776         // Clear the results buffer
777         {
778             const Allocation &alloc = resultBuffer.getAllocation();
779 
780             deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes));
781             flushAlloc(vk, device, alloc);
782         }
783 
784         // Reset the command buffer and begin recording.
785         beginCommandBuffer(vk, *cmdBuffer);
786         beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, makeRect2D(1, 1));
787 
788         vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
789         vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u,
790                                  &descriptorSet.get(), 0u, DE_NULL);
791 
792         // Process a single abstract vertex.
793         vk.cmdDraw(*cmdBuffer, 1u, 1u, 0u, 0u);
794         endRenderPass(vk, *cmdBuffer);
795 
796         {
797             const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(
798                 VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, 0ull, resultBufferSizeBytes);
799 
800             vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u,
801                                   DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL);
802         }
803 
804         endCommandBuffer(vk, *cmdBuffer);
805         submitCommandsAndWait(vk, device, queue, *cmdBuffer);
806 
807         // Verify results
808         {
809             const Allocation &resultAlloc = resultBuffer.getAllocation();
810 
811             invalidateAlloc(vk, device, resultAlloc);
812 
813             const int32_t numResults                      = *static_cast<int32_t *>(resultAlloc.getHostPtr());
814             const std::vector<tcu::Vec3> resultTessCoords = readInterleavedData<tcu::Vec3>(
815                 numResults, resultAlloc.getHostPtr(), resultBufferTessCoordsOffset, sizeof(tcu::Vec4));
816             const std::vector<tcu::Vec3> &referenceTessCoords = allReferenceTessCoords[tessLevelCaseNdx];
817             const int numExpectedResults                      = static_cast<int>(referenceTessCoords.size());
818             tcu::TestLog &log                                 = m_context.getTestContext().getLog();
819 
820             if (numResults < numExpectedResults)
821             {
822                 log << tcu::TestLog::Message << "Failure: generated " << numResults
823                     << " coordinates, but the expected reference value is " << numExpectedResults
824                     << tcu::TestLog::EndMessage;
825             }
826             else if (numResults == numExpectedResults)
827                 log << tcu::TestLog::Message << "Note: generated " << numResults << " tessellation coordinates"
828                     << tcu::TestLog::EndMessage;
829             else
830             {
831                 log << tcu::TestLog::Message << "Note: generated " << numResults << " coordinates (out of which "
832                     << numExpectedResults << " must be unique)" << tcu::TestLog::EndMessage;
833             }
834 
835             if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
836                 log << tcu::TestLog::Message
837                     << "Note: in the following visualization(s), the u=1, v=1, w=1 corners are at the right, top, and "
838                        "left corners, respectively"
839                     << tcu::TestLog::EndMessage;
840             else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
841                 log << tcu::TestLog::Message
842                     << "Note: in the following visualization(s), u and v coordinate go left-to-right and "
843                        "bottom-to-top, respectively"
844                     << tcu::TestLog::EndMessage;
845             else
846                 DE_ASSERT(false);
847 
848             if (compareTessCoords(log, m_primitiveType, referenceTessCoords, resultTessCoords) &&
849                 (numResults >= numExpectedResults))
850                 ++numPassedCases;
851         }
852     } // for tessLevelCaseNdx
853 
854     return (numPassedCases == tessLevelCases.size() ? tcu::TestStatus::pass("OK") :
855                                                       tcu::TestStatus::fail("Some cases have failed"));
856 }
857 
createInstance(Context & context) const858 TestInstance *TessCoordTest::createInstance(Context &context) const
859 {
860     requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(),
861                     FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS);
862 
863     return new TessCoordTestInstance(context, m_primitiveType, m_spacingMode);
864 }
865 
866 } // namespace
867 
868 //! Based on dEQP-GLES31.functional.tessellation.tesscoord.*
869 //! \note Transform feedback is replaced with SSBO. Because of that, this version allows duplicate coordinates from shader invocations.
870 //! The test still fails if not enough coordinates are generated, or if coordinates don't match the reference data.
createCoordinatesTests(tcu::TestContext & testCtx)871 tcu::TestCaseGroup *createCoordinatesTests(tcu::TestContext &testCtx)
872 {
873     de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "tesscoord"));
874 
875     for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; ++primitiveTypeNdx)
876         for (int spacingModeNdx = 0; spacingModeNdx < SPACINGMODE_LAST; ++spacingModeNdx)
877         {
878             group->addChild(
879                 new TessCoordTest(testCtx, (TessPrimitiveType)primitiveTypeNdx, (SpacingMode)spacingModeNdx));
880 
881             // test if TessCoord builtin has correct value in Evaluation shader when execution mode is set only in Control shader
882             group->addChild(
883                 new TessCoordTest(testCtx, (TessPrimitiveType)primitiveTypeNdx, (SpacingMode)spacingModeNdx, false));
884         }
885 
886     return group.release();
887 }
888 
889 } // namespace tessellation
890 } // namespace vkt
891