// Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include 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(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); QList 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; } QList devLayers; if (inst->layers().contains("VK_LAYER_KHRONOS_validation")) devLayers.append("VK_LAYER_KHRONOS_validation"); QList 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( inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); m_vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast( inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR")); // note: device-specific functions m_vkCreateSwapchainKHR = reinterpret_cast(f->vkGetDeviceProcAddr(m_vkDev, "vkCreateSwapchainKHR")); m_vkDestroySwapchainKHR = reinterpret_cast(f->vkGetDeviceProcAddr(m_vkDev, "vkDestroySwapchainKHR")); m_vkGetSwapchainImagesKHR = reinterpret_cast(f->vkGetDeviceProcAddr(m_vkDev, "vkGetSwapchainImagesKHR")); m_vkAcquireNextImageKHR = reinterpret_cast(f->vkGetDeviceProcAddr(m_vkDev, "vkAcquireNextImageKHR")); m_vkQueuePresentKHR = reinterpret_cast(f->vkGetDeviceProcAddr(m_vkDev, "vkQueuePresentKHR")); } VkColorSpaceKHR colorSpace = VkColorSpaceKHR(0); uint32_t formatCount = 0; m_vkGetPhysicalDeviceSurfaceFormatsKHR(m_vkPhysDev, m_vkSurface, &formatCount, nullptr); if (formatCount) { QList 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_KHRONOS_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(); }