• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2023 The Khronos Group Inc.
6  * Copyright (c) 2023 Valve Corporation.
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 Input Attribute Offset Tests
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktPipelineInputAttributeOffsetTests.hpp"
26 
27 #include "vkBarrierUtil.hpp"
28 #include "vkCmdUtil.hpp"
29 #include "vkImageUtil.hpp"
30 #include "vkMemUtil.hpp"
31 #include "vkObjUtil.hpp"
32 #include "vkQueryUtil.hpp"
33 #include "vkTypeUtil.hpp"
34 
35 #include "tcuImageCompare.hpp"
36 
37 #include <string>
38 #include <sstream>
39 #include <memory>
40 #include <vector>
41 #include <array>
42 
43 namespace vkt
44 {
45 namespace pipeline
46 {
47 
48 using namespace vk;
49 
50 namespace
51 {
52 
53 // StrideCase determines the way we're going to store vertex data in the vertex buffer.
54 //
55 // With packed vertices:
56 //
57 //     Vertex buffer
58 //    +-----+---------------------------------------------------------------------+
59 //    |     +---------------------------------------------------------------------+
60 //    |     |    +--------+--------+                                              |
61 //    |     |    |Attr    |Attr    |                                              |
62 //    |     |    |        |        | ...                                          |
63 //    |     |    +--------+--------+                                              |
64 //    |     +---------------------------------------------------------------------+
65 //    +-----+---------------------------------------------------------------------+
66 //
67 //    -------
68 //    Vertex binding offset
69 //
70 //          ------
71 //          Attribute offset
72 //
73 // With padded vertices:
74 //
75 //     Vertex buffer
76 //    +-----+---------------------------------------------------------------------+
77 //    |     +---------------------------------------------------------------------+
78 //    |     |    +--------+--------+--------+                                     |
79 //    |     |    |Attr    |Pad     |Attr    |                                     |
80 //    |     |    |        |        |        |                                     |
81 //    |     |    +--------+--------+--------+                                     |
82 //    |     +---------------------------------------------------------------------+
83 //    +-----+---------------------------------------------------------------------+
84 //
85 //    -------
86 //    Vertex binding offset
87 //
88 //          ------
89 //          Attribute offset
90 //
91 // With overlapping vertices, the case is similar to packed. However, the data type in the _shader_ will be a Vec4, stored in the
92 // buffer as Vec2's. In the shader, only the XY coordinates are properly used (ZW coordinates would belong to the next vertex).
93 //
94 enum class StrideCase { PACKED = 0, PADDED = 1, OVERLAPPING = 2, };
95 
getTypeSize(glu::DataType dataType)96 uint32_t getTypeSize (glu::DataType dataType)
97 {
98 	switch (dataType)
99 	{
100 	case glu::TYPE_FLOAT_VEC2:	return static_cast<uint32_t>(sizeof(tcu::Vec2));
101 	case glu::TYPE_FLOAT_VEC4:	return static_cast<uint32_t>(sizeof(tcu::Vec4));
102 	default:					break;
103 	}
104 
105 	DE_ASSERT(false);
106 	return 0u;
107 }
108 
109 struct TestParams
110 {
111 	const PipelineConstructionType	constructionType;
112 	const glu::DataType				dataType;			// vec2 or vec4.
113 	const uint32_t					bindingOffset;		// When binding vertex buffer.
114 	const StrideCase				strideCase;			// Pack all data or include some padding.
115 	const bool						useMemoryOffset;	// Apply an offset when binding memory to the buffer.
116 	const bool						dynamic;			// Use dynamic state or not.
117 
attributeSizevkt::pipeline::__anon012a83ec0111::TestParams118 	uint32_t attributeSize (void) const
119 	{
120 		return getTypeSize(dataType);
121 	}
122 
isOverlappingvkt::pipeline::__anon012a83ec0111::TestParams123 	bool isOverlapping (void) const
124 	{
125 		return (strideCase == StrideCase::OVERLAPPING);
126 	}
127 
attributeFormatvkt::pipeline::__anon012a83ec0111::TestParams128 	VkFormat attributeFormat (void) const
129 	{
130 		switch (dataType)
131 		{
132 		case glu::TYPE_FLOAT_VEC2:	return (isOverlapping() ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_R32G32_SFLOAT);
133 		case glu::TYPE_FLOAT_VEC4:	return VK_FORMAT_R32G32B32A32_SFLOAT;
134 		default:					break;
135 		}
136 
137 		DE_ASSERT(false);
138 		return VK_FORMAT_UNDEFINED;
139 	}
140 
141 	// Given the vertex buffer binding offset, calculate the appropriate attribute offset to make them aligned.
attributeOffsetvkt::pipeline::__anon012a83ec0111::TestParams142 	uint32_t attributeOffset (void) const
143 	{
144 		const auto attribSize = attributeSize();
145 		DE_ASSERT(bindingOffset < attribSize);
146 		return ((attribSize - bindingOffset) % attribSize);
147 	}
148 
149 	// Calculates proper padding size between elements according to strideCase.
vertexDataPaddingvkt::pipeline::__anon012a83ec0111::TestParams150 	uint32_t vertexDataPadding (void) const
151 	{
152 		if (strideCase == StrideCase::PADDED)
153 			return attributeSize();
154 		return 0u;
155 	}
156 
157 	// Calculates proper binding stride according to strideCase.
bindingStridevkt::pipeline::__anon012a83ec0111::TestParams158 	uint32_t bindingStride (void) const
159 	{
160 		return attributeSize() + vertexDataPadding();
161 	}
162 };
163 
164 using VertexVec	= std::vector<tcu::Vec2>;
165 using BytesVec	= std::vector<uint8_t>;
166 
buildVertexBufferData(const VertexVec & origVertices,const TestParams & params)167 BytesVec buildVertexBufferData (const VertexVec& origVertices, const TestParams& params)
168 {
169 	DE_ASSERT(!origVertices.empty());
170 
171 	VertexVec vertices (origVertices);
172 
173 	if (params.isOverlapping())
174 	{
175 		// Each vertex will be read as a vec4, so we need one extra element at the end to make the last vec4 read valid and avoid going beyond the end of the buffer.
176 		DE_ASSERT(params.dataType == glu::TYPE_FLOAT_VEC2);
177 		vertices.push_back(tcu::Vec2(0.0f, 0.0f));
178 	}
179 
180 	const auto			vertexCount		= de::sizeU32(vertices);
181 	const auto			dataSize		= params.bindingOffset + params.attributeOffset() + vertexCount * params.bindingStride();
182 	const tcu::Vec2		zw				(0.0f, 1.0f);
183 	const auto			zwSize			= static_cast<uint32_t>(sizeof(zw));
184 	const auto			srcVertexSize	= static_cast<uint32_t>(sizeof(VertexVec::value_type));
185 	const bool			needsZW			(params.attributeSize() > srcVertexSize); // vec4 needs each vec2 with zw appended.
186 	const auto			paddingSize		= params.vertexDataPadding();
187 	BytesVec			data			(dataSize, uint8_t{0});
188 
189 	uint8_t* nextVertexPtr = data.data() + params.bindingOffset + params.attributeOffset();
190 
191 	for (uint32_t vertexIdx = 0u; vertexIdx < vertexCount; ++vertexIdx)
192 	{
193 		// Copy vertex.
194 		deMemcpy(nextVertexPtr, &vertices.at(vertexIdx), srcVertexSize);
195 		nextVertexPtr += srcVertexSize;
196 
197 		// Copy extra ZW values if needed.
198 		if (needsZW)
199 		{
200 			deMemcpy(nextVertexPtr, &zw, zwSize);
201 			nextVertexPtr += zwSize;
202 		}
203 
204 		// Skip the padding bytes.
205 		nextVertexPtr += paddingSize;
206 	}
207 
208 	return data;
209 }
210 
getDefaultColor(void)211 tcu::Vec4 getDefaultColor (void)
212 {
213 	return tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
214 }
215 
getClearColor(void)216 tcu::Vec4 getClearColor (void)
217 {
218 	return tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f);
219 }
220 
getDefaultExtent(void)221 tcu::IVec3 getDefaultExtent (void)
222 {
223 	return tcu::IVec3(4, 4, 1); // Multiple pixels and vertices, not too big.
224 }
225 
226 // Generate one triangle per pixel.
generateVertices(uint32_t width,uint32_t height)227 VertexVec generateVertices (uint32_t width, uint32_t height)
228 {
229 	VertexVec vertices;
230 	vertices.reserve(width * height * 3u); // 3 points (1 triangle) per pixel.
231 
232 	// Normalized pixel width and height.
233 	const auto pixelWidth	= 2.0f / static_cast<float>(width);
234 	const auto pixelHeight	= 2.0f / static_cast<float>(height);
235 	const auto widthMargin	= pixelWidth / 4.0f;
236 	const auto heightMargin	= pixelHeight / 4.0f;
237 
238 	for (uint32_t y = 0; y < height; ++y)
239 		for (uint32_t x = 0; x < width; ++x)
240 		{
241 			// Normalized pixel center.
242 			const auto pixelCenterX = ((static_cast<float>(x) + 0.5f) / static_cast<float>(width)) * 2.0f - 1.0f;
243 			const auto pixelCenterY = ((static_cast<float>(y) + 0.5f) / static_cast<float>(height)) * 2.0f - 1.0f;
244 
245 			vertices.push_back(tcu::Vec2(pixelCenterX, pixelCenterY - heightMargin));				// Top
246 			vertices.push_back(tcu::Vec2(pixelCenterX - widthMargin, pixelCenterY + heightMargin));	// Bottom left.
247 			vertices.push_back(tcu::Vec2(pixelCenterX + widthMargin, pixelCenterY + heightMargin));	// Bottom right.
248 		}
249 
250 	return vertices;
251 }
252 
253 class InputAttributeOffsetCase : public vkt::TestCase
254 {
255 public:
InputAttributeOffsetCase(tcu::TestContext & testCtx,const std::string & name,const TestParams & params)256 					InputAttributeOffsetCase	(tcu::TestContext& testCtx, const std::string& name, const TestParams& params)
257 						: vkt::TestCase	(testCtx, name)
258 						, m_params		(params)
259 						{}
~InputAttributeOffsetCase(void)260 	virtual			~InputAttributeOffsetCase	(void) {}
261 	void			initPrograms				(vk::SourceCollections& programCollection) const override;
262 	TestInstance*	createInstance				(Context& context) const override;
263 	void			checkSupport				(Context& context) const override;
264 
265 protected:
266 	const TestParams m_params;
267 };
268 
269 class InputAttributeOffsetInstance : public vkt::TestInstance
270 {
271 public:
InputAttributeOffsetInstance(Context & context,const TestParams & params)272 						InputAttributeOffsetInstance	(Context& context, const TestParams& params)
273 							: vkt::TestInstance (context)
274 							, m_params(params)
275 							{}
~InputAttributeOffsetInstance(void)276 	virtual				~InputAttributeOffsetInstance	(void) {}
277 	tcu::TestStatus		iterate							(void) override;
278 
279 protected:
280 	const TestParams m_params;
281 };
282 
createInstance(Context & context) const283 TestInstance* InputAttributeOffsetCase::createInstance (Context& context) const
284 {
285 	return new InputAttributeOffsetInstance(context, m_params);
286 }
287 
checkSupport(Context & context) const288 void InputAttributeOffsetCase::checkSupport (Context &context) const
289 {
290 	const auto&			vki				= context.getInstanceInterface();
291 	const auto			physicalDevice	= context.getPhysicalDevice();
292 
293 	checkPipelineConstructionRequirements(vki, physicalDevice, m_params.constructionType);
294 
295 #ifndef CTS_USES_VULKANSC
296 	if (context.isDeviceFunctionalitySupported("VK_KHR_portability_subset"))
297 	{
298 		const auto&	properties		= context.getPortabilitySubsetProperties();
299 		const auto&	minStrideAlign	= properties.minVertexInputBindingStrideAlignment;
300 		const auto	bindingStride	= m_params.bindingStride();
301 
302 		if (bindingStride < minStrideAlign || bindingStride % minStrideAlign != 0u)
303 			TCU_THROW(NotSupportedError, "Binding stride " + std::to_string(bindingStride) + " not a multiple of " + std::to_string(minStrideAlign));
304 	}
305 #endif // CTS_USES_VULKANSC
306 
307 	if (m_params.dynamic)
308 		context.requireDeviceFunctionality("VK_EXT_vertex_input_dynamic_state");
309 }
310 
initPrograms(vk::SourceCollections & programCollection) const311 void InputAttributeOffsetCase::initPrograms (vk::SourceCollections& programCollection) const
312 {
313 	{
314 		std::ostringstream frag;
315 		frag
316 			<< "#version 460\n"
317 			<< "layout (location=0) out vec4 outColor;\n"
318 			<< "void main (void) { outColor = vec4" << getDefaultColor() << "; }\n"
319 			;
320 		programCollection.glslSources.add("frag") << glu::FragmentSource(frag.str());
321 	}
322 
323 	{
324 		const auto extraComponents	= ((m_params.dataType == glu::TYPE_FLOAT_VEC4)
325 									? ""
326 									: ((m_params.isOverlapping())
327 									// Simulate that we use the .zw components in order to force the implementation to read them.
328 									? ", floor(abs(inPos.z) / 1000.0), (floor(abs(inPos.w) / 2500.0) + 1.0)" // Should result in 0.0, 1.0.
329 									:", 0.0, 1.0"));
330 		const auto componentSelect	= (m_params.isOverlapping() ? ".xy" : "");
331 
332 		std::ostringstream vert;
333 		vert
334 			<< "#version 460\n"
335 			<< "layout (location=0) in " << glu::getDataTypeName(m_params.isOverlapping() ? glu::TYPE_FLOAT_VEC4 : m_params.dataType) << " inPos;\n"
336 			<< "void main (void) { gl_Position = vec4(inPos" << componentSelect << extraComponents << "); }\n"
337 			;
338 		programCollection.glslSources.add("vert") << glu::VertexSource(vert.str());
339 	}
340 }
341 
iterate(void)342 tcu::TestStatus InputAttributeOffsetInstance::iterate (void)
343 {
344 	const auto				ctx					= m_context.getContextCommonData();
345 	const auto				fbExtent			= getDefaultExtent();
346 	const auto				vkExtent			= makeExtent3D(fbExtent);
347 	const auto				vertices			= generateVertices(vkExtent.width, vkExtent.height);
348 	const auto				vertexBufferData	= buildVertexBufferData(vertices, m_params);
349 	const auto				colorFormat			= VK_FORMAT_R8G8B8A8_UNORM;
350 	const auto				colorUsage			= (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
351 
352 	// Vertex buffer.
353 	const auto	vertexBufferSize	= static_cast<VkDeviceSize>(de::dataSize(vertexBufferData));
354 	const auto	vertexBufferInfo	= makeBufferCreateInfo(vertexBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
355 	const auto	vertexBuffer		= makeBuffer(ctx.vkd, ctx.device, vertexBufferInfo);
356 	const auto	vertexBufferOffset	= static_cast<VkDeviceSize>(m_params.bindingOffset);
357 
358 	// Allocate and bind buffer memory.
359 	// If useMemoryOffset is true, we'll allocate extra memory that satisfies alignment requirements for the buffer and the attributes.
360 	auto		vertexBufferReqs	= getBufferMemoryRequirements(ctx.vkd, ctx.device, *vertexBuffer);
361 	const auto	memoryOffset		= (m_params.useMemoryOffset ? (de::lcm(vertexBufferReqs.alignment, static_cast<VkDeviceSize>(m_params.attributeSize()))) : 0ull);
362 	vertexBufferReqs.size			+= memoryOffset;
363 	auto		vertexBufferAlloc	= ctx.allocator.allocate(vertexBufferReqs, MemoryRequirement::HostVisible);
364 	VK_CHECK(ctx.vkd.bindBufferMemory(ctx.device, *vertexBuffer, vertexBufferAlloc->getMemory(), memoryOffset));
365 
366 	// Copy vertices to vertex buffer.
367 	const auto dstPtr = reinterpret_cast<char*>(vertexBufferAlloc->getHostPtr()) + memoryOffset; // Need to add offset manually here.
368 	deMemcpy(dstPtr, de::dataOrNull(vertexBufferData), de::dataSize(vertexBufferData));
369 	flushAlloc(ctx.vkd, ctx.device, *vertexBufferAlloc);
370 
371 	// Color buffer.
372 	ImageWithBuffer colorBuffer(ctx.vkd, ctx.device, ctx.allocator, vkExtent, colorFormat, colorUsage, VK_IMAGE_TYPE_2D);
373 
374 	// Render pass and framebuffer.
375 	auto renderPass	= RenderPassWrapper(m_params.constructionType, ctx.vkd, ctx.device, colorFormat);
376 	renderPass.createFramebuffer(ctx.vkd, ctx.device, colorBuffer.getImage(), colorBuffer.getImageView(), vkExtent.width, vkExtent.height);
377 
378 	// Shaders.
379 	const auto&	binaries	= m_context.getBinaryCollection();
380 	const auto	vertModule	= ShaderWrapper(ctx.vkd, ctx.device, binaries.get("vert"));
381 	const auto	fragModule	= ShaderWrapper(ctx.vkd, ctx.device, binaries.get("frag"));
382 
383 	std::vector<VkDynamicState> dynamicStates;
384 	if (m_params.dynamic)
385 		dynamicStates.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
386 
387 	const VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo =
388 	{
389 		VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,	//	VkStructureType						sType;
390 		nullptr,												//	const void*							pNext;
391 		0u,														//	VkPipelineDynamicStateCreateFlags	flags;
392 		de::sizeU32(dynamicStates),								//	uint32_t							dynamicStateCount;
393 		de::dataOrNull(dynamicStates),							//	const VkDynamicState*				pDynamicStates;
394 	};
395 
396 	// Vertex input values according to test parameters.
397 	const auto vertexInputBinding	= makeVertexInputBindingDescription(0u, m_params.bindingStride(), VK_VERTEX_INPUT_RATE_VERTEX);
398 	const auto vertexInputAttribute	= makeVertexInputAttributeDescription(0u, 0u, m_params.attributeFormat(), m_params.attributeOffset());
399 
400 	using VertexInputStatePtr = std::unique_ptr<VkPipelineVertexInputStateCreateInfo>;
401 	VertexInputStatePtr pipelineVertexInputState;
402 	if (!m_params.dynamic)
403 	{
404 		pipelineVertexInputState.reset(new VkPipelineVertexInputStateCreateInfo);
405 		*pipelineVertexInputState									= initVulkanStructure();
406 		pipelineVertexInputState->vertexBindingDescriptionCount		= 1u;
407 		pipelineVertexInputState->pVertexBindingDescriptions		= &vertexInputBinding;
408 		pipelineVertexInputState->vertexAttributeDescriptionCount	= 1u;
409 		pipelineVertexInputState->pVertexAttributeDescriptions		= &vertexInputAttribute;
410 	}
411 
412 	const std::vector<VkViewport>	viewports	(1u, makeViewport(vkExtent));
413 	const std::vector<VkRect2D>		scissors	(1u, makeRect2D(vkExtent));
414 
415 	// Pipeline.
416 	const PipelineLayoutWrapper pipelineLayout(m_params.constructionType, ctx.vkd, ctx.device);
417 	GraphicsPipelineWrapper pipelineWrapper (ctx.vki, ctx.vkd, ctx.physicalDevice, ctx.device, m_context.getDeviceExtensions(), m_params.constructionType);
418 	pipelineWrapper
419 		.setMonolithicPipelineLayout(pipelineLayout)
420 		.setDefaultDepthStencilState()
421 		.setDefaultColorBlendState()
422 		.setDefaultRasterizationState()
423 		.setDefaultMultisampleState()
424 		.setDefaultVertexInputState(false)
425 		.setDefaultTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
426 		.setDynamicState(&dynamicStateCreateInfo)
427 		.setupVertexInputState(pipelineVertexInputState.get())
428 		.setupPreRasterizationShaderState(viewports, scissors, pipelineLayout, *renderPass, 0u, vertModule)
429 		.setupFragmentShaderState(pipelineLayout, *renderPass, 0u, fragModule)
430 		.setupFragmentOutputState(*renderPass, 0u)
431 		.buildPipeline();
432 
433 	CommandPoolWithBuffer cmd (ctx.vkd, ctx.device, ctx.qfIndex);
434 	const auto cmdBuffer = *cmd.cmdBuffer;
435 
436 	// Draw and copy image to verification buffer.
437 	beginCommandBuffer(ctx.vkd, cmdBuffer);
438 	{
439 		renderPass.begin(ctx.vkd, cmdBuffer, scissors.at(0u), getClearColor());
440 		pipelineWrapper.bind(cmdBuffer);
441 		ctx.vkd.cmdBindVertexBuffers(cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset);
442 		if (m_params.dynamic)
443 		{
444 			VkVertexInputBindingDescription2EXT dynamicBinding = initVulkanStructure();
445 			dynamicBinding.binding		= vertexInputBinding.binding;
446 			dynamicBinding.inputRate	= vertexInputBinding.inputRate;
447 			dynamicBinding.stride		= vertexInputBinding.stride;
448 			dynamicBinding.divisor		= 1u;
449 
450 			VkVertexInputAttributeDescription2EXT dynamicAttribute = initVulkanStructure();
451 			dynamicAttribute.location	= vertexInputAttribute.location;
452 			dynamicAttribute.binding	= vertexInputAttribute.binding;
453 			dynamicAttribute.format		= vertexInputAttribute.format;
454 			dynamicAttribute.offset		= vertexInputAttribute.offset;
455 
456 			ctx.vkd.cmdSetVertexInputEXT(cmdBuffer, 1u, &dynamicBinding, 1u, &dynamicAttribute);
457 		}
458 		ctx.vkd.cmdDraw(cmdBuffer, de::sizeU32(vertices), 1u, 0u, 0u);
459 		renderPass.end(ctx.vkd, cmdBuffer);
460 	}
461 	{
462 		copyImageToBuffer(
463 			ctx.vkd,
464 			cmdBuffer,
465 			colorBuffer.getImage(),
466 			colorBuffer.getBuffer(),
467 			tcu::IVec2(fbExtent.x(), fbExtent.y()),
468 			VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
469 			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
470 			1u,
471 			VK_IMAGE_ASPECT_COLOR_BIT,
472 			VK_IMAGE_ASPECT_COLOR_BIT,
473 			VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
474 	}
475 	endCommandBuffer(ctx.vkd, cmdBuffer);
476 	submitCommandsAndWait(ctx.vkd, ctx.device, ctx.queue, cmdBuffer);
477 	invalidateAlloc(ctx.vkd, ctx.device, colorBuffer.getBufferAllocation());
478 
479 	// Check color buffer.
480 	auto&								log				= m_context.getTestContext().getLog();
481 	const auto							tcuFormat		= mapVkFormat(colorFormat);
482 	const tcu::ConstPixelBufferAccess	resultAccess	(tcuFormat, fbExtent, colorBuffer.getBufferAllocation().getHostPtr());
483 	const tcu::Vec4						threshold		(0.0f, 0.0f, 0.0f, 0.0f);
484 
485 	if (!tcu::floatThresholdCompare(log, "Result", "", getDefaultColor(), resultAccess, threshold, tcu::COMPARE_LOG_ON_ERROR))
486 		return tcu::TestStatus::fail("Unexpected color buffer contents -- check log for details");
487 
488 	return tcu::TestStatus::pass("Pass");
489 }
490 
491 } // anonymous namespace
492 
createInputAttributeOffsetTests(tcu::TestContext & testCtx,vk::PipelineConstructionType pipelineConstructionType)493 tcu::TestCaseGroup* createInputAttributeOffsetTests (tcu::TestContext& testCtx, vk::PipelineConstructionType pipelineConstructionType)
494 {
495 	using GroupPtr = de::MovePtr<tcu::TestCaseGroup>;
496 	GroupPtr mainGroup (new tcu::TestCaseGroup(testCtx, "input_attribute_offset"));
497 
498 	for (const auto dataType : { glu::TYPE_FLOAT_VEC2, glu::TYPE_FLOAT_VEC4 })
499 	{
500 		const auto typeSize = getTypeSize(dataType);
501 		GroupPtr dataTypeGrp (new tcu::TestCaseGroup(testCtx, glu::getDataTypeName(dataType)));
502 
503 		for (uint32_t offset = 0u; offset < typeSize; ++offset)
504 		{
505 			const auto offsetGrpName = "offset_" + std::to_string(offset);
506 			GroupPtr offsetGrp (new tcu::TestCaseGroup(testCtx, offsetGrpName.c_str()));
507 
508 			for (const auto strideCase : { StrideCase::PACKED, StrideCase::PADDED, StrideCase::OVERLAPPING })
509 			{
510 				if (strideCase == StrideCase::OVERLAPPING && dataType != glu::TYPE_FLOAT_VEC2)
511 					continue;
512 
513 				const std::array<const char*, 3> strideNames { "packed", "padded", "overlapping" };
514 				GroupPtr strideGrp (new tcu::TestCaseGroup(testCtx, strideNames.at(static_cast<int>(strideCase))));
515 
516 				for (const auto useMemoryOffset : { false, true })
517 				{
518 					const std::array<const char*, 2> memoryOffsetGrpNames { "no_memory_offset", "with_memory_offset" };
519 					GroupPtr memoryOffsetGrp (new tcu::TestCaseGroup(testCtx, memoryOffsetGrpNames.at(static_cast<int>(useMemoryOffset))));
520 
521 					for (const auto& dynamic : { false, true })
522 					{
523 						const TestParams params
524 						{
525 							pipelineConstructionType,
526 							dataType,
527 							offset,
528 							strideCase,
529 							useMemoryOffset,
530 							dynamic,
531 						};
532 						const auto testName = (dynamic ? "dynamic" : "static");
533 						memoryOffsetGrp->addChild(new InputAttributeOffsetCase(testCtx, testName, params));
534 					}
535 
536 					strideGrp->addChild(memoryOffsetGrp.release());
537 				}
538 
539 				offsetGrp->addChild(strideGrp.release());
540 			}
541 
542 			dataTypeGrp->addChild(offsetGrp.release());
543 		}
544 
545 		mainGroup->addChild(dataTypeGrp.release());
546 	}
547 
548 	return mainGroup.release();
549 }
550 
551 } // pipeline
552 } // vkt
553