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
71namespace vkt
72{
73namespace pipeline
74{
75
76struct 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
86class DynamicControlPointsTestCase : public vkt::TestCase
87{
88public:
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
94private:
95	TestConfig m_config;
96};
97
98
99class DynamicControlPointsTestInstance : public vkt::TestInstance
100{
101public:
102	DynamicControlPointsTestInstance(Context& context, TestConfig config);
103	virtual tcu::TestStatus iterate (void);
104private:
105	TestConfig m_config;
106};
107
108
109DynamicControlPointsTestCase::DynamicControlPointsTestCase(tcu::TestContext& context, const std::string& name,
110	TestConfig config) : vkt::TestCase (context, name), m_config(config)
111{
112}
113
114void 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
124void 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
230TestInstance* DynamicControlPointsTestCase::createInstance(Context& context) const
231{
232	return new DynamicControlPointsTestInstance(context, m_config);
233}
234
235DynamicControlPointsTestInstance::DynamicControlPointsTestInstance(Context& context, TestConfig config) :
236	vkt::TestInstance (context), m_config (config) { }
237
238//make a buffer to read an image back after rendering
239std::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
249tcu::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
424tcu::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