1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2022 The Khronos Group Inc.
6  * Copyright (c) 2022 Valve Corporation.
7  * Copyright (c) 2023 LunarG, Inc.
8  * Copyright (c) 2023 Nintendo
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  *
22  *//*
23  * \file
24  * \brief Tests involving dynamic patch control points
25 *//*--------------------------------------------------------------------*/
26 
27 #include "vktPipelineDynamicControlPoints.hpp"
28 #include "vktTestCase.hpp"
29 #include "vkPrograms.hpp"
30 #include "vkRefUtil.hpp"
31 #include "vktTestCaseUtil.hpp"
32 #include "vkImageWithMemory.hpp"
33 
34 #include "vktPipelineImageUtil.hpp"
35 #include "vktTestCase.hpp"
36 
37 #include "vkDefs.hpp"
38 #include "vkTypeUtil.hpp"
39 #include "vkQueryUtil.hpp"
40 #include "vkObjUtil.hpp"
41 #include "vkBufferWithMemory.hpp"
42 #include "vkImageWithMemory.hpp"
43 #include "vkBuilderUtil.hpp"
44 #include "vkCmdUtil.hpp"
45 #include "vkImageUtil.hpp"
46 
47 #include "tcuVector.hpp"
48 #include "tcuMaybe.hpp"
49 #include "tcuImageCompare.hpp"
50 #include "tcuDefs.hpp"
51 #include "tcuTextureUtil.hpp"
52 #include "tcuTestLog.hpp"
53 #include "tcuVectorUtil.hpp"
54 #include "tcuStringTemplate.hpp"
55 
56 #include "deUniquePtr.hpp"
57 #include "deStringUtil.hpp"
58 
59 #include <vector>
60 #include <sstream>
61 #include <algorithm>
62 #include <utility>
63 #include <iterator>
64 #include <string>
65 #include <limits>
66 #include <memory>
67 #include <functional>
68 #include <cstddef>
69 #include <set>
70 
71 namespace vkt
72 {
73 namespace pipeline
74 {
75 
76 struct TestConfig
77 {
78     vk::PipelineConstructionType constructionType;
79     bool changeOutput;
80     bool firstClockwise;
81     bool secondClockwise;
82     vk::VkCullModeFlags cullMode;
83     tcu::Vec4 expectedFirst;
84     tcu::Vec4 expectedSecond;
85 };
86 
87 class DynamicControlPointsTestCase : public vkt::TestCase
88 {
89 public:
90     DynamicControlPointsTestCase(tcu::TestContext &context, const std::string &name, TestConfig config);
91     void initPrograms(vk::SourceCollections &programCollection) const override;
92     TestInstance *createInstance(Context &context) const override;
93     void checkSupport(Context &context) const override;
94 
95 private:
96     TestConfig m_config;
97 };
98 
99 class DynamicControlPointsTestInstance : public vkt::TestInstance
100 {
101 public:
102     DynamicControlPointsTestInstance(Context &context, TestConfig config);
103     virtual tcu::TestStatus iterate(void);
104 
105 private:
106     TestConfig m_config;
107 };
108 
DynamicControlPointsTestCase(tcu::TestContext & context,const std::string & name,TestConfig config)109 DynamicControlPointsTestCase::DynamicControlPointsTestCase(tcu::TestContext &context, const std::string &name,
110                                                            TestConfig config)
111     : vkt::TestCase(context, name)
112     , m_config(config)
113 {
114 }
115 
checkSupport(Context & context) const116 void DynamicControlPointsTestCase::checkSupport(Context &context) const
117 {
118     context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_TESSELLATION_SHADER);
119     checkPipelineConstructionRequirements(context.getInstanceInterface(), context.getPhysicalDevice(),
120                                           m_config.constructionType);
121     const auto &eds2Features = context.getExtendedDynamicState2FeaturesEXT();
122     if (!eds2Features.extendedDynamicState2PatchControlPoints)
123     {
124         TCU_THROW(NotSupportedError, "Dynamic patch control points aren't supported");
125     }
126 }
127 
initPrograms(vk::SourceCollections & collection) const128 void DynamicControlPointsTestCase::initPrograms(vk::SourceCollections &collection) const
129 {
130     const std::string firstWinding  = m_config.firstClockwise ? "cw" : "ccw";
131     const std::string secondWinding = m_config.secondClockwise ? "cw" : "ccw";
132 
133     {
134         std::ostringstream src;
135         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
136             << "vec2 positions[6] = vec2[](\n"
137             << "        vec2(-1.0, -1.0),"
138             << "        vec2(-1.0, 1.0),"
139             << "        vec2(1.0, -1.0),"
140             << "        vec2(1.0, -1.0),"
141             << "        vec2(-1.0, 1.0),"
142             << "        vec2(1.0, 1.0)"
143             << ");\n"
144             << "void main() {\n"
145             << "        gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);\n"
146             << "}";
147         collection.glslSources.add("vert") << glu::VertexSource(src.str());
148     }
149 
150     {
151         std::ostringstream src;
152         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
153             << "layout(location = 0) out vec4 outColor;\n"
154             << "layout(location = 0) in vec3 fragColor;"
155             << "void main() {\n"
156             << "    outColor = vec4(fragColor, 1.0);\n"
157             << "}";
158         collection.glslSources.add("frag") << glu::FragmentSource(src.str());
159     }
160 
161     {
162         std::ostringstream src;
163         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
164             << "layout(vertices = 3) out;\n"
165             << "void main (void)\n"
166             << "{\n"
167             << "    gl_TessLevelInner[0] = 2.0;\n"
168             << "    gl_TessLevelOuter[0] = 2.0;\n"
169             << "    gl_TessLevelOuter[1] = 2.0;\n"
170             << "    gl_TessLevelOuter[2] = 2.0;\n"
171             << "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
172             << "}\n";
173         collection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
174     }
175 
176     {
177         std::ostringstream src;
178         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
179             << "layout(triangles, " << firstWinding << ") in;\n"
180             << "layout(location = 0) out vec3 fragColor;"
181             << "\n"
182             << "void main (void)\n"
183             << "{\n"
184             << "    vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
185             << "    vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
186             << "    vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
187             << "    gl_Position = p0 + p1 + p2;\n"
188             << "    fragColor = vec3(1.0, 0.0, 1.0); "
189             << "}\n";
190         collection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str());
191     }
192 
193     {
194         std::ostringstream src;
195         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
196             << "layout(vertices = " << (m_config.changeOutput ? 4 : 3) << ") out;\n"
197             << "void main (void)\n"
198             << "{\n"
199             << "    gl_TessLevelInner[0] = 2;\n"
200             << "    gl_TessLevelOuter[0] = 2.0;\n"
201             << "    gl_TessLevelOuter[1] = 2.0;\n"
202             << "    gl_TessLevelOuter[2] = 2.0;\n"
203             << "if (gl_InvocationID < 3) {"
204             << "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
205             << "} else {"
206             << "    gl_out[gl_InvocationID].gl_Position = vec4(1.0, 0.0, 1.0, 1.0);"
207             << "}"
208             << "}\n";
209         collection.glslSources.add("tesc2") << glu::TessellationControlSource(src.str());
210     }
211 
212     {
213         std::ostringstream src;
214         src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
215             << "layout(triangles, " << secondWinding << ") in;\n"
216             << "layout(location = 0) out vec3 fragColor;"
217             << "\n"
218             << "void main (void)\n"
219             << "{\n"
220             << "    vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
221             << "    vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
222             << "    vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
223             << "    gl_Position = p0 + p1 + p2;\n";
224         if (m_config.changeOutput)
225         {
226             src << "    fragColor = vec3(gl_in[3].gl_Position.xyz);";
227         }
228         else
229         {
230             src << "    fragColor = vec3(1.0, 0.0, 1.0);";
231         }
232         src << "}\n";
233         collection.glslSources.add("tese2") << glu::TessellationEvaluationSource(src.str());
234     }
235 }
236 
createInstance(Context & context) const237 TestInstance *DynamicControlPointsTestCase::createInstance(Context &context) const
238 {
239     return new DynamicControlPointsTestInstance(context, m_config);
240 }
241 
DynamicControlPointsTestInstance(Context & context,TestConfig config)242 DynamicControlPointsTestInstance::DynamicControlPointsTestInstance(Context &context, TestConfig config)
243     : vkt::TestInstance(context)
244     , m_config(config)
245 {
246 }
247 
248 //make a buffer to read an image back after rendering
makeBufferForImage(const vk::DeviceInterface & vkd,const vk::VkDevice device,vk::Allocator & allocator,tcu::TextureFormat tcuFormat,vk::VkExtent3D imageExtent)249 std::unique_ptr<vk::BufferWithMemory> makeBufferForImage(const vk::DeviceInterface &vkd, const vk::VkDevice device,
250                                                          vk::Allocator &allocator, tcu::TextureFormat tcuFormat,
251                                                          vk::VkExtent3D imageExtent)
252 {
253     const auto outBufferSize  = static_cast<vk::VkDeviceSize>(static_cast<uint32_t>(tcu::getPixelSize(tcuFormat)) *
254                                                              imageExtent.width * imageExtent.height);
255     const auto outBufferUsage = vk::VK_BUFFER_USAGE_TRANSFER_DST_BIT;
256     const auto outBufferInfo  = makeBufferCreateInfo(outBufferSize, outBufferUsage);
257     auto outBuffer            = std::unique_ptr<vk::BufferWithMemory>(
258         new vk::BufferWithMemory(vkd, device, allocator, outBufferInfo, vk::MemoryRequirement::HostVisible));
259 
260     return outBuffer;
261 }
262 
iterate(void)263 tcu::TestStatus DynamicControlPointsTestInstance::iterate(void)
264 {
265     const auto &vki           = m_context.getInstanceInterface();
266     const auto &vkd           = m_context.getDeviceInterface();
267     const auto physicalDevice = m_context.getPhysicalDevice();
268     const auto device         = m_context.getDevice();
269     auto &alloc               = m_context.getDefaultAllocator();
270     auto imageFormat          = vk::VK_FORMAT_R8G8B8A8_UNORM;
271     auto imageExtent          = vk::makeExtent3D(4, 4, 1u);
272     auto mappedFormat         = mapVkFormat(imageFormat);
273 
274     const tcu::IVec3 imageDim(static_cast<int>(imageExtent.width), static_cast<int>(imageExtent.height),
275                               static_cast<int>(imageExtent.depth));
276     const tcu::IVec2 imageSize(imageDim.x(), imageDim.y());
277 
278     de::MovePtr<vk::ImageWithMemory> colorAttachment;
279 
280     vk::GraphicsPipelineWrapper pipeline1(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(),
281                                           m_config.constructionType);
282     vk::GraphicsPipelineWrapper pipeline2(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(),
283                                           m_config.constructionType);
284     const auto qIndex = m_context.getUniversalQueueFamilyIndex();
285 
286     const auto imageUsage = static_cast<vk::VkImageUsageFlags>(vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
287                                                                vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
288     const vk::VkImageCreateInfo imageCreateInfo = {
289         vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType;
290         nullptr,                                 // const void* pNext;
291         0u,                                      // VkImageCreateFlags flags;
292         vk::VK_IMAGE_TYPE_2D,                    // VkImageType imageType;
293         imageFormat,                             // VkFormat format;
294         imageExtent,                             // VkExtent3D extent;
295         1u,                                      // uint32_t mipLevels;
296         1u,                                      // uint32_t arrayLayers;
297         vk::VK_SAMPLE_COUNT_1_BIT,               // VkSampleCountFlagBits samples;
298         vk::VK_IMAGE_TILING_OPTIMAL,             // VkImageTiling tiling;
299         imageUsage,                              // VkImageUsageFlags usage;
300         vk::VK_SHARING_MODE_EXCLUSIVE,           // VkSharingMode sharingMode;
301         0,                                       // uint32_t queueFamilyIndexCount;
302         nullptr,                                 // const uint32_t* pQueueFamilyIndices;
303         vk::VK_IMAGE_LAYOUT_UNDEFINED,           // VkImageLayout initialLayout;
304     };
305 
306     const auto subresourceRange = vk::makeImageSubresourceRange(vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u);
307     colorAttachment             = de::MovePtr<vk::ImageWithMemory>(
308         new vk::ImageWithMemory(vkd, device, alloc, imageCreateInfo, vk::MemoryRequirement::Any));
309     auto colorAttachmentView = vk::makeImageView(vkd, device, colorAttachment->get(), vk::VK_IMAGE_VIEW_TYPE_2D,
310                                                  imageFormat, subresourceRange);
311 
312     vk::RenderPassWrapper renderPass(m_config.constructionType, vkd, device, imageFormat);
313     renderPass.createFramebuffer(vkd, device, **colorAttachment, colorAttachmentView.get(), imageExtent.width,
314                                  imageExtent.height);
315 
316     //buffer to read the output image
317     auto outBuffer       = makeBufferForImage(vkd, device, alloc, mappedFormat, imageExtent);
318     auto &outBufferAlloc = outBuffer->getAllocation();
319     void *outBufferData  = outBufferAlloc.getHostPtr();
320 
321     const vk::VkPipelineVertexInputStateCreateInfo vertexInputState = vk::initVulkanStructure();
322 
323     const std::vector<vk::VkViewport> viewport_left{
324         vk::makeViewport(0.0f, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f)};
325     const std::vector<vk::VkViewport> viewport_right{vk::makeViewport(
326         (float)imageExtent.width / 2, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f)};
327     const std::vector<vk::VkRect2D> scissors_left{vk::makeRect2D(0, 0, imageExtent.width / 2, imageExtent.height)};
328     const std::vector<vk::VkRect2D> scissors_right{
329         vk::makeRect2D(imageExtent.width / 2, 0, imageExtent.width / 2, imageExtent.height)};
330     const vk::PipelineLayoutWrapper graphicsPipelineLayout(m_config.constructionType, vkd, device);
331 
332     auto vtxshader  = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("vert"));
333     auto frgshader  = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("frag"));
334     auto tscshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tesc"));
335     auto tscshader2 = vk::ShaderWrapper(vkd, device,
336                                         m_config.changeOutput ? m_context.getBinaryCollection().get("tesc2") :
337                                                                 m_context.getBinaryCollection().get("tesc"));
338     auto tseshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese"));
339     auto tseshader2 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese2"));
340 
341     vk::VkPipelineRasterizationStateCreateInfo raster = {
342         vk::VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType                                sType
343         DE_NULL,                             // const void*                                    pNext
344         0u,                                  // VkPipelineRasterizationStateCreateFlags        flags
345         VK_FALSE,                            // VkBool32                                        depthClampEnable
346         VK_FALSE,                            // VkBool32                                        rasterizerDiscardEnable
347         vk::VK_POLYGON_MODE_FILL,            // VkPolygonMode                                polygonMode
348         m_config.cullMode,                   // VkCullModeFlags                                cullMode
349         vk::VK_FRONT_FACE_COUNTER_CLOCKWISE, // VkFrontFace                                    frontFace
350         VK_FALSE,                            // VkBool32                                        depthBiasEnable
351         0.0f,                                // float                                        depthBiasConstantFactor
352         0.0f,                                // float                                        depthBiasClamp
353         0.0f,                                // float                                        depthBiasSlopeFactor
354         1.0f                                 // float                                        lineWidth
355     };
356 
357     vk::VkDynamicState dynamicStates                 = vk::VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT;
358     vk::VkPipelineDynamicStateCreateInfo dynamicInfo = {vk::VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
359                                                         nullptr, 0, 1, &dynamicStates};
360 
361     pipeline1.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
362         .setDynamicState(&dynamicInfo)
363         .setDefaultRasterizationState()
364         .setDefaultMultisampleState()
365         .setDefaultDepthStencilState()
366         .setDefaultColorBlendState()
367         .setupVertexInputState(&vertexInputState)
368         .setupPreRasterizationShaderState(viewport_left, scissors_left, graphicsPipelineLayout, *renderPass, 0u,
369                                           vtxshader, &raster, tscshader1, tseshader1)
370         .setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u, frgshader, 0)
371         .setupFragmentOutputState(*renderPass, 0u)
372         .setMonolithicPipelineLayout(graphicsPipelineLayout)
373         .buildPipeline();
374 
375     pipeline2.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
376         .setDynamicState(&dynamicInfo)
377         .setDefaultRasterizationState()
378         .setDefaultMultisampleState()
379         .setDefaultDepthStencilState()
380         .setDefaultColorBlendState()
381         .setupVertexInputState(&vertexInputState)
382         .setupPreRasterizationShaderState(viewport_right, scissors_right, graphicsPipelineLayout, *renderPass, 0u,
383                                           vtxshader, &raster, tscshader2, tseshader2)
384         .setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u, frgshader, 0)
385         .setupFragmentOutputState(*renderPass, 0u)
386         .setMonolithicPipelineLayout(graphicsPipelineLayout)
387         .buildPipeline();
388 
389     auto commandPool   = createCommandPool(vkd, device, vk::VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, qIndex);
390     auto commandBuffer = vk::allocateCommandBuffer(vkd, device, commandPool.get(), vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY);
391 
392     const tcu::Vec4 clearColor(1.0f, 1.0f, 1.0f, 1.0f);
393 
394     const vk::VkRect2D renderArea = {{0u, 0u}, {imageExtent.width, imageExtent.height}};
395 
396     //render 2 triangles with each pipeline, covering the entire screen
397     //depending on the test settings one of them might be culled
398     vk::beginCommandBuffer(vkd, commandBuffer.get());
399     renderPass.begin(vkd, *commandBuffer, renderArea, clearColor);
400     vkd.cmdSetPatchControlPointsEXT(commandBuffer.get(), 3);
401     pipeline1.bind(commandBuffer.get());
402     vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
403     pipeline2.bind(commandBuffer.get());
404     vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
405     renderPass.end(vkd, commandBuffer.get());
406     vk::copyImageToBuffer(vkd, commandBuffer.get(), colorAttachment.get()->get(), (*outBuffer).get(), imageSize);
407     vk::endCommandBuffer(vkd, commandBuffer.get());
408     vk::submitCommandsAndWait(vkd, device, m_context.getUniversalQueue(), commandBuffer.get());
409 
410     invalidateAlloc(vkd, device, outBufferAlloc);
411     tcu::ConstPixelBufferAccess outPixels(mappedFormat, imageDim, outBufferData);
412 
413     auto expectedFirst  = m_config.expectedFirst;
414     auto expectedSecond = m_config.expectedSecond;
415 
416     tcu::TextureLevel referenceLevel(mappedFormat, imageExtent.height, imageExtent.height);
417     tcu::PixelBufferAccess reference = referenceLevel.getAccess();
418     tcu::clear(getSubregion(reference, 0, 0, imageExtent.width / 2, imageExtent.height), expectedFirst);
419     tcu::clear(getSubregion(reference, imageExtent.width / 2, 0, imageExtent.width / 2, imageExtent.height),
420                expectedSecond);
421 
422     if (!tcu::floatThresholdCompare(m_context.getTestContext().getLog(), "Compare", "Result comparison", reference,
423                                     outPixels, tcu::Vec4(0.0), tcu::COMPARE_LOG_ON_ERROR))
424         return tcu::TestStatus::fail("Color output does not match reference, image added to log");
425 
426     return tcu::TestStatus::pass("Pass");
427 }
428 
createDynamicControlPointTests(tcu::TestContext & testCtx,vk::PipelineConstructionType pipelineConstructionType)429 tcu::TestCaseGroup *createDynamicControlPointTests(tcu::TestContext &testCtx,
430                                                    vk::PipelineConstructionType pipelineConstructionType)
431 {
432     // Tests checking dynamic bind points and switching pipelines
433     de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "dynamic_control_points"));
434 
435     // test switching pipelines with dynamic control points while changing the number of tcs invocations
436     group->addChild(new DynamicControlPointsTestCase(testCtx, "change_output",
437                                                      TestConfig{
438                                                          pipelineConstructionType,
439                                                          true,
440                                                          false,
441                                                          false,
442                                                          vk::VK_CULL_MODE_NONE,
443                                                          tcu::Vec4(1.0, 0.0, 1.0, 1.0),
444                                                          tcu::Vec4(1.0, 0.0, 1.0, 1.0),
445                                                      }));
446 
447     // test switching pipelines with dynamic control points while switching winding
448     group->addChild(new DynamicControlPointsTestCase(
449         testCtx, "change_winding",
450         TestConfig{pipelineConstructionType, false, true, false, vk::VK_CULL_MODE_FRONT_BIT,
451                    tcu::Vec4(1.0, 1.0, 1.0, 1.0), tcu::Vec4(1.0, 0.0, 1.0, 1.0)}));
452 
453     // test switching pipelines with dynamic control points while switching winding and number of tcs invocations
454     group->addChild(new DynamicControlPointsTestCase(
455         testCtx, "change_output_winding",
456         TestConfig{pipelineConstructionType, true, true, false, vk::VK_CULL_MODE_FRONT_BIT,
457                    tcu::Vec4(1.0, 1.0, 1.0, 1.0), tcu::Vec4(1.0, 0.0, 1.0, 1.0)}));
458     return group.release();
459 }
460 
461 } // namespace pipeline
462 } // namespace vkt
463