1/*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2016 The Khronos Group Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Negative viewport height (part of VK_KHR_maintenance1)
22 *//*--------------------------------------------------------------------*/
23
24#include "vktDrawNegativeViewportHeightTests.hpp"
25#include "vktDrawCreateInfoUtil.hpp"
26#include "vktDrawImageObjectUtil.hpp"
27#include "vktDrawBufferObjectUtil.hpp"
28#include "vktTestGroupUtil.hpp"
29#include "vktTestCaseUtil.hpp"
30
31#include "vkPrograms.hpp"
32#include "vkTypeUtil.hpp"
33#include "vkImageUtil.hpp"
34
35#include "tcuVector.hpp"
36#include "tcuTextureUtil.hpp"
37#include "tcuImageCompare.hpp"
38
39#include "deSharedPtr.hpp"
40
41namespace vkt
42{
43namespace Draw
44{
45namespace
46{
47using namespace vk;
48using tcu::Vec4;
49using de::SharedPtr;
50using de::MovePtr;
51
52enum Constants
53{
54	WIDTH	= 256,
55	HEIGHT	= WIDTH/2,
56};
57
58struct TestParams
59{
60	VkFrontFace				frontFace;
61	VkCullModeFlagBits		cullMode;
62};
63
64class NegativeViewportHeightTestInstance : public TestInstance
65{
66public:
67									NegativeViewportHeightTestInstance	(Context& context, const TestParams& params);
68	tcu::TestStatus					iterate								(void);
69	tcu::ConstPixelBufferAccess		draw								(const VkViewport viewport);
70	MovePtr<tcu::TextureLevel>		generateReferenceImage				(void) const;
71	bool							isCulled							(const VkFrontFace triangleFace) const;
72
73private:
74	const TestParams				m_params;
75	const VkFormat					m_colorAttachmentFormat;
76	SharedPtr<Image>				m_colorTargetImage;
77	Move<VkImageView>				m_colorTargetView;
78	SharedPtr<Buffer>				m_vertexBuffer;
79	Move<VkRenderPass>				m_renderPass;
80	Move<VkFramebuffer>				m_framebuffer;
81	Move<VkPipelineLayout>			m_pipelineLayout;
82	Move<VkPipeline>				m_pipeline;
83};
84
85NegativeViewportHeightTestInstance::NegativeViewportHeightTestInstance (Context& context, const TestParams& params)
86	: TestInstance				(context)
87	, m_params					(params)
88	, m_colorAttachmentFormat	(VK_FORMAT_R8G8B8A8_UNORM)
89{
90	const DeviceInterface&	vk		= m_context.getDeviceInterface();
91	const VkDevice			device	= m_context.getDevice();
92
93	// Vertex data
94	{
95		std::vector<Vec4> vertexData;
96
97		// CCW triangle
98		vertexData.push_back(Vec4(-0.8f, -0.6f, 0.0f, 1.0f));	//  0-----2
99		vertexData.push_back(Vec4(-0.8f,  0.6f, 0.0f, 1.0f));	//   |  /
100		vertexData.push_back(Vec4(-0.2f, -0.6f, 0.0f, 1.0f));	//  1|/
101
102		// CW triangle
103		vertexData.push_back(Vec4( 0.2f, -0.6f, 0.0f, 1.0f));	//  0-----1
104		vertexData.push_back(Vec4( 0.8f, -0.6f, 0.0f, 1.0f));	//    \  |
105		vertexData.push_back(Vec4( 0.8f,  0.6f, 0.0f, 1.0f));	//      \|2
106
107		const VkDeviceSize dataSize = vertexData.size() * sizeof(Vec4);
108		m_vertexBuffer = Buffer::createAndAlloc(vk, device, BufferCreateInfo(dataSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
109												m_context.getDefaultAllocator(), MemoryRequirement::HostVisible);
110
111		deMemcpy(m_vertexBuffer->getBoundMemory().getHostPtr(), &vertexData[0], static_cast<std::size_t>(dataSize));
112		flushMappedMemoryRange(vk, device, m_vertexBuffer->getBoundMemory().getMemory(), m_vertexBuffer->getBoundMemory().getOffset(), VK_WHOLE_SIZE);
113	}
114
115	// Render pass
116	{
117		const VkExtent3D		targetImageExtent		= { WIDTH, HEIGHT, 1 };
118		const VkImageUsageFlags	targetImageUsageFlags	= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
119
120		const ImageCreateInfo	targetImageCreateInfo(
121			VK_IMAGE_TYPE_2D,						// imageType,
122			m_colorAttachmentFormat,				// format,
123			targetImageExtent,						// extent,
124			1u,										// mipLevels,
125			1u,										// arrayLayers,
126			VK_SAMPLE_COUNT_1_BIT,					// samples,
127			VK_IMAGE_TILING_OPTIMAL,				// tiling,
128			targetImageUsageFlags);					// usage,
129
130		m_colorTargetImage = Image::createAndAlloc(vk, device, targetImageCreateInfo, m_context.getDefaultAllocator());
131
132		RenderPassCreateInfo	renderPassCreateInfo;
133		renderPassCreateInfo.addAttachment(AttachmentDescription(
134			m_colorAttachmentFormat,				// format
135			VK_SAMPLE_COUNT_1_BIT,					// samples
136			VK_ATTACHMENT_LOAD_OP_LOAD,				// loadOp
137			VK_ATTACHMENT_STORE_OP_STORE,			// storeOp
138			VK_ATTACHMENT_LOAD_OP_DONT_CARE,		// stencilLoadOp
139			VK_ATTACHMENT_STORE_OP_DONT_CARE,		// stencilStoreOp
140			VK_IMAGE_LAYOUT_GENERAL,				// initialLayout
141			VK_IMAGE_LAYOUT_GENERAL));				// finalLayout
142
143		const VkAttachmentReference colorAttachmentReference =
144		{
145			0u,
146			VK_IMAGE_LAYOUT_GENERAL
147		};
148
149		renderPassCreateInfo.addSubpass(SubpassDescription(
150			VK_PIPELINE_BIND_POINT_GRAPHICS,		// pipelineBindPoint
151			(VkSubpassDescriptionFlags)0,			// flags
152			0u,										// inputAttachmentCount
153			DE_NULL,								// inputAttachments
154			1u,										// colorAttachmentCount
155			&colorAttachmentReference,				// colorAttachments
156			DE_NULL,								// resolveAttachments
157			AttachmentReference(),					// depthStencilAttachment
158			0u,										// preserveAttachmentCount
159			DE_NULL));								// preserveAttachments
160
161		m_renderPass = createRenderPass(vk, device, &renderPassCreateInfo);
162	}
163
164	// Framebuffer
165	{
166		const ImageViewCreateInfo colorTargetViewInfo (m_colorTargetImage->object(), VK_IMAGE_VIEW_TYPE_2D, m_colorAttachmentFormat);
167		m_colorTargetView = createImageView(vk, device, &colorTargetViewInfo);
168
169		std::vector<VkImageView> colorAttachments(1);
170		colorAttachments[0] = *m_colorTargetView;
171
172		const FramebufferCreateInfo	framebufferCreateInfo(*m_renderPass, colorAttachments, WIDTH, HEIGHT, 1);
173		m_framebuffer = createFramebuffer(vk, device, &framebufferCreateInfo);
174	}
175
176	// Vertex input
177
178	const VkVertexInputBindingDescription		vertexInputBindingDescription =
179	{
180		0u,										// uint32_t             binding;
181		sizeof(Vec4),							// uint32_t             stride;
182		VK_VERTEX_INPUT_RATE_VERTEX,			// VkVertexInputRate    inputRate;
183	};
184
185	const VkVertexInputAttributeDescription		vertexInputAttributeDescription =
186	{
187		0u,										// uint32_t    location;
188		0u,										// uint32_t    binding;
189		VK_FORMAT_R32G32B32A32_SFLOAT,			// VkFormat    format;
190		0u										// uint32_t    offset;
191	};
192
193	const PipelineCreateInfo::VertexInputState	vertexInputState = PipelineCreateInfo::VertexInputState(1, &vertexInputBindingDescription,
194																										1, &vertexInputAttributeDescription);
195
196	// Graphics pipeline
197
198	const VkRect2D scissor =
199	{
200		{ 0,		0		},		// x, y
201		{ WIDTH,	HEIGHT	},		// width, height
202	};
203
204	std::vector<VkDynamicState>		dynamicStates;
205	dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT);
206
207	const Unique<VkShaderModule>	vertexModule	(createShaderModule(vk, device, m_context.getBinaryCollection().get("vert"), 0));
208	const Unique<VkShaderModule>	fragmentModule	(createShaderModule(vk, device, m_context.getBinaryCollection().get("frag"), 0));
209
210	const PipelineLayoutCreateInfo	pipelineLayoutCreateInfo;
211	m_pipelineLayout = createPipelineLayout(vk, device, &pipelineLayoutCreateInfo);
212
213	const PipelineCreateInfo::ColorBlendState::Attachment colorBlendAttachmentState;
214
215	PipelineCreateInfo pipelineCreateInfo(*m_pipelineLayout, *m_renderPass, 0, (VkPipelineCreateFlags)0);
216	pipelineCreateInfo.addShader(PipelineCreateInfo::PipelineShaderStage(*vertexModule,   "main", VK_SHADER_STAGE_VERTEX_BIT));
217	pipelineCreateInfo.addShader(PipelineCreateInfo::PipelineShaderStage(*fragmentModule, "main", VK_SHADER_STAGE_FRAGMENT_BIT));
218	pipelineCreateInfo.addState (PipelineCreateInfo::VertexInputState	(vertexInputState));
219	pipelineCreateInfo.addState (PipelineCreateInfo::InputAssemblerState(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST));
220	pipelineCreateInfo.addState (PipelineCreateInfo::ColorBlendState	(1, &colorBlendAttachmentState));
221	pipelineCreateInfo.addState (PipelineCreateInfo::ViewportState		(1, std::vector<VkViewport>(), std::vector<VkRect2D>(1, scissor)));
222	pipelineCreateInfo.addState (PipelineCreateInfo::DepthStencilState	());
223	pipelineCreateInfo.addState (PipelineCreateInfo::RasterizerState	(
224		VK_FALSE,					// depthClampEnable
225		VK_FALSE,					// rasterizerDiscardEnable
226		VK_POLYGON_MODE_FILL,		// polygonMode
227		m_params.cullMode,			// cullMode
228		m_params.frontFace,			// frontFace
229		VK_FALSE,					// depthBiasEnable
230		0.0f,						// depthBiasConstantFactor
231		0.0f,						// depthBiasClamp
232		0.0f,						// depthBiasSlopeFactor
233		1.0f));						// lineWidth
234	pipelineCreateInfo.addState (PipelineCreateInfo::MultiSampleState	());
235	pipelineCreateInfo.addState (PipelineCreateInfo::DynamicState		(dynamicStates));
236
237	m_pipeline = createGraphicsPipeline(vk, device, DE_NULL, &pipelineCreateInfo);
238}
239
240tcu::ConstPixelBufferAccess NegativeViewportHeightTestInstance::draw (const VkViewport viewport)
241{
242	const DeviceInterface&	vk					= m_context.getDeviceInterface();
243	const VkDevice			device				= m_context.getDevice();
244	const VkQueue			queue				= m_context.getUniversalQueue();
245	const deUint32			queueFamilyIndex	= m_context.getUniversalQueueFamilyIndex();
246
247	// Command buffer
248
249	const CmdPoolCreateInfo		cmdPoolCreateInfo	(queueFamilyIndex);
250	const Unique<VkCommandPool>	cmdPool				(createCommandPool(vk, device, &cmdPoolCreateInfo));
251
252	const VkCommandBufferAllocateInfo cmdBufferAllocateInfo =
253	{
254		VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,		// VkStructureType			sType;
255		DE_NULL,											// const void*				pNext;
256		*cmdPool,											// VkCommandPool			commandPool;
257		VK_COMMAND_BUFFER_LEVEL_PRIMARY,					// VkCommandBufferLevel		level;
258		1u,													// deUint32					bufferCount;
259	};
260	const Unique<VkCommandBuffer> cmdBuffer(allocateCommandBuffer(vk, device, &cmdBufferAllocateInfo));
261
262	// Draw
263
264	{
265		const CmdBufferBeginInfo beginInfo;
266		vk.beginCommandBuffer(*cmdBuffer, &beginInfo);
267	}
268
269	vk.cmdSetViewport(*cmdBuffer, 0u, 1u, &viewport);
270
271	{
272		const VkClearColorValue		clearColor			= makeClearValueColorF32(0.0f, 0.0f, 0.0f, 1.0f).color;
273		const ImageSubresourceRange subresourceRange	(VK_IMAGE_ASPECT_COLOR_BIT);
274
275		initialTransitionColor2DImage(vk, *cmdBuffer, m_colorTargetImage->object(), VK_IMAGE_LAYOUT_GENERAL);
276		vk.cmdClearColorImage(*cmdBuffer, m_colorTargetImage->object(), VK_IMAGE_LAYOUT_GENERAL, &clearColor, 1, &subresourceRange);
277	}
278	{
279		const VkMemoryBarrier memBarrier =
280		{
281			VK_STRUCTURE_TYPE_MEMORY_BARRIER,												// VkStructureType    sType;
282			DE_NULL,																		// const void*        pNext;
283			VK_ACCESS_TRANSFER_WRITE_BIT,													// VkAccessFlags      srcAccessMask;
284			VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT		// VkAccessFlags      dstAccessMask;
285		};
286
287		vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 1, &memBarrier, 0, DE_NULL, 0, DE_NULL);
288	}
289	{
290		const VkRect2D				renderArea		= { { 0, 0 }, { WIDTH, HEIGHT } };
291		const RenderPassBeginInfo	renderPassBegin	(*m_renderPass, *m_framebuffer, renderArea);
292
293		vk.cmdBeginRenderPass(*cmdBuffer, &renderPassBegin, VK_SUBPASS_CONTENTS_INLINE);
294	}
295	{
296		const VkDeviceSize	offset	= 0;
297		const VkBuffer		buffer	= m_vertexBuffer->object();
298
299		vk.cmdBindVertexBuffers(*cmdBuffer, 0, 1, &buffer, &offset);
300	}
301
302	vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipeline);
303	vk.cmdDraw(*cmdBuffer, 6, 1, 0, 0);
304	vk.cmdEndRenderPass(*cmdBuffer);
305	vk.endCommandBuffer(*cmdBuffer);
306
307	// Submit
308	{
309		const VkFenceCreateInfo fenceInfo	=
310		{
311			VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,		// VkStructureType       sType;
312			DE_NULL,									// const void*           pNext;
313			(VkFenceCreateFlags)0,						// VkFenceCreateFlags    flags;
314		};
315		const Unique<VkFence>	fence		(createFence(vk, device, &fenceInfo));
316		const VkSubmitInfo		submitInfo	=
317		{
318			VK_STRUCTURE_TYPE_SUBMIT_INFO,				// VkStructureType                sType;
319			DE_NULL,									// const void*                    pNext;
320			0,											// uint32_t                       waitSemaphoreCount;
321			DE_NULL,									// const VkSemaphore*             pWaitSemaphores;
322			(const VkPipelineStageFlags*)DE_NULL,		// const VkPipelineStageFlags*    pWaitDstStageMask;
323			1,											// uint32_t                       commandBufferCount;
324			&cmdBuffer.get(),							// const VkCommandBuffer*         pCommandBuffers;
325			0,											// uint32_t                       signalSemaphoreCount;
326			DE_NULL										// const VkSemaphore*             pSignalSemaphores;
327		};
328
329		VK_CHECK(vk.queueSubmit(queue, 1, &submitInfo, *fence));
330		VK_CHECK(vk.waitForFences(device, 1u, &fence.get(), VK_TRUE, ~0ull));
331	}
332
333	// Get result
334	{
335		const VkOffset3D zeroOffset = { 0, 0, 0 };
336		return m_colorTargetImage->readSurface(queue, m_context.getDefaultAllocator(), VK_IMAGE_LAYOUT_GENERAL, zeroOffset, WIDTH, HEIGHT, VK_IMAGE_ASPECT_COLOR_BIT);
337	}
338}
339
340//! Determine if a triangle with triangleFace orientation will be culled or not
341bool NegativeViewportHeightTestInstance::isCulled (const VkFrontFace triangleFace) const
342{
343	const bool isFrontFacing = (triangleFace == m_params.frontFace);
344
345	if (m_params.cullMode == VK_CULL_MODE_FRONT_BIT && isFrontFacing)
346		return true;
347	if (m_params.cullMode == VK_CULL_MODE_BACK_BIT  && !isFrontFacing)
348		return true;
349
350	return m_params.cullMode == VK_CULL_MODE_FRONT_AND_BACK;
351}
352
353MovePtr<tcu::TextureLevel> NegativeViewportHeightTestInstance::generateReferenceImage (void) const
354{
355	DE_ASSERT(HEIGHT == WIDTH/2);
356
357	MovePtr<tcu::TextureLevel>		image	(new tcu::TextureLevel(mapVkFormat(m_colorAttachmentFormat), WIDTH, HEIGHT));
358	const tcu::PixelBufferAccess	access	(image->getAccess());
359	const Vec4						black	(0.0f, 0.0f, 0.0f, 1.0f);
360	const Vec4						white	(1.0f);
361	const Vec4						gray	(0.5f, 0.5f, 0.5f, 1.0f);
362
363	tcu::clear(access, black);
364
365	const int p1 =      static_cast<int>(static_cast<float>(HEIGHT) * (1.0f - 0.6f) / 2.0f);
366	const int p2 = p1 + static_cast<int>(static_cast<float>(HEIGHT) * (2.0f * 0.6f) / 2.0f);
367
368	// left triangle (CCW -> CW after y-flip)
369	if (!isCulled(VK_FRONT_FACE_CLOCKWISE))
370	{
371		const Vec4& color = (m_params.frontFace == VK_FRONT_FACE_CLOCKWISE ? white : gray);
372
373		for (int y = p1; y <= p2; ++y)
374		for (int x = p1; x <  y;  ++x)
375			access.setPixel(color, x, y);
376	}
377
378	// right triangle (CW -> CCW after y-flip)
379	if (!isCulled(VK_FRONT_FACE_COUNTER_CLOCKWISE))
380	{
381		const Vec4& color = (m_params.frontFace == VK_FRONT_FACE_COUNTER_CLOCKWISE ? white : gray);
382
383		for (int y = p1;        y <= p2;          ++y)
384		for (int x = WIDTH - y; x <  p2 + HEIGHT; ++x)
385			access.setPixel(color, x, y);
386	}
387
388	return image;
389}
390
391std::string getCullModeStr (const VkCullModeFlagBits cullMode)
392{
393	// Cull mode flags are a bit special, because there's a meaning to 0 and or'ed flags.
394	// The function getCullModeFlagsStr() doesn't work too well in this case.
395
396	switch (cullMode)
397	{
398		case VK_CULL_MODE_NONE:				return "VK_CULL_MODE_NONE";
399		case VK_CULL_MODE_FRONT_BIT:		return "VK_CULL_MODE_FRONT_BIT";
400		case VK_CULL_MODE_BACK_BIT:			return "VK_CULL_MODE_BACK_BIT";
401		case VK_CULL_MODE_FRONT_AND_BACK:	return "VK_CULL_MODE_FRONT_AND_BACK";
402
403		default:
404			DE_ASSERT(0);
405			return std::string();
406	}
407}
408
409tcu::TestStatus NegativeViewportHeightTestInstance::iterate (void)
410{
411	// Check requirements
412
413	if (!de::contains(m_context.getDeviceExtensions().begin(), m_context.getDeviceExtensions().end(), std::string("VK_KHR_maintenance1")))
414		TCU_THROW(NotSupportedError, "Missing extension: VK_KHR_maintenance1");
415
416	// Set up the viewport and draw
417
418	const VkViewport viewport =
419	{
420		0.0f,							// float    x;
421		static_cast<float>(HEIGHT),		// float    y;
422		static_cast<float>(WIDTH),		// float    width;
423		-static_cast<float>(HEIGHT),	// float    height;
424		0.0f,							// float    minDepth;
425		1.0f,							// float    maxDepth;
426	};
427
428	const tcu::ConstPixelBufferAccess	resultImage	= draw(viewport);
429
430	// Verify the results
431
432	tcu::TestLog&				log				= m_context.getTestContext().getLog();
433	MovePtr<tcu::TextureLevel>	referenceImage	= generateReferenceImage();
434
435	log << tcu::TestLog::Message
436		<< "Drawing two triangles with negative viewport height, which will cause a y-flip. This changes the sign of the triangle's area."
437		<< tcu::TestLog::EndMessage;
438	log << tcu::TestLog::Message
439		<< "After the flip, the triangle on the left is CW and the triangle on the right is CCW. Right angles of the both triangles should be at the bottom of the image."
440		<< " Front face is white, back face is gray."
441		<< tcu::TestLog::EndMessage;
442	log << tcu::TestLog::Message
443		<< "Front face: " << getFrontFaceName(m_params.frontFace) << "\n"
444		<< "Cull mode: "  << getCullModeStr  (m_params.cullMode)  << "\n"
445		<< tcu::TestLog::EndMessage;
446
447	if (!tcu::fuzzyCompare(log, "Image compare", "Image compare", referenceImage->getAccess(), resultImage, 0.02f, tcu::COMPARE_LOG_RESULT))
448		return tcu::TestStatus::fail("Rendered image is incorrect");
449	else
450		return tcu::TestStatus::pass("Pass");
451}
452
453class NegativeViewportHeightTest : public TestCase
454{
455public:
456	NegativeViewportHeightTest (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const TestParams& params)
457		: TestCase	(testCtx, name, description)
458		, m_params	(params)
459	{
460	}
461
462	void initPrograms (SourceCollections& programCollection) const
463	{
464		// Vertex shader
465		{
466			std::ostringstream src;
467			src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
468				<< "\n"
469				<< "layout(location = 0) in vec4 in_position;\n"
470				<< "\n"
471				<< "out gl_PerVertex {\n"
472				<< "    vec4  gl_Position;\n"
473				<< "};\n"
474				<< "\n"
475				<< "void main(void)\n"
476				<< "{\n"
477				<< "    gl_Position = in_position;\n"
478				<< "}\n";
479
480			programCollection.glslSources.add("vert") << glu::VertexSource(src.str());
481		}
482
483		// Fragment shader
484		{
485			std::ostringstream src;
486			src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_450) << "\n"
487				<< "\n"
488				<< "layout(location = 0) out vec4 out_color;\n"
489				<< "\n"
490				<< "void main(void)\n"
491				<< "{\n"
492				<< "    if (gl_FrontFacing)\n"
493				<< "        out_color = vec4(1.0);\n"
494				<< "    else\n"
495				<< "        out_color = vec4(vec3(0.5), 1.0);\n"
496				<< "}\n";
497
498			programCollection.glslSources.add("frag") << glu::FragmentSource(src.str());
499		}
500	}
501
502	virtual TestInstance* createInstance (Context& context) const
503	{
504		return new NegativeViewportHeightTestInstance(context, m_params);
505	}
506
507private:
508	const TestParams	m_params;
509};
510
511void populateTestGroup (tcu::TestCaseGroup* testGroup)
512{
513	const struct
514	{
515		const char* const	name;
516		VkFrontFace			frontFace;
517	} frontFace[] =
518	{
519		{ "front_ccw",	VK_FRONT_FACE_COUNTER_CLOCKWISE	},
520		{ "front_cw",	VK_FRONT_FACE_CLOCKWISE			},
521	};
522
523	const struct
524	{
525		const char* const	name;
526		VkCullModeFlagBits	cullMode;
527	} cullMode[] =
528	{
529		{ "cull_none",	VK_CULL_MODE_NONE			},
530		{ "cull_front",	VK_CULL_MODE_FRONT_BIT		},
531		{ "cull_back",	VK_CULL_MODE_BACK_BIT		},
532		{ "cull_both",	VK_CULL_MODE_FRONT_AND_BACK	},
533	};
534
535	for (int ndxFrontFace = 0; ndxFrontFace < DE_LENGTH_OF_ARRAY(frontFace); ++ndxFrontFace)
536	for (int ndxCullMode  = 0; ndxCullMode  < DE_LENGTH_OF_ARRAY(cullMode);  ++ndxCullMode)
537	{
538		const TestParams params =
539		{
540			frontFace[ndxFrontFace].frontFace,
541			cullMode[ndxCullMode].cullMode,
542		};
543		std::ostringstream	name;
544		name << frontFace[ndxFrontFace].name << "_" << cullMode[ndxCullMode].name;
545
546		testGroup->addChild(new NegativeViewportHeightTest(testGroup->getTestContext(), name.str(), "", params));
547	}
548}
549
550}	// anonymous
551
552tcu::TestCaseGroup*	createNegativeViewportHeightTests (tcu::TestContext& testCtx)
553{
554	return createTestGroup(testCtx, "negative_viewport_height", "Negative viewport height (VK_KHR_maintenance1)", populateTestGroup);
555}
556
557}	// Draw
558}	// vkt
559