| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QGuiApplication> |
| #include <QVulkanInstance> |
| #include <QVulkanFunctions> |
| #include <QWindow> |
| #include <QLoggingCategory> |
| #include <qevent.h> |
| |
| static const int SWAPCHAIN_BUFFER_COUNT = 2; |
| static const int FRAME_LAG = 2; |
| |
| class VWindow : public QWindow |
| { |
| public: |
| VWindow() { setSurfaceType(VulkanSurface); } |
| ~VWindow() { releaseResources(); } |
| |
| private: |
| void exposeEvent(QExposeEvent *) override; |
| void resizeEvent(QResizeEvent *) override; |
| bool event(QEvent *) override; |
| |
| void init(); |
| void releaseResources(); |
| void recreateSwapChain(); |
| void createDefaultRenderPass(); |
| void releaseSwapChain(); |
| void render(); |
| void buildDrawCalls(); |
| |
| bool m_inited = false; |
| VkSurfaceKHR m_vkSurface; |
| VkPhysicalDevice m_vkPhysDev; |
| VkPhysicalDeviceProperties m_physDevProps; |
| VkDevice m_vkDev = 0; |
| QVulkanDeviceFunctions *m_devFuncs; |
| VkQueue m_vkGfxQueue; |
| VkQueue m_vkPresQueue; |
| VkCommandPool m_vkCmdPool = 0; |
| |
| PFN_vkCreateSwapchainKHR m_vkCreateSwapchainKHR = nullptr; |
| PFN_vkDestroySwapchainKHR m_vkDestroySwapchainKHR; |
| PFN_vkGetSwapchainImagesKHR m_vkGetSwapchainImagesKHR; |
| PFN_vkAcquireNextImageKHR m_vkAcquireNextImageKHR; |
| PFN_vkQueuePresentKHR m_vkQueuePresentKHR; |
| PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR m_vkGetPhysicalDeviceSurfaceCapabilitiesKHR; |
| PFN_vkGetPhysicalDeviceSurfaceFormatsKHR m_vkGetPhysicalDeviceSurfaceFormatsKHR; |
| |
| QSize m_swapChainImageSize; |
| VkFormat m_colorFormat; |
| VkSwapchainKHR m_swapChain = 0; |
| uint32_t m_swapChainBufferCount = 0; |
| |
| struct ImageResources { |
| VkImage image = 0; |
| VkImageView imageView = 0; |
| VkCommandBuffer cmdBuf = 0; |
| VkFence cmdFence = 0; |
| bool cmdFenceWaitable = false; |
| VkFramebuffer fb = 0; |
| } m_imageRes[SWAPCHAIN_BUFFER_COUNT]; |
| |
| uint32_t m_currentImage; |
| |
| struct FrameResources { |
| VkFence fence = 0; |
| bool fenceWaitable = false; |
| VkSemaphore imageSem = 0; |
| VkSemaphore drawSem = 0; |
| } m_frameRes[FRAME_LAG]; |
| |
| uint32_t m_currentFrame; |
| |
| VkRenderPass m_defaultRenderPass = 0; |
| }; |
| |
| void VWindow::exposeEvent(QExposeEvent *) |
| { |
| if (isExposed() && !m_inited) { |
| qDebug("initializing"); |
| m_inited = true; |
| init(); |
| recreateSwapChain(); |
| render(); |
| } |
| |
| // Release everything when unexposed - the meaning of which is platform specific. |
| // Can be essential on mobile, to release resources while in background. |
| #if 1 |
| if (!isExposed() && m_inited) { |
| m_inited = false; |
| releaseSwapChain(); |
| releaseResources(); |
| } |
| #endif |
| } |
| |
| void VWindow::resizeEvent(QResizeEvent *) |
| { |
| // Nothing to do here - recreating the swapchain is handled in render(), |
| // in fact calling recreateSwapChain() from here leads to problems. |
| } |
| |
| bool VWindow::event(QEvent *e) |
| { |
| switch (e->type()) { |
| case QEvent::UpdateRequest: |
| render(); |
| break; |
| |
| // Now the fun part: the swapchain must be destroyed before the surface as per |
| // spec. This is not ideal for us because the surface is managed by the |
| // QPlatformWindow which may be gone already when the unexpose comes, making the |
| // validation layer scream. The solution is to listen to the PlatformSurface events. |
| case QEvent::PlatformSurface: |
| if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) |
| releaseSwapChain(); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return QWindow::event(e); |
| } |
| |
| void VWindow::init() |
| { |
| m_vkSurface = QVulkanInstance::surfaceForWindow(this); |
| if (!m_vkSurface) |
| qFatal("Failed to get surface for window"); |
| |
| QVulkanInstance *inst = vulkanInstance(); |
| QVulkanFunctions *f = inst->functions(); |
| uint32_t devCount = 0; |
| f->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, nullptr); |
| qDebug("%d physical devices", devCount); |
| if (!devCount) |
| qFatal("No physical devices"); |
| |
| // Just pick the first physical device for now. |
| devCount = 1; |
| VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, &m_vkPhysDev); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to enumerate physical devices: %d", err); |
| |
| f->vkGetPhysicalDeviceProperties(m_vkPhysDev, &m_physDevProps); |
| qDebug("Device name: %s Driver version: %d.%d.%d", m_physDevProps.deviceName, |
| VK_VERSION_MAJOR(m_physDevProps.driverVersion), VK_VERSION_MINOR(m_physDevProps.driverVersion), |
| VK_VERSION_PATCH(m_physDevProps.driverVersion)); |
| |
| uint32_t queueCount = 0; |
| f->vkGetPhysicalDeviceQueueFamilyProperties(m_vkPhysDev, &queueCount, nullptr); |
| QVector<VkQueueFamilyProperties> queueFamilyProps(queueCount); |
| f->vkGetPhysicalDeviceQueueFamilyProperties(m_vkPhysDev, &queueCount, queueFamilyProps.data()); |
| int gfxQueueFamilyIdx = -1; |
| int presQueueFamilyIdx = -1; |
| // First look for a queue that supports both. |
| for (int i = 0; i < queueFamilyProps.count(); ++i) { |
| qDebug("queue family %d: flags=0x%x count=%d", i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount); |
| if (gfxQueueFamilyIdx == -1 |
| && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) |
| && inst->supportsPresent(m_vkPhysDev, i, this)) |
| gfxQueueFamilyIdx = i; |
| } |
| if (gfxQueueFamilyIdx != -1) { |
| presQueueFamilyIdx = gfxQueueFamilyIdx; |
| } else { |
| // Separate queues then. |
| qDebug("No queue with graphics+present; trying separate queues"); |
| for (int i = 0; i < queueFamilyProps.count(); ++i) { |
| if (gfxQueueFamilyIdx == -1 && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) |
| gfxQueueFamilyIdx = i; |
| if (presQueueFamilyIdx == -1 && inst->supportsPresent(m_vkPhysDev, i, this)) |
| presQueueFamilyIdx = i; |
| } |
| } |
| if (gfxQueueFamilyIdx == -1) |
| qFatal("No graphics queue family found"); |
| if (presQueueFamilyIdx == -1) |
| qFatal("No present queue family found"); |
| |
| VkDeviceQueueCreateInfo queueInfo[2]; |
| const float prio[] = { 0 }; |
| memset(queueInfo, 0, sizeof(queueInfo)); |
| queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; |
| queueInfo[0].queueFamilyIndex = gfxQueueFamilyIdx; |
| queueInfo[0].queueCount = 1; |
| queueInfo[0].pQueuePriorities = prio; |
| if (gfxQueueFamilyIdx != presQueueFamilyIdx) { |
| queueInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; |
| queueInfo[1].queueFamilyIndex = presQueueFamilyIdx; |
| queueInfo[1].queueCount = 1; |
| queueInfo[1].pQueuePriorities = prio; |
| } |
| |
| QVector<const char *> devLayers; |
| if (inst->layers().contains("VK_LAYER_LUNARG_standard_validation")) |
| devLayers.append("VK_LAYER_LUNARG_standard_validation"); |
| |
| QVector<const char *> devExts; |
| devExts.append("VK_KHR_swapchain"); |
| |
| VkDeviceCreateInfo devInfo; |
| memset(&devInfo, 0, sizeof(devInfo)); |
| devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; |
| devInfo.queueCreateInfoCount = gfxQueueFamilyIdx == presQueueFamilyIdx ? 1 : 2; |
| devInfo.pQueueCreateInfos = queueInfo; |
| devInfo.enabledLayerCount = devLayers.count(); |
| devInfo.ppEnabledLayerNames = devLayers.constData(); |
| devInfo.enabledExtensionCount = devExts.count(); |
| devInfo.ppEnabledExtensionNames = devExts.constData(); |
| |
| err = f->vkCreateDevice(m_vkPhysDev, &devInfo, nullptr, &m_vkDev); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create device: %d", err); |
| |
| m_devFuncs = inst->deviceFunctions(m_vkDev); |
| |
| m_devFuncs->vkGetDeviceQueue(m_vkDev, gfxQueueFamilyIdx, 0, &m_vkGfxQueue); |
| if (gfxQueueFamilyIdx == presQueueFamilyIdx) |
| m_vkPresQueue = m_vkGfxQueue; |
| else |
| m_devFuncs->vkGetDeviceQueue(m_vkDev, presQueueFamilyIdx, 0, &m_vkPresQueue); |
| |
| VkCommandPoolCreateInfo poolInfo; |
| memset(&poolInfo, 0, sizeof(poolInfo)); |
| poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; |
| poolInfo.queueFamilyIndex = gfxQueueFamilyIdx; |
| err = m_devFuncs->vkCreateCommandPool(m_vkDev, &poolInfo, nullptr, &m_vkCmdPool); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create command pool: %d", err); |
| |
| m_colorFormat = VK_FORMAT_B8G8R8A8_UNORM; // may get changed later when setting up the swapchain |
| } |
| |
| void VWindow::releaseResources() |
| { |
| if (!m_vkDev) |
| return; |
| |
| m_devFuncs->vkDeviceWaitIdle(m_vkDev); |
| |
| if (m_vkCmdPool) { |
| m_devFuncs->vkDestroyCommandPool(m_vkDev, m_vkCmdPool, nullptr); |
| m_vkCmdPool = 0; |
| } |
| |
| if (m_vkDev) { |
| m_devFuncs->vkDestroyDevice(m_vkDev, nullptr); |
| |
| // Play nice and notify QVulkanInstance that the QVulkanDeviceFunctions |
| // for m_vkDev needs to be invalidated. |
| vulkanInstance()->resetDeviceFunctions(m_vkDev); |
| |
| m_vkDev = 0; |
| } |
| |
| m_vkSurface = 0; |
| } |
| |
| void VWindow::recreateSwapChain() |
| { |
| m_swapChainImageSize = size(); |
| |
| if (m_swapChainImageSize.isEmpty()) |
| return; |
| |
| QVulkanInstance *inst = vulkanInstance(); |
| QVulkanFunctions *f = inst->functions(); |
| m_devFuncs->vkDeviceWaitIdle(m_vkDev); |
| |
| if (!m_vkCreateSwapchainKHR) { |
| m_vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>( |
| inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); |
| m_vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>( |
| inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR")); |
| // note: device-specific functions |
| m_vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkCreateSwapchainKHR")); |
| m_vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkDestroySwapchainKHR")); |
| m_vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkGetSwapchainImagesKHR")); |
| m_vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkAcquireNextImageKHR")); |
| m_vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkQueuePresentKHR")); |
| } |
| |
| VkColorSpaceKHR colorSpace = VkColorSpaceKHR(0); |
| uint32_t formatCount = 0; |
| m_vkGetPhysicalDeviceSurfaceFormatsKHR(m_vkPhysDev, m_vkSurface, &formatCount, nullptr); |
| if (formatCount) { |
| QVector<VkSurfaceFormatKHR> formats(formatCount); |
| m_vkGetPhysicalDeviceSurfaceFormatsKHR(m_vkPhysDev, m_vkSurface, &formatCount, formats.data()); |
| if (formats[0].format != VK_FORMAT_UNDEFINED) { |
| m_colorFormat = formats[0].format; |
| colorSpace = formats[0].colorSpace; |
| } |
| } |
| |
| VkSurfaceCapabilitiesKHR surfaceCaps; |
| m_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_vkPhysDev, m_vkSurface, &surfaceCaps); |
| uint32_t reqBufferCount = SWAPCHAIN_BUFFER_COUNT; |
| if (surfaceCaps.maxImageCount) |
| reqBufferCount = qBound(surfaceCaps.minImageCount, reqBufferCount, surfaceCaps.maxImageCount); |
| |
| VkExtent2D bufferSize = surfaceCaps.currentExtent; |
| if (bufferSize.width == uint32_t(-1)) |
| bufferSize.width = m_swapChainImageSize.width(); |
| if (bufferSize.height == uint32_t(-1)) |
| bufferSize.height = m_swapChainImageSize.height(); |
| |
| VkSurfaceTransformFlagBitsKHR preTransform = |
| (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) |
| ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR |
| : surfaceCaps.currentTransform; |
| |
| VkCompositeAlphaFlagBitsKHR compositeAlpha = |
| (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) |
| ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR |
| : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; |
| |
| VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; |
| |
| VkSwapchainKHR oldSwapChain = m_swapChain; |
| VkSwapchainCreateInfoKHR swapChainInfo; |
| memset(&swapChainInfo, 0, sizeof(swapChainInfo)); |
| swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
| swapChainInfo.surface = m_vkSurface; |
| swapChainInfo.minImageCount = reqBufferCount; |
| swapChainInfo.imageFormat = m_colorFormat; |
| swapChainInfo.imageColorSpace = colorSpace; |
| swapChainInfo.imageExtent = bufferSize; |
| swapChainInfo.imageArrayLayers = 1; |
| swapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; |
| swapChainInfo.preTransform = preTransform; |
| swapChainInfo.compositeAlpha = compositeAlpha; |
| swapChainInfo.presentMode = presentMode; |
| swapChainInfo.clipped = true; |
| swapChainInfo.oldSwapchain = oldSwapChain; |
| |
| qDebug("creating new swap chain of %d buffers, size %dx%d", reqBufferCount, bufferSize.width, bufferSize.height); |
| |
| VkSwapchainKHR newSwapChain; |
| VkResult err = m_vkCreateSwapchainKHR(m_vkDev, &swapChainInfo, nullptr, &newSwapChain); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create swap chain: %d", err); |
| |
| if (oldSwapChain) |
| releaseSwapChain(); |
| |
| m_swapChain = newSwapChain; |
| |
| m_swapChainBufferCount = 0; |
| err = m_vkGetSwapchainImagesKHR(m_vkDev, m_swapChain, &m_swapChainBufferCount, nullptr); |
| if (err != VK_SUCCESS || m_swapChainBufferCount < 2) |
| qFatal("Failed to get swapchain images: %d (count=%d)", err, m_swapChainBufferCount); |
| |
| qDebug("actual swap chain buffer count: %d", m_swapChainBufferCount); |
| Q_ASSERT(m_swapChainBufferCount <= SWAPCHAIN_BUFFER_COUNT); |
| |
| VkImage swapChainImages[SWAPCHAIN_BUFFER_COUNT]; |
| err = m_vkGetSwapchainImagesKHR(m_vkDev, m_swapChain, &m_swapChainBufferCount, swapChainImages); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to get swapchain images: %d", err); |
| |
| // Now that we know m_colorFormat, create the default renderpass, the framebuffers will need it. |
| createDefaultRenderPass(); |
| |
| VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT }; |
| |
| for (uint32_t i = 0; i < m_swapChainBufferCount; ++i) { |
| ImageResources &image(m_imageRes[i]); |
| image.image = swapChainImages[i]; |
| |
| VkImageViewCreateInfo imgViewInfo; |
| memset(&imgViewInfo, 0, sizeof(imgViewInfo)); |
| imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
| imgViewInfo.image = swapChainImages[i]; |
| imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
| imgViewInfo.format = m_colorFormat; |
| imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
| imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
| imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
| imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
| imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
| imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; |
| err = m_devFuncs->vkCreateImageView(m_vkDev, &imgViewInfo, nullptr, &image.imageView); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create swapchain image view %d: %d", i, err); |
| |
| err = m_devFuncs->vkCreateFence(m_vkDev, &fenceInfo, nullptr, &image.cmdFence); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create command buffer fence: %d", err); |
| image.cmdFenceWaitable = true; |
| |
| VkImageView views[1] = { image.imageView }; |
| VkFramebufferCreateInfo fbInfo; |
| memset(&fbInfo, 0, sizeof(fbInfo)); |
| fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; |
| fbInfo.renderPass = m_defaultRenderPass; |
| fbInfo.attachmentCount = 1; |
| fbInfo.pAttachments = views; |
| fbInfo.width = m_swapChainImageSize.width(); |
| fbInfo.height = m_swapChainImageSize.height(); |
| fbInfo.layers = 1; |
| VkResult err = m_devFuncs->vkCreateFramebuffer(m_vkDev, &fbInfo, nullptr, &image.fb); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create framebuffer: %d", err); |
| } |
| |
| m_currentImage = 0; |
| |
| VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 }; |
| for (uint32_t i = 0; i < FRAME_LAG; ++i) { |
| FrameResources &frame(m_frameRes[i]); |
| m_devFuncs->vkCreateFence(m_vkDev, &fenceInfo, nullptr, &frame.fence); |
| frame.fenceWaitable = true; |
| m_devFuncs->vkCreateSemaphore(m_vkDev, &semInfo, nullptr, &frame.imageSem); |
| m_devFuncs->vkCreateSemaphore(m_vkDev, &semInfo, nullptr, &frame.drawSem); |
| } |
| |
| m_currentFrame = 0; |
| } |
| |
| void VWindow::createDefaultRenderPass() |
| { |
| VkAttachmentDescription attDesc[1]; |
| memset(attDesc, 0, sizeof(attDesc)); |
| attDesc[0].format = m_colorFormat; |
| attDesc[0].samples = VK_SAMPLE_COUNT_1_BIT; |
| attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
| attDesc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; |
| attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, |
| attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| attDesc[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
| |
| VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
| |
| VkSubpassDescription subPassDesc; |
| memset(&subPassDesc, 0, sizeof(subPassDesc)); |
| subPassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
| subPassDesc.colorAttachmentCount = 1; |
| subPassDesc.pColorAttachments = &colorRef; |
| |
| VkRenderPassCreateInfo rpInfo; |
| memset(&rpInfo, 0, sizeof(rpInfo)); |
| rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; |
| rpInfo.attachmentCount = 1; |
| rpInfo.pAttachments = attDesc; |
| rpInfo.subpassCount = 1; |
| rpInfo.pSubpasses = &subPassDesc; |
| VkResult err = m_devFuncs->vkCreateRenderPass(m_vkDev, &rpInfo, nullptr, &m_defaultRenderPass); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to create renderpass: %d", err); |
| } |
| |
| void VWindow::releaseSwapChain() |
| { |
| if (!m_vkDev) |
| return; |
| |
| m_devFuncs->vkDeviceWaitIdle(m_vkDev); |
| |
| if (m_defaultRenderPass) { |
| m_devFuncs->vkDestroyRenderPass(m_vkDev, m_defaultRenderPass, nullptr); |
| m_defaultRenderPass = 0; |
| } |
| |
| for (uint32_t i = 0; i < FRAME_LAG; ++i) { |
| FrameResources &frame(m_frameRes[i]); |
| if (frame.fence) { |
| if (frame.fenceWaitable) |
| m_devFuncs->vkWaitForFences(m_vkDev, 1, &frame.fence, VK_TRUE, UINT64_MAX); |
| m_devFuncs->vkDestroyFence(m_vkDev, frame.fence, nullptr); |
| frame.fence = 0; |
| frame.fenceWaitable = false; |
| } |
| if (frame.imageSem) { |
| m_devFuncs->vkDestroySemaphore(m_vkDev, frame.imageSem, nullptr); |
| frame.imageSem = 0; |
| } |
| if (frame.drawSem) { |
| m_devFuncs->vkDestroySemaphore(m_vkDev, frame.drawSem, nullptr); |
| frame.drawSem = 0; |
| } |
| } |
| |
| for (uint32_t i = 0; i < m_swapChainBufferCount; ++i) { |
| ImageResources &image(m_imageRes[i]); |
| if (image.cmdFence) { |
| if (image.cmdFenceWaitable) |
| m_devFuncs->vkWaitForFences(m_vkDev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX); |
| m_devFuncs->vkDestroyFence(m_vkDev, image.cmdFence, nullptr); |
| image.cmdFence = 0; |
| image.cmdFenceWaitable = false; |
| } |
| if (image.fb) { |
| m_devFuncs->vkDestroyFramebuffer(m_vkDev, image.fb, nullptr); |
| image.fb = 0; |
| } |
| if (image.imageView) { |
| m_devFuncs->vkDestroyImageView(m_vkDev, image.imageView, nullptr); |
| image.imageView = 0; |
| } |
| if (image.cmdBuf) { |
| m_devFuncs->vkFreeCommandBuffers(m_vkDev, m_vkCmdPool, 1, &image.cmdBuf); |
| image.cmdBuf = 0; |
| } |
| } |
| |
| if (m_swapChain) { |
| m_vkDestroySwapchainKHR(m_vkDev, m_swapChain, nullptr); |
| m_swapChain = 0; |
| } |
| } |
| |
| void VWindow::render() |
| { |
| if (!m_swapChain) |
| return; |
| |
| if (size() != m_swapChainImageSize) { |
| recreateSwapChain(); |
| if (!m_swapChain) |
| return; |
| } |
| |
| FrameResources &frame(m_frameRes[m_currentFrame]); |
| |
| // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate |
| // (note that we are using FIFO mode -> vsync) |
| if (frame.fenceWaitable) { |
| m_devFuncs->vkWaitForFences(m_vkDev, 1, &frame.fence, VK_TRUE, UINT64_MAX); |
| m_devFuncs->vkResetFences(m_vkDev, 1, &frame.fence); |
| } |
| |
| // move on to next swapchain image |
| VkResult err = m_vkAcquireNextImageKHR(m_vkDev, m_swapChain, UINT64_MAX, |
| frame.imageSem, frame.fence, &m_currentImage); |
| if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) { |
| frame.fenceWaitable = true; |
| } else if (err == VK_ERROR_OUT_OF_DATE_KHR) { |
| frame.fenceWaitable = false; |
| recreateSwapChain(); |
| requestUpdate(); |
| return; |
| } else { |
| qWarning("Failed to acquire next swapchain image: %d", err); |
| frame.fenceWaitable = false; |
| requestUpdate(); |
| return; |
| } |
| |
| // make sure the previous draw for the same image has finished |
| ImageResources &image(m_imageRes[m_currentImage]); |
| if (image.cmdFenceWaitable) { |
| m_devFuncs->vkWaitForFences(m_vkDev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX); |
| m_devFuncs->vkResetFences(m_vkDev, 1, &image.cmdFence); |
| } |
| |
| // build new draw command buffer |
| buildDrawCalls(); |
| |
| // submit draw calls |
| VkSubmitInfo submitInfo; |
| memset(&submitInfo, 0, sizeof(submitInfo)); |
| submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; |
| submitInfo.commandBufferCount = 1; |
| submitInfo.pCommandBuffers = &image.cmdBuf; |
| submitInfo.waitSemaphoreCount = 1; |
| submitInfo.pWaitSemaphores = &frame.imageSem; |
| submitInfo.signalSemaphoreCount = 1; |
| submitInfo.pSignalSemaphores = &frame.drawSem; |
| VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
| submitInfo.pWaitDstStageMask = &psf; |
| |
| err = m_devFuncs->vkQueueSubmit(m_vkGfxQueue, 1, &submitInfo, image.cmdFence); |
| if (err == VK_SUCCESS) { |
| image.cmdFenceWaitable = true; |
| } else { |
| qWarning("Failed to submit to command queue: %d", err); |
| image.cmdFenceWaitable = false; |
| } |
| |
| // queue present |
| VkPresentInfoKHR presInfo; |
| memset(&presInfo, 0, sizeof(presInfo)); |
| presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
| presInfo.swapchainCount = 1; |
| presInfo.pSwapchains = &m_swapChain; |
| presInfo.pImageIndices = &m_currentImage; |
| presInfo.waitSemaphoreCount = 1; |
| presInfo.pWaitSemaphores = &frame.drawSem; |
| |
| // we do not currently handle the case when the present queue is separate |
| Q_ASSERT(m_vkGfxQueue == m_vkPresQueue); |
| |
| err = m_vkQueuePresentKHR(m_vkGfxQueue, &presInfo); |
| if (err != VK_SUCCESS) { |
| if (err == VK_ERROR_OUT_OF_DATE_KHR) { |
| recreateSwapChain(); |
| requestUpdate(); |
| return; |
| } else if (err != VK_SUBOPTIMAL_KHR) { |
| qWarning("Failed to present: %d", err); |
| } |
| } |
| |
| vulkanInstance()->presentQueued(this); |
| |
| m_currentFrame = (m_currentFrame + 1) % FRAME_LAG; |
| requestUpdate(); |
| } |
| |
| void VWindow::buildDrawCalls() |
| { |
| ImageResources &image(m_imageRes[m_currentImage]); |
| |
| if (image.cmdBuf) { |
| m_devFuncs->vkFreeCommandBuffers(m_vkDev, m_vkCmdPool, 1, &image.cmdBuf); |
| image.cmdBuf = 0; |
| } |
| |
| VkCommandBufferAllocateInfo cmdBufInfo = { |
| VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, m_vkCmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 }; |
| VkResult err = m_devFuncs->vkAllocateCommandBuffers(m_vkDev, &cmdBufInfo, &image.cmdBuf); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to allocate frame command buffer: %d", err); |
| |
| VkCommandBufferBeginInfo cmdBufBeginInfo = { |
| VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, 0, nullptr }; |
| err = m_devFuncs->vkBeginCommandBuffer(image.cmdBuf, &cmdBufBeginInfo); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to begin frame command buffer: %d", err); |
| |
| static float g = 0; |
| g += 0.005f; |
| if (g > 1.0f) |
| g = 0.0f; |
| VkClearColorValue clearColor = { 0.0f, g, 0.0f, 1.0f }; |
| VkClearValue clearValues[1]; |
| clearValues[0].color = clearColor; |
| |
| VkRenderPassBeginInfo rpBeginInfo; |
| memset(&rpBeginInfo, 0, sizeof(rpBeginInfo)); |
| rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; |
| rpBeginInfo.renderPass = m_defaultRenderPass; |
| rpBeginInfo.framebuffer = image.fb; |
| rpBeginInfo.renderArea.extent.width = m_swapChainImageSize.width(); |
| rpBeginInfo.renderArea.extent.height = m_swapChainImageSize.height(); |
| rpBeginInfo.clearValueCount = 1; |
| rpBeginInfo.pClearValues = clearValues; |
| m_devFuncs->vkCmdBeginRenderPass(image.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE); |
| |
| m_devFuncs->vkCmdEndRenderPass(image.cmdBuf); |
| |
| err = m_devFuncs->vkEndCommandBuffer(image.cmdBuf); |
| if (err != VK_SUCCESS) |
| qFatal("Failed to end frame command buffer: %d", err); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| QGuiApplication app(argc, argv); |
| |
| QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true")); |
| |
| QVulkanInstance inst; |
| // Test the early queries for supported layers/exts. |
| qDebug() << inst.supportedLayers() << inst.supportedExtensions(); |
| |
| // Enable validation layer, if supported. |
| inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); |
| |
| bool ok = inst.create(); |
| qDebug("QVulkanInstance::create() returned %d", ok); |
| if (!ok) |
| return 1; |
| |
| VWindow w; |
| w.setVulkanInstance(&inst); |
| w.resize(1024, 768); |
| w.show(); |
| |
| return app.exec(); |
| } |