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 Scissor multi viewport tests
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktFragmentOperationsScissorMultiViewportTests.hpp"
26 #include "vktTestCaseUtil.hpp"
27 #include "vktFragmentOperationsMakeUtil.hpp"
28 
29 #include "vkDefs.hpp"
30 #include "vkRefUtil.hpp"
31 #include "vkTypeUtil.hpp"
32 #include "vkMemUtil.hpp"
33 #include "vkPrograms.hpp"
34 #include "vkImageUtil.hpp"
35 #include "vkQueryUtil.hpp"
36 #include "vkCmdUtil.hpp"
37 #include "vkObjUtil.hpp"
38 
39 #include "tcuTestLog.hpp"
40 #include "tcuVector.hpp"
41 #include "tcuImageCompare.hpp"
42 #include "tcuTextureUtil.hpp"
43 
44 #include "deUniquePtr.hpp"
45 #include "deMath.h"
46 
47 namespace vkt
48 {
49 namespace FragmentOperations
50 {
51 using namespace vk;
52 using de::UniquePtr;
53 using de::MovePtr;
54 using tcu::Vec4;
55 using tcu::Vec2;
56 using tcu::IVec2;
57 using tcu::IVec4;
58 
59 namespace
60 {
61 
62 enum Constants
63 {
64 	MIN_MAX_VIEWPORTS = 16,		//!< Minimum number of viewports for an implementation supporting multiViewport.
65 };
66 
67 template<typename T>
sizeInBytes(const std::vector<T> & vec)68 inline VkDeviceSize sizeInBytes(const std::vector<T>& vec)
69 {
70 	return vec.size() * sizeof(vec[0]);
71 }
72 
makeImageCreateInfo(const VkFormat format,const IVec2 & size,VkImageUsageFlags usage)73 VkImageCreateInfo makeImageCreateInfo (const VkFormat format, const IVec2& size, VkImageUsageFlags usage)
74 {
75 	const VkImageCreateInfo imageParams =
76 	{
77 		VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,			// VkStructureType			sType;
78 		DE_NULL,										// const void*				pNext;
79 		(VkImageCreateFlags)0,							// VkImageCreateFlags		flags;
80 		VK_IMAGE_TYPE_2D,								// VkImageType				imageType;
81 		format,											// VkFormat					format;
82 		makeExtent3D(size.x(), size.y(), 1),			// VkExtent3D				extent;
83 		1u,												// deUint32					mipLevels;
84 		1u,												// deUint32					arrayLayers;
85 		VK_SAMPLE_COUNT_1_BIT,							// VkSampleCountFlagBits	samples;
86 		VK_IMAGE_TILING_OPTIMAL,						// VkImageTiling			tiling;
87 		usage,											// VkImageUsageFlags		usage;
88 		VK_SHARING_MODE_EXCLUSIVE,						// VkSharingMode			sharingMode;
89 		0u,												// deUint32					queueFamilyIndexCount;
90 		DE_NULL,										// const deUint32*			pQueueFamilyIndices;
91 		VK_IMAGE_LAYOUT_UNDEFINED,						// VkImageLayout			initialLayout;
92 	};
93 	return imageParams;
94 }
95 
makeGraphicsPipeline(const DeviceInterface & vk,const VkDevice device,const VkPipelineLayout pipelineLayout,const VkRenderPass renderPass,const VkShaderModule vertexModule,const VkShaderModule geometryModule,const VkShaderModule fragmentModule,const IVec2 renderSize,const int numViewports,const std::vector<IVec4> scissors)96 Move<VkPipeline> makeGraphicsPipeline (const DeviceInterface&		vk,
97 									   const VkDevice				device,
98 									   const VkPipelineLayout		pipelineLayout,
99 									   const VkRenderPass			renderPass,
100 									   const VkShaderModule			vertexModule,
101 									   const VkShaderModule			geometryModule,
102 									   const VkShaderModule			fragmentModule,
103 									   const IVec2					renderSize,
104 									   const int					numViewports,
105 									   const std::vector<IVec4>		scissors)
106 {
107 	const VkViewport defaultViewport = makeViewport(renderSize);
108 	const std::vector<VkViewport> viewports(numViewports, defaultViewport);
109 
110 	DE_ASSERT(numViewports == static_cast<int>(scissors.size()));
111 
112 	std::vector<VkRect2D> rectScissors;
113 	rectScissors.reserve(numViewports);
114 
115 	for (std::vector<IVec4>::const_iterator it = scissors.begin(); it != scissors.end(); ++it)
116 	{
117 		const VkRect2D rect =
118 		{
119 			makeOffset2D(it->x(), it->y()),
120 			makeExtent2D(static_cast<deUint32>(it->z()), static_cast<deUint32>(it->w())),
121 		};
122 		rectScissors.push_back(rect);
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 									rectScissors,						// const std::vector<VkRect2D>&      scissors
136 									VK_PRIMITIVE_TOPOLOGY_POINT_LIST);	// const VkPrimitiveTopology         topology
137 }
138 
zeroBuffer(const DeviceInterface & vk,const VkDevice device,const Allocation & alloc,const VkDeviceSize size)139 void zeroBuffer (const DeviceInterface& vk, const VkDevice device, const Allocation& alloc, const VkDeviceSize size)
140 {
141 	deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(size));
142 	flushAlloc(vk, device, alloc);
143 }
144 
requireFeatureMultiViewport(const InstanceInterface & vki,const VkPhysicalDevice physDevice)145 void requireFeatureMultiViewport (const InstanceInterface& vki, const VkPhysicalDevice physDevice)
146 {
147 	const VkPhysicalDeviceFeatures	features	= getPhysicalDeviceFeatures(vki, physDevice);
148 	const VkPhysicalDeviceLimits	limits		= getPhysicalDeviceProperties(vki, physDevice).limits;
149 
150 	if (!features.geometryShader)
151 		TCU_THROW(NotSupportedError, "Required feature is not supported: geometryShader");
152 
153 	if (!features.multiViewport)
154 		TCU_THROW(NotSupportedError, "Required feature is not supported: multiViewport");
155 
156 	if (limits.maxViewports < MIN_MAX_VIEWPORTS)
157 		TCU_THROW(NotSupportedError, "Implementation doesn't support minimum required number of viewports");
158 }
159 
generateScissors(const int numScissors,const IVec2 & renderSize)160 std::vector<IVec4> generateScissors (const int numScissors, const IVec2& renderSize)
161 {
162 	// Scissor rects will be arranged in a grid-like fashion.
163 
164 	const int numCols		= deCeilFloatToInt32(deFloatSqrt(static_cast<float>(numScissors)));
165 	const int numRows		= deCeilFloatToInt32(static_cast<float>(numScissors) / static_cast<float>(numCols));
166 	const int rectWidth		= renderSize.x() / numCols;
167 	const int rectHeight	= renderSize.y() / numRows;
168 
169 	std::vector<IVec4> scissors;
170 	scissors.reserve(numScissors);
171 
172 	int x = 0;
173 	int y = 0;
174 
175 	for (int scissorNdx = 0; scissorNdx < numScissors; ++scissorNdx)
176 	{
177 		const bool nextRow = (scissorNdx != 0) && (scissorNdx % numCols == 0);
178 		if (nextRow)
179 		{
180 			x  = 0;
181 			y += rectHeight;
182 		}
183 
184 		scissors.push_back(IVec4(x, y, rectWidth, rectHeight));
185 
186 		x += rectWidth;
187 	}
188 
189 	return scissors;
190 }
191 
generateColors(const int numColors)192 std::vector<Vec4> generateColors (const int numColors)
193 {
194 	const Vec4 colors[] =
195 	{
196 		Vec4(0.18f, 0.42f, 0.17f, 1.0f),
197 		Vec4(0.29f, 0.62f, 0.28f, 1.0f),
198 		Vec4(0.59f, 0.84f, 0.44f, 1.0f),
199 		Vec4(0.96f, 0.95f, 0.72f, 1.0f),
200 		Vec4(0.94f, 0.55f, 0.39f, 1.0f),
201 		Vec4(0.82f, 0.19f, 0.12f, 1.0f),
202 		Vec4(0.46f, 0.15f, 0.26f, 1.0f),
203 		Vec4(0.24f, 0.14f, 0.24f, 1.0f),
204 		Vec4(0.49f, 0.31f, 0.26f, 1.0f),
205 		Vec4(0.78f, 0.52f, 0.33f, 1.0f),
206 		Vec4(0.94f, 0.82f, 0.31f, 1.0f),
207 		Vec4(0.98f, 0.65f, 0.30f, 1.0f),
208 		Vec4(0.22f, 0.65f, 0.53f, 1.0f),
209 		Vec4(0.67f, 0.81f, 0.91f, 1.0f),
210 		Vec4(0.43f, 0.44f, 0.75f, 1.0f),
211 		Vec4(0.26f, 0.24f, 0.48f, 1.0f),
212 	};
213 
214 	DE_ASSERT(numColors <= DE_LENGTH_OF_ARRAY(colors));
215 
216 	return std::vector<Vec4>(colors, colors + numColors);
217 }
218 
219 //! Renders a colorful grid of rectangles.
generateReferenceImage(const tcu::TextureFormat format,const IVec2 & renderSize,const Vec4 & clearColor,const std::vector<IVec4> & scissors,const std::vector<Vec4> & scissorColors)220 tcu::TextureLevel generateReferenceImage (const tcu::TextureFormat	format,
221 										  const IVec2&				renderSize,
222 										  const Vec4&				clearColor,
223 										  const std::vector<IVec4>&	scissors,
224 										  const std::vector<Vec4>&	scissorColors)
225 {
226 	DE_ASSERT(scissors.size() == scissorColors.size());
227 
228 	tcu::TextureLevel image(format, renderSize.x(), renderSize.y());
229 	tcu::clear(image.getAccess(), clearColor);
230 
231 	for (std::size_t i = 0; i < scissors.size(); ++i)
232 	{
233 		tcu::clear(
234 			tcu::getSubregion(image.getAccess(), scissors[i].x(), scissors[i].y(), scissors[i].z(), scissors[i].w()),
235 			scissorColors[i]);
236 	}
237 
238 	return image;
239 }
240 
initPrograms(SourceCollections & programCollection,const int numViewports)241 void initPrograms (SourceCollections& programCollection, const int numViewports)
242 {
243 	DE_UNREF(numViewports);
244 
245 	// Vertex shader
246 	{
247 		std::ostringstream src;
248 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
249 			<< "\n"
250 			<< "layout(location = 0) in  vec4 in_color;\n"
251 			<< "layout(location = 0) out vec4 out_color;\n"
252 			<< "\n"
253 			<< "void main(void)\n"
254 			<< "{\n"
255 			<< "    out_color = in_color;\n"
256 			<< "}\n";
257 
258 		programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
259 	}
260 
261 	// Geometry shader
262 	{
263 		// Each input point generates a fullscreen quad.
264 
265 		std::ostringstream src;
266 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
267 			<< "\n"
268 			<< "layout(points) in;\n"
269 			<< "layout(triangle_strip, max_vertices=4) out;\n"
270 			<< "\n"
271 			<< "out gl_PerVertex {\n"
272 			<< "    vec4 gl_Position;\n"
273 			<< "};\n"
274 			<< "\n"
275 			<< "layout(location = 0) in  vec4 in_color[];\n"
276 			<< "layout(location = 0) out vec4 out_color;\n"
277 			<< "\n"
278 			<< "void main(void)\n"
279 			<< "{\n"
280 			<< "    gl_ViewportIndex = gl_PrimitiveIDIn;\n"
281 			<< "    gl_Position      = vec4(-1.0, -1.0, 0.0, 1.0);\n"
282 			<< "    out_color        = in_color[0];\n"
283 			<< "    EmitVertex();"
284 			<< "\n"
285 			<< "    gl_ViewportIndex = gl_PrimitiveIDIn;\n"
286 			<< "    gl_Position      = vec4(-1.0, 1.0, 0.0, 1.0);\n"
287 			<< "    out_color        = in_color[0];\n"
288 			<< "    EmitVertex();"
289 			<< "\n"
290 			<< "    gl_ViewportIndex = gl_PrimitiveIDIn;\n"
291 			<< "    gl_Position      = vec4(1.0, -1.0, 0.0, 1.0);\n"
292 			<< "    out_color        = in_color[0];\n"
293 			<< "    EmitVertex();"
294 			<< "\n"
295 			<< "    gl_ViewportIndex = gl_PrimitiveIDIn;\n"
296 			<< "    gl_Position      = vec4(1.0, 1.0, 0.0, 1.0);\n"
297 			<< "    out_color        = in_color[0];\n"
298 			<< "    EmitVertex();"
299 			<< "}\n";
300 
301 		programCollection.glslSources.add("geom") << glu::GeometrySource(src.str());
302 	}
303 
304 	// Fragment shader
305 	{
306 		std::ostringstream src;
307 		src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
308 			<< "\n"
309 			<< "layout(location = 0) in  vec4 in_color;\n"
310 			<< "layout(location = 0) out vec4 out_color;\n"
311 			<< "\n"
312 			<< "void main(void)\n"
313 			<< "{\n"
314 			<< "    out_color = in_color;\n"
315 			<< "}\n";
316 
317 		programCollection.glslSources.add("frag") << glu::FragmentSource(src.str());
318 	}
319 }
320 
321 class ScissorRenderer
322 {
323 public:
ScissorRenderer(Context & context,const IVec2 & renderSize,const int numViewports,const std::vector<IVec4> & scissors,const VkFormat colorFormat,const Vec4 & clearColor,const std::vector<Vec4> & vertices)324 	ScissorRenderer (Context&					context,
325 					 const IVec2&				renderSize,
326 					 const int					numViewports,
327 					 const std::vector<IVec4>&	scissors,
328 					 const VkFormat				colorFormat,
329 					 const Vec4&				clearColor,
330 					 const std::vector<Vec4>&	vertices)
331 		: m_renderSize				(renderSize)
332 		, m_colorFormat				(colorFormat)
333 		, m_colorSubresourceRange	(makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u))
334 		, m_clearColor				(clearColor)
335 		, m_numViewports			(numViewports)
336 		, m_vertexBufferSize		(sizeInBytes(vertices))
337 	{
338 		const DeviceInterface&		vk					= context.getDeviceInterface();
339 		const VkDevice				device				= context.getDevice();
340 		const deUint32				queueFamilyIndex	= context.getUniversalQueueFamilyIndex();
341 		Allocator&					allocator			= context.getDefaultAllocator();
342 
343 		m_colorImage		= makeImage				(vk, device, makeImageCreateInfo(m_colorFormat, m_renderSize, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT));
344 		m_colorImageAlloc	= bindImage				(vk, device, allocator, *m_colorImage, MemoryRequirement::Any);
345 		m_colorAttachment	= makeImageView			(vk, device, *m_colorImage, VK_IMAGE_VIEW_TYPE_2D, m_colorFormat, m_colorSubresourceRange);
346 
347 		m_vertexBuffer		= makeBuffer			(vk, device, makeBufferCreateInfo(m_vertexBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT));
348 		m_vertexBufferAlloc	= bindBuffer			(vk, device, allocator, *m_vertexBuffer, MemoryRequirement::HostVisible);
349 
350 		{
351 			deMemcpy(m_vertexBufferAlloc->getHostPtr(), &vertices[0], static_cast<std::size_t>(m_vertexBufferSize));
352 			flushAlloc(vk, device, *m_vertexBufferAlloc);
353 		}
354 
355 		m_vertexModule		= createShaderModule	(vk, device, context.getBinaryCollection().get("vert"), 0u);
356 		m_geometryModule	= createShaderModule	(vk, device, context.getBinaryCollection().get("geom"), 0u);
357 		m_fragmentModule	= createShaderModule	(vk, device, context.getBinaryCollection().get("frag"), 0u);
358 		m_renderPass		= makeRenderPass		(vk, device, m_colorFormat);
359 		m_framebuffer		= makeFramebuffer		(vk, device, *m_renderPass, 1u, &m_colorAttachment.get(),
360 													 static_cast<deUint32>(m_renderSize.x()),  static_cast<deUint32>(m_renderSize.y()));
361 		m_pipelineLayout	= makePipelineLayout	(vk, device);
362 		m_pipeline			= makeGraphicsPipeline	(vk, device, *m_pipelineLayout, *m_renderPass, *m_vertexModule, *m_geometryModule, *m_fragmentModule,
363 													 m_renderSize, m_numViewports, scissors);
364 		m_cmdPool			= createCommandPool		(vk, device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, queueFamilyIndex);
365 		m_cmdBuffer			= allocateCommandBuffer	(vk, device, *m_cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY);
366 	}
367 
draw(Context & context,const VkBuffer colorBuffer) const368 	void draw (Context& context, const VkBuffer colorBuffer) const
369 	{
370 		const DeviceInterface&		vk			= context.getDeviceInterface();
371 		const VkDevice				device		= context.getDevice();
372 		const VkQueue				queue		= context.getUniversalQueue();
373 
374 		beginCommandBuffer(vk, *m_cmdBuffer);
375 
376 		beginRenderPass(vk, *m_cmdBuffer, *m_renderPass, *m_framebuffer, makeRect2D(0, 0, m_renderSize.x(), m_renderSize.y()), m_clearColor);
377 
378 		vk.cmdBindPipeline(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipeline);
379 		{
380 			const VkDeviceSize vertexBufferOffset = 0ull;
381 			vk.cmdBindVertexBuffers(*m_cmdBuffer, 0u, 1u, &m_vertexBuffer.get(), &vertexBufferOffset);
382 		}
383 		vk.cmdDraw(*m_cmdBuffer, static_cast<deUint32>(m_numViewports), 1u, 0u, 0u);	// one vertex per viewport
384 		endRenderPass(vk, *m_cmdBuffer);
385 
386 		copyImageToBuffer(vk, *m_cmdBuffer, *m_colorImage, colorBuffer, m_renderSize);
387 
388 		endCommandBuffer(vk, *m_cmdBuffer);
389 		submitCommandsAndWait(vk, device, queue, *m_cmdBuffer);
390 	}
391 
392 private:
393 	const IVec2						m_renderSize;
394 	const VkFormat					m_colorFormat;
395 	const VkImageSubresourceRange	m_colorSubresourceRange;
396 	const Vec4						m_clearColor;
397 	const int						m_numViewports;
398 	const VkDeviceSize				m_vertexBufferSize;
399 
400 	Move<VkImage>					m_colorImage;
401 	MovePtr<Allocation>				m_colorImageAlloc;
402 	Move<VkImageView>				m_colorAttachment;
403 	Move<VkBuffer>					m_vertexBuffer;
404 	MovePtr<Allocation>				m_vertexBufferAlloc;
405 	Move<VkShaderModule>			m_vertexModule;
406 	Move<VkShaderModule>			m_geometryModule;
407 	Move<VkShaderModule>			m_fragmentModule;
408 	Move<VkRenderPass>				m_renderPass;
409 	Move<VkFramebuffer>				m_framebuffer;
410 	Move<VkPipelineLayout>			m_pipelineLayout;
411 	Move<VkPipeline>				m_pipeline;
412 	Move<VkCommandPool>				m_cmdPool;
413 	Move<VkCommandBuffer>			m_cmdBuffer;
414 
415 	// "deleted"
416 						ScissorRenderer	(const ScissorRenderer&);
417 	ScissorRenderer&	operator=		(const ScissorRenderer&);
418 };
419 
test(Context & context,const int numViewports)420 tcu::TestStatus test (Context& context, const int numViewports)
421 {
422 	requireFeatureMultiViewport(context.getInstanceInterface(), context.getPhysicalDevice());
423 
424 	const DeviceInterface&			vk					= context.getDeviceInterface();
425 	const VkDevice					device				= context.getDevice();
426 	Allocator&						allocator			= context.getDefaultAllocator();
427 
428 	const IVec2						renderSize			(128, 128);
429 	const VkFormat					colorFormat			= VK_FORMAT_R8G8B8A8_UNORM;
430 	const Vec4						clearColor			(0.5f, 0.5f, 0.5f, 1.0f);
431 	const std::vector<Vec4>			vertexColors		= generateColors(numViewports);
432 	const std::vector<IVec4>		scissors			= generateScissors(numViewports, renderSize);
433 
434 	const VkDeviceSize				colorBufferSize		= renderSize.x() * renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat));
435 	const Unique<VkBuffer>			colorBuffer			(makeBuffer(vk, device, makeBufferCreateInfo(colorBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT)));
436 	const UniquePtr<Allocation>		colorBufferAlloc	(bindBuffer(vk, device, allocator, *colorBuffer, MemoryRequirement::HostVisible));
437 
438 	zeroBuffer(vk, device, *colorBufferAlloc, colorBufferSize);
439 
440 	{
441 		context.getTestContext().getLog()
442 			<< tcu::TestLog::Message << "Rendering a colorful grid of " << numViewports << " rectangle(s)." << tcu::TestLog::EndMessage
443 			<< tcu::TestLog::Message << "Not covered area will be filled with a gray color." << tcu::TestLog::EndMessage;
444 	}
445 
446 	// Draw
447 	{
448 		const ScissorRenderer renderer (context, renderSize, numViewports, scissors, colorFormat, clearColor, vertexColors);
449 		renderer.draw(context, *colorBuffer);
450 	}
451 
452 	// Log image
453 	{
454 		invalidateAlloc(vk, device, *colorBufferAlloc);
455 
456 		const tcu::ConstPixelBufferAccess	resultImage		(mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1u, colorBufferAlloc->getHostPtr());
457 		const tcu::TextureLevel				referenceImage	= generateReferenceImage(mapVkFormat(colorFormat), renderSize, clearColor, scissors, vertexColors);
458 
459 		// Images should now match.
460 		if (!tcu::floatThresholdCompare(context.getTestContext().getLog(), "color", "Image compare", referenceImage.getAccess(), resultImage, Vec4(0.02f), tcu::COMPARE_LOG_RESULT))
461 			return tcu::TestStatus::fail("Rendered image is not correct");
462 	}
463 
464 	return tcu::TestStatus::pass("OK");
465 }
466 
467 } // anonymous
468 
createScissorMultiViewportTests(tcu::TestContext & testCtx)469 tcu::TestCaseGroup* createScissorMultiViewportTests	(tcu::TestContext& testCtx)
470 {
471 	MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, "multi_viewport", ""));
472 
473 	for (int numViewports = 1; numViewports <= MIN_MAX_VIEWPORTS; ++numViewports)
474 		addFunctionCaseWithPrograms(group.get(), "scissor_" + de::toString(numViewports), "", initPrograms, test, numViewports);
475 
476 	return group.release();
477 }
478 
479 } // FragmentOperations
480 } // vkt
481