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