/*------------------------------------------------------------------------- * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Tests for shared presentable image extension *//*--------------------------------------------------------------------*/ #include "vktWsiSharedPresentableImageTests.hpp" #include "vktCustomInstancesDevices.hpp" #include "vktTestCaseUtil.hpp" #include "vktTestGroupUtil.hpp" #include "vkRefUtil.hpp" #include "vkWsiPlatform.hpp" #include "vkWsiUtil.hpp" #include "vkQueryUtil.hpp" #include "vkDeviceUtil.hpp" #include "vkPlatform.hpp" #include "vkTypeUtil.hpp" #include "vkPrograms.hpp" #include "vkCmdUtil.hpp" #include "vkObjUtil.hpp" #include "vkWsiUtil.hpp" #include "tcuPlatform.hpp" #include "tcuResultCollector.hpp" #include "tcuTestLog.hpp" #include "tcuCommandLine.hpp" #include #include using std::vector; using std::string; using tcu::Maybe; using tcu::UVec2; using tcu::TestLog; namespace vkt { namespace wsi { namespace { enum Scaling { SCALING_NONE, SCALING_UP, SCALING_DOWN }; typedef vector Extensions; void checkAllSupported (const Extensions& supportedExtensions, const vector& requiredExtensions) { for (vector::const_iterator requiredExtName = requiredExtensions.begin(); requiredExtName != requiredExtensions.end(); ++requiredExtName) { if (!isExtensionStructSupported(supportedExtensions, vk::RequiredExtension(*requiredExtName))) TCU_THROW(NotSupportedError, (*requiredExtName + " is not supported").c_str()); } } CustomInstance createInstanceWithWsi (Context& context, const Extensions& supportedExtensions, vk::wsi::Type wsiType) { const deUint32 version = context.getUsedApiVersion(); vector extensions; if (!vk::isCoreInstanceExtension(version, "VK_KHR_get_physical_device_properties2")) extensions.push_back("VK_KHR_get_physical_device_properties2"); extensions.push_back("VK_KHR_surface"); extensions.push_back("VK_KHR_get_surface_capabilities2"); // Required for device extension to expose new physical device bits (in this // case, presentation mode enums) extensions.push_back(getExtensionName(wsiType)); if (isDisplaySurface(wsiType)) extensions.push_back("VK_KHR_display"); checkAllSupported(supportedExtensions, extensions); return vkt::createCustomInstanceWithExtensions(context, extensions); } vk::VkPhysicalDeviceFeatures getDeviceNullFeatures (void) { vk::VkPhysicalDeviceFeatures features; deMemset(&features, 0, sizeof(features)); return features; } vk::Move createDeviceWithWsi (const vk::PlatformInterface& vkp, vk::VkInstance instance, const vk::InstanceInterface& vki, vk::VkPhysicalDevice physicalDevice, const Extensions& supportedExtensions, const deUint32 queueFamilyIndex, bool requiresSharedPresentableImage, bool validationEnabled, const vk::VkAllocationCallbacks* pAllocator = DE_NULL) { const float queuePriorities[] = { 1.0f }; const vk::VkDeviceQueueCreateInfo queueInfos[] = { { vk::VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, DE_NULL, (vk::VkDeviceQueueCreateFlags)0, queueFamilyIndex, DE_LENGTH_OF_ARRAY(queuePriorities), &queuePriorities[0] } }; const vk::VkPhysicalDeviceFeatures features = getDeviceNullFeatures(); const char* const extensions[] = { "VK_KHR_swapchain", "VK_KHR_shared_presentable_image" }; const vk::VkDeviceCreateInfo deviceParams = { vk::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, DE_NULL, (vk::VkDeviceCreateFlags)0, DE_LENGTH_OF_ARRAY(queueInfos), &queueInfos[0], 0u, DE_NULL, requiresSharedPresentableImage ? 2u : 1u, DE_ARRAY_BEGIN(extensions), &features }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(extensions); ++ndx) { if (!isExtensionStructSupported(supportedExtensions, vk::RequiredExtension(extensions[ndx]))) TCU_THROW(NotSupportedError, (string(extensions[ndx]) + " is not supported").c_str()); } return createCustomDevice(validationEnabled, vkp, instance, vki, physicalDevice, &deviceParams, pAllocator); } de::MovePtr createDisplay (const vk::Platform& platform, const Extensions& supportedExtensions, vk::wsi::Type wsiType) { try { return de::MovePtr(platform.createWsiDisplay(wsiType)); } catch (const tcu::NotSupportedError& e) { if (isExtensionStructSupported(supportedExtensions, vk::RequiredExtension(getExtensionName(wsiType))) && platform.hasDisplay(wsiType)) { // If VK_KHR_{platform}_surface was supported, vk::Platform implementation // must support creating native display & window for that WSI type. throw tcu::TestError(e.getMessage()); } else throw; } } de::MovePtr createWindow (const vk::wsi::Display& display, const Maybe& initialSize) { try { return de::MovePtr(display.createWindow(initialSize)); } catch (const tcu::NotSupportedError& e) { // See createDisplay - assuming that wsi::Display was supported platform port // should also support creating a window. throw tcu::TestError(e.getMessage()); } } bool wsiTypeSupportsScaling (vk::wsi::Type wsiType) { return vk::wsi::getPlatformProperties(wsiType).swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_SCALED_TO_WINDOW_SIZE; } void initSemaphores (const vk::DeviceInterface& vkd, vk::VkDevice device, std::vector& semaphores) { for (size_t ndx = 0; ndx < semaphores.size(); ndx++) semaphores[ndx] = createSemaphore(vkd, device).disown(); } void deinitSemaphores (const vk::DeviceInterface& vkd, vk::VkDevice device, std::vector& semaphores) { for (size_t ndx = 0; ndx < semaphores.size(); ndx++) { if (semaphores[ndx] != (vk::VkSemaphore)0) vkd.destroySemaphore(device, semaphores[ndx], DE_NULL); semaphores[ndx] = (vk::VkSemaphore)0; } semaphores.clear(); } void initFences (const vk::DeviceInterface& vkd, vk::VkDevice device, std::vector& fences) { for (size_t ndx = 0; ndx < fences.size(); ndx++) fences[ndx] = createFence(vkd, device).disown(); } void deinitFences (const vk::DeviceInterface& vkd, vk::VkDevice device, std::vector& fences) { for (size_t ndx = 0; ndx < fences.size(); ndx++) { if (fences[ndx] != (vk::VkFence)0) vkd.destroyFence(device, fences[ndx], DE_NULL); fences[ndx] = (vk::VkFence)0; } fences.clear(); } void cmdRenderFrame (const vk::DeviceInterface& vkd, vk::VkCommandBuffer commandBuffer, vk::VkPipelineLayout pipelineLayout, vk::VkPipeline pipeline, size_t frameNdx, deUint32 quadCount) { const deUint32 frameNdxValue = (deUint32)frameNdx; vkd.cmdPushConstants(commandBuffer, pipelineLayout, vk::VK_SHADER_STAGE_FRAGMENT_BIT, 0u, 4u, &frameNdxValue); vkd.cmdBindPipeline(commandBuffer, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkd.cmdDraw(commandBuffer, quadCount * 6u, 1u, 0u, 0u); } vk::Move createCommandBuffer (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkCommandPool commandPool, vk::VkPipelineLayout pipelineLayout, vk::VkRenderPass renderPass, vk::VkFramebuffer framebuffer, vk::VkPipeline pipeline, size_t frameNdx, deUint32 quadCount, deUint32 imageWidth, deUint32 imageHeight) { const vk::VkCommandBufferAllocateInfo allocateInfo = { vk::VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, DE_NULL, commandPool, vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 }; vk::Move commandBuffer (vk::allocateCommandBuffer(vkd, device, &allocateInfo)); beginCommandBuffer(vkd, *commandBuffer, 0u); beginRenderPass(vkd, *commandBuffer, renderPass, framebuffer, vk::makeRect2D(0, 0, imageWidth, imageHeight), tcu::Vec4(0.25f, 0.5f, 0.75f, 1.0f)); cmdRenderFrame(vkd, *commandBuffer, pipelineLayout, pipeline, frameNdx, quadCount); endRenderPass(vkd, *commandBuffer); endCommandBuffer(vkd, *commandBuffer); return commandBuffer; } void deinitCommandBuffers (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkCommandPool commandPool, std::vector& commandBuffers) { for (size_t ndx = 0; ndx < commandBuffers.size(); ndx++) { if (commandBuffers[ndx] != (vk::VkCommandBuffer)0) vkd.freeCommandBuffers(device, commandPool, 1u, &commandBuffers[ndx]); commandBuffers[ndx] = (vk::VkCommandBuffer)0; } commandBuffers.clear(); } vk::Move createCommandPool (const vk::DeviceInterface& vkd, vk::VkDevice device, deUint32 queueFamilyIndex) { const vk::VkCommandPoolCreateInfo createInfo = { vk::VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, DE_NULL, 0u, queueFamilyIndex }; return vk::createCommandPool(vkd, device, &createInfo); } vk::Move createFramebuffer (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkRenderPass renderPass, vk::VkImageView imageView, deUint32 width, deUint32 height) { const vk::VkFramebufferCreateInfo createInfo = { vk::VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, DE_NULL, 0u, renderPass, 1u, &imageView, width, height, 1u }; return vk::createFramebuffer(vkd, device, &createInfo); } vk::Move createImageView (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkImage image, vk::VkFormat format) { const vk::VkImageViewCreateInfo createInfo = { vk::VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, DE_NULL, 0u, image, vk::VK_IMAGE_VIEW_TYPE_2D, format, vk::makeComponentMappingRGBA(), { vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u } }; return vk::createImageView(vkd, device, &createInfo, DE_NULL); } vk::Move createRenderPass (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkFormat format) { const vk::VkAttachmentDescription attachments[] = { { 0u, format, vk::VK_SAMPLE_COUNT_1_BIT, vk::VK_ATTACHMENT_LOAD_OP_LOAD, vk::VK_ATTACHMENT_STORE_OP_STORE, vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE, vk::VK_ATTACHMENT_STORE_OP_DONT_CARE, // This differs from the usual layout handling in that the // swapchain image remains in IMAGE_LAYOUT_SHARED_PRESENT_KHR all // the time. We should not ever transition it away (or discard the // contents with a transition from UNDEFINED) as the PE is accessing // the image concurrently with our rendering. vk::VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, vk::VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR } }; const vk::VkAttachmentReference colorAttachmentRefs[] = { { 0u, vk::VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR } }; const vk::VkSubpassDescription subpasses[] = { { 0u, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, 0u, DE_NULL, DE_LENGTH_OF_ARRAY(colorAttachmentRefs), colorAttachmentRefs, DE_NULL, DE_NULL, 0u, DE_NULL } }; const vk::VkRenderPassCreateInfo createInfo = { vk::VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, DE_NULL, 0u, DE_LENGTH_OF_ARRAY(attachments), attachments, DE_LENGTH_OF_ARRAY(subpasses), subpasses, 0u, DE_NULL }; return vk::createRenderPass(vkd, device, &createInfo); } vk::Move createPipeline (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkRenderPass renderPass, vk::VkPipelineLayout layout, vk::VkShaderModule vertexShaderModule, vk::VkShaderModule fragmentShaderModule, deUint32 width, deUint32 height) { const vk::VkPipelineVertexInputStateCreateInfo vertexInputState = { vk::VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, DE_NULL, 0u, 0u, DE_NULL, 0u, DE_NULL }; const std::vector viewports (1, vk::makeViewport(tcu::UVec2(width, height))); const std::vector scissors (1, vk::makeRect2D(tcu::UVec2(width, height))); return vk::makeGraphicsPipeline(vkd, // const DeviceInterface& vk device, // const VkDevice device layout, // const VkPipelineLayout pipelineLayout vertexShaderModule, // const VkShaderModule vertexShaderModule DE_NULL, // const VkShaderModule tessellationControlShaderModule DE_NULL, // const VkShaderModule tessellationEvalShaderModule DE_NULL, // const VkShaderModule geometryShaderModule fragmentShaderModule, // const VkShaderModule fragmentShaderModule renderPass, // const VkRenderPass renderPass viewports, // const std::vector& viewports scissors, // const std::vector& scissors vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, // const VkPrimitiveTopology topology 0u, // const deUint32 subpass 0u, // const deUint32 patchControlPoints &vertexInputState); // const VkPipelineVertexInputStateCreateInfo* vertexInputStateCreateInfo } vk::Move createPipelineLayout (const vk::DeviceInterface& vkd, vk::VkDevice device) { const vk::VkPushConstantRange pushConstants[] = { { vk::VK_SHADER_STAGE_FRAGMENT_BIT, 0u, 4u } }; const vk::VkPipelineLayoutCreateInfo createInfo = { vk::VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, DE_NULL, 0u, 0u, DE_NULL, DE_LENGTH_OF_ARRAY(pushConstants), pushConstants, }; return vk::createPipelineLayout(vkd, device, &createInfo); } struct TestConfig { vk::wsi::Type wsiType; Scaling scaling; bool useSharedPresentableImage; vk::VkPresentModeKHR presentMode; vk::VkSurfaceTransformFlagsKHR transform; vk::VkCompositeAlphaFlagsKHR alpha; }; class SharedPresentableImageTestInstance : public TestInstance { public: SharedPresentableImageTestInstance (Context& context, const TestConfig& testConfig); ~SharedPresentableImageTestInstance (void); tcu::TestStatus iterate (void); private: const TestConfig m_testConfig; const deUint32 m_quadCount; const vk::PlatformInterface& m_vkp; const Extensions m_instanceExtensions; const CustomInstance m_instance; const vk::InstanceDriver& m_vki; const vk::VkPhysicalDevice m_physicalDevice; const de::UniquePtr m_nativeDisplay; const de::UniquePtr m_nativeWindow; const vk::Unique m_surface; const deUint32 m_queueFamilyIndex; const Extensions m_deviceExtensions; const vk::Unique m_device; const vk::DeviceDriver m_vkd; const vk::VkQueue m_queue; const vk::Unique m_commandPool; const vk::Unique m_vertexShaderModule; const vk::Unique m_fragmentShaderModule; const vk::Unique m_pipelineLayout; vk::VkImageUsageFlags m_supportedUsageFlags; const vk::VkSurfaceCapabilitiesKHR m_surfaceProperties; const vector m_surfaceFormats; const vector m_presentModes; tcu::ResultCollector m_resultCollector; vk::Move m_swapchain; vk::VkImage m_swapchainImage; // NOTE: not owning. lifetime managed by swapchain vk::Move m_swapchainImageView; vk::Move m_framebuffer; vk::Move m_renderPass; vk::Move m_pipeline; std::vector m_commandBuffers; std::vector m_renderSemaphores; std::vector m_fences; std::vector m_swapchainConfigs; size_t m_swapchainConfigNdx; const size_t m_frameCount; size_t m_frameNdx; const size_t m_maxOutOfDateCount; size_t m_outOfDateCount; void initSwapchainResources (void); void deinitSwapchainResources (void); void render (void); }; std::vector generateSwapchainConfigs (const vk::InstanceDriver& vki, const vk::VkPhysicalDevice physicalDevice, vk::VkSurfaceKHR surface, deUint32 queueFamilyIndex, Scaling scaling, const vk::VkSurfaceCapabilitiesKHR& properties, const vector& formats, const vector& presentModes, vk::VkPresentModeKHR presentMode, vk::VkImageUsageFlags supportedImageUsage, const vk::VkSurfaceTransformFlagsKHR transform, const vk::VkCompositeAlphaFlagsKHR alpha) { const deUint32 imageLayers = 1u; const vk::VkImageUsageFlags imageUsage = properties.supportedUsageFlags & supportedImageUsage; const vk::VkBool32 clipped = VK_FALSE; vector createInfos; const deUint32 currentWidth = properties.currentExtent.width != 0xFFFFFFFFu ? properties.currentExtent.width : de::min(1024u, properties.minImageExtent.width + ((properties.maxImageExtent.width - properties.minImageExtent.width) / 2)); const deUint32 currentHeight = properties.currentExtent.height != 0xFFFFFFFFu ? properties.currentExtent.height : de::min(1024u, properties.minImageExtent.height + ((properties.maxImageExtent.height - properties.minImageExtent.height) / 2)); const deUint32 imageWidth = scaling == SCALING_NONE ? currentWidth : (scaling == SCALING_UP ? de::max(31u, properties.minImageExtent.width) : de::min(deSmallestGreaterOrEquallPowerOfTwoU32(currentWidth+1), properties.maxImageExtent.width)); const deUint32 imageHeight = scaling == SCALING_NONE ? currentHeight : (scaling == SCALING_UP ? de::max(31u, properties.minImageExtent.height) : de::min(deSmallestGreaterOrEquallPowerOfTwoU32(currentHeight+1), properties.maxImageExtent.height)); const vk::VkExtent2D imageSize = { imageWidth, imageHeight }; { size_t presentModeNdx; for (presentModeNdx = 0; presentModeNdx < presentModes.size(); presentModeNdx++) { if (presentModes[presentModeNdx] == presentMode) break; } if (presentModeNdx == presentModes.size()) TCU_THROW(NotSupportedError, "Present mode not supported"); if ((properties.supportedTransforms & transform) == 0) TCU_THROW(NotSupportedError, "Transform not supported"); if ((properties.supportedCompositeAlpha & alpha) == 0) TCU_THROW(NotSupportedError, "Composite alpha not supported"); } for (size_t formatNdx = 0; formatNdx < formats.size(); formatNdx++) { const vk::VkSurfaceTransformFlagBitsKHR preTransform = (vk::VkSurfaceTransformFlagBitsKHR)transform; const vk::VkCompositeAlphaFlagBitsKHR compositeAlpha = (vk::VkCompositeAlphaFlagBitsKHR)alpha; const vk::VkFormat imageFormat = formats[formatNdx].format; const vk::VkColorSpaceKHR imageColorSpace = formats[formatNdx].colorSpace; const vk::VkSwapchainCreateInfoKHR createInfo = { vk::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, DE_NULL, 0u, surface, 1, // Always 1 image for a shared presentable image swapchain. imageFormat, imageColorSpace, imageSize, imageLayers, imageUsage, vk::VK_SHARING_MODE_EXCLUSIVE, 1u, &queueFamilyIndex, preTransform, compositeAlpha, presentMode, clipped, (vk::VkSwapchainKHR)0 }; { vk::VkImageFormatProperties imageFormatProperties; deMemset(&imageFormatProperties, 0, sizeof(imageFormatProperties)); vk::VkResult result = vki.getPhysicalDeviceImageFormatProperties(physicalDevice, imageFormat, vk::VK_IMAGE_TYPE_2D, vk::VK_IMAGE_TILING_OPTIMAL, imageUsage, 0, &imageFormatProperties); if (result == vk::VK_ERROR_FORMAT_NOT_SUPPORTED) continue; } createInfos.push_back(createInfo); } return createInfos; } vk::VkSurfaceCapabilitiesKHR getPhysicalDeviceSurfaceCapabilities (const vk::InstanceInterface& vki, vk::VkPhysicalDevice physicalDevice, vk::VkSurfaceKHR surface, vk::VkImageUsageFlags* usage) { const vk::VkPhysicalDeviceSurfaceInfo2KHR info = { vk::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, DE_NULL, surface }; vk::VkSharedPresentSurfaceCapabilitiesKHR sharedCapabilities; vk::VkSurfaceCapabilities2KHR capabilities; sharedCapabilities.sType = vk::VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR; sharedCapabilities.pNext = DE_NULL; capabilities.sType = vk::VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR; capabilities.pNext = &sharedCapabilities; VK_CHECK(vki.getPhysicalDeviceSurfaceCapabilities2KHR(physicalDevice, &info, &capabilities)); TCU_CHECK(sharedCapabilities.sharedPresentSupportedUsageFlags & vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); *usage = sharedCapabilities.sharedPresentSupportedUsageFlags; return capabilities.surfaceCapabilities; } SharedPresentableImageTestInstance::SharedPresentableImageTestInstance (Context& context, const TestConfig& testConfig) : TestInstance (context) , m_testConfig (testConfig) , m_quadCount (16u) , m_vkp (context.getPlatformInterface()) , m_instanceExtensions (vk::enumerateInstanceExtensionProperties(m_vkp, DE_NULL)) , m_instance (createInstanceWithWsi(context, m_instanceExtensions, testConfig.wsiType)) , m_vki (m_instance.getDriver()) , m_physicalDevice (vk::chooseDevice(m_vki, m_instance, context.getTestContext().getCommandLine())) , m_nativeDisplay (createDisplay(context.getTestContext().getPlatform().getVulkanPlatform(), m_instanceExtensions, testConfig.wsiType)) , m_nativeWindow (createWindow(*m_nativeDisplay, tcu::Nothing)) , m_surface (vk::wsi::createSurface(m_vki, m_instance, testConfig.wsiType, *m_nativeDisplay, *m_nativeWindow, context.getTestContext().getCommandLine())) , m_queueFamilyIndex (vk::wsi::chooseQueueFamilyIndex(m_vki, m_physicalDevice, *m_surface)) , m_deviceExtensions (vk::enumerateDeviceExtensionProperties(m_vki, m_physicalDevice, DE_NULL)) , m_device (createDeviceWithWsi(m_vkp, m_instance, m_vki, m_physicalDevice, m_deviceExtensions, m_queueFamilyIndex, testConfig.useSharedPresentableImage, context.getTestContext().getCommandLine().isValidationEnabled())) , m_vkd (m_vkp, m_instance, *m_device) , m_queue (getDeviceQueue(m_vkd, *m_device, m_queueFamilyIndex, 0u)) , m_commandPool (createCommandPool(m_vkd, *m_device, m_queueFamilyIndex)) , m_vertexShaderModule (vk::createShaderModule(m_vkd, *m_device, context.getBinaryCollection().get("quad-vert"), 0u)) , m_fragmentShaderModule (vk::createShaderModule(m_vkd, *m_device, context.getBinaryCollection().get("quad-frag"), 0u)) , m_pipelineLayout (createPipelineLayout(m_vkd, *m_device)) , m_supportedUsageFlags (0u) , m_surfaceProperties (getPhysicalDeviceSurfaceCapabilities(m_vki, m_physicalDevice, *m_surface, &m_supportedUsageFlags)) , m_surfaceFormats (vk::wsi::getPhysicalDeviceSurfaceFormats(m_vki, m_physicalDevice, *m_surface)) , m_presentModes (vk::wsi::getPhysicalDeviceSurfacePresentModes(m_vki, m_physicalDevice, *m_surface)) , m_swapchainConfigs (generateSwapchainConfigs(m_vki, m_physicalDevice, *m_surface, m_queueFamilyIndex, testConfig.scaling, m_surfaceProperties, m_surfaceFormats, m_presentModes, testConfig.presentMode, m_supportedUsageFlags, testConfig.transform, testConfig.alpha)) , m_swapchainConfigNdx (0u) , m_frameCount (60u * 5u) , m_frameNdx (0u) , m_maxOutOfDateCount (20u) , m_outOfDateCount (0u) { { const tcu::ScopedLogSection surfaceInfo (m_context.getTestContext().getLog(), "SurfaceCapabilities", "SurfaceCapabilities"); m_context.getTestContext().getLog() << TestLog::Message << m_surfaceProperties << TestLog::EndMessage; m_context.getTestContext().getLog() << TestLog::Message << "SharedPresentSupportedUsageFlags: " << m_supportedUsageFlags << TestLog::EndMessage; } } SharedPresentableImageTestInstance::~SharedPresentableImageTestInstance (void) { deinitSwapchainResources(); } void SharedPresentableImageTestInstance::initSwapchainResources (void) { const size_t fenceCount = 6; const deUint32 imageWidth = m_swapchainConfigs[m_swapchainConfigNdx].imageExtent.width; const deUint32 imageHeight = m_swapchainConfigs[m_swapchainConfigNdx].imageExtent.height; const vk::VkFormat imageFormat = m_swapchainConfigs[m_swapchainConfigNdx].imageFormat; m_swapchain = vk::createSwapchainKHR(m_vkd, *m_device, &m_swapchainConfigs[m_swapchainConfigNdx]); m_swapchainImage = vk::wsi::getSwapchainImages(m_vkd, *m_device, *m_swapchain).front(); m_renderPass = createRenderPass(m_vkd, *m_device, imageFormat); m_pipeline = createPipeline(m_vkd, *m_device, *m_renderPass, *m_pipelineLayout, *m_vertexShaderModule, *m_fragmentShaderModule, imageWidth, imageHeight); m_swapchainImageView = createImageView(m_vkd, *m_device, m_swapchainImage, imageFormat); m_framebuffer = createFramebuffer(m_vkd, *m_device, *m_renderPass, *m_swapchainImageView, imageWidth, imageHeight); m_renderSemaphores = std::vector(fenceCount, (vk::VkSemaphore)0); m_fences = std::vector(fenceCount, (vk::VkFence)0); m_commandBuffers = std::vector(m_fences.size(), (vk::VkCommandBuffer)0); initSemaphores(m_vkd, *m_device, m_renderSemaphores); initFences(m_vkd, *m_device, m_fences); // Unlike a traditional swapchain, where we'd acquire a new image from the // PE every frame, a shared image swapchain has a single image that is // acquired upfront. We acquire it here, transition it to the proper layout, // and present it. // Acquire the one image const deUint64 foreverNs = 0xFFFFFFFFFFFFFFFFul; vk::Move semaphore(createSemaphore(m_vkd, *m_device)); deUint32 imageIndex = 42; // initialize to junk value VK_CHECK(m_vkd.acquireNextImageKHR(*m_device, *m_swapchain, foreverNs, *semaphore, 0u, &imageIndex)); TCU_CHECK(imageIndex == 0); // Transition to IMAGE_LAYOUT_SHARED_PRESENT_KHR const vk::VkCommandBufferAllocateInfo allocateInfo = { vk::VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, DE_NULL, *m_commandPool, vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 }; const vk::Unique commandBuffer (vk::allocateCommandBuffer(m_vkd, *m_device, &allocateInfo)); beginCommandBuffer(m_vkd, *commandBuffer, 0u); const vk::VkImageMemoryBarrier barrier = { vk::VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, DE_NULL, 0, 0, vk::VK_IMAGE_LAYOUT_UNDEFINED, vk::VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, m_swapchainImage, { vk::VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }, }; m_vkd.cmdPipelineBarrier(*commandBuffer, vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0u, 0, DE_NULL, 0, DE_NULL, 1, &barrier); endCommandBuffer(m_vkd, *commandBuffer); const vk::VkPipelineStageFlags waitDstStages[] = { vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; const vk::VkSubmitInfo submitInfo = { vk::VK_STRUCTURE_TYPE_SUBMIT_INFO, DE_NULL, 1, &*semaphore, waitDstStages, 1, &*commandBuffer, 0, DE_NULL, }; VK_CHECK(m_vkd.queueSubmit(m_queue, 1u, &submitInfo, (vk::VkFence)0)); VK_CHECK(m_vkd.queueWaitIdle(m_queue)); } void SharedPresentableImageTestInstance::deinitSwapchainResources (void) { VK_CHECK(m_vkd.queueWaitIdle(m_queue)); deinitSemaphores(m_vkd, *m_device, m_renderSemaphores); deinitFences(m_vkd, *m_device, m_fences); deinitCommandBuffers(m_vkd, *m_device, *m_commandPool, m_commandBuffers); m_framebuffer = vk::Move(); m_swapchainImageView = vk::Move(); m_swapchainImage = (vk::VkImage)0; m_swapchain = vk::Move(); m_renderPass = vk::Move(); m_pipeline = vk::Move(); } void SharedPresentableImageTestInstance::render (void) { const deUint64 foreverNs = 0xFFFFFFFFFFFFFFFFul; const vk::VkFence fence = m_fences[m_frameNdx % m_fences.size()]; const deUint32 width = m_swapchainConfigs[m_swapchainConfigNdx].imageExtent.width; const deUint32 height = m_swapchainConfigs[m_swapchainConfigNdx].imageExtent.height; // Throttle execution if (m_frameNdx >= m_fences.size()) { VK_CHECK(m_vkd.waitForFences(*m_device, 1u, &fence, VK_TRUE, foreverNs)); VK_CHECK(m_vkd.resetFences(*m_device, 1u, &fence)); m_vkd.freeCommandBuffers(*m_device, *m_commandPool, 1u, &m_commandBuffers[m_frameNdx % m_commandBuffers.size()]); m_commandBuffers[m_frameNdx % m_commandBuffers.size()] = (vk::VkCommandBuffer)0; } deUint32 imageIndex = 0; // There is only one image. const vk::VkSemaphore currentRenderSemaphore = m_renderSemaphores[m_frameNdx % m_renderSemaphores.size()]; const bool willPresent = m_swapchainConfigs[m_swapchainConfigNdx].presentMode == vk::VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR || !m_frameNdx; // Create command buffer m_commandBuffers[m_frameNdx % m_commandBuffers.size()] = createCommandBuffer(m_vkd, *m_device, *m_commandPool, *m_pipelineLayout, *m_renderPass, *m_framebuffer, *m_pipeline, m_frameNdx, m_quadCount, width, height).disown(); // Submit command buffer { const vk::VkSubmitInfo submitInfo = { vk::VK_STRUCTURE_TYPE_SUBMIT_INFO, DE_NULL, 0u, DE_NULL, DE_NULL, 1u, &m_commandBuffers[m_frameNdx % m_commandBuffers.size()], willPresent ? 1u : 0u, // Only signal the semaphore if we're going to call QueuePresent. ¤tRenderSemaphore }; // With a traditional swapchain, we'd fence on completion of // AcquireNextImage. We never call that for a shared image swapchain, so // fence on completion of the rendering work instead. A real shared // image application would want a more substantial pacing mechanism. VK_CHECK(m_vkd.queueSubmit(m_queue, 1u, &submitInfo, fence)); } // DEMAND_REFRESH requires us to call QueuePresent whenever we want to be // assured the PE has picked up a new frame. The PE /may/ also pick up // changes whenever it likes. // // For CONTINUOUS_REFRESH, we need to just call QueuePresent once on the // first frame to kick things off. if (willPresent) { // Present frame vk::VkResult result; const vk::VkPresentInfoKHR presentInfo = { vk::VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, DE_NULL, 1u, ¤tRenderSemaphore, 1u, &*m_swapchain, &imageIndex, &result }; VK_CHECK_WSI(m_vkd.queuePresentKHR(m_queue, &presentInfo)); VK_CHECK_WSI(result); } // With either present mode, we can call GetSwapchainStatus at any time // to detect possible OUT_OF_DATE conditions. Let's do that every frame. const vk::VkResult swapchainStatus = m_vkd.getSwapchainStatusKHR(*m_device, *m_swapchain); VK_CHECK(swapchainStatus); } tcu::TestStatus SharedPresentableImageTestInstance::iterate (void) { // Initialize swapchain specific resources // Render test try { if (m_frameNdx == 0) { if (m_outOfDateCount == 0) m_context.getTestContext().getLog() << tcu::TestLog::Message << "Swapchain: " << m_swapchainConfigs[m_swapchainConfigNdx] << tcu::TestLog::EndMessage; initSwapchainResources(); } render(); } catch (const vk::Error& error) { if (error.getError() == vk::VK_ERROR_OUT_OF_DATE_KHR) { m_swapchainConfigs = generateSwapchainConfigs(m_vki, m_physicalDevice, *m_surface, m_queueFamilyIndex, m_testConfig.scaling, m_surfaceProperties, m_surfaceFormats, m_presentModes, m_testConfig.presentMode, m_supportedUsageFlags, m_testConfig.transform, m_testConfig.alpha); if (m_outOfDateCount < m_maxOutOfDateCount) { m_context.getTestContext().getLog() << TestLog::Message << "Frame " << m_frameNdx << ": Swapchain out of date. Recreating resources." << TestLog::EndMessage; deinitSwapchainResources(); m_frameNdx = 0; m_outOfDateCount++; return tcu::TestStatus::incomplete(); } else { m_context.getTestContext().getLog() << TestLog::Message << "Frame " << m_frameNdx << ": Swapchain out of date." << TestLog::EndMessage; m_resultCollector.fail("Received too many VK_ERROR_OUT_OF_DATE_KHR errors. Received " + de::toString(m_outOfDateCount) + ", max " + de::toString(m_maxOutOfDateCount)); } } else { m_resultCollector.fail(error.what()); } deinitSwapchainResources(); m_swapchainConfigNdx++; m_frameNdx = 0; m_outOfDateCount = 0; if (m_swapchainConfigNdx >= m_swapchainConfigs.size()) return tcu::TestStatus(m_resultCollector.getResult(), m_resultCollector.getMessage()); else return tcu::TestStatus::incomplete(); } m_frameNdx++; if (m_frameNdx >= m_frameCount) { m_frameNdx = 0; m_outOfDateCount = 0; m_swapchainConfigNdx++; deinitSwapchainResources(); if (m_swapchainConfigNdx >= m_swapchainConfigs.size()) return tcu::TestStatus(m_resultCollector.getResult(), m_resultCollector.getMessage()); else return tcu::TestStatus::incomplete(); } else return tcu::TestStatus::incomplete(); } struct Programs { static void init (vk::SourceCollections& dst, TestConfig) { dst.glslSources.add("quad-vert") << glu::VertexSource( "#version 450\n" "out gl_PerVertex {\n" "\tvec4 gl_Position;\n" "};\n" "layout(location = 0) out highp uint quadIndex;\n" "highp float;\n" "void main (void) {\n" "\tgl_Position = vec4(((gl_VertexIndex + 2) / 3) % 2 == 0 ? -1.0 : 1.0,\n" "\t ((gl_VertexIndex + 1) / 3) % 2 == 0 ? -1.0 : 1.0, 0.0, 1.0);\n" "\tquadIndex = gl_VertexIndex / 6;\n" "}\n"); dst.glslSources.add("quad-frag") << glu::FragmentSource( "#version 310 es\n" "layout(location = 0) flat in highp uint quadIndex;\n" "layout(location = 0) out highp vec4 o_color;\n" "layout(push_constant) uniform PushConstant {\n" "\thighp uint frameNdx;\n" "} pushConstants;\n" "void main (void)\n" "{\n" "\thighp uint frameNdx = pushConstants.frameNdx;\n" "\thighp uint cellX = bitfieldExtract(uint(gl_FragCoord.x), 7, 10);\n" "\thighp uint cellY = bitfieldExtract(uint(gl_FragCoord.y), 7, 10);\n" "\thighp uint x = quadIndex ^ (frameNdx + (uint(gl_FragCoord.x) >> cellX));\n" "\thighp uint y = quadIndex ^ (frameNdx + (uint(gl_FragCoord.y) >> cellY));\n" "\thighp uint r = 128u * bitfieldExtract(x, 0, 1)\n" "\t + 64u * bitfieldExtract(y, 1, 1)\n" "\t + 32u * bitfieldExtract(x, 3, 1);\n" "\thighp uint g = 128u * bitfieldExtract(y, 0, 1)\n" "\t + 64u * bitfieldExtract(x, 2, 1)\n" "\t + 32u * bitfieldExtract(y, 3, 1);\n" "\thighp uint b = 128u * bitfieldExtract(x, 1, 1)\n" "\t + 64u * bitfieldExtract(y, 2, 1)\n" "\t + 32u * bitfieldExtract(x, 4, 1);\n" "\to_color = vec4(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0, 1.0);\n" "}\n"); } }; } // anonymous void createSharedPresentableImageTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType) { const struct { Scaling scaling; const char* name; } scaling [] = { { SCALING_NONE, "scale_none" }, { SCALING_UP, "scale_up" }, { SCALING_DOWN, "scale_down" } }; const struct { vk::VkPresentModeKHR mode; const char* name; } presentModes[] = { { vk::VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, "demand" }, { vk::VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR, "continuous" }, }; const struct { vk::VkSurfaceTransformFlagsKHR transform; const char* name; } transforms[] = { { vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, "identity" }, { vk::VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, "rotate_90" }, { vk::VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR, "rotate_180" }, { vk::VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, "rotate_270" }, { vk::VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR, "horizontal_mirror" }, { vk::VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR, "horizontal_mirror_rotate_90" }, { vk::VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR, "horizontal_mirror_rotate_180" }, { vk::VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR, "horizontal_mirror_rotate_270" }, { vk::VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR, "inherit" } }; const struct { vk::VkCompositeAlphaFlagsKHR alpha; const char* name; } alphas[] = { { vk::VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, "opaque" }, { vk::VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, "pre_multiplied" }, { vk::VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, "post_multiplied" }, { vk::VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, "inherit" } }; for (size_t scalingNdx = 0; scalingNdx < DE_LENGTH_OF_ARRAY(scaling); scalingNdx++) { if (scaling[scalingNdx].scaling == SCALING_NONE || wsiTypeSupportsScaling(wsiType)) { de::MovePtr scaleGroup (new tcu::TestCaseGroup(testGroup->getTestContext(), scaling[scalingNdx].name, scaling[scalingNdx].name)); for (size_t transformNdx = 0; transformNdx < DE_LENGTH_OF_ARRAY(transforms); transformNdx++) { de::MovePtr transformGroup (new tcu::TestCaseGroup(testGroup->getTestContext(), transforms[transformNdx].name, transforms[transformNdx].name)); for (size_t alphaNdx = 0; alphaNdx < DE_LENGTH_OF_ARRAY(alphas); alphaNdx++) { de::MovePtr alphaGroup (new tcu::TestCaseGroup(testGroup->getTestContext(), alphas[alphaNdx].name, alphas[alphaNdx].name)); for (size_t presentModeNdx = 0; presentModeNdx < DE_LENGTH_OF_ARRAY(presentModes); presentModeNdx++) { const char* const name = presentModes[presentModeNdx].name; TestConfig config; config.wsiType = wsiType; config.useSharedPresentableImage = true; config.scaling = scaling[scalingNdx].scaling; config.transform = transforms[transformNdx].transform; config.alpha = alphas[alphaNdx].alpha; config.presentMode = presentModes[presentModeNdx].mode; alphaGroup->addChild(new vkt::InstanceFactory1(testGroup->getTestContext(), tcu::NODETYPE_SELF_VALIDATE, name, name, Programs(), config)); } transformGroup->addChild(alphaGroup.release()); } scaleGroup->addChild(transformGroup.release()); } testGroup->addChild(scaleGroup.release()); } } } } // wsi } // vkt