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