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 Primitive Discard Tests
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktTessellationPrimitiveDiscardTests.hpp"
26 #include "vktTestCaseUtil.hpp"
27 #include "vktTessellationUtil.hpp"
28 
29 #include "tcuTestLog.hpp"
30 
31 #include "vkDefs.hpp"
32 #include "vkQueryUtil.hpp"
33 #include "vkBuilderUtil.hpp"
34 #include "vkImageUtil.hpp"
35 #include "vkTypeUtil.hpp"
36 #include "vkCmdUtil.hpp"
37 #include "vkObjUtil.hpp"
38 #include "vkBarrierUtil.hpp"
39 #include "vkBufferWithMemory.hpp"
40 #include "vkImageWithMemory.hpp"
41 
42 #include "deUniquePtr.hpp"
43 #include "deStringUtil.hpp"
44 
45 #include <string>
46 #include <vector>
47 
48 namespace vkt
49 {
50 namespace tessellation
51 {
52 
53 using namespace vk;
54 
55 namespace
56 {
57 
58 struct CaseDefinition
59 {
60 	TessPrimitiveType	primitiveType;
61 	SpacingMode			spacingMode;
62 	Winding				winding;
63 	bool				usePointMode;
64 	bool				useLessThanOneInnerLevels;
65 };
66 
lessThanOneInnerLevelsDefined(const CaseDefinition& caseDef)67 bool lessThanOneInnerLevelsDefined (const CaseDefinition& caseDef)
68 {
69 	// From Vulkan API specification:
70 	// >> When tessellating triangles or quads (with/without point mode) with fractional odd spacing, the tessellator
71 	// >> ***may*** produce interior vertices that are positioned on the edge of the patch if an inner
72 	// >> tessellation level is less than or equal to one.
73 	return !((caseDef.primitiveType == vkt::tessellation::TESSPRIMITIVETYPE_QUADS      ||
74 			  caseDef.primitiveType == vkt::tessellation::TESSPRIMITIVETYPE_TRIANGLES) &&
75 			 caseDef.spacingMode == vkt::tessellation::SPACINGMODE_FRACTIONAL_ODD);
76 }
77 
intPow(int base, int exp)78 int intPow (int base, int exp)
79 {
80 	DE_ASSERT(exp >= 0);
81 	if (exp == 0)
82 		return 1;
83 	else
84 	{
85 		const int sub = intPow(base, exp/2);
86 		if (exp % 2 == 0)
87 			return sub*sub;
88 		else
89 			return sub*sub*base;
90 	}
91 }
92 
genAttributes(bool useLessThanOneInnerLevels)93 std::vector<float> genAttributes (bool useLessThanOneInnerLevels)
94 {
95 	// Generate input attributes (tessellation levels, and position scale and
96 	// offset) for a number of primitives. Each primitive has a different
97 	// combination of tessellatio levels; each level is either a valid
98 	// value or an "invalid" value (negative or zero, chosen from
99 	// invalidTessLevelChoices).
100 
101 	// \note The attributes are generated in such an order that all of the
102 	//		 valid attribute tuples come before the first invalid one both
103 	//		 in the result vector, and when scanning the resulting 2d grid
104 	//		 of primitives is scanned in y-major order. This makes
105 	//		 verification somewhat simpler.
106 
107 	static const float	baseTessLevels[6]			= { 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f };
108 	static const float	invalidTessLevelChoices[]	= { -0.42f, 0.0f };
109 	const int			numChoices					= 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices);
110 	float				choices[6][numChoices];
111 	std::vector<float>	result;
112 
113 	for (int levelNdx = 0; levelNdx < 6; levelNdx++)
114 		for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++)
115 			choices[levelNdx][choiceNdx] = (choiceNdx == 0 || !useLessThanOneInnerLevels) ? baseTessLevels[levelNdx] : invalidTessLevelChoices[choiceNdx-1];
116 
117 	{
118 		const int	numCols	= intPow(numChoices, 6/2); // sqrt(numChoices**6) == sqrt(number of primitives)
119 		const int	numRows	= numCols;
120 		int			index	= 0;
121 		int			i[6];
122 		// We could do this with some generic combination-generation function, but meh, it's not that bad.
123 		for (i[2] = 0; i[2] < numChoices; i[2]++) // First  outer
124 		for (i[3] = 0; i[3] < numChoices; i[3]++) // Second outer
125 		for (i[4] = 0; i[4] < numChoices; i[4]++) // Third  outer
126 		for (i[5] = 0; i[5] < numChoices; i[5]++) // Fourth outer
127 		for (i[0] = 0; i[0] < numChoices; i[0]++) // First  inner
128 		for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner
129 		{
130 			for (int j = 0; j < 6; j++)
131 				result.push_back(choices[j][i[j]]);
132 
133 			{
134 				const int col = index % numCols;
135 				const int row = index / numCols;
136 				// Position scale.
137 				result.push_back((float)2.0f / (float)numCols);
138 				result.push_back((float)2.0f / (float)numRows);
139 				// Position offset.
140 				result.push_back((float)col / (float)numCols * 2.0f - 1.0f);
141 				result.push_back((float)row / (float)numRows * 2.0f - 1.0f);
142 			}
143 
144 			index++;
145 		}
146 	}
147 
148 	return result;
149 }
150 
151 //! Check that white pixels are found around every non-discarded patch,
152 //! and that only black pixels are found after the last non-discarded patch.
153 //! Returns true on successful comparison.
verifyResultImage(tcu::TestLog& log, const int numPrimitives, const int numAttribsPerPrimitive, const TessPrimitiveType primitiveType, const std::vector<float>& attributes, const tcu::ConstPixelBufferAccess pixels)154 bool verifyResultImage (tcu::TestLog&						log,
155 						const int							numPrimitives,
156 						const int							numAttribsPerPrimitive,
157 						const TessPrimitiveType				primitiveType,
158 						const std::vector<float>&			attributes,
159 						const tcu::ConstPixelBufferAccess	pixels)
160 {
161 	const tcu::Vec4 black(0.0f, 0.0f, 0.0f, 1.0f);
162 	const tcu::Vec4 white(1.0f, 1.0f, 1.0f, 1.0f);
163 
164 	int lastWhitePixelRow								= 0;
165 	int secondToLastWhitePixelRow						= 0;
166 	int	lastWhitePixelColumnOnSecondToLastWhitePixelRow	= 0;
167 
168 	for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx)
169 	{
170 		const float* const	attr			= &attributes[numAttribsPerPrimitive*patchNdx];
171 		const bool			validLevels		= !isPatchDiscarded(primitiveType, &attr[2]);
172 
173 		if (validLevels)
174 		{
175 			// Not a discarded patch; check that at least one white pixel is found in its area.
176 
177 			const float* const	scale		= &attr[6];
178 			const float* const	offset		= &attr[8];
179 			const int			x0			= (int)((			offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) - 1;
180 			const int			x1			= (int)((scale[0] + offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) + 1;
181 			const int			y0			= (int)((			offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) - 1;
182 			const int			y1			= (int)((scale[1] + offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) + 1;
183 			bool				pixelOk		= false;
184 
185 			if (y1 > lastWhitePixelRow)
186 			{
187 				secondToLastWhitePixelRow	= lastWhitePixelRow;
188 				lastWhitePixelRow			= y1;
189 			}
190 			lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1;
191 
192 			for (int y = y0; y <= y1 && !pixelOk; y++)
193 			for (int x = x0; x <= x1 && !pixelOk; x++)
194 			{
195 				if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight()))
196 					continue;
197 
198 				if (pixels.getPixel(x, y) == white)
199 					pixelOk = true;
200 			}
201 
202 			if (!pixelOk)
203 			{
204 				log << tcu::TestLog::Message
205 					<< "Failure: expected at least one white pixel in the rectangle "
206 					<< "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]"
207 					<< tcu::TestLog::EndMessage
208 					<< tcu::TestLog::Message
209 					<< "Note: the rectangle approximately corresponds to the patch with these tessellation levels: "
210 					<< getTessellationLevelsString(&attr[0], &attr[1])
211 					<< tcu::TestLog::EndMessage;
212 
213 				return false;
214 			}
215 		}
216 		else
217 		{
218 			// First discarded primitive patch; the remaining are guaranteed to be discarded ones as well.
219 
220 			for (int y = 0; y < pixels.getHeight(); y++)
221 			for (int x = 0; x < pixels.getWidth(); x++)
222 			{
223 				if (y > lastWhitePixelRow || (y > secondToLastWhitePixelRow && x > lastWhitePixelColumnOnSecondToLastWhitePixelRow))
224 				{
225 					if (pixels.getPixel(x, y) != black)
226 					{
227 						log << tcu::TestLog::Message
228 							<< "Failure: expected all pixels to be black in the area "
229 							<< (lastWhitePixelColumnOnSecondToLastWhitePixelRow < pixels.getWidth()-1
230 								? std::string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " + de::toString(secondToLastWhitePixelRow)
231 												+ " && x > " + de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) + ")"
232 								: std::string() + "y > " + de::toString(lastWhitePixelRow))
233 							<< " (they all correspond to patches that should be discarded)"
234 							<< tcu::TestLog::EndMessage
235 							<< tcu::TestLog::Message << "Note: pixel " << tcu::IVec2(x, y) << " isn't black" << tcu::TestLog::EndMessage;
236 
237 						return false;
238 					}
239 				}
240 			}
241 			break;
242 		}
243 	}
244 	return true;
245 }
246 
247 int expectedVertexCount (const int					numPrimitives,
248 						 const int					numAttribsPerPrimitive,
249 						 const TessPrimitiveType	primitiveType,
250 						 const SpacingMode			spacingMode,
251 						 const std::vector<float>&	attributes)
252 {
253 	int count = 0;
254 	for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx)
255 		count += referenceVertexCount(primitiveType, spacingMode, true, &attributes[numAttribsPerPrimitive*patchNdx+0], &attributes[numAttribsPerPrimitive*patchNdx+2]);
256 	return count;
257 }
258 
259 void initPrograms (vk::SourceCollections& programCollection, const CaseDefinition caseDef)
260 {
261 	// Vertex shader
262 	{
263 		std::ostringstream src;
264 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
265 			<< "\n"
266 			<< "layout(location = 0) in  highp float in_v_attr;\n"
267 			<< "layout(location = 0) out highp float in_tc_attr;\n"
268 			<< "\n"
269 			<< "void main (void)\n"
270 			<< "{\n"
271 			<< "    in_tc_attr = in_v_attr;\n"
272 			<< "}\n";
273 
274 		programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
275 	}
276 
277 	// Tessellation control shader
278 	{
279 		std::ostringstream src;
280 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
281 			<< "#extension GL_EXT_tessellation_shader : require\n"
282 			<< "\n"
283 			<< "layout(vertices = 1) out;\n"
284 			<< "\n"
285 			<< "layout(location = 0) in highp float in_tc_attr[];\n"
286 			<< "\n"
287 			<< "layout(location = 0) patch out highp vec2 in_te_positionScale;\n"
288 			<< "layout(location = 1) patch out highp vec2 in_te_positionOffset;\n"
289 			<< "\n"
290 			<< "void main (void)\n"
291 			<< "{\n"
292 			<< "    in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
293 			<< "    in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
294 			<< "\n"
295 			<< "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
296 			<< "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
297 			<< "\n"
298 			<< "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
299 			<< "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
300 			<< "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
301 			<< "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
302 			<< "}\n";
303 
304 		programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str());
305 	}
306 
307 	// Tessellation evaluation shader
308 	// When using point mode we need two variants of the shader, one for the case where
309 	// shaderTessellationAndGeometryPointSize is enabled (in which the tessellation evaluation
310 	// shader needs to write to gl_PointSize for it to be defined) and one for the case where
311 	// it is disabled, in which we can't write to gl_PointSize but it has a default value
312 	// of 1.0
313 	{
314 		const deUint32	numVariants			= caseDef.usePointMode ? 2 : 1;
315 		for (deUint32 variant = 0; variant < numVariants; variant++)
316 		{
317 			const bool	needPointSizeWrite	= caseDef.usePointMode && variant == 1;
318 
319 			std::ostringstream src;
320 			src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
321 				<< "#extension GL_EXT_tessellation_shader : require\n";
322 			if (needPointSizeWrite)
323 			{
324 				src << "#extension GL_EXT_tessellation_point_size : require\n";
325 			}
326 			src << "\n"
327 				<< "layout(" << getTessPrimitiveTypeShaderName(caseDef.primitiveType) << ", "
328 							 << getSpacingModeShaderName(caseDef.spacingMode) << ", "
329 							 << getWindingShaderName(caseDef.winding)
330 							 << (caseDef.usePointMode ? ", point_mode" : "") << ") in;\n"
331 				<< "\n"
332 				<< "layout(location = 0) patch in highp vec2 in_te_positionScale;\n"
333 				<< "layout(location = 1) patch in highp vec2 in_te_positionOffset;\n"
334 				<< "\n"
335 				<< "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n"
336 				<< "    int  numInvocations;\n"
337 				<< "} sb_out;\n"
338 				<< "\n"
339 				<< "void main (void)\n"
340 				<< "{\n"
341 				<< "    atomicAdd(sb_out.numInvocations, 1);\n"
342 				<< "\n"
343 				<< "    gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n";
344 			if (needPointSizeWrite)
345 			{
346 				src << "    gl_PointSize = 1.0;\n";
347 			}
348 			src << "}\n";
349 
350 			programCollection.glslSources.add(needPointSizeWrite ? "tese_psw" : "tese") << glu::TessellationEvaluationSource(src.str());
351 		}
352 	}
353 
354 	// Fragment shader
355 	{
356 		std::ostringstream src;
357 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n"
358 			<< "\n"
359 			<< "layout(location = 0) out mediump vec4 o_color;\n"
360 			<< "\n"
361 			<< "void main (void)\n"
362 			<< "{\n"
363 			<< "    o_color = vec4(1.0);\n"
364 			<< "}\n";
365 
366 		programCollection.glslSources.add("frag") << glu::FragmentSource(src.str());
367 	}
368 }
369 
370 /*--------------------------------------------------------------------*//*!
371  * \brief Test that patch is discarded if relevant outer level <= 0.0
372  *
373  * Draws patches with different combinations of tessellation levels,
374  * varying which levels are negative. Verifies by checking that white
375  * pixels exist inside the area of valid primitives, and only black pixels
376  * exist inside the area of discarded primitives. An additional sanity
377  * test is done, checking that the number of primitives written by shader is
378  * correct.
379  *//*--------------------------------------------------------------------*/
380 tcu::TestStatus test (Context& context, const CaseDefinition caseDef)
381 {
382 	requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(), FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS);
383 
384 	const DeviceInterface&	vk					= context.getDeviceInterface();
385 	const VkDevice			device				= context.getDevice();
386 	const VkQueue			queue				= context.getUniversalQueue();
387 	const deUint32			queueFamilyIndex	= context.getUniversalQueueFamilyIndex();
388 	Allocator&				allocator			= context.getDefaultAllocator();
389 
390 	const std::vector<float>	attributes				= genAttributes(caseDef.useLessThanOneInnerLevels);
391 	const int					numAttribsPerPrimitive	= 6 + 2 + 2; // Tess levels, scale, offset.
392 	const int					numPrimitives			= static_cast<int>(attributes.size() / numAttribsPerPrimitive);
393 	const int					numExpectedVertices		= expectedVertexCount(numPrimitives, numAttribsPerPrimitive, caseDef.primitiveType, caseDef.spacingMode, attributes);
394 
395 	// Check the convenience assertion that all discarded patches come after the last non-discarded patch.
396 	{
397 		bool discardedPatchEncountered = false;
398 		for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx)
399 		{
400 			const bool discard = isPatchDiscarded(caseDef.primitiveType, &attributes[numAttribsPerPrimitive*patchNdx + 2]);
401 			DE_ASSERT(discard || !discardedPatchEncountered);
402 			discardedPatchEncountered = discard;
403 		}
404 		DE_UNREF(discardedPatchEncountered);
405 	}
406 
407 	// Vertex input attributes buffer
408 
409 	const VkFormat			vertexFormat		= VK_FORMAT_R32_SFLOAT;
410 	const deUint32			vertexStride		= tcu::getPixelSize(mapVkFormat(vertexFormat));
411 	const VkDeviceSize		vertexDataSizeBytes	= sizeInBytes(attributes);
412 	const BufferWithMemory	vertexBuffer		(vk, device, allocator, makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT), MemoryRequirement::HostVisible);
413 
414 	DE_ASSERT(static_cast<int>(attributes.size()) == numPrimitives * numAttribsPerPrimitive);
415 	DE_ASSERT(sizeof(attributes[0]) == vertexStride);
416 
417 	{
418 		const Allocation& alloc = vertexBuffer.getAllocation();
419 
420 		deMemcpy(alloc.getHostPtr(), &attributes[0], static_cast<std::size_t>(vertexDataSizeBytes));
421 		flushAlloc(vk, device, alloc);
422 		// No barrier needed, flushed memory is automatically visible
423 	}
424 
425 	// Output buffer: number of invocations
426 
427 	const VkDeviceSize		resultBufferSizeBytes = sizeof(deInt32);
428 	const BufferWithMemory	resultBuffer			 (vk, device, allocator, makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), MemoryRequirement::HostVisible);
429 
430 	{
431 		const Allocation& alloc = resultBuffer.getAllocation();
432 
433 		deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes));
434 		flushAlloc(vk, device, alloc);
435 	}
436 
437 	// Color attachment
438 
439 	const tcu::IVec2			  renderSize				 = tcu::IVec2(256, 256);
440 	const VkFormat				  colorFormat				 = VK_FORMAT_R8G8B8A8_UNORM;
441 	const VkImageSubresourceRange colorImageSubresourceRange = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u);
442 	const ImageWithMemory		  colorAttachmentImage		 (vk, device, allocator,
443 															 makeImageCreateInfo(renderSize, colorFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, 1u),
444 															 MemoryRequirement::Any);
445 
446 	// Color output buffer: image will be copied here for verification
447 
448 	const VkDeviceSize		colorBufferSizeBytes = renderSize.x()*renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat));
449 	const BufferWithMemory	colorBuffer(vk, device, allocator,
450 		makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT), MemoryRequirement::HostVisible);
451 
452 	// Descriptors
453 
454 	const Unique<VkDescriptorSetLayout> descriptorSetLayout(DescriptorSetLayoutBuilder()
455 		.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)
456 		.build(vk, device));
457 
458 	const Unique<VkDescriptorPool> descriptorPool(DescriptorPoolBuilder()
459 		.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
460 		.build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
461 
462 	const Unique<VkDescriptorSet> descriptorSet    (makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
463 	const VkDescriptorBufferInfo  resultBufferInfo = makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes);
464 
465 	DescriptorSetUpdateBuilder()
466 		.writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo)
467 		.update(vk, device);
468 
469 	// Pipeline
470 
471 	const Unique<VkImageView>		colorAttachmentView	(makeImageView(vk, device, *colorAttachmentImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorImageSubresourceRange));
472 	const Unique<VkRenderPass>		renderPass			(makeRenderPass(vk, device, colorFormat));
473 	const Unique<VkFramebuffer>		framebuffer			(makeFramebuffer(vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y()));
474 	const Unique<VkPipelineLayout>	pipelineLayout		(makePipelineLayout(vk, device, *descriptorSetLayout));
475 	const Unique<VkCommandPool>		cmdPool				(makeCommandPool(vk, device, queueFamilyIndex));
476 	const Unique<VkCommandBuffer>	cmdBuffer			(allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
477 	const bool						needPointSizeWrite	= getPhysicalDeviceFeatures(context.getInstanceInterface(), context.getPhysicalDevice()).shaderTessellationAndGeometryPointSize && caseDef.usePointMode;
478 
479 	const Unique<VkPipeline> pipeline(GraphicsPipelineBuilder()
480 		.setRenderSize				  (renderSize)
481 		.setPatchControlPoints		  (numAttribsPerPrimitive)
482 		.setVertexInputSingleAttribute(vertexFormat, vertexStride)
483 		.setShader					  (vk, device, VK_SHADER_STAGE_VERTEX_BIT,					context.getBinaryCollection().get("vert"), DE_NULL)
484 		.setShader					  (vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,	context.getBinaryCollection().get("tesc"), DE_NULL)
485 		.setShader					  (vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, context.getBinaryCollection().get(needPointSizeWrite ? "tese_psw" : "tese"), DE_NULL)
486 		.setShader					  (vk, device, VK_SHADER_STAGE_FRAGMENT_BIT,				context.getBinaryCollection().get("frag"), DE_NULL)
487 		.build						  (vk, device, *pipelineLayout, *renderPass));
488 
489 	context.getTestContext().getLog()
490 		<< tcu::TestLog::Message
491 		<< "Note: rendering " << numPrimitives << " patches; first patches have valid relevant outer levels, "
492 		<< "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels"
493 		<< tcu::TestLog::EndMessage;
494 
495 	// Draw commands
496 
497 	beginCommandBuffer(vk, *cmdBuffer);
498 
499 	// Change color attachment image layout
500 	{
501 		const VkImageMemoryBarrier colorAttachmentLayoutBarrier = makeImageMemoryBarrier(
502 			(VkAccessFlags)0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
503 			VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
504 			*colorAttachmentImage, colorImageSubresourceRange);
505 
506 		vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0u,
507 			0u, DE_NULL, 0u, DE_NULL, 1u, &colorAttachmentLayoutBarrier);
508 	}
509 
510 	// Begin render pass
511 	{
512 		const VkRect2D	renderArea	= makeRect2D(renderSize);
513 		const tcu::Vec4	clearColor	= tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
514 
515 		beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, renderArea, clearColor);
516 	}
517 
518 	vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
519 	vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u, &descriptorSet.get(), 0u, DE_NULL);
520 	{
521 		const VkDeviceSize vertexBufferOffset = 0ull;
522 		vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset);
523 	}
524 
525 	vk.cmdDraw(*cmdBuffer, static_cast<deUint32>(attributes.size()), 1u, 0u, 0u);
526 	endRenderPass(vk, *cmdBuffer);
527 
528 	// Copy render result to a host-visible buffer
529 	copyImageToBuffer(vk, *cmdBuffer, *colorAttachmentImage, *colorBuffer, renderSize);
530 	{
531 		const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(
532 			VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, 0ull, resultBufferSizeBytes);
533 
534 		vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u,
535 			0u, DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL);
536 	}
537 
538 	endCommandBuffer(vk, *cmdBuffer);
539 	submitCommandsAndWait(vk, device, queue, *cmdBuffer);
540 
541 	{
542 		// Log rendered image
543 		const Allocation&					colorBufferAlloc	= colorBuffer.getAllocation();
544 
545 		invalidateAlloc(vk, device, colorBufferAlloc);
546 
547 		const tcu::ConstPixelBufferAccess	imagePixelAccess	(mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1, colorBufferAlloc.getHostPtr());
548 		tcu::TestLog&						log					= context.getTestContext().getLog();
549 
550 		log << tcu::TestLog::Image("color0", "Rendered image", imagePixelAccess);
551 
552 		// Verify case result
553 		const Allocation&					resultAlloc			= resultBuffer.getAllocation();
554 
555 		invalidateAlloc(vk, device, resultAlloc);
556 
557 		const deInt32						numResultVertices	= *static_cast<deInt32*>(resultAlloc.getHostPtr());
558 
559 		if (!lessThanOneInnerLevelsDefined(caseDef) && caseDef.useLessThanOneInnerLevels)
560 		{
561 			// Since we cannot explicitly determine whether or not such interior vertices are going to be
562 			// generated, we will not verify the number of generated vertices for fractional odd + quads/triangles
563 			// tessellation configurations.
564 			log << tcu::TestLog::Message
565 				<< "Note: shader invocations generated " << numResultVertices << " vertices (not verified as number of vertices is implementation-dependent)"
566 				<< tcu::TestLog::EndMessage;
567 		}
568 		else if (numResultVertices < numExpectedVertices)
569 		{
570 			log << tcu::TestLog::Message
571 				<< "Failure: expected " << numExpectedVertices << " vertices from shader invocations, but got only " << numResultVertices
572 				<< tcu::TestLog::EndMessage;
573 			return tcu::TestStatus::fail("Wrong number of tessellation coordinates");
574 		}
575 		else if (numResultVertices == numExpectedVertices)
576 		{
577 			log << tcu::TestLog::Message
578 				<< "Note: shader invocations generated " << numResultVertices << " vertices"
579 				<< tcu::TestLog::EndMessage;
580 		}
581 		else
582 		{
583 			log << tcu::TestLog::Message
584 				<< "Note: shader invocations generated " << numResultVertices << " vertices (expected " << numExpectedVertices << ", got "
585 				<< (numResultVertices - numExpectedVertices) << " extra)"
586 				<< tcu::TestLog::EndMessage;
587 		}
588 
589 		return (verifyResultImage(log, numPrimitives, numAttribsPerPrimitive, caseDef.primitiveType, attributes, imagePixelAccess)
590 				? tcu::TestStatus::pass("OK") : tcu::TestStatus::fail("Image verification failed"));
591 	}
592 }
593 
594 } // anonymous
595 
596 //! These tests correspond to dEQP-GLES31.functional.tessellation.primitive_discard.*
597 //! \note Original test used transform feedback (TF) to capture the number of output vertices. The behavior of TF differs significantly from SSBO approach,
598 //!       especially for non-point_mode rendering. TF returned all coordinates, while SSBO computes the count based on the number of shader invocations
599 //!       which yields a much smaller number because invocations for duplicate coordinates are often eliminated.
600 //!       Because of this, the test was changed to:
601 //!       - always compute the number of expected coordinates as if point_mode was enabled
602 //!       - not fail if implementation returned more coordinates than expected
603 tcu::TestCaseGroup* createPrimitiveDiscardTests (tcu::TestContext& testCtx)
604 {
605 	de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, "primitive_discard", "Test primitive discard with relevant outer tessellation level <= 0.0"));
606 
607 	for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; primitiveTypeNdx++)
608 	for (int spacingModeNdx = 0; spacingModeNdx < SPACINGMODE_LAST; spacingModeNdx++)
609 	for (int windingNdx = 0; windingNdx < WINDING_LAST; windingNdx++)
610 	for (int usePointModeNdx = 0; usePointModeNdx <= 1; usePointModeNdx++)
611 	for (int lessThanOneInnerLevelsNdx = 0; lessThanOneInnerLevelsNdx <= 1; lessThanOneInnerLevelsNdx++)
612 	{
613 		const CaseDefinition caseDef =
614 		{
615 			(TessPrimitiveType)primitiveTypeNdx,
616 			(SpacingMode)spacingModeNdx,
617 			(Winding)windingNdx,
618 			(usePointModeNdx != 0),
619 			(lessThanOneInnerLevelsNdx != 0)
620 		};
621 
622 		if (lessThanOneInnerLevelsDefined(caseDef) && !caseDef.useLessThanOneInnerLevels)
623 			continue; // No point generating a separate case as <= 1 inner level behavior is well-defined
624 
625 		const std::string caseName = std::string() + getTessPrimitiveTypeShaderName(caseDef.primitiveType)
626 									 + "_" + getSpacingModeShaderName(caseDef.spacingMode)
627 									 + "_" + getWindingShaderName(caseDef.winding)
628 									 + (caseDef.usePointMode ? "_point_mode" : "")
629 									 + (caseDef.useLessThanOneInnerLevels ? "" : "_valid_levels");
630 
631 		addFunctionCaseWithPrograms(group.get(), caseName, "", checkSupportCase, initPrograms, test, caseDef);
632 	}
633 
634 	return group.release();
635 }
636 
637 } // tessellation
638 } // vkt
639