Home | History | Annotate | Download | only in geometry
      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 
     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 
     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 
    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 
    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.
    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.
    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 
    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 
    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.
    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