1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2016 The Khronos Group Inc.
6  * Copyright (c) 2014 The Android Open Source Project
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 Geometry shader instanced rendering tests
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktGeometryInstancedRenderingTests.hpp"
26 #include "vktTestCase.hpp"
27 #include "vktTestCaseUtil.hpp"
28 #include "vktGeometryTestsUtil.hpp"
29 
30 #include "vkPrograms.hpp"
31 #include "vkQueryUtil.hpp"
32 #include "vkMemUtil.hpp"
33 #include "vkRefUtil.hpp"
34 #include "vkTypeUtil.hpp"
35 #include "vkImageUtil.hpp"
36 #include "vkCmdUtil.hpp"
37 #include "vkObjUtil.hpp"
38 
39 #include "tcuTextureUtil.hpp"
40 #include "tcuImageCompare.hpp"
41 #include "tcuTestLog.hpp"
42 
43 #include "deRandom.hpp"
44 #include "deMath.h"
45 
46 namespace vkt
47 {
48 namespace geometry
49 {
50 namespace
51 {
52 using namespace vk;
53 using de::MovePtr;
54 using de::UniquePtr;
55 using tcu::Vec4;
56 using tcu::UVec2;
57 
58 struct TestParams
59 {
60 	int	numDrawInstances;
61 	int	numInvocations;
62 };
63 
makeImageCreateInfo(const VkFormat format,const VkExtent3D size,const VkImageUsageFlags usage)64 VkImageCreateInfo makeImageCreateInfo (const VkFormat format, const VkExtent3D size, const VkImageUsageFlags usage)
65 {
66 	const VkImageCreateInfo imageParams =
67 	{
68 		VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,			// VkStructureType			sType;
69 		DE_NULL,										// const void*				pNext;
70 		(VkImageCreateFlags)0,							// VkImageCreateFlags		flags;
71 		VK_IMAGE_TYPE_2D,								// VkImageType				imageType;
72 		format,											// VkFormat					format;
73 		size,											// VkExtent3D				extent;
74 		1u,												// deUint32					mipLevels;
75 		1u,												// deUint32					arrayLayers;
76 		VK_SAMPLE_COUNT_1_BIT,							// VkSampleCountFlagBits	samples;
77 		VK_IMAGE_TILING_OPTIMAL,						// VkImageTiling			tiling;
78 		usage,											// VkImageUsageFlags		usage;
79 		VK_SHARING_MODE_EXCLUSIVE,						// VkSharingMode			sharingMode;
80 		0u,												// deUint32					queueFamilyIndexCount;
81 		DE_NULL,										// const deUint32*			pQueueFamilyIndices;
82 		VK_IMAGE_LAYOUT_UNDEFINED,						// VkImageLayout			initialLayout;
83 	};
84 	return imageParams;
85 }
86 
makeGraphicsPipeline(const DeviceInterface & vk,const VkDevice device,const VkPipelineLayout pipelineLayout,const VkRenderPass renderPass,const VkShaderModule vertexModule,const VkShaderModule geometryModule,const VkShaderModule fragmentModule,const VkExtent2D renderSize)87 Move<VkPipeline> makeGraphicsPipeline (const DeviceInterface&	vk,
88 									   const VkDevice			device,
89 									   const VkPipelineLayout	pipelineLayout,
90 									   const VkRenderPass		renderPass,
91 									   const VkShaderModule		vertexModule,
92 									   const VkShaderModule		geometryModule,
93 									   const VkShaderModule		fragmentModule,
94 									   const VkExtent2D			renderSize)
95 {
96 	const std::vector<VkViewport>				viewports						(1, makeViewport(renderSize));
97 	const std::vector<VkRect2D>					scissors						(1, makeRect2D(renderSize));
98 
99 	const VkVertexInputBindingDescription		vertexInputBindingDescription	=
100 	{
101 		0u,								// deUint32             binding;
102 		sizeof(Vec4),					// deUint32             stride;
103 		VK_VERTEX_INPUT_RATE_INSTANCE	// VkVertexInputRate    inputRate;
104 	};
105 
106 	const VkVertexInputAttributeDescription		vertexInputAttributeDescription	=
107 	{
108 		0u,								// deUint32         location;
109 		0u,								// deUint32         binding;
110 		VK_FORMAT_R32G32B32A32_SFLOAT,	// VkFormat         format;
111 		0u								// deUint32         offset;
112 	};
113 
114 	const VkPipelineVertexInputStateCreateInfo	vertexInputStateCreateInfo		=
115 	{
116 		VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,	// VkStructureType                             sType;
117 		DE_NULL,													// const void*                                 pNext;
118 		(VkPipelineVertexInputStateCreateFlags)0,					// VkPipelineVertexInputStateCreateFlags       flags;
119 		1u,															// deUint32                                    vertexBindingDescriptionCount;
120 		&vertexInputBindingDescription,								// const VkVertexInputBindingDescription*      pVertexBindingDescriptions;
121 		1u,															// deUint32                                    vertexAttributeDescriptionCount;
122 		&vertexInputAttributeDescription							// const VkVertexInputAttributeDescription*    pVertexAttributeDescriptions;
123 	};
124 
125 	return vk::makeGraphicsPipeline(vk,									// const DeviceInterface&                        vk
126 									device,								// const VkDevice                                device
127 									pipelineLayout,						// const VkPipelineLayout                        pipelineLayout
128 									vertexModule,						// const VkShaderModule                          vertexShaderModule
129 									DE_NULL,							// const VkShaderModule                          tessellationControlModule
130 									DE_NULL,							// const VkShaderModule                          tessellationEvalModule
131 									geometryModule,						// const VkShaderModule                          geometryShaderModule
132 									fragmentModule,						// const VkShaderModule                          fragmentShaderModule
133 									renderPass,							// const VkRenderPass                            renderPass
134 									viewports,							// const std::vector<VkViewport>&                viewports
135 									scissors,							// const std::vector<VkRect2D>&                  scissors
136 									VK_PRIMITIVE_TOPOLOGY_POINT_LIST,	// const VkPrimitiveTopology                     topology
137 									0u,									// const deUint32                                subpass
138 									0u,									// const deUint32                                patchControlPoints
139 									&vertexInputStateCreateInfo);		// const VkPipelineVertexInputStateCreateInfo*   vertexInputStateCreateInfo
140 }
141 
draw(Context & context,const UVec2 & renderSize,const VkFormat colorFormat,const Vec4 & clearColor,const VkBuffer colorBuffer,const int numDrawInstances,const std::vector<Vec4> & perInstanceAttribute)142 void draw (Context&					context,
143 		   const UVec2&				renderSize,
144 		   const VkFormat			colorFormat,
145 		   const Vec4&				clearColor,
146 		   const VkBuffer			colorBuffer,
147 		   const int				numDrawInstances,
148 		   const std::vector<Vec4>& perInstanceAttribute)
149 {
150 	const DeviceInterface&			vk						= context.getDeviceInterface();
151 	const VkDevice					device					= context.getDevice();
152 	const deUint32					queueFamilyIndex		= context.getUniversalQueueFamilyIndex();
153 	const VkQueue					queue					= context.getUniversalQueue();
154 	Allocator&						allocator				= context.getDefaultAllocator();
155 
156 	const VkImageSubresourceRange	colorSubresourceRange	(makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u));
157 	const VkExtent3D				colorImageExtent		(makeExtent3D(renderSize.x(), renderSize.y(), 1u));
158 	const VkExtent2D				renderExtent			(makeExtent2D(renderSize.x(), renderSize.y()));
159 
160 	const Unique<VkImage>			colorImage				(makeImage		(vk, device, makeImageCreateInfo(colorFormat, colorImageExtent, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT)));
161 	const UniquePtr<Allocation>		colorImageAlloc			(bindImage		(vk, device, allocator, *colorImage, MemoryRequirement::Any));
162 	const Unique<VkImageView>		colorAttachment			(makeImageView	(vk, device, *colorImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorSubresourceRange));
163 
164 	const VkDeviceSize				vertexBufferSize		= sizeInBytes(perInstanceAttribute);
165 	const Unique<VkBuffer>			vertexBuffer			(makeBuffer(vk, device, makeBufferCreateInfo(vertexBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)));
166 	const UniquePtr<Allocation>		vertexBufferAlloc		(bindBuffer(vk, device, allocator, *vertexBuffer, MemoryRequirement::HostVisible));
167 
168 	const Unique<VkShaderModule>	vertexModule			(createShaderModule	(vk, device, context.getBinaryCollection().get("vert"), 0u));
169 	const Unique<VkShaderModule>	geometryModule			(createShaderModule	(vk, device, context.getBinaryCollection().get("geom"), 0u));
170 	const Unique<VkShaderModule>	fragmentModule			(createShaderModule	(vk, device, context.getBinaryCollection().get("frag"), 0u));
171 
172 	const Unique<VkRenderPass>		renderPass				(vk::makeRenderPass		(vk, device, colorFormat));
173 	const Unique<VkFramebuffer>		framebuffer				(makeFramebuffer		(vk, device, *renderPass, *colorAttachment, renderSize.x(), renderSize.y(), 1u));
174 	const Unique<VkPipelineLayout>	pipelineLayout			(makePipelineLayout		(vk, device));
175 	const Unique<VkPipeline>		pipeline				(makeGraphicsPipeline	(vk, device, *pipelineLayout, *renderPass, *vertexModule, *geometryModule, *fragmentModule, renderExtent));
176 
177 	const Unique<VkCommandPool>		cmdPool					(createCommandPool		(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex));
178 	const Unique<VkCommandBuffer>	cmdBuffer				(allocateCommandBuffer	(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
179 
180 	// Initialize vertex data
181 	{
182 		deMemcpy(vertexBufferAlloc->getHostPtr(), &perInstanceAttribute[0], (size_t)vertexBufferSize);
183 		flushMappedMemoryRange(vk, device, vertexBufferAlloc->getMemory(), vertexBufferAlloc->getOffset(), vertexBufferSize);
184 	}
185 
186 	beginCommandBuffer(vk, *cmdBuffer);
187 
188 	beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, makeRect2D(renderExtent), clearColor);
189 
190 	vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
191 	{
192 		const VkDeviceSize offset = 0ull;
193 		vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &offset);
194 	}
195 	vk.cmdDraw(*cmdBuffer, 1u, static_cast<deUint32>(numDrawInstances), 0u, 0u);
196 	endRenderPass(vk, *cmdBuffer);
197 
198 	copyImageToBuffer(vk, *cmdBuffer, *colorImage, colorBuffer, tcu::IVec2(renderSize.x(), renderSize.y()));
199 
200 	endCommandBuffer(vk, *cmdBuffer);
201 	submitCommandsAndWait(vk, device, queue, *cmdBuffer);
202 }
203 
generatePerInstancePosition(const int numInstances)204 std::vector<Vec4> generatePerInstancePosition (const int numInstances)
205 {
206 	de::Random			rng(1234);
207 	std::vector<Vec4>	positions;
208 
209 	for (int i = 0; i < numInstances; ++i)
210 	{
211 		const float flipX	= rng.getBool() ? 1.0f : -1.0f;
212 		const float flipY	= rng.getBool() ? 1.0f : -1.0f;
213 		const float x		= flipX * rng.getFloat(0.1f, 0.9f);	// x mustn't be 0.0, because we are using sign() in the shader
214 		const float y		= flipY * rng.getFloat(0.0f, 0.7f);
215 
216 		positions.push_back(Vec4(x, y, 0.0f, 1.0f));
217 	}
218 
219 	return positions;
220 }
221 
222 //! Get a rectangle region of an image, using NDC coordinates (i.e. [-1, 1] range).
223 //! Result rect is cropped in either dimension to be inside the bounds of the image.
getSubregion(tcu::PixelBufferAccess image,const float x,const float y,const float size)224 tcu::PixelBufferAccess getSubregion (tcu::PixelBufferAccess image, const float x, const float y, const float size)
225 {
226 	const float w	= static_cast<float>(image.getWidth());
227 	const float h	= static_cast<float>(image.getHeight());
228 	const float x1	= w * (x + 1.0f) * 0.5f;
229 	const float y1	= h * (y + 1.0f) * 0.5f;
230 	const float sx	= w * size * 0.5f;
231 	const float sy	= h * size * 0.5f;
232 	const float x2	= x1 + sx;
233 	const float y2	= y1 + sy;
234 
235 	// Round and clamp only after all of the above.
236 	const int	ix1	= std::max(deRoundFloatToInt32(x1), 0);
237 	const int	ix2	= std::min(deRoundFloatToInt32(x2), image.getWidth());
238 	const int	iy1	= std::max(deRoundFloatToInt32(y1), 0);
239 	const int	iy2	= std::min(deRoundFloatToInt32(y2), image.getHeight());
240 
241 	return tcu::getSubregion(image, ix1, iy1, ix2 - ix1, iy2 - iy1);
242 }
243 
244 //! Must be in sync with the geometry shader code.
generateReferenceImage(tcu::PixelBufferAccess image,const Vec4 & clearColor,const std::vector<Vec4> & perInstancePosition,const int numInvocations)245 void generateReferenceImage(tcu::PixelBufferAccess image, const Vec4& clearColor, const std::vector<Vec4>& perInstancePosition, const int numInvocations)
246 {
247 	tcu::clear(image, clearColor);
248 
249 	for (std::vector<Vec4>::const_iterator iterPosition = perInstancePosition.begin(); iterPosition != perInstancePosition.end(); ++iterPosition)
250 	for (int invocationNdx = 0; invocationNdx < numInvocations; ++invocationNdx)
251 	{
252 		const float x			= iterPosition->x();
253 		const float y			= iterPosition->y();
254 		const float	modifier	= (numInvocations > 1 ? static_cast<float>(invocationNdx) / static_cast<float>(numInvocations - 1) : 0.0f);
255 		const Vec4	color		(deFloatAbs(x), deFloatAbs(y), 0.2f + 0.8f * modifier, 1.0f);
256 		const float size		= 0.05f + 0.03f * modifier;
257 		const float dx			= (deFloatSign(-x) - x) / static_cast<float>(numInvocations);
258 		const float xOffset		= static_cast<float>(invocationNdx) * dx;
259 		const float yOffset		= 0.3f * deFloatSin(12.0f * modifier);
260 
261 		tcu::PixelBufferAccess rect = getSubregion(image, x + xOffset - size, y + yOffset - size, size + size);
262 		tcu::clear(rect, color);
263 	}
264 }
265 
initPrograms(SourceCollections & programCollection,const TestParams params)266 void initPrograms (SourceCollections& programCollection, const TestParams params)
267 {
268 	// Vertex shader
269 	{
270 		std::ostringstream src;
271 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
272 			<< "\n"
273 			<< "layout(location = 0) in vec4 in_position;\n"
274 			<< "\n"
275 			<< "out gl_PerVertex {\n"
276 			<< "    vec4 gl_Position;\n"
277 			<< "};\n"
278 			<< "\n"
279 			<< "void main(void)\n"
280 			<< "{\n"
281 			<< "    gl_Position = in_position;\n"
282 			<< "}\n";
283 
284 		programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
285 	}
286 
287 	// Geometry shader
288 	{
289 		// The shader must be in sync with reference image rendering routine.
290 
291 		std::ostringstream src;
292 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
293 			<< "\n"
294 			<< "layout(points, invocations = " << params.numInvocations << ") in;\n"
295 			<< "layout(triangle_strip, max_vertices = 4) out;\n"
296 			<< "\n"
297 			<< "layout(location = 0) out vec4 out_color;\n"
298 			<< "\n"
299 			<< "in gl_PerVertex {\n"
300 			<< "    vec4 gl_Position;\n"
301 			<< "} gl_in[];\n"
302 			<< "\n"
303 			<< "out gl_PerVertex {\n"
304 			<< "    vec4 gl_Position;\n"
305 			<< "};\n"
306 			<< "\n"
307 			<< "void main(void)\n"
308 			<< "{\n"
309 			<< "    const vec4  pos       = gl_in[0].gl_Position;\n"
310 			<< "    const float modifier  = " << (params.numInvocations > 1 ? "float(gl_InvocationID) / float(" + de::toString(params.numInvocations - 1) + ")" : "0.0") << ";\n"
311 			<< "    const vec4  color     = vec4(abs(pos.x), abs(pos.y), 0.2 + 0.8 * modifier, 1.0);\n"
312 			<< "    const float size      = 0.05 + 0.03 * modifier;\n"
313 			<< "    const float dx        = (sign(-pos.x) - pos.x) / float(" << params.numInvocations << ");\n"
314 			<< "    const vec4  offsetPos = pos + vec4(float(gl_InvocationID) * dx,\n"
315 			<< "                                       0.3 * sin(12.0 * modifier),\n"
316 			<< "                                       0.0,\n"
317 			<< "                                       0.0);\n"
318 			<< "\n"
319 			<< "    gl_Position = offsetPos + vec4(-size, -size, 0.0, 0.0);\n"
320 			<< "    out_color   = color;\n"
321 			<< "    EmitVertex();\n"
322 			<< "\n"
323 			<< "    gl_Position = offsetPos + vec4(-size,  size, 0.0, 0.0);\n"
324 			<< "    out_color   = color;\n"
325 			<< "    EmitVertex();\n"
326 			<< "\n"
327 			<< "    gl_Position = offsetPos + vec4( size, -size, 0.0, 0.0);\n"
328 			<< "    out_color   = color;\n"
329 			<< "    EmitVertex();\n"
330 			<< "\n"
331 			<< "    gl_Position = offsetPos + vec4( size,  size, 0.0, 0.0);\n"
332 			<< "    out_color   = color;\n"
333 			<< "    EmitVertex();\n"
334 			<<	"}\n";
335 
336 		programCollection.glslSources.add("geom") << glu::GeometrySource(src.str());
337 	}
338 
339 	// Fragment shader
340 	{
341 		std::ostringstream src;
342 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
343 			<< "\n"
344 			<< "layout(location = 0) in  vec4 in_color;\n"
345 			<< "layout(location = 0) out vec4 o_color;\n"
346 			<< "\n"
347 			<< "void main(void)\n"
348 			<< "{\n"
349 			<< "    o_color = in_color;\n"
350 			<< "}\n";
351 
352 		programCollection.glslSources.add("frag") << glu::FragmentSource(src.str());
353 	}
354 }
355 
test(Context & context,const TestParams params)356 tcu::TestStatus test (Context& context, const TestParams params)
357 {
358 	const DeviceInterface&			vk					= context.getDeviceInterface();
359 	const InstanceInterface&		vki					= context.getInstanceInterface();
360 	const VkDevice					device				= context.getDevice();
361 	const VkPhysicalDevice			physDevice			= context.getPhysicalDevice();
362 	Allocator&						allocator			= context.getDefaultAllocator();
363 
364 	checkGeometryShaderSupport(vki, physDevice, params.numInvocations);
365 
366 	const UVec2						renderSize			(128u, 128u);
367 	const VkFormat					colorFormat			= VK_FORMAT_R8G8B8A8_UNORM;
368 	const Vec4						clearColor			= Vec4(0.0f, 0.0f, 0.0f, 1.0f);
369 
370 	const VkDeviceSize				colorBufferSize		= renderSize.x() * renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat));
371 	const Unique<VkBuffer>			colorBuffer			(makeBuffer(vk, device, makeBufferCreateInfo(colorBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT)));
372 	const UniquePtr<Allocation>		colorBufferAlloc	(bindBuffer(vk, device, allocator, *colorBuffer, MemoryRequirement::HostVisible));
373 
374 	const std::vector<Vec4>			perInstancePosition	= generatePerInstancePosition(params.numDrawInstances);
375 
376 	{
377 		context.getTestContext().getLog()
378 			<< tcu::TestLog::Message << "Rendering " << params.numDrawInstances << " instance(s) of colorful quads." << tcu::TestLog::EndMessage
379 			<< tcu::TestLog::Message << "Drawing " << params.numInvocations << " quad(s), each drawn by a geometry shader invocation." << tcu::TestLog::EndMessage;
380 	}
381 
382 	zeroBuffer(vk, device, *colorBufferAlloc, colorBufferSize);
383 	draw(context, renderSize, colorFormat, clearColor, *colorBuffer, params.numDrawInstances, perInstancePosition);
384 
385 	// Compare result
386 	{
387 		invalidateMappedMemoryRange(vk, device, colorBufferAlloc->getMemory(), colorBufferAlloc->getOffset(), colorBufferSize);
388 		const tcu::ConstPixelBufferAccess result(mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1u, colorBufferAlloc->getHostPtr());
389 
390 		tcu::TextureLevel reference(mapVkFormat(colorFormat), renderSize.x(), renderSize.y());
391 		generateReferenceImage(reference.getAccess(), clearColor, perInstancePosition, params.numInvocations);
392 
393 		if (!tcu::fuzzyCompare(context.getTestContext().getLog(), "Image Compare", "Image Compare", reference.getAccess(), result, 0.01f, tcu::COMPARE_LOG_RESULT))
394 			return tcu::TestStatus::fail("Rendered image is incorrect");
395 		else
396 			return tcu::TestStatus::pass("OK");
397 	}
398 }
399 
400 } // anonymous
401 
402 //! \note CTS requires shaders to be known ahead of time (some platforms use precompiled shaders), so we can't query a limit at runtime and generate
403 //!       a shader based on that. This applies to number of GS invocations which can't be injected into the shader.
createInstancedRenderingTests(tcu::TestContext & testCtx)404 tcu::TestCaseGroup* createInstancedRenderingTests (tcu::TestContext& testCtx)
405 {
406 	MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "instanced", "Instanced rendering tests."));
407 
408 	const int drawInstanceCases[]	=
409 	{
410 		1, 2, 4, 8,
411 	};
412 	const int invocationCases[]		=
413 	{
414 		1, 2, 8, 32,	// required by the Vulkan spec
415 		64, 127,		// larger than the minimum, but perhaps some implementations support it, so we'll try
416 	};
417 
418 	for (const int* pNumDrawInstances = drawInstanceCases; pNumDrawInstances != drawInstanceCases + DE_LENGTH_OF_ARRAY(drawInstanceCases); ++pNumDrawInstances)
419 	for (const int* pNumInvocations   = invocationCases;   pNumInvocations   != invocationCases   + DE_LENGTH_OF_ARRAY(invocationCases);   ++pNumInvocations)
420 	{
421 		std::ostringstream caseName;
422 		caseName << "draw_" << *pNumDrawInstances << "_instances_" << *pNumInvocations << "_geometry_invocations";
423 
424 		const TestParams params =
425 		{
426 			*pNumDrawInstances,
427 			*pNumInvocations,
428 		};
429 
430 		addFunctionCaseWithPrograms(group.get(), caseName.str(), "", initPrograms, test, params);
431 	}
432 
433 	return group.release();
434 }
435 
436 } // geometry
437 } // vkt
438