1 /*-------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2017 The Khronos Group Inc.
6 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
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 Protected memory interaction with VkSwapchain Tests
23 *//*--------------------------------------------------------------------*/
24
25 #include "vktProtectedMemWsiSwapchainTests.hpp"
26
27 #include "vktTestCaseUtil.hpp"
28 #include "vktTestGroupUtil.hpp"
29
30 #include "vkDefs.hpp"
31 #include "vkPlatform.hpp"
32 #include "vkStrUtil.hpp"
33 #include "vkRef.hpp"
34 #include "vkRefUtil.hpp"
35 #include "vkQueryUtil.hpp"
36 #include "vkMemUtil.hpp"
37 #include "vkDeviceUtil.hpp"
38 #include "vkPrograms.hpp"
39 #include "vkTypeUtil.hpp"
40 #include "vkObjUtil.hpp"
41 #include "vkWsiPlatform.hpp"
42 #include "vkWsiUtil.hpp"
43 #include "vkAllocationCallbackUtil.hpp"
44 #include "vkCmdUtil.hpp"
45
46 #include "tcuTestLog.hpp"
47 #include "tcuFormatUtil.hpp"
48 #include "tcuPlatform.hpp"
49 #include "tcuResultCollector.hpp"
50
51 #include "deUniquePtr.hpp"
52 #include "deStringUtil.hpp"
53 #include "deArrayUtil.hpp"
54 #include "deSharedPtr.hpp"
55
56 #include <limits>
57 #include <cstring>
58
59 #include "vktProtectedMemContext.hpp"
60 #include "vktProtectedMemUtils.hpp"
61
62 namespace vkt
63 {
64 namespace ProtectedMem
65 {
66
67 namespace
68 {
69
70 typedef std::vector<vk::VkExtensionProperties> Extensions;
71
checkAllSupported(const Extensions & supportedExtensions,const std::vector<std::string> & requiredExtensions)72 void checkAllSupported (const Extensions& supportedExtensions, const std::vector<std::string>& requiredExtensions)
73 {
74 for (std::vector<std::string>::const_iterator requiredExtName = requiredExtensions.begin();
75 requiredExtName != requiredExtensions.end();
76 ++requiredExtName)
77 {
78 if (!isExtensionStructSupported(supportedExtensions, vk::RequiredExtension(*requiredExtName)))
79 TCU_THROW(NotSupportedError, (*requiredExtName + " is not supported").c_str());
80 }
81 }
82
getRequiredWsiExtensions(const Extensions & supportedExtensions,vk::wsi::Type wsiType)83 std::vector<std::string> getRequiredWsiExtensions (const Extensions& supportedExtensions,
84 vk::wsi::Type wsiType)
85 {
86 std::vector<std::string> extensions;
87
88 extensions.push_back("VK_KHR_surface");
89 extensions.push_back(getExtensionName(wsiType));
90 if (isDisplaySurface(wsiType))
91 extensions.push_back("VK_KHR_display");
92
93 // VK_EXT_swapchain_colorspace adds new surface formats. Driver can enumerate
94 // the formats regardless of whether VK_EXT_swapchain_colorspace was enabled,
95 // but using them without enabling the extension is not allowed. Thus we have
96 // two options:
97 //
98 // 1) Filter out non-core formats to stay within valid usage.
99 //
100 // 2) Enable VK_EXT_swapchain colorspace if advertised by the driver.
101 //
102 // We opt for (2) as it provides basic coverage for the extension as a bonus.
103 if (isExtensionStructSupported(supportedExtensions, vk::RequiredExtension("VK_EXT_swapchain_colorspace")))
104 extensions.push_back("VK_EXT_swapchain_colorspace");
105
106 // VK_KHR_surface_protected_capabilities adds a way to check if swapchain can be
107 // created for protected VkSurface, so if this extension is enabled then we can
108 // check for that capability.
109 // To check this capability, vkGetPhysicalDeviceSurfaceCapabilities2KHR needs
110 // to be called so add VK_KHR_get_surface_capabilities2 for this.
111 if (isExtensionStructSupported(supportedExtensions, vk::RequiredExtension("VK_KHR_surface_protected_capabilities")))
112 {
113 extensions.push_back("VK_KHR_get_surface_capabilities2");
114 extensions.push_back("VK_KHR_surface_protected_capabilities");
115 }
116
117 checkAllSupported(supportedExtensions, extensions);
118
119 return extensions;
120 }
121
createDisplay(const vk::Platform & platform,const Extensions & supportedExtensions,vk::wsi::Type wsiType)122 de::MovePtr<vk::wsi::Display> createDisplay (const vk::Platform& platform,
123 const Extensions& supportedExtensions,
124 vk::wsi::Type wsiType)
125 {
126 try
127 {
128 return de::MovePtr<vk::wsi::Display>(platform.createWsiDisplay(wsiType));
129 }
130 catch (const tcu::NotSupportedError& e)
131 {
132 if (isExtensionStructSupported(supportedExtensions, vk::RequiredExtension(getExtensionName(wsiType))) &&
133 platform.hasDisplay(wsiType))
134 {
135 // If VK_KHR_{platform}_surface was supported, vk::Platform implementation
136 // must support creating native display & window for that WSI type.
137 throw tcu::TestError(e.getMessage());
138 }
139 else
140 throw;
141 }
142 }
143
createWindow(const vk::wsi::Display & display,const tcu::Maybe<tcu::UVec2> & initialSize)144 de::MovePtr<vk::wsi::Window> createWindow (const vk::wsi::Display& display, const tcu::Maybe<tcu::UVec2>& initialSize)
145 {
146 try
147 {
148 return de::MovePtr<vk::wsi::Window>(display.createWindow(initialSize));
149 }
150 catch (const tcu::NotSupportedError& e)
151 {
152 // See createDisplay - assuming that wsi::Display was supported platform port
153 // should also support creating a window.
154 throw tcu::TestError(e.getMessage());
155 }
156 }
157
158 struct NativeObjects
159 {
160 const de::UniquePtr<vk::wsi::Display> display;
161 const de::UniquePtr<vk::wsi::Window> window;
162
NativeObjectsvkt::ProtectedMem::__anon5d8586710111::NativeObjects163 NativeObjects (Context& context,
164 const Extensions& supportedExtensions,
165 vk::wsi::Type wsiType,
166 const tcu::Maybe<tcu::UVec2>& initialWindowSize = tcu::Nothing)
167 : display (createDisplay(context.getTestContext().getPlatform().getVulkanPlatform(), supportedExtensions, wsiType))
168 , window (createWindow(*display, initialWindowSize))
169 {}
170 };
171
172 enum TestDimension
173 {
174 TEST_DIMENSION_MIN_IMAGE_COUNT = 0, //!< Test all supported image counts
175 TEST_DIMENSION_IMAGE_FORMAT, //!< Test all supported formats
176 TEST_DIMENSION_IMAGE_EXTENT, //!< Test various (supported) extents
177 TEST_DIMENSION_IMAGE_ARRAY_LAYERS,
178 TEST_DIMENSION_IMAGE_USAGE,
179 TEST_DIMENSION_IMAGE_SHARING_MODE,
180 TEST_DIMENSION_PRE_TRANSFORM,
181 TEST_DIMENSION_COMPOSITE_ALPHA,
182 TEST_DIMENSION_PRESENT_MODE,
183 TEST_DIMENSION_CLIPPED,
184
185 TEST_DIMENSION_LAST
186 };
187
getTestDimensionName(TestDimension dimension)188 const char* getTestDimensionName (TestDimension dimension)
189 {
190 static const char* const s_names[] =
191 {
192 "min_image_count",
193 "image_format",
194 "image_extent",
195 "image_array_layers",
196 "image_usage",
197 "image_sharing_mode",
198 "pre_transform",
199 "composite_alpha",
200 "present_mode",
201 "clipped"
202 };
203 return de::getSizedArrayElement<TEST_DIMENSION_LAST>(s_names, dimension);
204 }
205
206 struct TestParameters
207 {
208 vk::wsi::Type wsiType;
209 TestDimension dimension;
210
TestParametersvkt::ProtectedMem::__anon5d8586710111::TestParameters211 TestParameters (vk::wsi::Type wsiType_, TestDimension dimension_)
212 : wsiType (wsiType_)
213 , dimension (dimension_)
214 {}
215
TestParametersvkt::ProtectedMem::__anon5d8586710111::TestParameters216 TestParameters (void)
217 : wsiType (vk::wsi::TYPE_LAST)
218 , dimension (TEST_DIMENSION_LAST)
219 {}
220 };
221
firstSupportedCompositeAlpha(const vk::VkSurfaceCapabilitiesKHR & capabilities)222 static vk::VkCompositeAlphaFlagBitsKHR firstSupportedCompositeAlpha(const vk::VkSurfaceCapabilitiesKHR& capabilities)
223 {
224 deUint32 alphaMode = 1u;
225
226 for (;alphaMode < capabilities.supportedCompositeAlpha; alphaMode = alphaMode<<1u)
227 {
228 if ((alphaMode & capabilities.supportedCompositeAlpha) != 0)
229 {
230 break;
231 }
232 }
233
234 return (vk::VkCompositeAlphaFlagBitsKHR)alphaMode;
235 }
236
237 using SwapchainCreationExecutor = void (*)(const vk::DeviceDriver&, vk::VkDevice, const vk::VkSwapchainCreateInfoKHR&, tcu::TestLog&, uint32_t, uint32_t);
swapchainCreateExecutor(const vk::DeviceDriver & vk,vk::VkDevice device,const vk::VkSwapchainCreateInfoKHR & createInfo,tcu::TestLog & log,uint32_t caseIndex,uint32_t caseCount)238 void swapchainCreateExecutor(const vk::DeviceDriver& vk, vk::VkDevice device, const vk::VkSwapchainCreateInfoKHR& createInfo, tcu::TestLog& log, uint32_t caseIndex, uint32_t caseCount)
239 {
240 log << tcu::TestLog::Message << "Sub-case " << caseIndex << " / " << caseCount << ": " << createInfo << tcu::TestLog::EndMessage;
241 const vk::Unique<vk::VkSwapchainKHR> swapchain (createSwapchainKHR(vk, device, &createInfo));
242 }
243
executeSwapchainParameterCases(vk::wsi::Type wsiType,TestDimension dimension,const ProtectedContext & context,const vk::VkSurfaceCapabilitiesKHR & capabilities,const std::vector<vk::VkSurfaceFormatKHR> & formats,const std::vector<vk::VkPresentModeKHR> & presentModes,bool isExtensionForPresentModeEnabled,SwapchainCreationExecutor testExecutor)244 tcu::TestStatus executeSwapchainParameterCases (vk::wsi::Type wsiType,
245 TestDimension dimension,
246 const ProtectedContext& context,
247 const vk::VkSurfaceCapabilitiesKHR& capabilities,
248 const std::vector<vk::VkSurfaceFormatKHR>& formats,
249 const std::vector<vk::VkPresentModeKHR>& presentModes,
250 bool isExtensionForPresentModeEnabled,
251 SwapchainCreationExecutor testExecutor)
252 {
253 tcu::TestLog& log = context.getTestContext().getLog();
254 const vk::DeviceInterface& vki = context.getDeviceInterface();
255 const vk::DeviceDriver& vkd = context.getDeviceDriver();
256 vk::VkDevice device = context.getDevice();
257 const vk::wsi::PlatformProperties& platformProperties = getPlatformProperties(wsiType);
258 const vk::VkSurfaceTransformFlagBitsKHR defaultTransform = (capabilities.supportedTransforms & vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
259 ? vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : capabilities.currentTransform;
260 deUint32 queueIdx = context.getQueueFamilyIndex();
261 const vk::VkSurfaceKHR surface = context.getSurface();
262 const vk::VkSwapchainCreateInfoKHR baseParameters =
263 {
264 vk::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
265 DE_NULL,
266 #ifndef NOT_PROTECTED
267 vk::VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR,
268 #else
269 (vk::VkSwapchainCreateFlagsKHR)0,
270 #endif
271 surface,
272 capabilities.minImageCount,
273 formats[0].format,
274 formats[0].colorSpace,
275 (platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SETS_WINDOW_SIZE
276 ? capabilities.minImageExtent : capabilities.currentExtent),
277 1u, // imageArrayLayers
278 vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
279 vk::VK_SHARING_MODE_EXCLUSIVE,
280 1u,
281 &queueIdx,
282 defaultTransform,
283 firstSupportedCompositeAlpha(capabilities),
284 vk::VK_PRESENT_MODE_FIFO_KHR,
285 VK_FALSE, // clipped
286 (vk::VkSwapchainKHR)0 // oldSwapchain
287 };
288
289 vk::VkImageCreateFlags imageCreateFlag =
290 #ifndef NOT_PROTECTED
291 vk::VK_IMAGE_CREATE_PROTECTED_BIT;
292 #else
293 (vk::VkImageCreateFlags)0u;
294 #endif
295
296 switch (dimension)
297 {
298 case TEST_DIMENSION_MIN_IMAGE_COUNT:
299 {
300 // Estimate how much memory each swapchain image consumes. This isn't perfect, since
301 // swapchain images may have additional constraints that equivalent non-swapchain
302 // images don't have. But it's the best we can do.
303 vk::VkMemoryRequirements memoryRequirements;
304 {
305 const vk::VkImageCreateInfo imageInfo =
306 {
307 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
308 DE_NULL,
309 imageCreateFlag,
310 vk::VK_IMAGE_TYPE_2D,
311 baseParameters.imageFormat,
312 {
313 baseParameters.imageExtent.width,
314 baseParameters.imageExtent.height,
315 1,
316 },
317 1, // mipLevels
318 baseParameters.imageArrayLayers,
319 vk::VK_SAMPLE_COUNT_1_BIT,
320 vk::VK_IMAGE_TILING_OPTIMAL,
321 baseParameters.imageUsage,
322 baseParameters.imageSharingMode,
323 baseParameters.queueFamilyIndexCount,
324 baseParameters.pQueueFamilyIndices,
325 vk::VK_IMAGE_LAYOUT_UNDEFINED
326 };
327 vk::Move<vk::VkImage> image = vk::createImage(vki, device, &imageInfo);
328
329 memoryRequirements = vk::getImageMemoryRequirements(vki, device, *image);
330 }
331
332 // Determine the maximum memory heap space available for protected images
333 vk::VkPhysicalDeviceMemoryProperties memoryProperties = vk::getPhysicalDeviceMemoryProperties(context.getInstanceDriver(), context.getPhysicalDevice());
334 vk::VkDeviceSize protectedHeapSize = 0;
335 deUint32 protectedHeapMask = 0;
336
337 for (deUint32 memType = 0; memType < memoryProperties.memoryTypeCount; memType++)
338 {
339 deUint32 heapIndex = memoryProperties.memoryTypes[memType].heapIndex;
340 if ((memoryRequirements.memoryTypeBits & (1u << memType)) != 0 &&
341 #ifndef NOT_PROTECTED
342 (memoryProperties.memoryTypes[memType].propertyFlags & vk::VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0 &&
343 #endif
344 (protectedHeapMask & (1u << heapIndex)) == 0)
345 {
346 protectedHeapSize = de::max(protectedHeapSize, memoryProperties.memoryHeaps[heapIndex].size);
347 protectedHeapMask |= 1u << heapIndex;
348 }
349 }
350
351 vk::VkDeviceSize minProtectedHeapSize = 0u;
352 vk::VkDeviceSize maxProtectedHeapSize = protectedHeapSize;
353 bool passed = false;
354 // Apply binary search to see if we have suitable memory amount for test
355 while (minProtectedHeapSize <= maxProtectedHeapSize)
356 {
357 // If the implementation doesn't have a max image count, min+16 means we won't clamp.
358 // Limit it to how many protected images we estimate can be allocated
359 const deUint32 maxImageCount = de::min((capabilities.maxImageCount > 0u) ? capabilities.maxImageCount : capabilities.minImageCount + 16u,
360 deUint32(protectedHeapSize / memoryRequirements.size));
361 if (maxImageCount < capabilities.minImageCount)
362 {
363 minProtectedHeapSize = protectedHeapSize + 1u;
364 protectedHeapSize = minProtectedHeapSize + (maxProtectedHeapSize - minProtectedHeapSize) / 2u;
365 continue;
366 }
367
368 try
369 {
370 log << tcu::TestLog::Message << "Starting test cases with protectedHeapSize of " << protectedHeapSize << tcu::TestLog::EndMessage;
371 const deUint32 maxImageCountToTest = de::clamp(16u, capabilities.minImageCount, maxImageCount);
372 uint32_t testCount = maxImageCount - capabilities.minImageCount + 1u;
373 uint32_t testIndex = 0u;
374 // Loop downwards since we want to catch OOM errors as fast as possible
375 for (uint32_t imageCount = maxImageCountToTest; capabilities.minImageCount <= imageCount; --imageCount)
376 {
377 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
378 createInfo.minImageCount = imageCount;
379 testExecutor(vkd, device, createInfo, log, ++testIndex, testCount);
380 }
381 passed = true;
382 }
383 catch (vk::OutOfMemoryError&)
384 {
385 log << tcu::TestLog::Message << "Test cases failed with OOM error. Checking if smaller heap size is possible." << tcu::TestLog::EndMessage;
386 maxProtectedHeapSize = protectedHeapSize - 1u;
387 protectedHeapSize = minProtectedHeapSize + (maxProtectedHeapSize - minProtectedHeapSize) / 2u;
388 continue;
389 }
390
391 break;
392 }
393
394 if (!passed)
395 TCU_THROW(NotSupportedError, "Memory heap doesn't have enough memory!");
396
397 break;
398 }
399
400 case TEST_DIMENSION_IMAGE_FORMAT:
401 {
402 vk::VkPhysicalDeviceMemoryProperties memoryProperties = vk::getPhysicalDeviceMemoryProperties(context.getInstanceDriver(), context.getPhysicalDevice());
403 vk::VkDeviceSize protectedHeapSize = 0;
404
405 for (deUint32 memType = 0; memType < memoryProperties.memoryTypeCount; memType++)
406 {
407 deUint32 heapIndex = memoryProperties.memoryTypes[memType].heapIndex;
408 #ifndef NOT_PROTECTED
409 if (memoryProperties.memoryTypes[memType].propertyFlags & vk::VK_MEMORY_PROPERTY_PROTECTED_BIT)
410 #endif
411 {
412 protectedHeapSize = de::max(protectedHeapSize, memoryProperties.memoryHeaps[heapIndex].size);
413 }
414 }
415
416 bool atLeastOnePassed = false;
417 uint32_t testIndex = 0u;
418 uint32_t testCount = static_cast<uint32_t>(formats.size());
419 for (std::vector<vk::VkSurfaceFormatKHR>::const_iterator curFmt = formats.begin(); curFmt != formats.end(); ++curFmt)
420 {
421 vk::VkMemoryRequirements memoryRequirements;
422 {
423 const vk::VkImageCreateInfo imageInfo =
424 {
425 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
426 DE_NULL,
427 imageCreateFlag,
428 vk::VK_IMAGE_TYPE_2D,
429 curFmt->format,
430 {
431 platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SETS_WINDOW_SIZE
432 ? capabilities.minImageExtent.width : capabilities.currentExtent.width,
433 platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SETS_WINDOW_SIZE
434 ? capabilities.minImageExtent.height : capabilities.currentExtent.height,
435 1,
436 },
437 1, // mipLevels
438 baseParameters.imageArrayLayers,
439 vk::VK_SAMPLE_COUNT_1_BIT,
440 vk::VK_IMAGE_TILING_OPTIMAL,
441 baseParameters.imageUsage,
442 baseParameters.imageSharingMode,
443 baseParameters.queueFamilyIndexCount,
444 baseParameters.pQueueFamilyIndices,
445 vk::VK_IMAGE_LAYOUT_UNDEFINED
446 };
447
448 vk::Move<vk::VkImage> image = vk::createImage(vki, device, &imageInfo);
449
450 memoryRequirements = vk::getImageMemoryRequirements(vki, device, *image);
451 }
452
453 // Check for the image size requirement based on double/triple buffering
454 if (memoryRequirements.size * capabilities.minImageCount < protectedHeapSize)
455 {
456 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
457 createInfo.imageFormat = curFmt->format;
458 createInfo.imageColorSpace = curFmt->colorSpace;
459 try
460 {
461 testExecutor(vkd, device, createInfo, log, ++testIndex, testCount);
462 atLeastOnePassed |= true;
463 }
464 catch (vk::OutOfMemoryError&)
465 {
466 log << tcu::TestLog::Message << "Previous test case failed with OOM error." << tcu::TestLog::EndMessage;
467 continue;
468 }
469 }
470 else
471 log << tcu::TestLog::Message << "Skipping test case " << ++testIndex << "/" << testCount << tcu::TestLog::EndMessage;
472 }
473
474 if (!atLeastOnePassed)
475 TCU_THROW(NotSupportedError, "Memory heap doesn't have enough memory!");
476
477 break;
478 }
479
480 case TEST_DIMENSION_IMAGE_EXTENT:
481 {
482 static const vk::VkExtent2D s_testSizes[] =
483 {
484 { 1, 1 },
485 { 16, 32 },
486 { 32, 16 },
487 { 632, 231 },
488 { 117, 998 },
489 };
490
491 vk::VkPhysicalDeviceMemoryProperties memoryProperties = vk::getPhysicalDeviceMemoryProperties(context.getInstanceDriver(), context.getPhysicalDevice());
492 vk::VkDeviceSize protectedHeapSize = 0;
493
494 for (deUint32 memType = 0; memType < memoryProperties.memoryTypeCount; memType++)
495 {
496 deUint32 heapIndex = memoryProperties.memoryTypes[memType].heapIndex;
497 #ifndef NOT_PROTECTED
498 if (memoryProperties.memoryTypes[memType].propertyFlags & vk::VK_MEMORY_PROPERTY_PROTECTED_BIT)
499 #endif
500 {
501 protectedHeapSize = de::max(protectedHeapSize, memoryProperties.memoryHeaps[heapIndex].size);
502 }
503 }
504
505 bool atLeastOnePassed = false;
506 if (platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SETS_WINDOW_SIZE ||
507 platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SCALED_TO_WINDOW_SIZE)
508 {
509 uint32_t testCount = DE_LENGTH_OF_ARRAY(s_testSizes);
510 for (uint32_t ndx = 0; ndx < testCount; ++ndx)
511 {
512 vk::VkMemoryRequirements memoryRequirements;
513 {
514 const vk::VkImageCreateInfo imageInfo =
515 {
516 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
517 DE_NULL,
518 imageCreateFlag,
519 vk::VK_IMAGE_TYPE_2D,
520 baseParameters.imageFormat,
521 {
522 s_testSizes[ndx].width,
523 s_testSizes[ndx].height,
524 1,
525 },
526 1, // mipLevels
527 baseParameters.imageArrayLayers,
528 vk::VK_SAMPLE_COUNT_1_BIT,
529 vk::VK_IMAGE_TILING_OPTIMAL,
530 baseParameters.imageUsage,
531 baseParameters.imageSharingMode,
532 baseParameters.queueFamilyIndexCount,
533 baseParameters.pQueueFamilyIndices,
534 vk::VK_IMAGE_LAYOUT_UNDEFINED
535 };
536
537 vk::Move<vk::VkImage> image = vk::createImage(vki, device, &imageInfo);
538
539 memoryRequirements = vk::getImageMemoryRequirements(vki, device, *image);
540 }
541
542 // Check for the image size requirement based on double/triple buffering
543 if (memoryRequirements.size * capabilities.minImageCount < protectedHeapSize)
544 {
545 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
546 createInfo.imageExtent.width = de::clamp(s_testSizes[ndx].width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
547 createInfo.imageExtent.height = de::clamp(s_testSizes[ndx].height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
548 try
549 {
550 testExecutor(vkd, device, createInfo, log, ndx + 1u, testCount);
551 atLeastOnePassed |= true;
552 }
553 catch (vk::OutOfMemoryError&)
554 {
555 log << tcu::TestLog::Message << "Previous test case failed with OOM error." << tcu::TestLog::EndMessage;
556 continue;
557 }
558 }
559 else
560 log << tcu::TestLog::Message << "Skipping test case " << ndx + 1u << "/" << testCount << tcu::TestLog::EndMessage;
561 }
562 }
563
564 if (platformProperties.swapchainExtent != vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SETS_WINDOW_SIZE)
565 {
566 vk::VkMemoryRequirements memoryRequirements;
567 {
568 const vk::VkImageCreateInfo imageInfo =
569 {
570 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
571 DE_NULL,
572 imageCreateFlag,
573 vk::VK_IMAGE_TYPE_2D,
574 baseParameters.imageFormat,
575 {
576 capabilities.currentExtent.width,
577 capabilities.currentExtent.height,
578 1,
579 },
580 1, // mipLevels
581 baseParameters.imageArrayLayers,
582 vk::VK_SAMPLE_COUNT_1_BIT,
583 vk::VK_IMAGE_TILING_OPTIMAL,
584 baseParameters.imageUsage,
585 baseParameters.imageSharingMode,
586 baseParameters.queueFamilyIndexCount,
587 baseParameters.pQueueFamilyIndices,
588 vk::VK_IMAGE_LAYOUT_UNDEFINED
589 };
590
591 vk::Move<vk::VkImage> image = vk::createImage(vki, device, &imageInfo);
592
593 memoryRequirements = vk::getImageMemoryRequirements(vki, device, *image);
594 }
595
596 // Check for the image size requirement based on double/triple buffering
597 if (memoryRequirements.size * capabilities.minImageCount < protectedHeapSize)
598 {
599 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
600 createInfo.imageExtent = capabilities.currentExtent;
601 try
602 {
603 testExecutor(vkd, device, createInfo, log, 1u, 1u);
604 atLeastOnePassed |= true;
605 }
606 catch (vk::OutOfMemoryError&)
607 {
608 log << tcu::TestLog::Message << "Previous test case failed with OOM error." << tcu::TestLog::EndMessage;
609 }
610 }
611 else
612 log << tcu::TestLog::Message << "Skipping test case " << 1u << "/" << 1u << tcu::TestLog::EndMessage;
613 }
614
615 if (platformProperties.swapchainExtent != vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_MUST_MATCH_WINDOW_SIZE)
616 {
617 static const vk::VkExtent2D s_testExtentSizes[] =
618 {
619 { capabilities.minImageExtent.width, capabilities.minImageExtent.height },
620 { capabilities.maxImageExtent.width, capabilities.maxImageExtent.height },
621 };
622
623 uint32_t testCount = DE_LENGTH_OF_ARRAY(s_testExtentSizes);
624 for (uint32_t ndx = 0; ndx < testCount; ++ndx)
625 {
626 vk::VkMemoryRequirements memoryRequirements;
627 {
628 const vk::VkImageCreateInfo imageInfo =
629 {
630 vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
631 DE_NULL,
632 imageCreateFlag,
633 vk::VK_IMAGE_TYPE_2D,
634 baseParameters.imageFormat,
635 {
636 s_testExtentSizes[ndx].width,
637 s_testExtentSizes[ndx].height,
638 1,
639 },
640 1, // mipLevels
641 baseParameters.imageArrayLayers,
642 vk::VK_SAMPLE_COUNT_1_BIT,
643 vk::VK_IMAGE_TILING_OPTIMAL,
644 baseParameters.imageUsage,
645 baseParameters.imageSharingMode,
646 baseParameters.queueFamilyIndexCount,
647 baseParameters.pQueueFamilyIndices,
648 vk::VK_IMAGE_LAYOUT_UNDEFINED
649 };
650
651 vk::Move<vk::VkImage> image = vk::createImage(vki, device, &imageInfo);
652
653 memoryRequirements = vk::getImageMemoryRequirements(vki, device, *image);
654 }
655
656 // Check for the image size requirement based on double/triple buffering
657 if (memoryRequirements.size * capabilities.minImageCount < protectedHeapSize)
658 {
659 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
660 createInfo.imageExtent = s_testExtentSizes[ndx];
661 try
662 {
663 testExecutor(vkd, device, createInfo, log, ndx + 1u, testCount);
664 atLeastOnePassed |= true;
665 }
666 catch (vk::OutOfMemoryError&)
667 {
668 log << tcu::TestLog::Message << "Previous test case failed with OOM error." << tcu::TestLog::EndMessage;
669 continue;
670 }
671 }
672 else
673 log << tcu::TestLog::Message << "Skipping test case " << ndx + 1u << "/" << testCount << tcu::TestLog::EndMessage;
674 }
675 }
676
677 if (!atLeastOnePassed)
678 TCU_THROW(NotSupportedError, "Memory heap doesn't have enough memory!");
679
680 break;
681 }
682
683 case TEST_DIMENSION_IMAGE_ARRAY_LAYERS:
684 {
685 const deUint32 maxLayers = de::min(capabilities.maxImageArrayLayers, 16u);
686
687 for (deUint32 numLayers = 1; numLayers <= maxLayers; ++numLayers)
688 {
689 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
690 createInfo.imageArrayLayers = numLayers;
691 testExecutor(vkd, device, createInfo, log, numLayers, maxLayers + 1u);
692 }
693
694 break;
695 }
696
697 case TEST_DIMENSION_IMAGE_USAGE:
698 {
699 uint32_t testIndex = 0u;
700 uint32_t testCount = 0u;
701 for (deUint32 flags = 1u; flags <= capabilities.supportedUsageFlags; ++flags)
702 {
703 if ((flags & ~capabilities.supportedUsageFlags) == 0)
704 testCount++;
705 }
706
707 for (deUint32 flags = 1u; flags <= capabilities.supportedUsageFlags; ++flags)
708 {
709 if ((flags & ~capabilities.supportedUsageFlags) == 0)
710 {
711 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
712 createInfo.imageUsage = flags;
713 testExecutor(vkd, device, createInfo, log, ++testIndex, testCount);
714 }
715 }
716
717 break;
718 }
719
720 case TEST_DIMENSION_IMAGE_SHARING_MODE:
721 {
722 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
723 createInfo.imageSharingMode = vk::VK_SHARING_MODE_EXCLUSIVE;
724 testExecutor(vkd, device, createInfo, log, 1u, 2u);
725
726 uint32_t additionalQueueIndex = std::numeric_limits<uint32_t>::max();
727 {
728 const vk::InstanceDriver& instanceDriver = context.getInstanceDriver();
729 const vk::VkPhysicalDevice physicalDevice = context.getPhysicalDevice();
730 std::vector<vk::VkQueueFamilyProperties> properties;
731 deUint32 numFamilies = 0;
732
733 instanceDriver.getPhysicalDeviceQueueFamilyProperties(physicalDevice, &numFamilies, DE_NULL);
734 DE_ASSERT(numFamilies > 0);
735 properties.resize(numFamilies);
736
737 instanceDriver.getPhysicalDeviceQueueFamilyProperties(physicalDevice, &numFamilies, properties.data());
738
739 vk::VkQueueFlags requiredFlags = vk::VK_QUEUE_PROTECTED_BIT;
740 for (size_t idx = 0; idx < properties.size(); ++idx)
741 {
742 vk::VkQueueFlags flags = properties[idx].queueFlags;
743
744 if ((flags & requiredFlags) == requiredFlags && idx != queueIdx)
745 {
746 additionalQueueIndex = static_cast<uint32_t>(idx);
747 break;
748 }
749 }
750 }
751
752 // Can only test if we have multiple queues with required flags
753 if (additionalQueueIndex != std::numeric_limits<uint32_t>::max())
754 {
755 uint32_t queueIndices[2] = { queueIdx, additionalQueueIndex };
756 createInfo.imageSharingMode = vk::VK_SHARING_MODE_CONCURRENT;
757 createInfo.queueFamilyIndexCount = 2u;
758 createInfo.pQueueFamilyIndices = queueIndices;
759 testExecutor(vkd, device, createInfo, log, 2u, 2u);
760 }
761 else
762 log << tcu::TestLog::Message << "No 2 queues with required flags (VK_QUEUE_PROTECTED_BIT) found. Skipping test case " << 2u << "/" << 2u << tcu::TestLog::EndMessage;
763
764 break;
765 }
766
767 case TEST_DIMENSION_PRE_TRANSFORM:
768 {
769 uint32_t testIndex = 0u;
770 uint32_t testCount = 0u;
771 for (deUint32 transform = 1u;
772 transform <= capabilities.supportedTransforms;
773 transform = transform<<1u)
774 {
775 if ((transform & capabilities.supportedTransforms) != 0)
776 testCount++;
777 }
778
779 for (deUint32 transform = 1u;
780 transform <= capabilities.supportedTransforms;
781 transform = transform<<1u)
782 {
783 if ((transform & capabilities.supportedTransforms) != 0)
784 {
785 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
786 createInfo.preTransform = (vk::VkSurfaceTransformFlagBitsKHR)transform;
787 testExecutor(vkd, device, createInfo, log, ++testIndex, testCount);
788 }
789 }
790
791 break;
792 }
793
794 case TEST_DIMENSION_COMPOSITE_ALPHA:
795 {
796 uint32_t testIndex = 0u;
797 uint32_t testCount = 0u;
798 for (deUint32 alphaMode = 1u;
799 alphaMode <= capabilities.supportedCompositeAlpha;
800 alphaMode = alphaMode<<1u)
801 {
802 if ((alphaMode & capabilities.supportedCompositeAlpha) != 0)
803 testCount++;
804 }
805
806 for (deUint32 alphaMode = 1u;
807 alphaMode <= capabilities.supportedCompositeAlpha;
808 alphaMode = alphaMode<<1u)
809 {
810 if ((alphaMode & capabilities.supportedCompositeAlpha) != 0)
811 {
812 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
813 createInfo.compositeAlpha = (vk::VkCompositeAlphaFlagBitsKHR)alphaMode;
814 testExecutor(vkd, device, createInfo, log, ++testIndex, testCount);
815 }
816 }
817
818 break;
819 }
820
821 case TEST_DIMENSION_PRESENT_MODE:
822 {
823 uint32_t testIndex = 0u;
824 uint32_t testCount = static_cast<uint32_t>(presentModes.size());
825 for (std::vector<vk::VkPresentModeKHR>::const_iterator curMode = presentModes.begin(); curMode != presentModes.end(); ++curMode)
826 {
827 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
828 if (*curMode == vk::VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || *curMode == vk::VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR)
829 {
830 if (isExtensionForPresentModeEnabled)
831 createInfo.minImageCount = 1u; // Required by VUID-VkSwapchainCreateInfoKHR-minImageCount-01383
832 else
833 {
834 log << tcu::TestLog::Message << "Present mode (" << *curMode <<") not supported. Skipping test case " << ++testIndex << "/" << testCount << tcu::TestLog::EndMessage;
835 continue;
836 }
837 }
838 createInfo.presentMode = *curMode;
839 testExecutor(vkd, device, createInfo, log, ++testIndex, testCount);
840 }
841
842 break;
843 }
844
845 case TEST_DIMENSION_CLIPPED:
846 {
847 vk::VkSwapchainCreateInfoKHR createInfo = baseParameters;
848 createInfo.clipped = VK_FALSE;
849 testExecutor(vkd, device, createInfo, log, 1u, 2u);
850
851 createInfo.clipped = VK_TRUE;
852 testExecutor(vkd, device, createInfo, log, 2u, 2u);
853
854 break;
855 }
856
857 default:
858 DE_FATAL("Impossible");
859 }
860
861 return tcu::TestStatus::pass("Creating swapchain succeeded");
862 }
863
executeSwapchainParameterCases(vk::wsi::Type wsiType,TestDimension dimension,const ProtectedContext & context,vk::VkSurfaceKHR surface,bool isExtensionForPresentModeEnabled,SwapchainCreationExecutor testExecutor)864 tcu::TestStatus executeSwapchainParameterCases (vk::wsi::Type wsiType,
865 TestDimension dimension,
866 const ProtectedContext& context,
867 vk::VkSurfaceKHR surface,
868 bool isExtensionForPresentModeEnabled,
869 SwapchainCreationExecutor testExecutor)
870 {
871 const vk::InstanceInterface& vki = context.getInstanceDriver();
872 vk::VkPhysicalDevice physicalDevice = context.getPhysicalDevice();
873 const vk::VkSurfaceCapabilitiesKHR capabilities = vk::wsi::getPhysicalDeviceSurfaceCapabilities(vki,
874 physicalDevice,
875 surface);
876 const std::vector<vk::VkSurfaceFormatKHR> formats = vk::wsi::getPhysicalDeviceSurfaceFormats(vki,
877 physicalDevice,
878 surface);
879 const std::vector<vk::VkPresentModeKHR> presentModes = vk::wsi::getPhysicalDeviceSurfacePresentModes(vki,
880 physicalDevice,
881 surface);
882
883 return executeSwapchainParameterCases(wsiType, dimension, context, capabilities, formats, presentModes, isExtensionForPresentModeEnabled, testExecutor);
884 }
885
createSwapchainTest(Context & baseCtx,TestParameters params)886 tcu::TestStatus createSwapchainTest (Context& baseCtx, TestParameters params)
887 {
888 bool isExtensionForPresentModeEnabled = false;
889 std::vector<vk::VkExtensionProperties> supportedExtensions (enumerateInstanceExtensionProperties(baseCtx.getPlatformInterface(), DE_NULL));
890 std::vector<std::string> instExts = getRequiredWsiExtensions(supportedExtensions, params.wsiType);
891 std::vector<std::string> devExts;
892 devExts.push_back("VK_KHR_swapchain");
893
894 // Try to enable VK_KHR_shared_presentable_image for its respective present mode testing
895 if (params.dimension == TEST_DIMENSION_PRESENT_MODE)
896 {
897 Extensions deviceExtensions = vk::enumerateDeviceExtensionProperties(baseCtx.getInstanceInterface(), baseCtx.getPhysicalDevice(), DE_NULL);
898 for (size_t i = 0u; i < deviceExtensions.size(); ++i)
899 {
900 if (std::strcmp(deviceExtensions[i].extensionName, "VK_KHR_shared_presentable_image") == 0)
901 {
902 devExts.emplace_back("VK_KHR_shared_presentable_image");
903 isExtensionForPresentModeEnabled = true;
904 break;
905 }
906 }
907 }
908
909 const NativeObjects native (baseCtx, supportedExtensions, params.wsiType);
910 ProtectedContext context (baseCtx, params.wsiType, *native.display, *native.window, instExts, devExts);
911 vk::VkSurfaceKHR surface = context.getSurface();
912
913 return executeSwapchainParameterCases(params.wsiType, params.dimension, context, surface, isExtensionForPresentModeEnabled, swapchainCreateExecutor);
914 }
915
916 struct GroupParameters
917 {
918 typedef FunctionInstance1<TestParameters>::Function Function;
919
920 vk::wsi::Type wsiType;
921 Function function;
922
GroupParametersvkt::ProtectedMem::__anon5d8586710111::GroupParameters923 GroupParameters (vk::wsi::Type wsiType_, Function function_)
924 : wsiType (wsiType_)
925 , function (function_)
926 {}
927
GroupParametersvkt::ProtectedMem::__anon5d8586710111::GroupParameters928 GroupParameters (void)
929 : wsiType (vk::wsi::TYPE_LAST)
930 , function ((Function)DE_NULL)
931 {}
932 };
933
checkSupport(Context & context,TestParameters)934 void checkSupport (Context& context, TestParameters)
935 {
936 checkProtectedQueueSupport(context);
937 }
938
populateSwapchainGroup(tcu::TestCaseGroup * testGroup,GroupParameters params)939 void populateSwapchainGroup (tcu::TestCaseGroup* testGroup, GroupParameters params)
940 {
941 for (int dimensionNdx = 0; dimensionNdx < TEST_DIMENSION_LAST; ++dimensionNdx)
942 {
943 const TestDimension testDimension = (TestDimension)dimensionNdx;
944
945 addFunctionCase(testGroup, getTestDimensionName(testDimension), checkSupport, params.function, TestParameters(params.wsiType, testDimension));
946 }
947 }
948
getBasicSwapchainParameters(vk::wsi::Type wsiType,const vk::InstanceInterface & vki,vk::VkPhysicalDevice physicalDevice,vk::VkSurfaceKHR surface,const tcu::UVec2 & desiredSize,deUint32 desiredImageCount)949 vk::VkSwapchainCreateInfoKHR getBasicSwapchainParameters (vk::wsi::Type wsiType,
950 const vk::InstanceInterface& vki,
951 vk::VkPhysicalDevice physicalDevice,
952 vk::VkSurfaceKHR surface,
953 const tcu::UVec2& desiredSize,
954 deUint32 desiredImageCount)
955 {
956 const vk::VkSurfaceCapabilitiesKHR capabilities = vk::wsi::getPhysicalDeviceSurfaceCapabilities(vki,
957 physicalDevice,
958 surface);
959 const std::vector<vk::VkSurfaceFormatKHR> formats = vk::wsi::getPhysicalDeviceSurfaceFormats(vki,
960 physicalDevice,
961 surface);
962 const vk::wsi::PlatformProperties& platformProperties = vk::wsi::getPlatformProperties(wsiType);
963 const vk::VkSurfaceTransformFlagBitsKHR transform = (capabilities.supportedTransforms & vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
964 ? vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : capabilities.currentTransform;
965 const vk::VkSwapchainCreateInfoKHR parameters =
966 {
967 vk::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
968 DE_NULL,
969 vk::VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR,
970 surface,
971 de::clamp(desiredImageCount, capabilities.minImageCount, capabilities.maxImageCount > 0 ? capabilities.maxImageCount : capabilities.minImageCount + desiredImageCount),
972 formats[0].format,
973 formats[0].colorSpace,
974 (platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_MUST_MATCH_WINDOW_SIZE
975 ? capabilities.currentExtent : vk::makeExtent2D(desiredSize.x(), desiredSize.y())),
976 1u, // imageArrayLayers
977 vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
978 vk::VK_SHARING_MODE_EXCLUSIVE,
979 0u,
980 (const deUint32*)DE_NULL,
981 transform,
982 firstSupportedCompositeAlpha(capabilities),
983 vk::VK_PRESENT_MODE_FIFO_KHR,
984 VK_FALSE, // clipped
985 (vk::VkSwapchainKHR)0 // oldSwapchain
986 };
987
988 return parameters;
989 }
990
991 typedef de::SharedPtr<vk::Unique<vk::VkImageView> > ImageViewSp;
992 typedef de::SharedPtr<vk::Unique<vk::VkFramebuffer> > FramebufferSp;
993
994 class TriangleRenderer
995 {
996 public:
997 TriangleRenderer (ProtectedContext& context,
998 const vk::BinaryCollection& binaryRegistry,
999 const std::vector<vk::VkImage> swapchainImages,
1000 const vk::VkFormat framebufferFormat,
1001 const tcu::UVec2& renderSize);
1002 ~TriangleRenderer (void);
1003
1004 void recordFrame (vk::VkCommandBuffer cmdBuffer,
1005 deUint32 imageNdx,
1006 deUint32 frameNdx) const;
1007
1008 static void getPrograms (vk::SourceCollections& dst);
1009
1010 private:
1011 static vk::Move<vk::VkRenderPass> createRenderPass (const vk::DeviceInterface& vkd,
1012 const vk::VkDevice device,
1013 const vk::VkFormat colorAttachmentFormat);
1014 static vk::Move<vk::VkPipelineLayout> createPipelineLayout(const vk::DeviceInterface& vkd,
1015 vk::VkDevice device);
1016 static vk::Move<vk::VkPipeline> createPipeline (const vk::DeviceInterface& vkd,
1017 const vk::VkDevice device,
1018 const vk::VkRenderPass renderPass,
1019 const vk::VkPipelineLayout pipelineLayout,
1020 const vk::BinaryCollection& binaryCollection,
1021 const tcu::UVec2& renderSize);
1022
1023 const vk::DeviceInterface& m_vkd;
1024
1025 const std::vector<vk::VkImage> m_swapchainImages;
1026 const tcu::UVec2 m_renderSize;
1027
1028 const vk::Unique<vk::VkRenderPass> m_renderPass;
1029 const vk::Unique<vk::VkPipelineLayout> m_pipelineLayout;
1030 const vk::Unique<vk::VkPipeline> m_pipeline;
1031
1032 const de::UniquePtr<vk::BufferWithMemory> m_vertexBuffer;
1033
1034 std::vector<ImageViewSp> m_attachmentViews;
1035 std::vector<FramebufferSp> m_framebuffers;
1036 };
1037
createRenderPass(const vk::DeviceInterface & vkd,const vk::VkDevice device,const vk::VkFormat colorAttachmentFormat)1038 vk::Move<vk::VkRenderPass> TriangleRenderer::createRenderPass (const vk::DeviceInterface& vkd,
1039 const vk::VkDevice device,
1040 const vk::VkFormat colorAttachmentFormat)
1041 {
1042 const vk::VkAttachmentDescription colorAttDesc =
1043 {
1044 (vk::VkAttachmentDescriptionFlags)0,
1045 colorAttachmentFormat,
1046 vk::VK_SAMPLE_COUNT_1_BIT,
1047 vk::VK_ATTACHMENT_LOAD_OP_CLEAR,
1048 vk::VK_ATTACHMENT_STORE_OP_STORE,
1049 vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE,
1050 vk::VK_ATTACHMENT_STORE_OP_DONT_CARE,
1051 vk::VK_IMAGE_LAYOUT_UNDEFINED,
1052 vk::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
1053 };
1054 const vk::VkAttachmentReference colorAttRef =
1055 {
1056 0u,
1057 vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
1058 };
1059 const vk::VkSubpassDescription subpassDesc =
1060 {
1061 (vk::VkSubpassDescriptionFlags)0u,
1062 vk::VK_PIPELINE_BIND_POINT_GRAPHICS,
1063 0u, // inputAttachmentCount
1064 DE_NULL, // pInputAttachments
1065 1u, // colorAttachmentCount
1066 &colorAttRef, // pColorAttachments
1067 DE_NULL, // pResolveAttachments
1068 DE_NULL, // depthStencilAttachment
1069 0u, // preserveAttachmentCount
1070 DE_NULL, // pPreserveAttachments
1071 };
1072 const vk::VkSubpassDependency dependencies[] =
1073 {
1074 {
1075 VK_SUBPASS_EXTERNAL, // srcSubpass
1076 0u, // dstSubpass
1077 vk::VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
1078 vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
1079 vk::VK_ACCESS_MEMORY_READ_BIT,
1080 (vk::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|
1081 vk::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
1082 vk::VK_DEPENDENCY_BY_REGION_BIT
1083 },
1084 {
1085 0u, // srcSubpass
1086 VK_SUBPASS_EXTERNAL, // dstSubpass
1087 vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
1088 vk::VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
1089 (vk::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT|
1090 vk::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
1091 vk::VK_ACCESS_MEMORY_READ_BIT,
1092 vk::VK_DEPENDENCY_BY_REGION_BIT
1093 },
1094 };
1095 const vk::VkRenderPassCreateInfo renderPassParams =
1096 {
1097 vk::VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
1098 DE_NULL,
1099 (vk::VkRenderPassCreateFlags)0,
1100 1u,
1101 &colorAttDesc,
1102 1u,
1103 &subpassDesc,
1104 DE_LENGTH_OF_ARRAY(dependencies),
1105 dependencies,
1106 };
1107
1108 return vk::createRenderPass(vkd, device, &renderPassParams);
1109 }
1110
createPipelineLayout(const vk::DeviceInterface & vkd,const vk::VkDevice device)1111 vk::Move<vk::VkPipelineLayout> TriangleRenderer::createPipelineLayout (const vk::DeviceInterface& vkd,
1112 const vk::VkDevice device)
1113 {
1114 const vk::VkPushConstantRange pushConstantRange =
1115 {
1116 vk::VK_SHADER_STAGE_VERTEX_BIT,
1117 0u, // offset
1118 (deUint32)sizeof(deUint32), // size
1119 };
1120 const vk::VkPipelineLayoutCreateInfo pipelineLayoutParams =
1121 {
1122 vk::VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
1123 DE_NULL,
1124 (vk::VkPipelineLayoutCreateFlags)0,
1125 0u, // setLayoutCount
1126 DE_NULL, // pSetLayouts
1127 1u,
1128 &pushConstantRange,
1129 };
1130
1131 return vk::createPipelineLayout(vkd, device, &pipelineLayoutParams);
1132 }
1133
createPipeline(const vk::DeviceInterface & vkd,const vk::VkDevice device,const vk::VkRenderPass renderPass,const vk::VkPipelineLayout pipelineLayout,const vk::BinaryCollection & binaryCollection,const tcu::UVec2 & renderSize)1134 vk::Move<vk::VkPipeline> TriangleRenderer::createPipeline (const vk::DeviceInterface& vkd,
1135 const vk::VkDevice device,
1136 const vk::VkRenderPass renderPass,
1137 const vk::VkPipelineLayout pipelineLayout,
1138 const vk::BinaryCollection& binaryCollection,
1139 const tcu::UVec2& renderSize)
1140 {
1141 // \note VkShaderModules are fully consumed by vkCreateGraphicsPipelines()
1142 // and can be deleted immediately following that call.
1143 const vk::Unique<vk::VkShaderModule> vertShaderModule (createShaderModule(vkd, device, binaryCollection.get("tri-vert"), 0));
1144 const vk::Unique<vk::VkShaderModule> fragShaderModule (createShaderModule(vkd, device, binaryCollection.get("tri-frag"), 0));
1145 const std::vector<vk::VkViewport> viewports (1, vk::makeViewport(renderSize));
1146 const std::vector<vk::VkRect2D> scissors (1, vk::makeRect2D(renderSize));
1147
1148 return vk::makeGraphicsPipeline(vkd, // const DeviceInterface& vk
1149 device, // const VkDevice device
1150 pipelineLayout, // const VkPipelineLayout pipelineLayout
1151 *vertShaderModule, // const VkShaderModule vertexShaderModule
1152 DE_NULL, // const VkShaderModule tessellationControlShaderModule
1153 DE_NULL, // const VkShaderModule tessellationEvalShaderModule
1154 DE_NULL, // const VkShaderModule geometryShaderModule
1155 *fragShaderModule, // const VkShaderModule fragmentShaderModule
1156 renderPass, // const VkRenderPass renderPass
1157 viewports, // const std::vector<VkViewport>& viewports
1158 scissors); // const std::vector<VkRect2D>& scissors
1159 }
1160
TriangleRenderer(ProtectedContext & context,const vk::BinaryCollection & binaryRegistry,const std::vector<vk::VkImage> swapchainImages,const vk::VkFormat framebufferFormat,const tcu::UVec2 & renderSize)1161 TriangleRenderer::TriangleRenderer (ProtectedContext& context,
1162 const vk::BinaryCollection& binaryRegistry,
1163 const std::vector<vk::VkImage> swapchainImages,
1164 const vk::VkFormat framebufferFormat,
1165 const tcu::UVec2& renderSize)
1166 : m_vkd (context.getDeviceInterface())
1167 , m_swapchainImages (swapchainImages)
1168 , m_renderSize (renderSize)
1169 , m_renderPass (createRenderPass(m_vkd, context.getDevice(), framebufferFormat))
1170 , m_pipelineLayout (createPipelineLayout(m_vkd, context.getDevice()))
1171 , m_pipeline (createPipeline(m_vkd, context.getDevice(), *m_renderPass, *m_pipelineLayout, binaryRegistry, renderSize))
1172 , m_vertexBuffer (makeBuffer(context,
1173 PROTECTION_DISABLED,
1174 context.getQueueFamilyIndex(),
1175 (deUint32)(sizeof(float)*4*3),
1176 vk::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1177 vk::MemoryRequirement::HostVisible))
1178 {
1179 m_attachmentViews.resize(swapchainImages.size());
1180 m_framebuffers.resize(swapchainImages.size());
1181
1182 for (size_t imageNdx = 0; imageNdx < swapchainImages.size(); ++imageNdx)
1183 {
1184 m_attachmentViews[imageNdx] = ImageViewSp(new vk::Unique<vk::VkImageView>(createImageView(context, swapchainImages[imageNdx], framebufferFormat)));
1185 m_framebuffers[imageNdx] = FramebufferSp(new vk::Unique<vk::VkFramebuffer>(createFramebuffer(context,
1186 renderSize.x(),
1187 renderSize.y(),
1188 *m_renderPass,
1189 **m_attachmentViews[imageNdx])));
1190 }
1191
1192 // Upload vertex data
1193 {
1194 const tcu::Vec4 vertices[] =
1195 {
1196 tcu::Vec4(-0.5f, -0.5f, 0.0f, 1.0f),
1197 tcu::Vec4(+0.5f, -0.5f, 0.0f, 1.0f),
1198 tcu::Vec4( 0.0f, +0.5f, 0.0f, 1.0f)
1199 };
1200 DE_STATIC_ASSERT(sizeof(vertices) == sizeof(float)*4*3);
1201
1202 deMemcpy(m_vertexBuffer->getAllocation().getHostPtr(), &vertices[0], sizeof(vertices));
1203 flushAlloc(m_vkd, context.getDevice(), m_vertexBuffer->getAllocation());
1204 }
1205 }
1206
~TriangleRenderer(void)1207 TriangleRenderer::~TriangleRenderer (void)
1208 {
1209 }
1210
recordFrame(vk::VkCommandBuffer cmdBuffer,deUint32 imageNdx,deUint32 frameNdx) const1211 void TriangleRenderer::recordFrame (vk::VkCommandBuffer cmdBuffer,
1212 deUint32 imageNdx,
1213 deUint32 frameNdx) const
1214 {
1215 const vk::VkFramebuffer curFramebuffer = **m_framebuffers[imageNdx];
1216
1217 beginCommandBuffer(m_vkd, cmdBuffer, 0u);
1218
1219 beginRenderPass(m_vkd, cmdBuffer, *m_renderPass, curFramebuffer, vk::makeRect2D(0, 0, m_renderSize.x(), m_renderSize.y()), tcu::Vec4(0.125f, 0.25f, 0.75f, 1.0f));
1220 m_vkd.cmdBindPipeline(cmdBuffer, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipeline);
1221
1222 {
1223 const vk::VkDeviceSize bindingOffset = 0;
1224 m_vkd.cmdBindVertexBuffers(cmdBuffer, 0u, 1u, &m_vertexBuffer->get(), &bindingOffset);
1225 }
1226
1227 m_vkd.cmdPushConstants(cmdBuffer, *m_pipelineLayout, vk::VK_SHADER_STAGE_VERTEX_BIT, 0u, (deUint32)sizeof(deUint32), &frameNdx);
1228 m_vkd.cmdDraw(cmdBuffer, 3u, 1u, 0u, 0u);
1229 endRenderPass(m_vkd, cmdBuffer);
1230
1231 endCommandBuffer(m_vkd, cmdBuffer);
1232 }
1233
getPrograms(vk::SourceCollections & dst)1234 void TriangleRenderer::getPrograms (vk::SourceCollections& dst)
1235 {
1236 dst.glslSources.add("tri-vert") << glu::VertexSource(
1237 "#version 310 es\n"
1238 "layout(location = 0) in highp vec4 a_position;\n"
1239 "layout(push_constant) uniform FrameData\n"
1240 "{\n"
1241 " highp uint frameNdx;\n"
1242 "} frameData;\n"
1243 "void main (void)\n"
1244 "{\n"
1245 " highp float angle = float(frameData.frameNdx) / 100.0;\n"
1246 " highp float c = cos(angle);\n"
1247 " highp float s = sin(angle);\n"
1248 " highp mat4 t = mat4( c, -s, 0, 0,\n"
1249 " s, c, 0, 0,\n"
1250 " 0, 0, 1, 0,\n"
1251 " 0, 0, 0, 1);\n"
1252 " gl_Position = t * a_position;\n"
1253 "}\n");
1254 dst.glslSources.add("tri-frag") << glu::FragmentSource(
1255 "#version 310 es\n"
1256 "layout(location = 0) out lowp vec4 o_color;\n"
1257 "void main (void) { o_color = vec4(1.0, 0.0, 1.0, 1.0); }\n");
1258 }
1259
1260 typedef de::SharedPtr<vk::Unique<vk::VkCommandBuffer> > CommandBufferSp;
1261 typedef de::SharedPtr<vk::Unique<vk::VkFence> > FenceSp;
1262 typedef de::SharedPtr<vk::Unique<vk::VkSemaphore> > SemaphoreSp;
1263
createFences(const vk::DeviceInterface & vkd,const vk::VkDevice device,size_t numFences)1264 std::vector<FenceSp> createFences (const vk::DeviceInterface& vkd,
1265 const vk::VkDevice device,
1266 size_t numFences)
1267 {
1268 std::vector<FenceSp> fences(numFences);
1269
1270 for (size_t ndx = 0; ndx < numFences; ++ndx)
1271 fences[ndx] = FenceSp(new vk::Unique<vk::VkFence>(createFence(vkd, device)));
1272
1273 return fences;
1274 }
1275
createSemaphores(const vk::DeviceInterface & vkd,const vk::VkDevice device,size_t numSemaphores)1276 std::vector<SemaphoreSp> createSemaphores (const vk::DeviceInterface& vkd,
1277 const vk::VkDevice device,
1278 size_t numSemaphores)
1279 {
1280 std::vector<SemaphoreSp> semaphores(numSemaphores);
1281
1282 for (size_t ndx = 0; ndx < numSemaphores; ++ndx)
1283 semaphores[ndx] = SemaphoreSp(new vk::Unique<vk::VkSemaphore>(createSemaphore(vkd, device)));
1284
1285 return semaphores;
1286 }
1287
allocateCommandBuffers(const vk::DeviceInterface & vkd,const vk::VkDevice device,const vk::VkCommandPool commandPool,const vk::VkCommandBufferLevel level,const size_t numCommandBuffers)1288 std::vector<CommandBufferSp> allocateCommandBuffers (const vk::DeviceInterface& vkd,
1289 const vk::VkDevice device,
1290 const vk::VkCommandPool commandPool,
1291 const vk::VkCommandBufferLevel level,
1292 const size_t numCommandBuffers)
1293 {
1294 std::vector<CommandBufferSp> buffers (numCommandBuffers);
1295
1296 for (size_t ndx = 0; ndx < numCommandBuffers; ++ndx)
1297 buffers[ndx] = CommandBufferSp(new vk::Unique<vk::VkCommandBuffer>(allocateCommandBuffer(vkd, device, commandPool, level)));
1298
1299 return buffers;
1300 }
1301
basicRenderTest(Context & baseCtx,vk::wsi::Type wsiType)1302 tcu::TestStatus basicRenderTest (Context& baseCtx, vk::wsi::Type wsiType)
1303 {
1304 std::vector<vk::VkExtensionProperties> supportedExtensions (enumerateInstanceExtensionProperties(baseCtx.getPlatformInterface(), DE_NULL));
1305 std::vector<std::string> instExts = getRequiredWsiExtensions(supportedExtensions, wsiType);
1306 std::vector<std::string> devExts;
1307 devExts.push_back("VK_KHR_swapchain");
1308
1309 const tcu::UVec2 desiredSize (256, 256);
1310 const NativeObjects native (baseCtx, supportedExtensions, wsiType, tcu::just(desiredSize));
1311 ProtectedContext context (baseCtx, wsiType, *native.display, *native.window, instExts, devExts);
1312 vk::VkSurfaceKHR surface = context.getSurface();
1313 const vk::DeviceInterface& vkd = context.getDeviceInterface();
1314 const vk::VkDevice device = context.getDevice();
1315 const vk::VkSwapchainCreateInfoKHR swapchainInfo = getBasicSwapchainParameters(wsiType,
1316 context.getInstanceDriver(),
1317 context.getPhysicalDevice(),
1318 surface,
1319 desiredSize,
1320 2);
1321 const vk::Unique<vk::VkSwapchainKHR> swapchain (createSwapchainKHR(vkd, device, &swapchainInfo));
1322 const std::vector<vk::VkImage> swapchainImages = vk::wsi::getSwapchainImages(vkd, device, *swapchain);
1323
1324 const TriangleRenderer renderer (context,
1325 context.getBinaryCollection(),
1326 swapchainImages,
1327 swapchainInfo.imageFormat,
1328 tcu::UVec2(swapchainInfo.imageExtent.width, swapchainInfo.imageExtent.height));
1329
1330 const vk::Unique<vk::VkCommandPool> commandPool (makeCommandPool(vkd, device, PROTECTION_ENABLED,
1331 context.getQueueFamilyIndex()));
1332
1333 const size_t maxQueuedFrames = swapchainImages.size()*2;
1334
1335 // We need to keep hold of fences from vkAcquireNextImageKHR to actually
1336 // limit number of frames we allow to be queued.
1337 const std::vector<FenceSp> imageReadyFences (createFences(vkd, device, maxQueuedFrames));
1338
1339 // We need maxQueuedFrames+1 for imageReadySemaphores pool as we need to pass
1340 // the semaphore in same time as the fence we use to meter rendering.
1341 const std::vector<SemaphoreSp> imageReadySemaphores (createSemaphores(vkd, device, maxQueuedFrames+1));
1342
1343 // For rest we simply need maxQueuedFrames as we will wait for image
1344 // from frameNdx-maxQueuedFrames to become available to us, guaranteeing that
1345 // previous uses must have completed.
1346 const std::vector<SemaphoreSp> renderingCompleteSemaphores (createSemaphores(vkd, device, maxQueuedFrames));
1347 const std::vector<CommandBufferSp> commandBuffers (allocateCommandBuffers(vkd,
1348 device,
1349 *commandPool,
1350 vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY,
1351 maxQueuedFrames));
1352
1353 if (isExtensionStructSupported(supportedExtensions, vk::RequiredExtension("VK_KHR_surface_protected_capabilities")))
1354 {
1355 // Check if swapchain can be created for protected surface
1356 const vk::InstanceInterface& vki = context.getInstanceDriver();
1357 vk::VkSurfaceCapabilities2KHR extCapabilities;
1358 vk::VkSurfaceProtectedCapabilitiesKHR extProtectedCapabilities;
1359 const vk::VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo =
1360 {
1361 vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
1362 DE_NULL,
1363 surface
1364 };
1365
1366 extProtectedCapabilities.sType = vk::VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR;
1367 extProtectedCapabilities.pNext = DE_NULL;
1368 extProtectedCapabilities.supportsProtected = DE_FALSE;
1369
1370 extCapabilities.sType = vk::VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR;
1371 extCapabilities.pNext = &extProtectedCapabilities;
1372
1373 VK_CHECK(vki.getPhysicalDeviceSurfaceCapabilities2KHR(context.getPhysicalDevice(), &surfaceInfo, &extCapabilities));
1374
1375 if (extProtectedCapabilities.supportsProtected == DE_FALSE)
1376 TCU_THROW(NotSupportedError, "Swapchain creation for Protected VkSurface is not Supported.");
1377 }
1378
1379 try
1380 {
1381 const deUint32 numFramesToRender = 60*10;
1382
1383 for (deUint32 frameNdx = 0; frameNdx < numFramesToRender; ++frameNdx)
1384 {
1385 const vk::VkFence imageReadyFence = **imageReadyFences[frameNdx%imageReadyFences.size()];
1386 const vk::VkSemaphore imageReadySemaphore = **imageReadySemaphores[frameNdx%imageReadySemaphores.size()];
1387 deUint32 imageNdx = ~0u;
1388
1389 if (frameNdx >= maxQueuedFrames)
1390 VK_CHECK(vkd.waitForFences(device, 1u, &imageReadyFence, VK_TRUE, std::numeric_limits<deUint64>::max()));
1391
1392 VK_CHECK(vkd.resetFences(device, 1, &imageReadyFence));
1393
1394 {
1395 const vk::VkResult acquireResult = vkd.acquireNextImageKHR(device,
1396 *swapchain,
1397 std::numeric_limits<deUint64>::max(),
1398 imageReadySemaphore,
1399 0,
1400 &imageNdx);
1401
1402 if (acquireResult == vk::VK_SUBOPTIMAL_KHR)
1403 context.getTestContext().getLog() << tcu::TestLog::Message << "Got " << acquireResult << " at frame " << frameNdx << tcu::TestLog::EndMessage;
1404 else
1405 VK_CHECK(acquireResult);
1406 }
1407
1408 TCU_CHECK((size_t)imageNdx < swapchainImages.size());
1409
1410 {
1411 const vk::VkSemaphore renderingCompleteSemaphore = **renderingCompleteSemaphores[frameNdx%renderingCompleteSemaphores.size()];
1412 const vk::VkCommandBuffer commandBuffer = **commandBuffers[frameNdx%commandBuffers.size()];
1413 const vk::VkPipelineStageFlags waitDstStage = vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1414 vk::VkSubmitInfo submitInfo =
1415 {
1416 vk::VK_STRUCTURE_TYPE_SUBMIT_INFO,
1417 DE_NULL,
1418 1u,
1419 &imageReadySemaphore,
1420 &waitDstStage,
1421 1u,
1422 &commandBuffer,
1423 1u,
1424 &renderingCompleteSemaphore
1425 };
1426
1427 const vk::VkProtectedSubmitInfo protectedInfo =
1428 {
1429 vk::VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO, // sType
1430 DE_NULL, // pNext
1431 VK_TRUE, // protectedSubmit
1432 };
1433 submitInfo.pNext = &protectedInfo;
1434
1435 const vk::VkPresentInfoKHR presentInfo =
1436 {
1437 vk::VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
1438 DE_NULL,
1439 1u,
1440 &renderingCompleteSemaphore,
1441 1u,
1442 &*swapchain,
1443 &imageNdx,
1444 (vk::VkResult*)DE_NULL
1445 };
1446
1447 renderer.recordFrame(commandBuffer, imageNdx, frameNdx);
1448 VK_CHECK(vkd.queueSubmit(context.getQueue(), 1u, &submitInfo, imageReadyFence));
1449 VK_CHECK_WSI(vkd.queuePresentKHR(context.getQueue(), &presentInfo));
1450 }
1451 }
1452
1453 VK_CHECK(vkd.deviceWaitIdle(device));
1454 }
1455 catch (...)
1456 {
1457 // Make sure device is idle before destroying resources
1458 vkd.deviceWaitIdle(device);
1459 throw;
1460 }
1461
1462 return tcu::TestStatus::pass("Rendering tests succeeded");
1463 }
1464
getBasicRenderPrograms(vk::SourceCollections & dst,vk::wsi::Type)1465 void getBasicRenderPrograms (vk::SourceCollections& dst, vk::wsi::Type)
1466 {
1467 TriangleRenderer::getPrograms(dst);
1468 }
1469
checkSupport(Context & context,vk::wsi::Type)1470 void checkSupport (Context& context, vk::wsi::Type)
1471 {
1472 checkProtectedQueueSupport(context);
1473 }
1474
populateRenderGroup(tcu::TestCaseGroup * testGroup,vk::wsi::Type wsiType)1475 void populateRenderGroup (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
1476 {
1477 addFunctionCaseWithPrograms(testGroup, "basic", checkSupport, getBasicRenderPrograms, basicRenderTest, wsiType);
1478 }
1479
createSwapchainTests(tcu::TestCaseGroup * testGroup,vk::wsi::Type wsiType)1480 void createSwapchainTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
1481 {
1482 // Create VkSwapchain with various parameters
1483 addTestGroup(testGroup, "create", populateSwapchainGroup, GroupParameters(wsiType, createSwapchainTest));
1484 // Rendering Tests
1485 addTestGroup(testGroup, "render", populateRenderGroup, wsiType);
1486 }
1487
createTypeSpecificTests(tcu::TestCaseGroup * testGroup,vk::wsi::Type wsiType)1488 void createTypeSpecificTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
1489 {
1490 // VkSwapchain Tests
1491 addTestGroup(testGroup, "swapchain", createSwapchainTests, wsiType);
1492 }
1493
1494 } // anonymous
1495
createSwapchainTests(tcu::TestContext & testCtx)1496 tcu::TestCaseGroup* createSwapchainTests (tcu::TestContext& testCtx)
1497 {
1498 // WSI Tests
1499 de::MovePtr<tcu::TestCaseGroup> wsiTestGroup (new tcu::TestCaseGroup(testCtx, "wsi"));
1500
1501 for (int typeNdx = 0; typeNdx < vk::wsi::TYPE_LAST; ++typeNdx)
1502 {
1503 const vk::wsi::Type wsiType = (vk::wsi::Type)typeNdx;
1504
1505 addTestGroup(&*wsiTestGroup, getName(wsiType), createTypeSpecificTests, wsiType);
1506 }
1507
1508 return wsiTestGroup.release();
1509 }
1510
1511 } // wsi
1512 } // vkt
1513