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