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 	vk::PipelineConstructionType constructionType;
78 	bool changeOutput;
79 	bool firstClockwise;
80 	bool secondClockwise;
81 	vk::VkCullModeFlags cullMode;
82 	tcu::Vec4 expectedFirst;
83 	tcu::Vec4 expectedSecond;
84 };
85 
86 class DynamicControlPointsTestCase : public vkt::TestCase
87 {
88 public:
89 	DynamicControlPointsTestCase(tcu::TestContext& context, const std::string& name, TestConfig config);
90 	void            initPrograms            (vk::SourceCollections& programCollection) const override;
91 	TestInstance*   createInstance          (Context& context) const override;
92 	void            checkSupport            (Context& context) const override;
93 
94 private:
95 	TestConfig m_config;
96 };
97 
98 
99 class DynamicControlPointsTestInstance : public vkt::TestInstance
100 {
101 public:
102 	DynamicControlPointsTestInstance(Context& context, TestConfig config);
103 	virtual tcu::TestStatus iterate (void);
104 private:
105 	TestConfig m_config;
106 };
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) : vkt::TestCase (context, name), m_config(config)
111 {
112 }
113 
checkSupport(Context& context) const114 void DynamicControlPointsTestCase::checkSupport(Context& context) const
115 {
116 	context.requireDeviceCoreFeature(DEVICE_CORE_FEATURE_TESSELLATION_SHADER);
117 	checkPipelineConstructionRequirements(context.getInstanceInterface(), context.getPhysicalDevice(), m_config.constructionType);
118 	const auto& eds2Features  = context.getExtendedDynamicState2FeaturesEXT();
119 	if (!eds2Features.extendedDynamicState2PatchControlPoints) {
120 		TCU_THROW(NotSupportedError, "Dynamic patch control points aren't supported");
121 	}
122 }
123 
initPrograms(vk::SourceCollections& collection) const124 void DynamicControlPointsTestCase::initPrograms(vk::SourceCollections& collection) const
125 {
126 	const std::string firstWinding = m_config.firstClockwise ? "cw" : "ccw";
127 	const std::string secondWinding = m_config.secondClockwise ? "cw" : "ccw";
128 
129 	{
130 		std::ostringstream src;
131 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
132 		<< "vec2 positions[6] = vec2[](\n"
133 		<< "		vec2(-1.0, -1.0),"
134 		<< "		vec2(-1.0, 1.0),"
135 		<< "		vec2(1.0, -1.0),"
136 		<< "		vec2(1.0, -1.0),"
137 		<< "		vec2(-1.0, 1.0),"
138 		<< "		vec2(1.0, 1.0)"
139 		<< ");\n"
140 		<< "void main() {\n"
141 		<< "		gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);\n"
142 		<< "}";
143 		collection.glslSources.add("vert") << glu::VertexSource(src.str());
144 	}
145 
146 	{
147 		std::ostringstream src;
148 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
149 		<< "layout(location = 0) out vec4 outColor;\n"
150 		<< "layout(location = 0) in vec3 fragColor;"
151 		<< "void main() {\n"
152 		<< "	outColor = vec4(fragColor, 1.0);\n"
153 		<< "}";
154 		collection.glslSources.add("frag") << glu::FragmentSource(src.str());
155 	}
156 
157 	{
158 		std::ostringstream src;
159 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
160 		<< "layout(vertices = 3) out;\n"
161 		<< "void main (void)\n"
162 		<< "{\n"
163 		<< "    gl_TessLevelInner[0] = 2.0;\n"
164 		<< "    gl_TessLevelOuter[0] = 2.0;\n"
165 		<< "    gl_TessLevelOuter[1] = 2.0;\n"
166 		<< "    gl_TessLevelOuter[2] = 2.0;\n"
167 		<< "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
168 		<< "}\n";
169 		collection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
170 	}
171 
172 	{
173 		std::ostringstream src;
174 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
175 		<< "layout(triangles, " << firstWinding << ") in;\n"
176 		<< "layout(location = 0) out vec3 fragColor;"
177 		<< "\n"
178 		<< "void main (void)\n"
179 		<< "{\n"
180 		<< "    vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
181 		<< "    vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
182 		<< "    vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
183 		<< "    gl_Position = p0 + p1 + p2;\n"
184 		<< "    fragColor = vec3(1.0, 0.0, 1.0); "
185 		<< "}\n";
186 		collection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str());
187 	}
188 
189 	{
190 		std::ostringstream src;
191 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
192 		<< "layout(vertices = " << (m_config.changeOutput ? 4 : 3) <<  ") out;\n"
193 		<< "void main (void)\n"
194 		<< "{\n"
195 		<< "    gl_TessLevelInner[0] = 2;\n"
196 		<< "    gl_TessLevelOuter[0] = 2.0;\n"
197 		<< "    gl_TessLevelOuter[1] = 2.0;\n"
198 		<< "    gl_TessLevelOuter[2] = 2.0;\n"
199 		<< "if (gl_InvocationID < 3) {"
200 		<< "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
201 		<< "} else {"
202 		<< "    gl_out[gl_InvocationID].gl_Position = vec4(1.0, 0.0, 1.0, 1.0);"
203 		<< "}"
204 		<< "}\n";
205 		collection.glslSources.add("tesc2") << glu::TessellationControlSource(src.str());
206 	}
207 
208 	{
209 		std::ostringstream src;
210 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
211 		<< "layout(triangles, " << secondWinding << ") in;\n"
212 		<< "layout(location = 0) out vec3 fragColor;"
213 		<< "\n"
214 		<< "void main (void)\n"
215 		<< "{\n"
216 		<< "    vec4 p0 = gl_TessCoord.x * gl_in[0].gl_Position;\n"
217 		<< "    vec4 p1 = gl_TessCoord.y * gl_in[1].gl_Position;\n"
218 		<< "    vec4 p2 = gl_TessCoord.z * gl_in[2].gl_Position;\n"
219 		<< "    gl_Position = p0 + p1 + p2;\n";
220 		if (m_config.changeOutput) {
221 			src << "    fragColor = vec3(gl_in[3].gl_Position.xyz);";
222 		} else {
223 			src << "    fragColor = vec3(1.0, 0.0, 1.0);";
224 		}
225 		src << "}\n";
226 		collection.glslSources.add("tese2") << glu::TessellationEvaluationSource(src.str());
227 	}
228 }
229 
createInstance(Context& context) const230 TestInstance* DynamicControlPointsTestCase::createInstance(Context& context) const
231 {
232 	return new DynamicControlPointsTestInstance(context, m_config);
233 }
234 
DynamicControlPointsTestInstance(Context& context, TestConfig config)235 DynamicControlPointsTestInstance::DynamicControlPointsTestInstance(Context& context, TestConfig config) :
236 	vkt::TestInstance (context), m_config (config) { }
237 
238 //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)239 std::unique_ptr<vk::BufferWithMemory> makeBufferForImage(const vk::DeviceInterface& vkd, const vk::VkDevice device, vk::Allocator& allocator, tcu::TextureFormat tcuFormat, vk::VkExtent3D imageExtent)
240 {
241 	const auto	outBufferSize		= static_cast<vk::VkDeviceSize>(static_cast<uint32_t>(tcu::getPixelSize(tcuFormat)) * imageExtent.width * imageExtent.height);
242 	const auto	outBufferUsage	= vk::VK_BUFFER_USAGE_TRANSFER_DST_BIT;
243 	const auto	outBufferInfo		= makeBufferCreateInfo(outBufferSize, outBufferUsage);
244 	auto outBuffer = std::unique_ptr<vk::BufferWithMemory>(new vk::BufferWithMemory(vkd, device, allocator, outBufferInfo, vk::MemoryRequirement::HostVisible));
245 
246 	return outBuffer;
247 }
248 
iterate(void)249 tcu::TestStatus DynamicControlPointsTestInstance::iterate(void)
250 {
251 	const auto& vki				= m_context.getInstanceInterface();
252 	const auto& vkd				= m_context.getDeviceInterface();
253 	const auto physicalDevice	= m_context.getPhysicalDevice();
254 	const auto  device			= m_context.getDevice();
255 	auto& alloc					= m_context.getDefaultAllocator();
256 	auto imageFormat			= vk::VK_FORMAT_R8G8B8A8_UNORM;
257 	auto imageExtent			= vk::makeExtent3D(4, 4, 1u);
258 	auto mappedFormat			= mapVkFormat(imageFormat);
259 
260 	const tcu::IVec3 imageDim	(static_cast<int>(imageExtent.width), static_cast<int>(imageExtent.height), static_cast<int>(imageExtent.depth));
261 	const tcu::IVec2 imageSize	(imageDim.x(), imageDim.y());
262 
263 	de::MovePtr<vk::ImageWithMemory>  colorAttachment;
264 
265 	vk::GraphicsPipelineWrapper pipeline1(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(), m_config.constructionType);
266 	vk::GraphicsPipelineWrapper pipeline2(vki, vkd, physicalDevice, device, m_context.getDeviceExtensions(), m_config.constructionType);
267 	const auto  qIndex      = m_context.getUniversalQueueFamilyIndex();
268 
269 	const auto  imageUsage      = static_cast<vk::VkImageUsageFlags>(vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
270 	const vk::VkImageCreateInfo imageCreateInfo =
271 	{
272 		vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,	//	VkStructureType				sType;
273 		nullptr,									//	const void*					pNext;
274 		0u,											//	VkImageCreateFlags			flags;
275 		vk::VK_IMAGE_TYPE_2D,						//	VkImageType					imageType;
276 		imageFormat,								//	VkFormat					format;
277 		imageExtent,								//	VkExtent3D					extent;
278 		1u,											//	deUint32					mipLevels;
279 		1u,											//	deUint32					arrayLayers;
280 		vk::VK_SAMPLE_COUNT_1_BIT,					//	VkSampleCountFlagBits		samples;
281 		vk::VK_IMAGE_TILING_OPTIMAL,				//	VkImageTiling				tiling;
282 		imageUsage,									//	VkImageUsageFlags			usage;
283 		vk::VK_SHARING_MODE_EXCLUSIVE,				//	VkSharingMode				sharingMode;
284 		0,											//	deUint32					queueFamilyIndexCount;
285 		nullptr,									//	const deUint32*				pQueueFamilyIndices;
286 		vk::VK_IMAGE_LAYOUT_UNDEFINED,				//	VkImageLayout				initialLayout;
287 	};
288 
289 	const auto subresourceRange			= vk::makeImageSubresourceRange(vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u);
290 	colorAttachment						= de::MovePtr<vk::ImageWithMemory>(new vk::ImageWithMemory(vkd, device, alloc, imageCreateInfo, vk::MemoryRequirement::Any));
291 	auto colorAttachmentView			= vk::makeImageView(vkd, device, colorAttachment->get(), vk::VK_IMAGE_VIEW_TYPE_2D, imageFormat, subresourceRange);
292 
293 	vk::RenderPassWrapper renderPass	(m_config.constructionType, vkd, device, imageFormat);
294 	renderPass.createFramebuffer(vkd, device, **colorAttachment, colorAttachmentView.get(), imageExtent.width, imageExtent.height);
295 
296 	//buffer to read the output image
297 	auto outBuffer = makeBufferForImage(vkd, device, alloc, mappedFormat, imageExtent);
298 	auto&		outBufferAlloc	= outBuffer->getAllocation();
299 	void*		outBufferData		= outBufferAlloc.getHostPtr();
300 
301 	const vk::VkPipelineVertexInputStateCreateInfo vertexInputState = vk::initVulkanStructure();
302 
303 	const std::vector<vk::VkViewport>	viewport_left	{ vk::makeViewport(0.0f, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f) };
304 	const std::vector<vk::VkViewport>	viewport_right  { vk::makeViewport((float)imageExtent.width / 2, 0.0f, (float)imageExtent.width / 2, (float)imageExtent.height, 0.0f, 1.0f) };
305 	const std::vector<vk::VkRect2D>		scissors_left	{ vk::makeRect2D(0.0f, 0.0f, imageExtent.width / 2, imageExtent.height) };
306 	const std::vector<vk::VkRect2D>		scissors_right	{ vk::makeRect2D(imageExtent.width / 2, 0.0, imageExtent.width / 2, imageExtent.height) };
307 	const vk::PipelineLayoutWrapper		graphicsPipelineLayout (m_config.constructionType, vkd, device);
308 
309 	auto vtxshader  = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("vert"));
310 	auto frgshader  = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("frag"));
311 	auto tscshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tesc"));
312 	auto tscshader2 = vk::ShaderWrapper(vkd, device,
313 		m_config.changeOutput ? m_context.getBinaryCollection().get("tesc2") : m_context.getBinaryCollection().get("tesc"));
314 	auto tseshader1 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese"));
315 	auto tseshader2 = vk::ShaderWrapper(vkd, device, m_context.getBinaryCollection().get("tese2"));
316 
317 	vk::VkPipelineRasterizationStateCreateInfo raster = {
318 		vk::VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,		// VkStructureType								sType
319 		DE_NULL,															// const void*									pNext
320 		0u,																	// VkPipelineRasterizationStateCreateFlags		flags
321 		VK_FALSE,															// VkBool32										depthClampEnable
322 		VK_FALSE,															// VkBool32										rasterizerDiscardEnable
323 		vk::VK_POLYGON_MODE_FILL,											// VkPolygonMode								polygonMode
324 		m_config.cullMode,													// VkCullModeFlags								cullMode
325 		vk::VK_FRONT_FACE_COUNTER_CLOCKWISE,								// VkFrontFace									frontFace
326 		VK_FALSE,															// VkBool32										depthBiasEnable
327 		0.0f,																// float										depthBiasConstantFactor
328 		0.0f,																// float										depthBiasClamp
329 		0.0f,																// float										depthBiasSlopeFactor
330 		1.0f																// float										lineWidth
331 	};
332 
333 	vk::VkDynamicState dynamicStates = vk::VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT;
334 		vk::VkPipelineDynamicStateCreateInfo dynamicInfo = {
335 		vk::VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
336 		nullptr,
337 		0,
338 		1,
339 		&dynamicStates
340 	};
341 
342 	pipeline1.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
343 		.setDynamicState(&dynamicInfo)
344 		.setDefaultRasterizationState()
345 		.setDefaultMultisampleState()
346 		.setDefaultDepthStencilState()
347 		.setDefaultColorBlendState()
348 		.setupVertexInputState(&vertexInputState)
349 		.setupPreRasterizationShaderState(
350 			viewport_left,
351 			scissors_left,
352 			graphicsPipelineLayout,
353 			*renderPass,
354 			0u,
355 			vtxshader, &raster,
356 			tscshader1, tseshader1)
357 		.setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u,
358 			frgshader, 0)
359 		.setupFragmentOutputState(*renderPass, 0u)
360 		.setMonolithicPipelineLayout(graphicsPipelineLayout).buildPipeline();
361 
362 	pipeline2.setDefaultTopology(vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
363 		.setDynamicState(&dynamicInfo)
364 		.setDefaultRasterizationState()
365 		.setDefaultMultisampleState()
366 		.setDefaultDepthStencilState()
367 		.setDefaultColorBlendState()
368 		.setupVertexInputState(&vertexInputState)
369 		.setupPreRasterizationShaderState(
370 			viewport_right,
371 			scissors_right,
372 			graphicsPipelineLayout,
373 			*renderPass,
374 			0u,
375 			vtxshader, &raster,
376 			tscshader2, tseshader2)
377 		.setupFragmentShaderState(graphicsPipelineLayout, *renderPass, 0u,
378 			frgshader, 0)
379 		.setupFragmentOutputState(*renderPass, 0u)
380 		.setMonolithicPipelineLayout(graphicsPipelineLayout).buildPipeline();
381 
382 	auto commandPool = createCommandPool(vkd, device, vk::VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, qIndex);
383 	auto commandBuffer = vk::allocateCommandBuffer(vkd, device, commandPool.get(), vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY);
384 
385 	const tcu::Vec4 clearColor(1.0f, 1.0f, 1.0f, 1.0f);
386 
387 	const vk::VkRect2D renderArea =
388 	{
389 		{ 0u, 0u },
390 		{ imageExtent.width, imageExtent.height }
391 	};
392 
393 	//render 2 triangles with each pipeline, covering the entire screen
394 	//depending on the test settings one of them might be culled
395 	vk::beginCommandBuffer(vkd, commandBuffer.get());
396 	renderPass.begin(vkd, *commandBuffer, renderArea, clearColor);
397 	vkd.cmdSetPatchControlPointsEXT(commandBuffer.get(), 3);
398 	pipeline1.bind(commandBuffer.get());
399 	vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
400 	pipeline2.bind(commandBuffer.get());
401 	vkd.cmdDraw(commandBuffer.get(), 6, 1, 0, 0);
402 	renderPass.end(vkd, commandBuffer.get());
403 	vk::copyImageToBuffer(vkd, commandBuffer.get(), colorAttachment.get()->get(), (*outBuffer).get(), imageSize);
404 	vk::endCommandBuffer(vkd, commandBuffer.get());
405 	vk::submitCommandsAndWait(vkd, device, m_context.getUniversalQueue(), commandBuffer.get());
406 
407 	invalidateAlloc(vkd, device, outBufferAlloc);
408 	tcu::ConstPixelBufferAccess outPixels(mappedFormat, imageDim, outBufferData);
409 
410 	auto expectedFirst = m_config.expectedFirst;
411 	auto expectedSecond = m_config.expectedSecond;
412 
413 	tcu::TextureLevel referenceLevel(mappedFormat, imageExtent.height, imageExtent.height);
414 	tcu::PixelBufferAccess reference = referenceLevel.getAccess();
415 	tcu::clear(getSubregion(reference, 0, 0, imageExtent.width / 2, imageExtent.height), expectedFirst);
416 	tcu::clear(getSubregion(reference, imageExtent.width / 2, 0, imageExtent.width / 2, imageExtent.height), expectedSecond);
417 
418 	if (!tcu::floatThresholdCompare(m_context.getTestContext().getLog(), "Compare", "Result comparison", reference, outPixels, tcu::Vec4(0.0), tcu::COMPARE_LOG_ON_ERROR))
419 		return tcu::TestStatus::fail("Color output does not match reference, image added to log");
420 
421 	return tcu::TestStatus::pass("Pass");
422 }
423 
createDynamicControlPointTests(tcu::TestContext& testCtx, vk::PipelineConstructionType pipelineConstructionType)424 tcu::TestCaseGroup* createDynamicControlPointTests (tcu::TestContext& testCtx, vk::PipelineConstructionType pipelineConstructionType)
425 {
426 	// Tests checking dynamic bind points and switching pipelines
427 	de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "dynamic_control_points"));
428 
429 	// test switching pipelines with dynamic control points while changing the number of tcs invocations
430 	group->addChild(new DynamicControlPointsTestCase(testCtx, "change_output", TestConfig {
431 			pipelineConstructionType,
432 			true,
433 			false,
434 			false,
435 			vk::VK_CULL_MODE_NONE,
436 			tcu::Vec4(1.0, 0.0, 1.0, 1.0),
437 			tcu::Vec4(1.0, 0.0, 1.0, 1.0),
438 	}));
439 
440 	// test switching pipelines with dynamic control points while switching winding
441 	group->addChild(new DynamicControlPointsTestCase(testCtx, "change_winding", TestConfig {
442 			pipelineConstructionType,
443 			false,
444 			true,
445 			false,
446 			vk::VK_CULL_MODE_FRONT_BIT,
447 			tcu::Vec4(1.0, 1.0, 1.0, 1.0),
448 			tcu::Vec4(1.0, 0.0, 1.0, 1.0)
449 	}));
450 
451 	// test switching pipelines with dynamic control points while switching winding and number of tcs invocations
452 	group->addChild(new DynamicControlPointsTestCase(testCtx, "change_output_winding", TestConfig {
453 			pipelineConstructionType,
454 			true,
455 			true,
456 			false,
457 			vk::VK_CULL_MODE_FRONT_BIT,
458 			tcu::Vec4(1.0, 1.0, 1.0, 1.0),
459 			tcu::Vec4(1.0, 0.0, 1.0, 1.0)
460 	}));
461 	return group.release();
462 }
463 
464 } // pipeline
465 } // vkt
466