1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2019 The Khronos Group Inc.
6 * Copyright (c) 2019 Google Inc.
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 Tests for multiple interpolation decorations in a shader stage
23 *//*--------------------------------------------------------------------*/
24
25 #include "vktDrawMultipleInterpolationTests.hpp"
26
27 #include "tcuStringTemplate.hpp"
28 #include "vkCmdUtil.hpp"
29 #include "vkQueryUtil.hpp"
30 #include "vkTypeUtil.hpp"
31 #include "vkQueryUtil.hpp"
32 #include "vktDrawBaseClass.hpp"
33 #include "vktTestGroupUtil.hpp"
34 #include "tcuVectorUtil.hpp"
35
36 namespace vkt
37 {
38 namespace Draw
39 {
40 namespace
41 {
42
43 enum Interpolation
44 {
45 SMOOTH = 0,
46 FLAT = 1,
47 NOPERSPECTIVE = 2,
48 CENTROID = 3,
49 SAMPLE = 4,
50 COUNT = 5,
51 };
52
53 struct DrawParams
54 {
55 vk::VkFormat format;
56 tcu::UVec2 size;
57 vk::VkSampleCountFlagBits samples;
58 // From the SPIR-V point of view, structured test variants will allow us to test interpolation decorations on struct members
59 // instead of plain ids.
60 bool useStructure;
61 bool includeSampleDecoration;
62 const SharedGroupParams groupParams;
63 };
64
65 template<typename T>
makeSharedPtr(vk::Move<T> move)66 inline de::SharedPtr<vk::Move<T> > makeSharedPtr(vk::Move<T> move)
67 {
68 return de::SharedPtr<vk::Move<T> >(new vk::Move<T>(move));
69 }
70
interpolationToString(Interpolation interpolation)71 const char* interpolationToString (Interpolation interpolation)
72 {
73 switch (interpolation)
74 {
75 case SMOOTH:
76 return "smooth";
77 case FLAT:
78 return "flat";
79 case NOPERSPECTIVE:
80 return "noperspective";
81 case CENTROID:
82 return "centroid";
83 case SAMPLE:
84 return "sample";
85 default:
86 DE_FATAL("Invalid interpolation enum");
87 }
88
89 return "";
90 }
91
92 class DrawTestInstance : public TestInstance
93 {
94 public:
95 DrawTestInstance (Context& context, DrawParams params);
96 void render (de::SharedPtr<Image>& colorTargetImage,
97 tcu::ConstPixelBufferAccess* frame,
98 const char* vsName,
99 const char* fsName,
100 Interpolation interpolation,
101 bool sampleRateShading);
102 bool compare (const tcu::ConstPixelBufferAccess& result,
103 const tcu::ConstPixelBufferAccess& reference);
104 tcu::TestStatus iterate (void);
105
106 protected:
107 void preRenderCommands (vk::VkCommandBuffer cmdBuffer, vk::VkImage colorTargetImage) const;
108 void drawCommands (vk::VkCommandBuffer cmdBuffer, vk::VkPipeline pipeline, vk::VkPipelineLayout pipelineLayout,
109 vk::VkBuffer vertexBuffer, deUint32 pcData) const;
110
111 #ifndef CTS_USES_VULKANSC
112 void beginSecondaryCmdBuffer (vk::VkCommandBuffer cmdBuffer, vk::VkRenderingFlagsKHR renderingFlags = 0u) const;
113 void beginDynamicRender (vk::VkCommandBuffer cmdBuffer, vk::VkRect2D renderArea,
114 vk::VkClearValue clearValue, vk::VkRenderingFlagsKHR renderingFlags = 0u) const;
115 #endif // CTS_USES_VULKANSC
116
117 private:
118
119 DrawParams m_params;
120 de::SharedPtr<Image> m_multisampleImage;
121 std::vector<de::SharedPtr<vk::Move<vk::VkImageView> > > m_colorTargetViews;
122 std::vector<de::SharedPtr<vk::Move<vk::VkImageView> > > m_multisampleViews;
123 de::SharedPtr<Buffer> m_vertexBuffer;
124 vk::Move<vk::VkRenderPass> m_renderPass;
125 vk::Move<vk::VkFramebuffer> m_framebuffer;
126 vk::Move<vk::VkPipelineLayout> m_pipelineLayout;
127 vk::Move<vk::VkPipeline> m_pipeline;
128 };
129
DrawTestInstance(Context & context,DrawParams params)130 DrawTestInstance::DrawTestInstance (Context& context, DrawParams params)
131 : TestInstance (context)
132 , m_params (params)
133 {
134 }
135
136 class DrawTestCase : public TestCase
137 {
138 public:
139 DrawTestCase (tcu::TestContext& testCtx,
140 const std::string& name,
141 const std::string& description,
142 const DrawParams params);
143 ~DrawTestCase (void);
144 virtual void initPrograms (vk::SourceCollections& programCollection) const;
145 virtual void checkSupport (Context& context) const;
146 virtual TestInstance* createInstance (Context& context) const;
147 private:
148 const DrawParams m_params;
149 };
150
DrawTestCase(tcu::TestContext & testCtx,const std::string & name,const std::string & description,const DrawParams params)151 DrawTestCase::DrawTestCase (tcu::TestContext& testCtx,
152 const std::string& name,
153 const std::string& description,
154 const DrawParams params)
155 : TestCase (testCtx, name, description)
156 , m_params (params)
157 {
158 }
159
~DrawTestCase(void)160 DrawTestCase::~DrawTestCase (void)
161 {
162 }
163
initPrograms(vk::SourceCollections & programCollection) const164 void DrawTestCase::initPrograms (vk::SourceCollections& programCollection) const
165 {
166 const std::string blockName = "ifb";
167 const std::map<std::string, std::string> replacements =
168 {
169 std::pair<std::string, std::string>{"blockOpeningOut" , (m_params.useStructure ? "layout(location = 0) out InterfaceBlock {\n" : "")},
170 std::pair<std::string, std::string>{"blockOpeningIn" , (m_params.useStructure ? "layout(location = 0) in InterfaceBlock {\n" : "")},
171 std::pair<std::string, std::string>{"blockClosure" , (m_params.useStructure ? "} " + blockName + ";\n" : "")},
172 std::pair<std::string, std::string>{"extensions" , (m_params.useStructure ? "#extension GL_ARB_enhanced_layouts : require\n" : "")},
173 std::pair<std::string, std::string>{"accessPrefix" , (m_params.useStructure ? blockName + "." : "")},
174 std::pair<std::string, std::string>{"outQual" , (m_params.useStructure ? "" : "out ")},
175 std::pair<std::string, std::string>{"inQual" , (m_params.useStructure ? "" : "in ")},
176 std::pair<std::string, std::string>{"indent" , (m_params.useStructure ? " " : "")},
177 };
178
179 std::ostringstream vertShaderMultiStream;
180 vertShaderMultiStream
181 << "#version 430\n"
182 << "${extensions}"
183 << "\n"
184 << "layout(location = 0) in vec4 in_position;\n"
185 << "layout(location = 1) in vec4 in_color;\n"
186 << "\n"
187 << "${blockOpeningOut}"
188 << "${indent}layout(location = 0) ${outQual}vec4 out_color_smooth;\n"
189 << "${indent}layout(location = 1) ${outQual}flat vec4 out_color_flat;\n"
190 << "${indent}layout(location = 2) ${outQual}noperspective vec4 out_color_noperspective;\n"
191 << "${indent}layout(location = 3) ${outQual}centroid vec4 out_color_centroid;\n"
192 << (m_params.includeSampleDecoration ? "${indent}layout(location = 4) ${outQual}sample vec4 out_color_sample;\n" : "")
193 << "${blockClosure}"
194 << "\n"
195 << "void main()\n"
196 << "{\n"
197 << " ${accessPrefix}out_color_smooth = in_color;\n"
198 << " ${accessPrefix}out_color_flat = in_color;\n"
199 << " ${accessPrefix}out_color_noperspective = in_color;\n"
200 << " ${accessPrefix}out_color_centroid = in_color;\n"
201 << (m_params.includeSampleDecoration ? " ${accessPrefix}out_color_sample = in_color;\n" : "")
202 << " gl_Position = in_position;\n"
203 << "}\n"
204 ;
205 const tcu::StringTemplate vertShaderMulti(vertShaderMultiStream.str());
206
207 const auto colorCount = (m_params.includeSampleDecoration ? COUNT : (COUNT - 1));
208
209 std::ostringstream fragShaderMultiStream;
210 fragShaderMultiStream
211 << "#version 430\n"
212 << "${extensions}"
213 << "\n"
214 << "${blockOpeningIn}"
215 << "${indent}layout(location = 0) ${inQual}vec4 in_color_smooth;\n"
216 << "${indent}layout(location = 1) ${inQual}flat vec4 in_color_flat;\n"
217 << "${indent}layout(location = 2) ${inQual}noperspective vec4 in_color_noperspective;\n"
218 << "${indent}layout(location = 3) ${inQual}centroid vec4 in_color_centroid;\n"
219 << (m_params.includeSampleDecoration ? "${indent}layout(location = 4) ${inQual}sample vec4 in_color_sample;\n" : "")
220 << "${blockClosure}"
221 << "\n"
222 << "layout(push_constant, std430) uniform PushConstants {\n"
223 << " uint interpolationIndex;\n"
224 << "} pc;\n"
225 << "\n"
226 << "layout(location=0) out vec4 out_color;\n"
227 << "\n"
228 << "void main()\n"
229 << "{\n"
230 << " const vec4 in_colors[" + de::toString(colorCount) + "] = vec4[](\n"
231 << " ${accessPrefix}in_color_smooth,\n"
232 << " ${accessPrefix}in_color_flat,\n"
233 << " ${accessPrefix}in_color_noperspective,\n"
234 << " ${accessPrefix}in_color_centroid" << (m_params.includeSampleDecoration ? "," : "") << "\n"
235 << (m_params.includeSampleDecoration ? " ${accessPrefix}in_color_sample\n" : "")
236 << " );\n"
237 << " out_color = in_colors[pc.interpolationIndex];\n"
238 << "}\n"
239 ;
240 const tcu::StringTemplate fragShaderMulti(fragShaderMultiStream.str());
241
242 const tcu::StringTemplate vertShaderSingle
243 {
244 "#version 430\n"
245 "${extensions}"
246 "\n"
247 "layout(location = 0) in vec4 in_position;\n"
248 "layout(location = 1) in vec4 in_color;\n"
249 "\n"
250 "${blockOpeningOut}"
251 "${indent}layout(location = 0) ${outQual}${qualifier:opt}vec4 out_color;\n"
252 "${blockClosure}"
253 "\n"
254 "void main()\n"
255 "{\n"
256 " ${accessPrefix}out_color = in_color;\n"
257 " gl_Position = in_position;\n"
258 "}\n"
259 };
260
261 const tcu::StringTemplate fragShaderSingle
262 {
263 "#version 430\n"
264 "${extensions}"
265 "\n"
266 "${blockOpeningIn}"
267 "${indent}layout(location = 0) ${inQual}${qualifier:opt}vec4 in_color;\n"
268 "${blockClosure}"
269 "\n"
270 "layout(location = 0) out vec4 out_color;\n"
271 "\n"
272 "void main()\n"
273 "{\n"
274 " out_color = ${accessPrefix}in_color;\n"
275 "}\n"
276 };
277
278 std::map<std::string, std::string> smooth = replacements;
279 std::map<std::string, std::string> flat = replacements;
280 std::map<std::string, std::string> noperspective = replacements;
281 std::map<std::string, std::string> centroid = replacements;
282 std::map<std::string, std::string> sample = replacements;
283
284 flat["qualifier"] = "flat ";
285 noperspective["qualifier"] = "noperspective ";
286 centroid["qualifier"] = "centroid ";
287 sample["qualifier"] = "sample ";
288
289 programCollection.glslSources.add("vert_multi") << glu::VertexSource(vertShaderMulti.specialize(replacements));
290 programCollection.glslSources.add("frag_multi") << glu::FragmentSource(fragShaderMulti.specialize(replacements));
291 programCollection.glslSources.add("vert_smooth") << glu::VertexSource(vertShaderSingle.specialize(smooth));
292 programCollection.glslSources.add("frag_smooth") << glu::FragmentSource(fragShaderSingle.specialize(smooth));
293 programCollection.glslSources.add("vert_flat") << glu::VertexSource(vertShaderSingle.specialize(flat));
294 programCollection.glslSources.add("frag_flat") << glu::FragmentSource(fragShaderSingle.specialize(flat));
295 programCollection.glslSources.add("vert_noperspective") << glu::VertexSource(vertShaderSingle.specialize(noperspective));
296 programCollection.glslSources.add("frag_noperspective") << glu::FragmentSource(fragShaderSingle.specialize(noperspective));
297 programCollection.glslSources.add("vert_centroid") << glu::VertexSource(vertShaderSingle.specialize(centroid));
298 programCollection.glslSources.add("frag_centroid") << glu::FragmentSource(fragShaderSingle.specialize(centroid));
299
300 if (m_params.includeSampleDecoration)
301 {
302 programCollection.glslSources.add("vert_sample") << glu::VertexSource(vertShaderSingle.specialize(sample));
303 programCollection.glslSources.add("frag_sample") << glu::FragmentSource(fragShaderSingle.specialize(sample));
304 }
305 }
306
checkSupport(Context & context) const307 void DrawTestCase::checkSupport (Context& context) const
308 {
309 if (!(m_params.samples & context.getDeviceProperties().limits.framebufferColorSampleCounts))
310 TCU_THROW(NotSupportedError, "Multisampling with " + de::toString(m_params.samples) + " samples not supported");
311
312 if (m_params.includeSampleDecoration && !context.getDeviceFeatures().sampleRateShading)
313 TCU_THROW(NotSupportedError, "Sample rate shading not supported");
314
315 if (m_params.groupParams->useDynamicRendering)
316 context.requireDeviceFunctionality("VK_KHR_dynamic_rendering");
317 }
318
createInstance(Context & context) const319 TestInstance* DrawTestCase::createInstance (Context& context) const
320 {
321 return new DrawTestInstance(context, m_params);
322 }
323
render(de::SharedPtr<Image> & colorTargetImage,tcu::ConstPixelBufferAccess * frame,const char * vsName,const char * fsName,Interpolation interpolation,bool sampleRateShading)324 void DrawTestInstance::render (de::SharedPtr<Image>& colorTargetImage,
325 tcu::ConstPixelBufferAccess* frame,
326 const char* vsName,
327 const char* fsName,
328 Interpolation interpolation,
329 bool sampleRateShading)
330 {
331 const deUint32 pcData = static_cast<deUint32>(interpolation);
332 const deUint32 pcDataSize = static_cast<deUint32>(sizeof(pcData));
333 const bool useMultisampling = (m_params.samples != vk::VK_SAMPLE_COUNT_1_BIT);
334 const vk::VkBool32 sampleShadingEnable = (sampleRateShading ? VK_TRUE : VK_FALSE);
335 const vk::DeviceInterface& vk = m_context.getDeviceInterface();
336 const vk::VkDevice device = m_context.getDevice();
337 const vk::Unique<vk::VkShaderModule> vs (createShaderModule(vk, device, m_context.getBinaryCollection().get(vsName), 0));
338 const vk::Unique<vk::VkShaderModule> fs (createShaderModule(vk, device, m_context.getBinaryCollection().get(fsName), 0));
339 const CmdPoolCreateInfo cmdPoolCreateInfo = m_context.getUniversalQueueFamilyIndex();
340 vk::Move<vk::VkCommandPool> cmdPool = createCommandPool(vk, device, &cmdPoolCreateInfo);
341 vk::Move<vk::VkCommandBuffer> cmdBuffer = vk::allocateCommandBuffer(vk, device, *cmdPool, vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY);
342 vk::Move<vk::VkCommandBuffer> secCmdBuffer;
343
344 m_colorTargetViews.clear();
345 m_multisampleViews.clear();
346
347 // Create color buffer images
348 {
349 const vk::VkExtent3D targetImageExtent = { m_params.size.x(), m_params.size.y(), 1 };
350 const vk::VkImageUsageFlags usage = vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT | vk::VK_IMAGE_USAGE_TRANSFER_DST_BIT;
351 const ImageCreateInfo targetImageCreateInfo (vk::VK_IMAGE_TYPE_2D,
352 m_params.format,
353 targetImageExtent,
354 1,
355 1,
356 vk::VK_SAMPLE_COUNT_1_BIT,
357 vk::VK_IMAGE_TILING_OPTIMAL,
358 usage);
359
360 colorTargetImage = Image::createAndAlloc(vk, device, targetImageCreateInfo,
361 m_context.getDefaultAllocator(),
362 m_context.getUniversalQueueFamilyIndex());
363
364 if (useMultisampling)
365 {
366 const ImageCreateInfo multisampleImageCreateInfo (vk::VK_IMAGE_TYPE_2D,
367 m_params.format,
368 targetImageExtent,
369 1,
370 1,
371 m_params.samples,
372 vk::VK_IMAGE_TILING_OPTIMAL,
373 usage);
374
375 m_multisampleImage = Image::createAndAlloc(vk, device, multisampleImageCreateInfo,
376 m_context.getDefaultAllocator(),
377 m_context.getUniversalQueueFamilyIndex());
378 }
379 }
380
381 {
382 const ImageViewCreateInfo colorTargetViewInfo(colorTargetImage->object(),
383 vk::VK_IMAGE_VIEW_TYPE_2D,
384 m_params.format);
385
386 m_colorTargetViews.push_back(makeSharedPtr(createImageView(vk, device, &colorTargetViewInfo)));
387
388 if (useMultisampling)
389 {
390 const ImageViewCreateInfo multisamplingTargetViewInfo(m_multisampleImage->object(),
391 vk::VK_IMAGE_VIEW_TYPE_2D,
392 m_params.format);
393
394 m_multisampleViews.push_back(makeSharedPtr(createImageView(vk, device, &multisamplingTargetViewInfo)));
395 }
396 }
397
398 // Create render pass and framebuffer
399 if (!m_params.groupParams->useDynamicRendering)
400 {
401 RenderPassCreateInfo renderPassCreateInfo;
402 std::vector<vk::VkImageView> attachments;
403 std::vector<vk::VkAttachmentReference> colorAttachmentRefs;
404 std::vector<vk::VkAttachmentReference> multisampleAttachmentRefs;
405 deUint32 attachmentNdx = 0;
406
407 {
408 const vk::VkAttachmentReference colorAttachmentReference =
409 {
410 attachmentNdx++,
411 vk::VK_IMAGE_LAYOUT_GENERAL
412 };
413
414 colorAttachmentRefs.push_back(colorAttachmentReference);
415
416 renderPassCreateInfo.addAttachment(AttachmentDescription(m_params.format,
417 vk::VK_SAMPLE_COUNT_1_BIT,
418 vk::VK_ATTACHMENT_LOAD_OP_CLEAR,
419 vk::VK_ATTACHMENT_STORE_OP_STORE,
420 vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE,
421 vk::VK_ATTACHMENT_STORE_OP_DONT_CARE,
422 vk::VK_IMAGE_LAYOUT_UNDEFINED,
423 vk::VK_IMAGE_LAYOUT_GENERAL));
424
425 if (useMultisampling)
426 {
427 const vk::VkAttachmentReference multiSampleAttachmentReference =
428 {
429 attachmentNdx++,
430 vk::VK_IMAGE_LAYOUT_GENERAL
431 };
432
433 multisampleAttachmentRefs.push_back(multiSampleAttachmentReference);
434
435 renderPassCreateInfo.addAttachment(AttachmentDescription(m_params.format,
436 m_params.samples,
437 vk::VK_ATTACHMENT_LOAD_OP_CLEAR,
438 vk::VK_ATTACHMENT_STORE_OP_STORE,
439 vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE,
440 vk::VK_ATTACHMENT_STORE_OP_DONT_CARE,
441 vk::VK_IMAGE_LAYOUT_UNDEFINED,
442 vk::VK_IMAGE_LAYOUT_GENERAL));
443 }
444 }
445
446 renderPassCreateInfo.addSubpass(SubpassDescription(vk::VK_PIPELINE_BIND_POINT_GRAPHICS,
447 0,
448 0,
449 DE_NULL,
450 (deUint32)colorAttachmentRefs.size(),
451 useMultisampling ? &multisampleAttachmentRefs[0] : &colorAttachmentRefs[0],
452 useMultisampling ? &colorAttachmentRefs[0] : DE_NULL,
453 AttachmentReference(),
454 0,
455 DE_NULL));
456
457 m_renderPass = createRenderPass(vk, device, &renderPassCreateInfo);
458
459 for (deUint32 frameNdx = 0; frameNdx < m_colorTargetViews.size(); frameNdx++)
460 {
461 attachments.push_back(**m_colorTargetViews[frameNdx]);
462
463 if (useMultisampling)
464 attachments.push_back(**m_multisampleViews[frameNdx]);
465 }
466
467 const vk::VkFramebufferCreateInfo framebufferCreateInfo =
468 {
469 vk::VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
470 DE_NULL,
471 0u,
472 *m_renderPass,
473 (deUint32)attachments.size(),
474 &attachments[0],
475 m_params.size.x(),
476 m_params.size.y(),
477 1
478 };
479
480 m_framebuffer = createFramebuffer(vk, device, &framebufferCreateInfo);
481 }
482
483 // Create vertex buffer.
484 {
485 const PositionColorVertex vertices[] =
486 {
487 PositionColorVertex(
488 tcu::Vec4(-1.5f, -0.4f, 1.0f, 2.0f), // Coord
489 tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f)), // Color
490
491 PositionColorVertex(
492 tcu::Vec4(0.4f, -0.4f, 0.5f, 0.5f), // Coord
493 tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f)), // Color
494
495 PositionColorVertex(
496 tcu::Vec4(0.3f, 0.8f, 0.0f, 1.0f), // Coord
497 tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f)) // Color
498 };
499
500 const vk::VkDeviceSize dataSize = DE_LENGTH_OF_ARRAY(vertices) * sizeof(PositionColorVertex);
501 m_vertexBuffer = Buffer::createAndAlloc(vk,
502 device,
503 BufferCreateInfo(dataSize, vk::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
504 m_context.getDefaultAllocator(),
505 vk::MemoryRequirement::HostVisible);
506 deUint8* ptr = reinterpret_cast<deUint8*>(m_vertexBuffer->getBoundMemory().getHostPtr());
507
508 deMemcpy(ptr, vertices, static_cast<size_t>(dataSize));
509 flushMappedMemoryRange(vk,
510 device,
511 m_vertexBuffer->getBoundMemory().getMemory(),
512 m_vertexBuffer->getBoundMemory().getOffset(),
513 VK_WHOLE_SIZE);
514 }
515
516 // Create pipeline
517 {
518 const vk::VkViewport viewport = vk::makeViewport(m_params.size.x(), m_params.size.y());
519 const vk::VkRect2D scissor = vk::makeRect2D(m_params.size.x(), m_params.size.y());
520 const auto pcRange = vk::makePushConstantRange(vk::VK_SHADER_STAGE_FRAGMENT_BIT, 0u, pcDataSize);
521 const std::vector<vk::VkPushConstantRange> pcRanges (1u, pcRange);
522 const PipelineLayoutCreateInfo pipelineLayoutCreateInfo (0u, nullptr, static_cast<deUint32>(pcRanges.size()), pcRanges.data());
523
524 m_pipelineLayout = createPipelineLayout(vk, device, &pipelineLayoutCreateInfo);
525
526 PipelineCreateInfo pipelineCreateInfo (*m_pipelineLayout, *m_renderPass, 0, 0);
527
528 const vk::VkVertexInputBindingDescription vertexInputBindingDescription =
529 {
530 0,
531 (deUint32)sizeof(tcu::Vec4) * 2,
532 vk::VK_VERTEX_INPUT_RATE_VERTEX
533 };
534
535 const vk::VkVertexInputAttributeDescription vertexInputAttributeDescriptions[2] =
536 {
537 { 0u, 0u, vk::VK_FORMAT_R32G32B32A32_SFLOAT, 0u },
538 { 1u, 0u, vk::VK_FORMAT_R32G32B32A32_SFLOAT, (deUint32)(sizeof(float) * 4) }
539 };
540
541 std::vector<PipelineCreateInfo::ColorBlendState::Attachment> vkCbAttachmentStates (1u);
542 PipelineCreateInfo::VertexInputState vertexInputState = PipelineCreateInfo::VertexInputState(1,
543 &vertexInputBindingDescription,
544 2,
545 vertexInputAttributeDescriptions);
546
547 pipelineCreateInfo.addShader(PipelineCreateInfo::PipelineShaderStage(*vs, "main", vk::VK_SHADER_STAGE_VERTEX_BIT));
548 pipelineCreateInfo.addShader(PipelineCreateInfo::PipelineShaderStage(*fs, "main", vk::VK_SHADER_STAGE_FRAGMENT_BIT));
549 pipelineCreateInfo.addState(PipelineCreateInfo::VertexInputState(vertexInputState));
550 pipelineCreateInfo.addState(PipelineCreateInfo::InputAssemblerState(vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST));
551 pipelineCreateInfo.addState(PipelineCreateInfo::ColorBlendState((deUint32)vkCbAttachmentStates.size(), &vkCbAttachmentStates[0]));
552 pipelineCreateInfo.addState(PipelineCreateInfo::ViewportState(1, std::vector<vk::VkViewport>(1, viewport), std::vector<vk::VkRect2D>(1, scissor)));
553 pipelineCreateInfo.addState(PipelineCreateInfo::DepthStencilState());
554 pipelineCreateInfo.addState(PipelineCreateInfo::RasterizerState());
555 pipelineCreateInfo.addState(PipelineCreateInfo::MultiSampleState(m_params.samples, sampleShadingEnable, 1.0f));
556
557 #ifndef CTS_USES_VULKANSC
558 std::vector<vk::VkFormat> colorAttachmentFormats(m_colorTargetViews.size(), m_params.format);
559 vk::VkPipelineRenderingCreateInfoKHR renderingCreateInfo
560 {
561 vk::VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR,
562 DE_NULL,
563 0u,
564 static_cast<deUint32>(colorAttachmentFormats.size()),
565 colorAttachmentFormats.data(),
566 vk::VK_FORMAT_UNDEFINED,
567 vk::VK_FORMAT_UNDEFINED
568 };
569
570 if (m_params.groupParams->useDynamicRendering)
571 pipelineCreateInfo.pNext = &renderingCreateInfo;
572 #endif // CTS_USES_VULKANSC
573
574 m_pipeline = createGraphicsPipeline(vk, device, DE_NULL, &pipelineCreateInfo);
575 }
576
577 // Queue draw and read results.
578 {
579 const vk::VkQueue queue = m_context.getUniversalQueue();
580 const vk::VkRect2D renderArea = vk::makeRect2D(m_params.size.x(), m_params.size.y());
581 const vk::VkBuffer buffer = m_vertexBuffer->object();
582 const vk::VkOffset3D zeroOffset = { 0, 0, 0 };
583 const auto clearValueColor = vk::makeClearValueColor(tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
584
585 #ifndef CTS_USES_VULKANSC
586 if (m_params.groupParams->useSecondaryCmdBuffer)
587 {
588 secCmdBuffer = allocateCommandBuffer(vk, device, *cmdPool, vk::VK_COMMAND_BUFFER_LEVEL_SECONDARY);
589
590 // record secondary command buffer
591 if (m_params.groupParams->secondaryCmdBufferCompletelyContainsDynamicRenderpass)
592 {
593 beginSecondaryCmdBuffer(*secCmdBuffer, vk::VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT);
594 beginDynamicRender(*secCmdBuffer, renderArea, clearValueColor);
595 }
596 else
597 beginSecondaryCmdBuffer(*secCmdBuffer);
598
599 drawCommands(*secCmdBuffer, *m_pipeline, *m_pipelineLayout, buffer, pcData);
600
601 if (m_params.groupParams->secondaryCmdBufferCompletelyContainsDynamicRenderpass)
602 endRendering(vk, *secCmdBuffer);
603
604 endCommandBuffer(vk, *secCmdBuffer);
605
606 // record primary command buffer
607 beginCommandBuffer(vk, *cmdBuffer, 0u);
608 preRenderCommands(*cmdBuffer, colorTargetImage->object());
609
610 if (!m_params.groupParams->secondaryCmdBufferCompletelyContainsDynamicRenderpass)
611 beginDynamicRender(*cmdBuffer, renderArea, clearValueColor, vk::VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
612
613 vk.cmdExecuteCommands(*cmdBuffer, 1u, &*secCmdBuffer);
614
615 if (!m_params.groupParams->secondaryCmdBufferCompletelyContainsDynamicRenderpass)
616 endRendering(vk, *cmdBuffer);
617
618 endCommandBuffer(vk, *cmdBuffer);
619 }
620 else if (m_params.groupParams->useDynamicRendering)
621 {
622 beginCommandBuffer(vk, *cmdBuffer);
623
624 preRenderCommands(*cmdBuffer, colorTargetImage->object());
625 beginDynamicRender(*cmdBuffer, renderArea, clearValueColor);
626 drawCommands(*cmdBuffer, *m_pipeline, *m_pipelineLayout, buffer, pcData);
627 endRendering(vk, *cmdBuffer);
628
629 endCommandBuffer(vk, *cmdBuffer);
630 }
631 #endif // CTS_USES_VULKANSC
632
633 if (!m_params.groupParams->useDynamicRendering)
634 {
635 const deUint32 imagesCount = static_cast<deUint32>(m_colorTargetViews.size() + m_multisampleViews.size());
636 std::vector<vk::VkClearValue> clearValues (2, clearValueColor);
637
638 beginCommandBuffer(vk, *cmdBuffer);
639
640 preRenderCommands(*cmdBuffer, colorTargetImage->object());
641 beginRenderPass(vk, *cmdBuffer, *m_renderPass, *m_framebuffer, renderArea, imagesCount, &clearValues[0]);
642 drawCommands(*cmdBuffer, *m_pipeline, *m_pipelineLayout, buffer, pcData);
643 endRenderPass(vk, *cmdBuffer);
644
645 endCommandBuffer(vk, *cmdBuffer);
646 }
647
648 submitCommandsAndWait(vk, device, queue, *cmdBuffer);
649
650 *frame = colorTargetImage->readSurface(queue,
651 m_context.getDefaultAllocator(),
652 vk::VK_IMAGE_LAYOUT_GENERAL,
653 zeroOffset,
654 (int)m_params.size.x(),
655 (int)m_params.size.y(),
656 vk::VK_IMAGE_ASPECT_COLOR_BIT);
657 }
658 }
659
preRenderCommands(vk::VkCommandBuffer cmdBuffer,vk::VkImage colorTargetImage) const660 void DrawTestInstance::preRenderCommands(vk::VkCommandBuffer cmdBuffer, vk::VkImage colorTargetImage) const
661 {
662 if (!m_params.groupParams->useDynamicRendering)
663 return;
664
665 const vk::DeviceInterface& vk = m_context.getDeviceInterface();
666 initialTransitionColor2DImage(vk, cmdBuffer, colorTargetImage, vk::VK_IMAGE_LAYOUT_GENERAL,
667 vk::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
668
669 if (m_params.samples != vk::VK_SAMPLE_COUNT_1_BIT)
670 {
671 initialTransitionColor2DImage(vk, cmdBuffer, m_multisampleImage->object(), vk::VK_IMAGE_LAYOUT_GENERAL,
672 vk::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
673 }
674 }
675
drawCommands(vk::VkCommandBuffer cmdBuffer,vk::VkPipeline pipeline,vk::VkPipelineLayout pipelineLayout,vk::VkBuffer vertexBuffer,deUint32 pcData) const676 void DrawTestInstance::drawCommands(vk::VkCommandBuffer cmdBuffer, vk::VkPipeline pipeline, vk::VkPipelineLayout pipelineLayout,
677 vk::VkBuffer vertexBuffer, deUint32 pcData) const
678 {
679 const vk::DeviceInterface& vk = m_context.getDeviceInterface();
680 const vk::VkDeviceSize vertexBufferOffset = 0;
681 const deUint32 pcDataSize = static_cast<deUint32>(sizeof(pcData));
682
683 vk.cmdBindVertexBuffers(cmdBuffer, 0, 1, &vertexBuffer, &vertexBufferOffset);
684 vk.cmdBindPipeline(cmdBuffer, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
685 vk.cmdPushConstants(cmdBuffer, pipelineLayout, vk::VK_SHADER_STAGE_FRAGMENT_BIT, 0u, pcDataSize, &pcData);
686 vk.cmdDraw(cmdBuffer, 3u, 1u, 0u, 0u);
687 }
688
compare(const tcu::ConstPixelBufferAccess & result,const tcu::ConstPixelBufferAccess & reference)689 bool DrawTestInstance::compare (const tcu::ConstPixelBufferAccess& result, const tcu::ConstPixelBufferAccess& reference)
690 {
691 DE_ASSERT(result.getSize() == reference.getSize());
692
693 const tcu::IVec4 threshold (1u, 1u, 1u, 1u);
694
695 for (int y = 0; y < result.getHeight(); y++)
696 {
697 for (int x = 0; x < result.getWidth(); x++)
698 {
699 tcu::IVec4 refPix = reference.getPixelInt(x, y);
700 tcu::IVec4 cmpPix = result.getPixelInt(x, y);
701 tcu::IVec4 diff = tcu::abs(refPix - cmpPix);
702
703 if (!tcu::boolAll(tcu::lessThanEqual(diff, threshold)))
704 return false;
705 }
706 }
707
708 return true;
709 }
710
711 #ifndef CTS_USES_VULKANSC
beginSecondaryCmdBuffer(vk::VkCommandBuffer cmdBuffer,vk::VkRenderingFlagsKHR renderingFlags) const712 void DrawTestInstance::beginSecondaryCmdBuffer(vk::VkCommandBuffer cmdBuffer, vk::VkRenderingFlagsKHR renderingFlags) const
713 {
714 std::vector<vk::VkFormat> colorAttachmentFormats(m_colorTargetViews.size(), m_params.format);
715 vk::VkCommandBufferInheritanceRenderingInfoKHR inheritanceRenderingInfo
716 {
717 vk::VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_RENDERING_INFO_KHR, // VkStructureType sType;
718 DE_NULL, // const void* pNext;
719 renderingFlags, // VkRenderingFlagsKHR flags;
720 0u, // uint32_t viewMask;
721 (deUint32)colorAttachmentFormats.size(), // uint32_t colorAttachmentCount;
722 colorAttachmentFormats.data(), // const VkFormat* pColorAttachmentFormats;
723 vk::VK_FORMAT_UNDEFINED, // VkFormat depthAttachmentFormat;
724 vk::VK_FORMAT_UNDEFINED, // VkFormat stencilAttachmentFormat;
725 m_params.samples, // VkSampleCountFlagBits rasterizationSamples;
726 };
727 const vk::VkCommandBufferInheritanceInfo bufferInheritanceInfo = vk::initVulkanStructure(&inheritanceRenderingInfo);
728
729 vk::VkCommandBufferUsageFlags usageFlags = vk::VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
730 if (!m_params.groupParams->secondaryCmdBufferCompletelyContainsDynamicRenderpass)
731 usageFlags |= vk::VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
732
733 const vk::VkCommandBufferBeginInfo commandBufBeginParams
734 {
735 vk::VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, // VkStructureType sType;
736 DE_NULL, // const void* pNext;
737 usageFlags, // VkCommandBufferUsageFlags flags;
738 &bufferInheritanceInfo
739 };
740
741 const vk::DeviceInterface& vk = m_context.getDeviceInterface();
742 VK_CHECK(vk.beginCommandBuffer(cmdBuffer, &commandBufBeginParams));
743 }
744
beginDynamicRender(vk::VkCommandBuffer cmdBuffer,vk::VkRect2D renderArea,vk::VkClearValue clearValue,vk::VkRenderingFlagsKHR renderingFlags) const745 void DrawTestInstance::beginDynamicRender(vk::VkCommandBuffer cmdBuffer, vk::VkRect2D renderArea, vk::VkClearValue clearValue, vk::VkRenderingFlagsKHR renderingFlags) const
746 {
747 const vk::DeviceInterface& vk = m_context.getDeviceInterface();
748 const deUint32 imagesCount = static_cast<deUint32>(m_colorTargetViews.size());
749
750 std::vector<vk::VkRenderingAttachmentInfoKHR> colorAttachments(imagesCount,
751 {
752 vk::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, // VkStructureType sType;
753 DE_NULL, // const void* pNext;
754 DE_NULL, // VkImageView imageView;
755 vk::VK_IMAGE_LAYOUT_GENERAL, // VkImageLayout imageLayout;
756 vk::VK_RESOLVE_MODE_NONE, // VkResolveModeFlagBits resolveMode;
757 DE_NULL, // VkImageView resolveImageView;
758 vk::VK_IMAGE_LAYOUT_GENERAL, // VkImageLayout resolveImageLayout;
759 vk::VK_ATTACHMENT_LOAD_OP_CLEAR, // VkAttachmentLoadOp loadOp;
760 vk::VK_ATTACHMENT_STORE_OP_STORE, // VkAttachmentStoreOp storeOp;
761 clearValue // VkClearValue clearValue;
762 });
763
764 for (deUint32 i = 0; i < imagesCount; ++i)
765 {
766 if (m_params.samples != vk::VK_SAMPLE_COUNT_1_BIT)
767 {
768 colorAttachments[i].imageView = **m_multisampleViews[i];
769 colorAttachments[i].resolveMode = vk::VK_RESOLVE_MODE_AVERAGE_BIT;
770 colorAttachments[i].resolveImageView = **m_colorTargetViews[i];
771 }
772 else
773 colorAttachments[i].imageView = **m_colorTargetViews[i];
774 }
775
776 vk::VkRenderingInfoKHR renderingInfo
777 {
778 vk::VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
779 DE_NULL,
780 renderingFlags, // VkRenderingFlagsKHR flags;
781 renderArea, // VkRect2D renderArea;
782 1u, // deUint32 layerCount;
783 0u, // deUint32 viewMask;
784 imagesCount, // deUint32 colorAttachmentCount;
785 colorAttachments.data(), // const VkRenderingAttachmentInfoKHR* pColorAttachments;
786 DE_NULL, // const VkRenderingAttachmentInfoKHR* pDepthAttachment;
787 DE_NULL, // const VkRenderingAttachmentInfoKHR* pStencilAttachment;
788 };
789
790 vk.cmdBeginRendering(cmdBuffer, &renderingInfo);
791 }
792 #endif // CTS_USES_VULKANSC
793
iterate(void)794 tcu::TestStatus DrawTestInstance::iterate (void)
795 {
796 tcu::TestLog& log = m_context.getTestContext().getLog();
797 const bool useMultisampling = (m_params.samples != vk::VK_SAMPLE_COUNT_1_BIT);
798 const deUint32 frameCount = static_cast<deUint32>(COUNT);
799 std::vector<de::SharedPtr<Image> > resImages (frameCount);
800 de::SharedPtr<Image> smoothImage[2];
801 de::SharedPtr<Image> flatImage[2];
802 de::SharedPtr<Image> noperspectiveImage[2];
803 de::SharedPtr<Image> centroidImage[2];
804 de::SharedPtr<Image> sampleImage[2];
805 tcu::ConstPixelBufferAccess resFrames[frameCount];
806 tcu::ConstPixelBufferAccess refFrames[frameCount];
807 tcu::ConstPixelBufferAccess refSRSFrames[frameCount]; // Using sample rate shading.
808
809 for (int interpolationType = 0; interpolationType < COUNT; ++interpolationType)
810 {
811 // Avoid generating a result image for the sample decoration if we're not using it.
812 if (!m_params.includeSampleDecoration && interpolationType == Interpolation::SAMPLE)
813 continue;
814
815 render(resImages[interpolationType], &resFrames[interpolationType], "vert_multi", "frag_multi", static_cast<Interpolation>(interpolationType), false);
816 }
817
818 for (int i = 0; i < 2; ++i)
819 {
820 const bool useSampleRateShading = (i > 0);
821
822 // Sample rate shading is an alternative good result for cases using the sample decoration.
823 if (useSampleRateShading && !m_params.includeSampleDecoration)
824 continue;
825
826 tcu::ConstPixelBufferAccess *framesArray = (useSampleRateShading ? refSRSFrames : refFrames);
827
828 render(smoothImage[i], &framesArray[SMOOTH], "vert_smooth", "frag_smooth", SMOOTH, useSampleRateShading);
829 render(flatImage[i], &framesArray[FLAT], "vert_flat", "frag_flat", FLAT, useSampleRateShading);
830 render(noperspectiveImage[i], &framesArray[NOPERSPECTIVE], "vert_noperspective", "frag_noperspective", NOPERSPECTIVE, useSampleRateShading);
831 render(centroidImage[i], &framesArray[CENTROID], "vert_centroid", "frag_centroid", CENTROID, useSampleRateShading);
832
833 // Avoid generating a reference image for the sample interpolation if we're not using it.
834 if (m_params.includeSampleDecoration)
835 render(sampleImage[i], &framesArray[SAMPLE], "vert_sample", "frag_sample", SAMPLE, useSampleRateShading);
836 }
837
838 for (deUint32 resNdx = 0; resNdx < frameCount; resNdx++)
839 {
840 if (!m_params.includeSampleDecoration && resNdx == SAMPLE)
841 continue;
842
843 const std::string resName = interpolationToString((Interpolation)resNdx);
844
845 log << tcu::TestLog::ImageSet(resName, resName)
846 << tcu::TestLog::Image("Result", "Result", resFrames[resNdx])
847 << tcu::TestLog::Image("Reference", "Reference", refFrames[resNdx]);
848 if (m_params.includeSampleDecoration)
849 log << tcu::TestLog::Image("ReferenceSRS", "Reference with sample shading", refSRSFrames[resNdx]);
850 log << tcu::TestLog::EndImageSet;
851
852 for (deUint32 refNdx = 0; refNdx < frameCount; refNdx++)
853 {
854 if (!m_params.includeSampleDecoration && refNdx == SAMPLE)
855 continue;
856
857 const std::string refName = interpolationToString((Interpolation)refNdx);
858
859 if (resNdx == refNdx)
860 {
861 if (!compare(resFrames[resNdx], refFrames[refNdx]) && (!m_params.includeSampleDecoration || !compare(resFrames[resNdx], refSRSFrames[refNdx])))
862 return tcu::TestStatus::fail(resName + " produced different results");
863 }
864 else if (!useMultisampling &&
865 ((resNdx == SMOOTH && refNdx == CENTROID) ||
866 (resNdx == CENTROID && refNdx == SMOOTH) ||
867 (resNdx == SMOOTH && refNdx == SAMPLE) ||
868 (resNdx == SAMPLE && refNdx == SMOOTH) ||
869 (resNdx == CENTROID && refNdx == SAMPLE) ||
870 (resNdx == SAMPLE && refNdx == CENTROID)))
871 {
872 if (!compare(resFrames[resNdx], refFrames[refNdx]))
873 return tcu::TestStatus::fail(resName + " and " + refName + " produced different results without multisampling");
874 }
875 else
876 {
877 // "smooth" means lack of centroid and sample.
878 // Spec does not specify exactly what "smooth" should be, so it can match centroid or sample.
879 // "centroid" and "sample" may also produce the same results.
880 if (!((resNdx == SMOOTH && refNdx == CENTROID) ||
881 (resNdx == CENTROID && refNdx == SMOOTH) ||
882 (resNdx == SMOOTH && refNdx == SAMPLE) ||
883 (resNdx == SAMPLE && refNdx == SMOOTH) ||
884 (resNdx == CENTROID && refNdx == SAMPLE) ||
885 (resNdx == SAMPLE && refNdx == CENTROID) ))
886 {
887 if (compare(resFrames[resNdx], refFrames[refNdx]))
888 return tcu::TestStatus::fail(resName + " and " + refName + " produced same result");
889 }
890 }
891 }
892 }
893
894 return tcu::TestStatus::pass("Results differ and references match");
895 }
896
createTests(tcu::TestCaseGroup * testGroup,const SharedGroupParams groupParams)897 void createTests (tcu::TestCaseGroup* testGroup, const SharedGroupParams groupParams)
898 {
899 tcu::TestContext& testCtx = testGroup->getTestContext();
900 const vk::VkFormat format = vk::VK_FORMAT_R8G8B8A8_UNORM;
901 const tcu::UVec2 size (128, 128);
902
903 struct TestVariant
904 {
905 const std::string name;
906 const std::string desc;
907 const vk::VkSampleCountFlagBits samples;
908 };
909
910 static const std::vector<TestVariant> testVariants
911 {
912 { "1_sample", "Without multisampling", vk::VK_SAMPLE_COUNT_1_BIT },
913 { "2_samples", "2 samples", vk::VK_SAMPLE_COUNT_2_BIT },
914 { "4_samples", "4 samples", vk::VK_SAMPLE_COUNT_4_BIT },
915 { "8_samples", "8 samples", vk::VK_SAMPLE_COUNT_8_BIT },
916 { "16_samples", "16 samples", vk::VK_SAMPLE_COUNT_16_BIT },
917 { "32_samples", "32 samples", vk::VK_SAMPLE_COUNT_32_BIT },
918 { "64_samples", "64 samples", vk::VK_SAMPLE_COUNT_64_BIT },
919 };
920
921 struct GroupVariant
922 {
923 const bool useStructure;
924 const std::string groupName;
925 };
926
927 static const std::vector<GroupVariant> groupVariants
928 {
929 { false, "separate" },
930 { true, "structured" },
931 };
932
933 const struct
934 {
935 const bool includeSampleDecoration;
936 const std::string groupName;
937 } sampleVariants[]
938 {
939 { false, "no_sample_decoration" },
940 { true, "with_sample_decoration" },
941 };
942
943 for (const auto& grpVariant : groupVariants)
944 {
945 de::MovePtr<tcu::TestCaseGroup> group {new tcu::TestCaseGroup{testCtx, grpVariant.groupName.c_str(), ""}};
946
947 for (const auto& sampleVariant : sampleVariants)
948 {
949 de::MovePtr<tcu::TestCaseGroup> sampleGroup {new tcu::TestCaseGroup{testCtx, sampleVariant.groupName.c_str(), ""}};
950
951 for (const auto& testVariant : testVariants)
952 {
953 const DrawParams params {format, size, testVariant.samples, grpVariant.useStructure, sampleVariant.includeSampleDecoration, groupParams };
954 sampleGroup->addChild(new DrawTestCase(testCtx, testVariant.name, testVariant.desc, params));
955 }
956
957 group->addChild(sampleGroup.release());
958 }
959
960 testGroup->addChild(group.release());
961 }
962 }
963
964 } // anonymous
965
createMultipleInterpolationTests(tcu::TestContext & testCtx,const SharedGroupParams groupParams)966 tcu::TestCaseGroup* createMultipleInterpolationTests (tcu::TestContext& testCtx, const SharedGroupParams groupParams)
967 {
968 return createTestGroup(testCtx,
969 "multiple_interpolation",
970 "Tests for multiple interpolation decorations in a shader stage.",
971 createTests,
972 groupParams);
973 }
974
975 } // Draw
976 } // vkt
977