Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qvulkanwindow.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qvulkanwindow_p.h"
5#include "qvulkanfunctions.h"
6#include <QLoggingCategory>
7#include <QTimer>
8#include <QThread>
9#include <QCoreApplication>
10#include <qevent.h>
11
13
15
16
214QVulkanWindow::QVulkanWindow(QWindow *parent)
215 : QWindow(*(new QVulkanWindowPrivate), parent)
216{
217 setSurfaceType(QSurface::VulkanSurface);
218}
219
223QVulkanWindow::~QVulkanWindow()
224{
225}
226
227QVulkanWindowPrivate::~QVulkanWindowPrivate()
228{
229 // graphics resource cleanup is already done at this point due to
230 // QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed
231
232 delete renderer;
233}
234
252void QVulkanWindow::setFlags(Flags flags)
253{
254 Q_D(QVulkanWindow);
255 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
256 qWarning("QVulkanWindow: Attempted to set flags when already initialized");
257 return;
258 }
259 d->flags = flags;
260}
261
265QVulkanWindow::Flags QVulkanWindow::flags() const
266{
267 Q_D(const QVulkanWindow);
268 return d->flags;
269}
270
276QList<VkPhysicalDeviceProperties> QVulkanWindow::availablePhysicalDevices()
277{
278 Q_D(QVulkanWindow);
279 if (!d->physDevs.isEmpty() && !d->physDevProps.isEmpty())
280 return d->physDevProps;
281
282 QVulkanInstance *inst = vulkanInstance();
283 if (!inst) {
284 qWarning("QVulkanWindow: Attempted to call availablePhysicalDevices() without a QVulkanInstance");
285 return d->physDevProps;
286 }
287
288 QVulkanFunctions *f = inst->functions();
289 uint32_t count = 1;
290 VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &count, nullptr);
291 if (err != VK_SUCCESS) {
292 qWarning("QVulkanWindow: Failed to get physical device count: %d", err);
293 return d->physDevProps;
294 }
295
296 qCDebug(lcGuiVk, "%d physical devices", count);
297 if (!count)
298 return d->physDevProps;
299
300 QList<VkPhysicalDevice> devs(count);
301 err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &count, devs.data());
302 if (err != VK_SUCCESS) {
303 qWarning("QVulkanWindow: Failed to enumerate physical devices: %d", err);
304 return d->physDevProps;
305 }
306
307 d->physDevs = devs;
308 d->physDevProps.resize(count);
309 for (uint32_t i = 0; i < count; ++i) {
310 VkPhysicalDeviceProperties *p = &d->physDevProps[i];
311 f->vkGetPhysicalDeviceProperties(d->physDevs.at(i), p);
312 qCDebug(lcGuiVk, "Physical device [%d]: name '%s' version %d.%d.%d", i, p->deviceName,
313 VK_VERSION_MAJOR(p->driverVersion), VK_VERSION_MINOR(p->driverVersion),
314 VK_VERSION_PATCH(p->driverVersion));
315 }
316
317 return d->physDevProps;
318}
319
330void QVulkanWindow::setPhysicalDeviceIndex(int idx)
331{
332 Q_D(QVulkanWindow);
333 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
334 qWarning("QVulkanWindow: Attempted to set physical device when already initialized");
335 return;
336 }
337 const int count = availablePhysicalDevices().size();
338 if (idx < 0 || idx >= count) {
339 qWarning("QVulkanWindow: Invalid physical device index %d (total physical devices: %d)", idx, count);
340 return;
341 }
342 d->physDevIndex = idx;
343}
344
351QVulkanInfoVector<QVulkanExtension> QVulkanWindow::supportedDeviceExtensions()
352{
353 Q_D(QVulkanWindow);
354
355 availablePhysicalDevices();
356
357 if (d->physDevs.isEmpty()) {
358 qWarning("QVulkanWindow: No physical devices found");
359 return QVulkanInfoVector<QVulkanExtension>();
360 }
361
362 VkPhysicalDevice physDev = d->physDevs.at(d->physDevIndex);
363 if (d->supportedDevExtensions.contains(physDev))
364 return d->supportedDevExtensions.value(physDev);
365
366 QVulkanFunctions *f = vulkanInstance()->functions();
367 uint32_t count = 0;
368 VkResult err = f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &count, nullptr);
369 if (err == VK_SUCCESS) {
370 QList<VkExtensionProperties> extProps(count);
371 err = f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &count, extProps.data());
372 if (err == VK_SUCCESS) {
373 QVulkanInfoVector<QVulkanExtension> exts;
374 for (const VkExtensionProperties &prop : extProps) {
376 ext.name = prop.extensionName;
377 ext.version = prop.specVersion;
378 exts.append(ext);
379 }
380 d->supportedDevExtensions.insert(physDev, exts);
381 qDebug(lcGuiVk) << "Supported device extensions:" << exts;
382 return exts;
383 }
384 }
385
386 qWarning("QVulkanWindow: Failed to query device extension count: %d", err);
387 return QVulkanInfoVector<QVulkanExtension>();
388}
389
402void QVulkanWindow::setDeviceExtensions(const QByteArrayList &extensions)
403{
404 Q_D(QVulkanWindow);
405 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
406 qWarning("QVulkanWindow: Attempted to set device extensions when already initialized");
407 return;
408 }
409 d->requestedDevExtensions = extensions;
410}
411
437void QVulkanWindow::setPreferredColorFormats(const QList<VkFormat> &formats)
438{
439 Q_D(QVulkanWindow);
440 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
441 qWarning("QVulkanWindow: Attempted to set preferred color format when already initialized");
442 return;
443 }
444 d->requestedColorFormats = formats;
445}
446
447static struct {
448 VkSampleCountFlagBits mask;
449 int count;
450} q_vk_sampleCounts[] = {
451 // keep this sorted by 'count'
452 { VK_SAMPLE_COUNT_1_BIT, 1 },
453 { VK_SAMPLE_COUNT_2_BIT, 2 },
454 { VK_SAMPLE_COUNT_4_BIT, 4 },
455 { VK_SAMPLE_COUNT_8_BIT, 8 },
456 { VK_SAMPLE_COUNT_16_BIT, 16 },
457 { VK_SAMPLE_COUNT_32_BIT, 32 },
458 { VK_SAMPLE_COUNT_64_BIT, 64 }
460
473QList<int> QVulkanWindow::supportedSampleCounts()
474{
475 Q_D(const QVulkanWindow);
476 QList<int> result;
477
478 availablePhysicalDevices();
479
480 if (d->physDevs.isEmpty()) {
481 qWarning("QVulkanWindow: No physical devices found");
482 return result;
483 }
484
485 const VkPhysicalDeviceLimits *limits = &d->physDevProps[d->physDevIndex].limits;
486 VkSampleCountFlags color = limits->framebufferColorSampleCounts;
487 VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
488 VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
489
490 for (const auto &qvk_sampleCount : q_vk_sampleCounts) {
491 if ((color & qvk_sampleCount.mask)
492 && (depth & qvk_sampleCount.mask)
493 && (stencil & qvk_sampleCount.mask))
494 {
495 result.append(qvk_sampleCount.count);
496 }
497 }
498
499 return result;
500}
501
523void QVulkanWindow::setSampleCount(int sampleCount)
524{
525 Q_D(QVulkanWindow);
526 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
527 qWarning("QVulkanWindow: Attempted to set sample count when already initialized");
528 return;
529 }
530
531 // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
532 sampleCount = qBound(1, sampleCount, 64);
533
534 if (!supportedSampleCounts().contains(sampleCount)) {
535 qWarning("QVulkanWindow: Attempted to set unsupported sample count %d", sampleCount);
536 return;
537 }
538
539 for (const auto &qvk_sampleCount : q_vk_sampleCounts) {
540 if (qvk_sampleCount.count == sampleCount) {
541 d->sampleCount = qvk_sampleCount.mask;
542 return;
543 }
544 }
545
546 Q_UNREACHABLE();
547}
548
549void QVulkanWindowPrivate::init()
550{
551 Q_Q(QVulkanWindow);
552 Q_ASSERT(status == StatusUninitialized);
553
554 qCDebug(lcGuiVk, "QVulkanWindow init");
555
556 inst = q->vulkanInstance();
557 if (!inst) {
558 qWarning("QVulkanWindow: Attempted to initialize without a QVulkanInstance");
559 // This is a simple user error, recheck on the next expose instead of
560 // going into the permanent failure state.
561 status = StatusFailRetry;
562 return;
563 }
564
565 if (!renderer)
566 renderer = q->createRenderer();
567
568 surface = QVulkanInstance::surfaceForWindow(q);
569 if (surface == VK_NULL_HANDLE) {
570 qWarning("QVulkanWindow: Failed to retrieve Vulkan surface for window");
571 status = StatusFailRetry;
572 return;
573 }
574
575 q->availablePhysicalDevices();
576
577 if (physDevs.isEmpty()) {
578 qWarning("QVulkanWindow: No physical devices found");
579 status = StatusFail;
580 return;
581 }
582
583 if (physDevIndex < 0 || physDevIndex >= physDevs.size()) {
584 qWarning("QVulkanWindow: Invalid physical device index; defaulting to 0");
585 physDevIndex = 0;
586 }
587 qCDebug(lcGuiVk, "Using physical device [%d]", physDevIndex);
588
589 // Give a last chance to do decisions based on the physical device and the surface.
590 if (renderer)
591 renderer->preInitResources();
592
593 VkPhysicalDevice physDev = physDevs.at(physDevIndex);
594 QVulkanFunctions *f = inst->functions();
595
596 uint32_t queueCount = 0;
597 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
598 QList<VkQueueFamilyProperties> queueFamilyProps(queueCount);
599 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
600 gfxQueueFamilyIdx = uint32_t(-1);
601 presQueueFamilyIdx = uint32_t(-1);
602 for (int i = 0; i < queueFamilyProps.size(); ++i) {
603 const bool supportsPresent = inst->supportsPresent(physDev, i, q);
604 qCDebug(lcGuiVk, "queue family %d: flags=0x%x count=%d supportsPresent=%d", i,
605 queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount, supportsPresent);
606 if (gfxQueueFamilyIdx == uint32_t(-1)
607 && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
608 && supportsPresent)
609 gfxQueueFamilyIdx = i;
610 }
611 if (gfxQueueFamilyIdx != uint32_t(-1)) {
612 presQueueFamilyIdx = gfxQueueFamilyIdx;
613 } else {
614 qCDebug(lcGuiVk, "No queue with graphics+present; trying separate queues");
615 for (int i = 0; i < queueFamilyProps.size(); ++i) {
616 if (gfxQueueFamilyIdx == uint32_t(-1) && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
617 gfxQueueFamilyIdx = i;
618 if (presQueueFamilyIdx == uint32_t(-1) && inst->supportsPresent(physDev, i, q))
619 presQueueFamilyIdx = i;
620 }
621 }
622 if (gfxQueueFamilyIdx == uint32_t(-1)) {
623 qWarning("QVulkanWindow: No graphics queue family found");
624 status = StatusFail;
625 return;
626 }
627 if (presQueueFamilyIdx == uint32_t(-1)) {
628 qWarning("QVulkanWindow: No present queue family found");
629 status = StatusFail;
630 return;
631 }
632#ifdef QT_DEBUG
633 // allow testing the separate present queue case in debug builds on AMD cards
634 if (qEnvironmentVariableIsSet("QT_VK_PRESENT_QUEUE_INDEX"))
635 presQueueFamilyIdx = qEnvironmentVariableIntValue("QT_VK_PRESENT_QUEUE_INDEX");
636#endif
637 qCDebug(lcGuiVk, "Using queue families: graphics = %u present = %u", gfxQueueFamilyIdx, presQueueFamilyIdx);
638
639 QList<VkDeviceQueueCreateInfo> queueInfo;
640 queueInfo.reserve(2);
641 const float prio[] = { 0 };
642 VkDeviceQueueCreateInfo addQueueInfo;
643 memset(&addQueueInfo, 0, sizeof(addQueueInfo));
644 addQueueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
645 addQueueInfo.queueFamilyIndex = gfxQueueFamilyIdx;
646 addQueueInfo.queueCount = 1;
647 addQueueInfo.pQueuePriorities = prio;
648 queueInfo.append(addQueueInfo);
649 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
650 addQueueInfo.queueFamilyIndex = presQueueFamilyIdx;
651 addQueueInfo.queueCount = 1;
652 addQueueInfo.pQueuePriorities = prio;
653 queueInfo.append(addQueueInfo);
654 }
655 if (queueCreateInfoModifier) {
656 queueCreateInfoModifier(queueFamilyProps.constData(), queueCount, queueInfo);
657 bool foundGfxQueue = false;
658 bool foundPresQueue = false;
659 for (const VkDeviceQueueCreateInfo& createInfo : std::as_const(queueInfo)) {
660 foundGfxQueue |= createInfo.queueFamilyIndex == gfxQueueFamilyIdx;
661 foundPresQueue |= createInfo.queueFamilyIndex == presQueueFamilyIdx;
662 }
663 if (!foundGfxQueue) {
664 qWarning("QVulkanWindow: Graphics queue missing after call to queueCreateInfoModifier");
665 status = StatusFail;
666 return;
667 }
668 if (!foundPresQueue) {
669 qWarning("QVulkanWindow: Present queue missing after call to queueCreateInfoModifier");
670 status = StatusFail;
671 return;
672 }
673 }
674
675 // Filter out unsupported extensions in order to keep symmetry
676 // with how QVulkanInstance behaves. Add the swapchain extension.
677 QList<const char *> devExts;
678 QVulkanInfoVector<QVulkanExtension> supportedExtensions = q->supportedDeviceExtensions();
679 QByteArrayList reqExts = requestedDevExtensions;
680 reqExts.append("VK_KHR_swapchain");
681
682 QByteArray envExts = qgetenv("QT_VULKAN_DEVICE_EXTENSIONS");
683 if (!envExts.isEmpty()) {
684 QByteArrayList envExtList = envExts.split(';');
685 for (auto ext : reqExts)
686 envExtList.removeAll(ext);
687 reqExts.append(envExtList);
688 }
689
690 for (const QByteArray &ext : reqExts) {
691 if (supportedExtensions.contains(ext))
692 devExts.append(ext.constData());
693 }
694 qCDebug(lcGuiVk) << "Enabling device extensions:" << devExts;
695
696 VkDeviceCreateInfo devInfo;
697 memset(&devInfo, 0, sizeof(devInfo));
698 devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
699 devInfo.queueCreateInfoCount = queueInfo.size();
700 devInfo.pQueueCreateInfos = queueInfo.constData();
701 devInfo.enabledExtensionCount = devExts.size();
702 devInfo.ppEnabledExtensionNames = devExts.constData();
703
704 VkPhysicalDeviceFeatures features = {};
705 VkPhysicalDeviceFeatures2 features2 = {};
706 if (enabledFeatures2Modifier) {
707 features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
708 enabledFeatures2Modifier(features2);
709 devInfo.pNext = &features2;
710 } else if (enabledFeaturesModifier) {
711 enabledFeaturesModifier(features);
712 devInfo.pEnabledFeatures = &features;
713 } else {
714 // Enable all supported 1.0 core features, except ones that likely
715 // involve a performance penalty.
716 f->vkGetPhysicalDeviceFeatures(physDev, &features);
717 features.robustBufferAccess = VK_FALSE;
718 devInfo.pEnabledFeatures = &features;
719 }
720
721 // Device layers are not supported by QVulkanWindow since that's an already deprecated
722 // API. However, have a workaround for systems with older API and layers (f.ex. L4T
723 // 24.2 for the Jetson TX1 provides API 1.0.13 and crashes when the validation layer
724 // is enabled for the instance but not the device).
725 uint32_t apiVersion = physDevProps[physDevIndex].apiVersion;
726 if (VK_VERSION_MAJOR(apiVersion) == 1
727 && VK_VERSION_MINOR(apiVersion) == 0
728 && VK_VERSION_PATCH(apiVersion) <= 13)
729 {
730 // Make standard validation work at least.
731 const QByteArray stdValName = QByteArrayLiteral("VK_LAYER_KHRONOS_validation");
732 const char *stdValNamePtr = stdValName.constData();
733 if (inst->layers().contains(stdValName)) {
734 uint32_t count = 0;
735 VkResult err = f->vkEnumerateDeviceLayerProperties(physDev, &count, nullptr);
736 if (err == VK_SUCCESS) {
737 QList<VkLayerProperties> layerProps(count);
738 err = f->vkEnumerateDeviceLayerProperties(physDev, &count, layerProps.data());
739 if (err == VK_SUCCESS) {
740 for (const VkLayerProperties &prop : layerProps) {
741 if (!strncmp(prop.layerName, stdValNamePtr, stdValName.size())) {
742 devInfo.enabledLayerCount = 1;
743 devInfo.ppEnabledLayerNames = &stdValNamePtr;
744 break;
745 }
746 }
747 }
748 }
749 }
750 }
751
752 VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
753 if (err == VK_ERROR_DEVICE_LOST) {
754 qWarning("QVulkanWindow: Physical device lost");
755 if (renderer)
756 renderer->physicalDeviceLost();
757 // clear the caches so the list of physical devices is re-queried
758 physDevs.clear();
759 physDevProps.clear();
760 status = StatusUninitialized;
761 qCDebug(lcGuiVk, "Attempting to restart in 2 seconds");
762 QTimer::singleShot(2000, q, [this]() { ensureStarted(); });
763 return;
764 }
765 if (err != VK_SUCCESS) {
766 qWarning("QVulkanWindow: Failed to create device: %d", err);
767 status = StatusFail;
768 return;
769 }
770
771 devFuncs = inst->deviceFunctions(dev);
772 Q_ASSERT(devFuncs);
773
774 devFuncs->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, 0, &gfxQueue);
775 if (gfxQueueFamilyIdx == presQueueFamilyIdx)
776 presQueue = gfxQueue;
777 else
778 devFuncs->vkGetDeviceQueue(dev, presQueueFamilyIdx, 0, &presQueue);
779
780 VkCommandPoolCreateInfo poolInfo;
781 memset(&poolInfo, 0, sizeof(poolInfo));
782 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
783 poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
784 err = devFuncs->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
785 if (err != VK_SUCCESS) {
786 qWarning("QVulkanWindow: Failed to create command pool: %d", err);
787 status = StatusFail;
788 return;
789 }
790 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
791 poolInfo.queueFamilyIndex = presQueueFamilyIdx;
792 err = devFuncs->vkCreateCommandPool(dev, &poolInfo, nullptr, &presCmdPool);
793 if (err != VK_SUCCESS) {
794 qWarning("QVulkanWindow: Failed to create command pool for present queue: %d", err);
795 status = StatusFail;
796 return;
797 }
798 }
799
800 hostVisibleMemIndex = 0;
801 VkPhysicalDeviceMemoryProperties physDevMemProps;
802 bool hostVisibleMemIndexSet = false;
803 f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
804 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
805 const VkMemoryType *memType = physDevMemProps.memoryTypes;
806 qCDebug(lcGuiVk, "memtype %d: flags=0x%x", i, memType[i].propertyFlags);
807 // Find a host visible, host coherent memtype. If there is one that is
808 // cached as well (in addition to being coherent), prefer that.
809 const int hostVisibleAndCoherent = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
810 if ((memType[i].propertyFlags & hostVisibleAndCoherent) == hostVisibleAndCoherent) {
811 if (!hostVisibleMemIndexSet
812 || (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)) {
813 hostVisibleMemIndexSet = true;
814 hostVisibleMemIndex = i;
815 }
816 }
817 }
818 qCDebug(lcGuiVk, "Picked memtype %d for host visible memory", hostVisibleMemIndex);
819 deviceLocalMemIndex = 0;
820 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
821 const VkMemoryType *memType = physDevMemProps.memoryTypes;
822 // Just pick the first device local memtype.
823 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
824 deviceLocalMemIndex = i;
825 break;
826 }
827 }
828 qCDebug(lcGuiVk, "Picked memtype %d for device local memory", deviceLocalMemIndex);
829
830 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR) {
831 vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
832 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
833 vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
834 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
835 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR) {
836 qWarning("QVulkanWindow: Physical device surface queries not available");
837 status = StatusFail;
838 return;
839 }
840 }
841
842 // Figure out the color format here. Must not wait until recreateSwapChain()
843 // because the renderpass should be available already from initResources (so
844 // that apps do not have to defer pipeline creation to
845 // initSwapChainResources), but the renderpass needs the final color format.
846
847 uint32_t formatCount = 0;
848 vkGetPhysicalDeviceSurfaceFormatsKHR(physDev, surface, &formatCount, nullptr);
849 QList<VkSurfaceFormatKHR> formats(formatCount);
850 if (formatCount)
851 vkGetPhysicalDeviceSurfaceFormatsKHR(physDev, surface, &formatCount, formats.data());
852
853 colorFormat = VK_FORMAT_B8G8R8A8_UNORM; // our documented default if all else fails
854 colorSpace = VkColorSpaceKHR(0); // this is in fact VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
855
856 // Pick the preferred format, if there is one.
857 if (!formats.isEmpty() && formats[0].format != VK_FORMAT_UNDEFINED) {
858 colorFormat = formats[0].format;
859 colorSpace = formats[0].colorSpace;
860 }
861
862 // Try to honor the user request.
863 if (!formats.isEmpty() && !requestedColorFormats.isEmpty()) {
864 for (VkFormat reqFmt : std::as_const(requestedColorFormats)) {
865 auto r = std::find_if(formats.cbegin(), formats.cend(),
866 [reqFmt](const VkSurfaceFormatKHR &sfmt) { return sfmt.format == reqFmt; });
867 if (r != formats.cend()) {
868 colorFormat = r->format;
869 colorSpace = r->colorSpace;
870 break;
871 }
872 }
873 }
874
875 const VkFormat dsFormatCandidates[] = {
876 VK_FORMAT_D24_UNORM_S8_UINT,
877 VK_FORMAT_D32_SFLOAT_S8_UINT,
878 VK_FORMAT_D16_UNORM_S8_UINT
879 };
880 const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
881 int dsFormatIdx = 0;
882 while (dsFormatIdx < dsFormatCandidateCount) {
883 dsFormat = dsFormatCandidates[dsFormatIdx];
884 VkFormatProperties fmtProp;
885 f->vkGetPhysicalDeviceFormatProperties(physDev, dsFormat, &fmtProp);
886 if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
887 break;
888 ++dsFormatIdx;
889 }
890 if (dsFormatIdx == dsFormatCandidateCount)
891 qWarning("QVulkanWindow: Failed to find an optimal depth-stencil format");
892
893 qCDebug(lcGuiVk, "Color format: %d Depth-stencil format: %d", colorFormat, dsFormat);
894
895 if (!createDefaultRenderPass())
896 return;
897
898 if (renderer)
899 renderer->initResources();
900
901 status = StatusDeviceReady;
902}
903
904void QVulkanWindowPrivate::reset()
905{
906 if (!dev) // do not rely on 'status', a half done init must be cleaned properly too
907 return;
908
909 qCDebug(lcGuiVk, "QVulkanWindow reset");
910
911 devFuncs->vkDeviceWaitIdle(dev);
912
913 if (renderer) {
914 renderer->releaseResources();
915 devFuncs->vkDeviceWaitIdle(dev);
916 }
917
918 if (defaultRenderPass) {
919 devFuncs->vkDestroyRenderPass(dev, defaultRenderPass, nullptr);
920 defaultRenderPass = VK_NULL_HANDLE;
921 }
922
923 if (cmdPool) {
924 devFuncs->vkDestroyCommandPool(dev, cmdPool, nullptr);
925 cmdPool = VK_NULL_HANDLE;
926 }
927
928 if (presCmdPool) {
929 devFuncs->vkDestroyCommandPool(dev, presCmdPool, nullptr);
930 presCmdPool = VK_NULL_HANDLE;
931 }
932
933 if (frameGrabImage) {
934 devFuncs->vkDestroyImage(dev, frameGrabImage, nullptr);
935 frameGrabImage = VK_NULL_HANDLE;
936 }
937
938 if (frameGrabImageMem) {
939 devFuncs->vkFreeMemory(dev, frameGrabImageMem, nullptr);
940 frameGrabImageMem = VK_NULL_HANDLE;
941 }
942
943 if (dev) {
944 devFuncs->vkDestroyDevice(dev, nullptr);
945 inst->resetDeviceFunctions(dev);
946 dev = VK_NULL_HANDLE;
947 vkCreateSwapchainKHR = nullptr; // re-resolve swapchain funcs later on since some come via the device
948 }
949
950 surface = VK_NULL_HANDLE;
951
952 status = StatusUninitialized;
953}
954
955bool QVulkanWindowPrivate::createDefaultRenderPass()
956{
957 VkAttachmentDescription attDesc[3];
958 memset(attDesc, 0, sizeof(attDesc));
959
960 const bool msaa = sampleCount > VK_SAMPLE_COUNT_1_BIT;
961
962 // This is either the non-msaa render target or the resolve target.
963 attDesc[0].format = colorFormat;
964 attDesc[0].samples = VK_SAMPLE_COUNT_1_BIT;
965 attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // ignored when msaa
966 attDesc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
967 attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
968 attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
969 attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
970 attDesc[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
971
972 attDesc[1].format = dsFormat;
973 attDesc[1].samples = sampleCount;
974 attDesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
975 attDesc[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
976 attDesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
977 attDesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
978 attDesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
979 attDesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
980
981 if (msaa) {
982 // msaa render target
983 attDesc[2].format = colorFormat;
984 attDesc[2].samples = sampleCount;
985 attDesc[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
986 attDesc[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
987 attDesc[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
988 attDesc[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
989 attDesc[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
990 attDesc[2].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
991 }
992
993 VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
994 VkAttachmentReference resolveRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
995 VkAttachmentReference dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
996
997 VkSubpassDescription subPassDesc;
998 memset(&subPassDesc, 0, sizeof(subPassDesc));
999 subPassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1000 subPassDesc.colorAttachmentCount = 1;
1001 subPassDesc.pColorAttachments = &colorRef;
1002 subPassDesc.pDepthStencilAttachment = &dsRef;
1003
1004 VkRenderPassCreateInfo rpInfo;
1005 memset(&rpInfo, 0, sizeof(rpInfo));
1006 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1007 rpInfo.attachmentCount = 2;
1008 rpInfo.pAttachments = attDesc;
1009 rpInfo.subpassCount = 1;
1010 rpInfo.pSubpasses = &subPassDesc;
1011
1012 if (msaa) {
1013 colorRef.attachment = 2;
1014 subPassDesc.pResolveAttachments = &resolveRef;
1015 rpInfo.attachmentCount = 3;
1016 }
1017
1018 VkResult err = devFuncs->vkCreateRenderPass(dev, &rpInfo, nullptr, &defaultRenderPass);
1019 if (err != VK_SUCCESS) {
1020 qWarning("QVulkanWindow: Failed to create renderpass: %d", err);
1021 return false;
1022 }
1023
1024 return true;
1025}
1026
1027void QVulkanWindowPrivate::recreateSwapChain()
1028{
1029 Q_Q(QVulkanWindow);
1030 Q_ASSERT(status >= StatusDeviceReady);
1031
1032 swapChainImageSize = q->size() * q->devicePixelRatio(); // note: may change below due to surfaceCaps
1033
1034 if (swapChainImageSize.isEmpty()) // handle null window size gracefully
1035 return;
1036
1037 QVulkanInstance *inst = q->vulkanInstance();
1038 QVulkanFunctions *f = inst->functions();
1039 devFuncs->vkDeviceWaitIdle(dev);
1040
1041 if (!vkCreateSwapchainKHR) {
1042 vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
1043 vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
1044 vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
1045 vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
1046 vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
1047 }
1048
1049 VkPhysicalDevice physDev = physDevs.at(physDevIndex);
1050 VkSurfaceCapabilitiesKHR surfaceCaps;
1051 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, surface, &surfaceCaps);
1052 uint32_t reqBufferCount;
1053 if (surfaceCaps.maxImageCount == 0)
1054 reqBufferCount = qMax<uint32_t>(2, surfaceCaps.minImageCount);
1055 else
1056 reqBufferCount = qMax(qMin<uint32_t>(surfaceCaps.maxImageCount, 3), surfaceCaps.minImageCount);
1057
1058 VkExtent2D bufferSize = surfaceCaps.currentExtent;
1059 if (bufferSize.width == uint32_t(-1)) {
1060 Q_ASSERT(bufferSize.height == uint32_t(-1));
1061 bufferSize.width = swapChainImageSize.width();
1062 bufferSize.height = swapChainImageSize.height();
1063 } else {
1064 swapChainImageSize = QSize(bufferSize.width, bufferSize.height);
1065 }
1066
1067 VkSurfaceTransformFlagBitsKHR preTransform =
1068 (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
1069 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
1070 : surfaceCaps.currentTransform;
1071
1072 VkCompositeAlphaFlagBitsKHR compositeAlpha =
1073 (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
1074 ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
1075 : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1076
1077 if (q->requestedFormat().hasAlpha()) {
1078 if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
1079 compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
1080 else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
1081 compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
1082 }
1083
1084 VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1085 swapChainSupportsReadBack = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
1086 if (swapChainSupportsReadBack)
1087 usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1088
1089 VkSwapchainKHR oldSwapChain = swapChain;
1090 VkSwapchainCreateInfoKHR swapChainInfo;
1091 memset(&swapChainInfo, 0, sizeof(swapChainInfo));
1092 swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1093 swapChainInfo.surface = surface;
1094 swapChainInfo.minImageCount = reqBufferCount;
1095 swapChainInfo.imageFormat = colorFormat;
1096 swapChainInfo.imageColorSpace = colorSpace;
1097 swapChainInfo.imageExtent = bufferSize;
1098 swapChainInfo.imageArrayLayers = 1;
1099 swapChainInfo.imageUsage = usage;
1100 swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1101 swapChainInfo.preTransform = preTransform;
1102 swapChainInfo.compositeAlpha = compositeAlpha;
1103 swapChainInfo.presentMode = presentMode;
1104 swapChainInfo.clipped = true;
1105 swapChainInfo.oldSwapchain = oldSwapChain;
1106
1107 qCDebug(lcGuiVk, "Creating new swap chain of %d buffers, size %dx%d", reqBufferCount, bufferSize.width, bufferSize.height);
1108
1109 VkSwapchainKHR newSwapChain;
1110 VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
1111 if (err != VK_SUCCESS) {
1112 qWarning("QVulkanWindow: Failed to create swap chain: %d", err);
1113 return;
1114 }
1115
1116 if (oldSwapChain)
1117 releaseSwapChain();
1118
1119 swapChain = newSwapChain;
1120
1121 uint32_t actualSwapChainBufferCount = 0;
1122 err = vkGetSwapchainImagesKHR(dev, swapChain, &actualSwapChainBufferCount, nullptr);
1123 if (err != VK_SUCCESS || actualSwapChainBufferCount < 2) {
1124 qWarning("QVulkanWindow: Failed to get swapchain images: %d (count=%d)", err, actualSwapChainBufferCount);
1125 return;
1126 }
1127
1128 qCDebug(lcGuiVk, "Actual swap chain buffer count: %d (supportsReadback=%d)",
1129 actualSwapChainBufferCount, swapChainSupportsReadBack);
1130 if (actualSwapChainBufferCount > MAX_SWAPCHAIN_BUFFER_COUNT) {
1131 qWarning("QVulkanWindow: Too many swapchain buffers (%d)", actualSwapChainBufferCount);
1132 return;
1133 }
1134 swapChainBufferCount = actualSwapChainBufferCount;
1135
1136 VkImage swapChainImages[MAX_SWAPCHAIN_BUFFER_COUNT];
1137 err = vkGetSwapchainImagesKHR(dev, swapChain, &actualSwapChainBufferCount, swapChainImages);
1138 if (err != VK_SUCCESS) {
1139 qWarning("QVulkanWindow: Failed to get swapchain images: %d", err);
1140 return;
1141 }
1142
1143 if (!createTransientImage(dsFormat,
1144 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
1145 VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
1146 &dsImage,
1147 &dsMem,
1148 &dsView,
1149 1))
1150 {
1151 return;
1152 }
1153
1154 const bool msaa = sampleCount > VK_SAMPLE_COUNT_1_BIT;
1155 VkImage msaaImages[MAX_SWAPCHAIN_BUFFER_COUNT];
1156 VkImageView msaaViews[MAX_SWAPCHAIN_BUFFER_COUNT];
1157
1158 if (msaa) {
1159 if (!createTransientImage(colorFormat,
1160 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
1161 VK_IMAGE_ASPECT_COLOR_BIT,
1162 msaaImages,
1163 &msaaImageMem,
1164 msaaViews,
1165 swapChainBufferCount))
1166 {
1167 return;
1168 }
1169 }
1170
1171 VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT };
1172
1173 for (int i = 0; i < swapChainBufferCount; ++i) {
1174 ImageResources &image(imageRes[i]);
1175 image.image = swapChainImages[i];
1176
1177 if (msaa) {
1178 image.msaaImage = msaaImages[i];
1179 image.msaaImageView = msaaViews[i];
1180 }
1181
1182 VkImageViewCreateInfo imgViewInfo;
1183 memset(&imgViewInfo, 0, sizeof(imgViewInfo));
1184 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1185 imgViewInfo.image = swapChainImages[i];
1186 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1187 imgViewInfo.format = colorFormat;
1188 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1189 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1190 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1191 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1192 imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1193 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1194 err = devFuncs->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
1195 if (err != VK_SUCCESS) {
1196 qWarning("QVulkanWindow: Failed to create swapchain image view %d: %d", i, err);
1197 return;
1198 }
1199
1200 err = devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &image.cmdFence);
1201 if (err != VK_SUCCESS) {
1202 qWarning("QVulkanWindow: Failed to create command buffer fence: %d", err);
1203 return;
1204 }
1205 image.cmdFenceWaitable = true; // fence was created in signaled state
1206
1207 VkImageView views[3] = { image.imageView,
1208 dsView,
1209 msaa ? image.msaaImageView : VK_NULL_HANDLE };
1210 VkFramebufferCreateInfo fbInfo;
1211 memset(&fbInfo, 0, sizeof(fbInfo));
1212 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1213 fbInfo.renderPass = defaultRenderPass;
1214 fbInfo.attachmentCount = msaa ? 3 : 2;
1215 fbInfo.pAttachments = views;
1216 fbInfo.width = swapChainImageSize.width();
1217 fbInfo.height = swapChainImageSize.height();
1218 fbInfo.layers = 1;
1219 VkResult err = devFuncs->vkCreateFramebuffer(dev, &fbInfo, nullptr, &image.fb);
1220 if (err != VK_SUCCESS) {
1221 qWarning("QVulkanWindow: Failed to create framebuffer: %d", err);
1222 return;
1223 }
1224
1225 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
1226 // pre-build the static image-acquire-on-present-queue command buffer
1227 VkCommandBufferAllocateInfo cmdBufInfo = {
1228 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, presCmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
1229 err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &image.presTransCmdBuf);
1230 if (err != VK_SUCCESS) {
1231 qWarning("QVulkanWindow: Failed to allocate acquire-on-present-queue command buffer: %d", err);
1232 return;
1233 }
1234 VkCommandBufferBeginInfo cmdBufBeginInfo = {
1235 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
1236 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, nullptr };
1237 err = devFuncs->vkBeginCommandBuffer(image.presTransCmdBuf, &cmdBufBeginInfo);
1238 if (err != VK_SUCCESS) {
1239 qWarning("QVulkanWindow: Failed to begin acquire-on-present-queue command buffer: %d", err);
1240 return;
1241 }
1242 VkImageMemoryBarrier presTrans;
1243 memset(&presTrans, 0, sizeof(presTrans));
1244 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1245 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1246 presTrans.oldLayout = presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1247 presTrans.srcQueueFamilyIndex = gfxQueueFamilyIdx;
1248 presTrans.dstQueueFamilyIndex = presQueueFamilyIdx;
1249 presTrans.image = image.image;
1250 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1251 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
1252 devFuncs->vkCmdPipelineBarrier(image.presTransCmdBuf,
1253 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1254 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1255 0, 0, nullptr, 0, nullptr,
1256 1, &presTrans);
1257 err = devFuncs->vkEndCommandBuffer(image.presTransCmdBuf);
1258 if (err != VK_SUCCESS) {
1259 qWarning("QVulkanWindow: Failed to end acquire-on-present-queue command buffer: %d", err);
1260 return;
1261 }
1262 }
1263 }
1264
1265 currentImage = 0;
1266
1267 VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
1268 for (int i = 0; i < frameLag; ++i) {
1269 FrameResources &frame(frameRes[i]);
1270
1271 frame.imageAcquired = false;
1272 frame.imageSemWaitable = false;
1273
1274 devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &frame.fence);
1275 frame.fenceWaitable = true; // fence was created in signaled state
1276
1277 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
1278 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
1279 if (gfxQueueFamilyIdx != presQueueFamilyIdx)
1280 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.presTransSem);
1281 }
1282
1283 currentFrame = 0;
1284
1285 if (renderer)
1286 renderer->initSwapChainResources();
1287
1288 status = StatusReady;
1289}
1290
1291uint32_t QVulkanWindowPrivate::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
1292{
1293 VkPhysicalDeviceMemoryProperties physDevMemProps;
1294 inst->functions()->vkGetPhysicalDeviceMemoryProperties(physDevs[physDevIndex], &physDevMemProps);
1295
1296 VkMemoryRequirements memReq;
1297 devFuncs->vkGetImageMemoryRequirements(dev, img, &memReq);
1298 uint32_t memTypeIndex = uint32_t(-1);
1299
1300 if (memReq.memoryTypeBits) {
1301 // Find a device local + lazily allocated, or at least device local memtype.
1302 const VkMemoryType *memType = physDevMemProps.memoryTypes;
1303 bool foundDevLocal = false;
1304 for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
1305 if (memReq.memoryTypeBits & (1 << i)) {
1306 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
1307 if (!foundDevLocal) {
1308 foundDevLocal = true;
1309 memTypeIndex = i;
1310 }
1311 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
1312 memTypeIndex = i;
1313 break;
1314 }
1315 }
1316 }
1317 }
1318 }
1319
1320 return memTypeIndex;
1321}
1322
1323static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
1324{
1325 return (v + byteAlign - 1) & ~(byteAlign - 1);
1326}
1327
1328bool QVulkanWindowPrivate::createTransientImage(VkFormat format,
1329 VkImageUsageFlags usage,
1330 VkImageAspectFlags aspectMask,
1331 VkImage *images,
1332 VkDeviceMemory *mem,
1333 VkImageView *views,
1334 int count)
1335{
1336 VkMemoryRequirements memReq;
1337 VkResult err;
1338
1339 Q_ASSERT(count > 0);
1340 for (int i = 0; i < count; ++i) {
1341 VkImageCreateInfo imgInfo;
1342 memset(&imgInfo, 0, sizeof(imgInfo));
1343 imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1344 imgInfo.imageType = VK_IMAGE_TYPE_2D;
1345 imgInfo.format = format;
1346 imgInfo.extent.width = swapChainImageSize.width();
1347 imgInfo.extent.height = swapChainImageSize.height();
1348 imgInfo.extent.depth = 1;
1349 imgInfo.mipLevels = imgInfo.arrayLayers = 1;
1350 imgInfo.samples = sampleCount;
1351 imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1352 imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
1353
1354 err = devFuncs->vkCreateImage(dev, &imgInfo, nullptr, images + i);
1355 if (err != VK_SUCCESS) {
1356 qWarning("QVulkanWindow: Failed to create image: %d", err);
1357 return false;
1358 }
1359
1360 // Assume the reqs are the same since the images are same in every way.
1361 // Still, call GetImageMemReq for every image, in order to prevent the
1362 // validation layer from complaining.
1363 devFuncs->vkGetImageMemoryRequirements(dev, images[i], &memReq);
1364 }
1365
1366 VkMemoryAllocateInfo memInfo;
1367 memset(&memInfo, 0, sizeof(memInfo));
1368 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1369 memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * count;
1370
1371 uint32_t startIndex = 0;
1372 do {
1373 memInfo.memoryTypeIndex = chooseTransientImageMemType(images[0], startIndex);
1374 if (memInfo.memoryTypeIndex == uint32_t(-1)) {
1375 qWarning("QVulkanWindow: No suitable memory type found");
1376 return false;
1377 }
1378 startIndex = memInfo.memoryTypeIndex + 1;
1379 qCDebug(lcGuiVk, "Allocating %u bytes for transient image (memtype %u)",
1380 uint32_t(memInfo.allocationSize), memInfo.memoryTypeIndex);
1381 err = devFuncs->vkAllocateMemory(dev, &memInfo, nullptr, mem);
1382 if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
1383 qWarning("QVulkanWindow: Failed to allocate image memory: %d", err);
1384 return false;
1385 }
1386 } while (err != VK_SUCCESS);
1387
1388 VkDeviceSize ofs = 0;
1389 for (int i = 0; i < count; ++i) {
1390 err = devFuncs->vkBindImageMemory(dev, images[i], *mem, ofs);
1391 if (err != VK_SUCCESS) {
1392 qWarning("QVulkanWindow: Failed to bind image memory: %d", err);
1393 return false;
1394 }
1395 ofs += aligned(memReq.size, memReq.alignment);
1396
1397 VkImageViewCreateInfo imgViewInfo;
1398 memset(&imgViewInfo, 0, sizeof(imgViewInfo));
1399 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1400 imgViewInfo.image = images[i];
1401 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1402 imgViewInfo.format = format;
1403 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1404 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1405 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1406 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1407 imgViewInfo.subresourceRange.aspectMask = aspectMask;
1408 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1409
1410 err = devFuncs->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
1411 if (err != VK_SUCCESS) {
1412 qWarning("QVulkanWindow: Failed to create image view: %d", err);
1413 return false;
1414 }
1415 }
1416
1417 return true;
1418}
1419
1420void QVulkanWindowPrivate::releaseSwapChain()
1421{
1422 if (!dev || !swapChain) // do not rely on 'status', a half done init must be cleaned properly too
1423 return;
1424
1425 qCDebug(lcGuiVk, "Releasing swapchain");
1426
1427 devFuncs->vkDeviceWaitIdle(dev);
1428
1429 if (renderer) {
1430 renderer->releaseSwapChainResources();
1431 devFuncs->vkDeviceWaitIdle(dev);
1432 }
1433
1434 for (int i = 0; i < frameLag; ++i) {
1435 FrameResources &frame(frameRes[i]);
1436 if (frame.fence) {
1437 if (frame.fenceWaitable)
1438 devFuncs->vkWaitForFences(dev, 1, &frame.fence, VK_TRUE, UINT64_MAX);
1439 devFuncs->vkDestroyFence(dev, frame.fence, nullptr);
1440 frame.fence = VK_NULL_HANDLE;
1441 frame.fenceWaitable = false;
1442 }
1443 if (frame.imageSem) {
1444 devFuncs->vkDestroySemaphore(dev, frame.imageSem, nullptr);
1445 frame.imageSem = VK_NULL_HANDLE;
1446 }
1447 if (frame.drawSem) {
1448 devFuncs->vkDestroySemaphore(dev, frame.drawSem, nullptr);
1449 frame.drawSem = VK_NULL_HANDLE;
1450 }
1451 if (frame.presTransSem) {
1452 devFuncs->vkDestroySemaphore(dev, frame.presTransSem, nullptr);
1453 frame.presTransSem = VK_NULL_HANDLE;
1454 }
1455 }
1456
1457 for (int i = 0; i < swapChainBufferCount; ++i) {
1458 ImageResources &image(imageRes[i]);
1459 if (image.cmdFence) {
1460 if (image.cmdFenceWaitable)
1461 devFuncs->vkWaitForFences(dev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
1462 devFuncs->vkDestroyFence(dev, image.cmdFence, nullptr);
1463 image.cmdFence = VK_NULL_HANDLE;
1464 image.cmdFenceWaitable = false;
1465 }
1466 if (image.fb) {
1467 devFuncs->vkDestroyFramebuffer(dev, image.fb, nullptr);
1468 image.fb = VK_NULL_HANDLE;
1469 }
1470 if (image.imageView) {
1471 devFuncs->vkDestroyImageView(dev, image.imageView, nullptr);
1472 image.imageView = VK_NULL_HANDLE;
1473 }
1474 if (image.cmdBuf) {
1475 devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &image.cmdBuf);
1476 image.cmdBuf = VK_NULL_HANDLE;
1477 }
1478 if (image.presTransCmdBuf) {
1479 devFuncs->vkFreeCommandBuffers(dev, presCmdPool, 1, &image.presTransCmdBuf);
1480 image.presTransCmdBuf = VK_NULL_HANDLE;
1481 }
1482 if (image.msaaImageView) {
1483 devFuncs->vkDestroyImageView(dev, image.msaaImageView, nullptr);
1484 image.msaaImageView = VK_NULL_HANDLE;
1485 }
1486 if (image.msaaImage) {
1487 devFuncs->vkDestroyImage(dev, image.msaaImage, nullptr);
1488 image.msaaImage = VK_NULL_HANDLE;
1489 }
1490 }
1491
1492 if (msaaImageMem) {
1493 devFuncs->vkFreeMemory(dev, msaaImageMem, nullptr);
1494 msaaImageMem = VK_NULL_HANDLE;
1495 }
1496
1497 if (dsView) {
1498 devFuncs->vkDestroyImageView(dev, dsView, nullptr);
1499 dsView = VK_NULL_HANDLE;
1500 }
1501 if (dsImage) {
1502 devFuncs->vkDestroyImage(dev, dsImage, nullptr);
1503 dsImage = VK_NULL_HANDLE;
1504 }
1505 if (dsMem) {
1506 devFuncs->vkFreeMemory(dev, dsMem, nullptr);
1507 dsMem = VK_NULL_HANDLE;
1508 }
1509
1510 if (swapChain) {
1511 vkDestroySwapchainKHR(dev, swapChain, nullptr);
1512 swapChain = VK_NULL_HANDLE;
1513 }
1514
1515 if (status == StatusReady)
1516 status = StatusDeviceReady;
1517}
1518
1522void QVulkanWindow::exposeEvent(QExposeEvent *)
1523{
1524 Q_D(QVulkanWindow);
1525
1526 if (isExposed()) {
1527 d->ensureStarted();
1528 } else {
1529 if (!d->flags.testFlag(PersistentResources)) {
1530 d->releaseSwapChain();
1531 d->reset();
1532 }
1533 }
1534}
1535
1536void QVulkanWindowPrivate::ensureStarted()
1537{
1538 Q_Q(QVulkanWindow);
1539 if (status == QVulkanWindowPrivate::StatusFailRetry)
1540 status = QVulkanWindowPrivate::StatusUninitialized;
1541 if (status == QVulkanWindowPrivate::StatusUninitialized) {
1542 init();
1543 if (status == QVulkanWindowPrivate::StatusDeviceReady)
1544 recreateSwapChain();
1545 }
1546 if (status == QVulkanWindowPrivate::StatusReady)
1547 q->requestUpdate();
1548}
1549
1553void QVulkanWindow::resizeEvent(QResizeEvent *)
1554{
1555 // Nothing to do here - recreating the swapchain is handled when building the next frame.
1556}
1557
1561bool QVulkanWindow::event(QEvent *e)
1562{
1563 Q_D(QVulkanWindow);
1564
1565 switch (e->type()) {
1567 d->beginFrame();
1568 break;
1569
1570 // The swapchain must be destroyed before the surface as per spec. This is
1571 // not ideal for us because the surface is managed by the QPlatformWindow
1572 // which may be gone already when the unexpose comes, making the validation
1573 // layer scream. The solution is to listen to the PlatformSurface events.
1575 if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1576 d->releaseSwapChain();
1577 d->reset();
1578 }
1579 break;
1580
1581 default:
1582 break;
1583 }
1584
1585 return QWindow::event(e);
1586}
1587
1614void QVulkanWindow::setQueueCreateInfoModifier(const QueueCreateInfoModifier &modifier)
1615{
1616 Q_D(QVulkanWindow);
1617 d->queueCreateInfoModifier = modifier;
1618}
1619
1654void QVulkanWindow::setEnabledFeaturesModifier(const EnabledFeaturesModifier &modifier)
1655{
1656 Q_D(QVulkanWindow);
1657 d->enabledFeaturesModifier = modifier;
1658}
1659
1693void QVulkanWindow::setEnabledFeaturesModifier(EnabledFeatures2Modifier modifier)
1694{
1695 Q_D(QVulkanWindow);
1696 d->enabledFeatures2Modifier = std::move(modifier);
1697}
1698
1706bool QVulkanWindow::isValid() const
1707{
1708 Q_D(const QVulkanWindow);
1709 return d->status == QVulkanWindowPrivate::StatusReady;
1710}
1711
1723QVulkanWindowRenderer *QVulkanWindow::createRenderer()
1724{
1725 return nullptr;
1726}
1727
1731QVulkanWindowRenderer::~QVulkanWindowRenderer()
1732{
1733}
1734
1751void QVulkanWindowRenderer::preInitResources()
1752{
1753}
1754
1770void QVulkanWindowRenderer::initResources()
1771{
1772}
1773
1792void QVulkanWindowRenderer::initSwapChainResources()
1793{
1794}
1795
1815void QVulkanWindowRenderer::releaseSwapChainResources()
1816{
1817}
1818
1831void QVulkanWindowRenderer::releaseResources()
1832{
1833}
1834
1876void QVulkanWindowRenderer::physicalDeviceLost()
1877{
1878}
1879
1895void QVulkanWindowRenderer::logicalDeviceLost()
1896{
1897}
1898
1899QSize QVulkanWindowPrivate::surfacePixelSize() const
1900{
1901 Q_Q(const QVulkanWindow);
1902 VkSurfaceCapabilitiesKHR surfaceCaps = {};
1903 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevs.at(physDevIndex), surface, &surfaceCaps);
1904 VkExtent2D bufferSize = surfaceCaps.currentExtent;
1905 if (bufferSize.width == uint32_t(-1)) {
1906 Q_ASSERT(bufferSize.height == uint32_t(-1));
1907 return q->size() * q->devicePixelRatio();
1908 }
1909 return QSize(int(bufferSize.width), int(bufferSize.height));
1910}
1911
1912void QVulkanWindowPrivate::beginFrame()
1913{
1914 if (!swapChain || framePending)
1915 return;
1916
1917 Q_Q(QVulkanWindow);
1918 if (swapChainImageSize != surfacePixelSize()) {
1919 recreateSwapChain();
1920 if (!swapChain)
1921 return;
1922 }
1923
1924 FrameResources &frame(frameRes[currentFrame]);
1925
1926 if (!frame.imageAcquired) {
1927 // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate
1928 // (note that we are using FIFO mode -> vsync)
1929 if (frame.fenceWaitable) {
1930 devFuncs->vkWaitForFences(dev, 1, &frame.fence, VK_TRUE, UINT64_MAX);
1931 devFuncs->vkResetFences(dev, 1, &frame.fence);
1932 frame.fenceWaitable = false;
1933 }
1934
1935 // move on to next swapchain image
1936 VkResult err = vkAcquireNextImageKHR(dev, swapChain, UINT64_MAX,
1937 frame.imageSem, frame.fence, &currentImage);
1938 if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
1939 frame.imageSemWaitable = true;
1940 frame.imageAcquired = true;
1941 frame.fenceWaitable = true;
1942 } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
1943 recreateSwapChain();
1944 q->requestUpdate();
1945 return;
1946 } else {
1947 if (!checkDeviceLost(err))
1948 qWarning("QVulkanWindow: Failed to acquire next swapchain image: %d", err);
1949 q->requestUpdate();
1950 return;
1951 }
1952 }
1953
1954 // make sure the previous draw for the same image has finished
1955 ImageResources &image(imageRes[currentImage]);
1956 if (image.cmdFenceWaitable) {
1957 devFuncs->vkWaitForFences(dev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
1958 devFuncs->vkResetFences(dev, 1, &image.cmdFence);
1959 image.cmdFenceWaitable = false;
1960 }
1961
1962 // build new draw command buffer
1963 if (image.cmdBuf) {
1964 devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &image.cmdBuf);
1965 image.cmdBuf = nullptr;
1966 }
1967
1968 VkCommandBufferAllocateInfo cmdBufInfo = {
1969 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
1970 VkResult err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &image.cmdBuf);
1971 if (err != VK_SUCCESS) {
1972 if (!checkDeviceLost(err))
1973 qWarning("QVulkanWindow: Failed to allocate frame command buffer: %d", err);
1974 return;
1975 }
1976
1977 VkCommandBufferBeginInfo cmdBufBeginInfo = {
1978 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, 0, nullptr };
1979 err = devFuncs->vkBeginCommandBuffer(image.cmdBuf, &cmdBufBeginInfo);
1980 if (err != VK_SUCCESS) {
1981 if (!checkDeviceLost(err))
1982 qWarning("QVulkanWindow: Failed to begin frame command buffer: %d", err);
1983 return;
1984 }
1985
1986 if (frameGrabbing)
1987 frameGrabTargetImage = QImage(swapChainImageSize, QImage::Format_RGBA8888); // the format is as documented
1988
1989 if (renderer) {
1990 framePending = true;
1991 renderer->startNextFrame();
1992 // done for now - endFrame() will get invoked when frameReady() is called back
1993 } else {
1994 VkClearColorValue clearColor = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1995 VkClearDepthStencilValue clearDS = { 1.0f, 0 };
1996 VkClearValue clearValues[3];
1997 memset(clearValues, 0, sizeof(clearValues));
1998 clearValues[0].color = clearValues[2].color = clearColor;
1999 clearValues[1].depthStencil = clearDS;
2000
2001 VkRenderPassBeginInfo rpBeginInfo;
2002 memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
2003 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
2004 rpBeginInfo.renderPass = defaultRenderPass;
2005 rpBeginInfo.framebuffer = image.fb;
2006 rpBeginInfo.renderArea.extent.width = swapChainImageSize.width();
2007 rpBeginInfo.renderArea.extent.height = swapChainImageSize.height();
2008 rpBeginInfo.clearValueCount = sampleCount > VK_SAMPLE_COUNT_1_BIT ? 3 : 2;
2009 rpBeginInfo.pClearValues = clearValues;
2010 devFuncs->vkCmdBeginRenderPass(image.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
2011 devFuncs->vkCmdEndRenderPass(image.cmdBuf);
2012
2013 endFrame();
2014 }
2015}
2016
2017void QVulkanWindowPrivate::endFrame()
2018{
2019 Q_Q(QVulkanWindow);
2020
2021 FrameResources &frame(frameRes[currentFrame]);
2022 ImageResources &image(imageRes[currentImage]);
2023
2024 if (gfxQueueFamilyIdx != presQueueFamilyIdx && !frameGrabbing) {
2025 // Add the swapchain image release to the command buffer that will be
2026 // submitted to the graphics queue.
2027 VkImageMemoryBarrier presTrans;
2028 memset(&presTrans, 0, sizeof(presTrans));
2029 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2030 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
2031 presTrans.oldLayout = presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2032 presTrans.srcQueueFamilyIndex = gfxQueueFamilyIdx;
2033 presTrans.dstQueueFamilyIndex = presQueueFamilyIdx;
2034 presTrans.image = image.image;
2035 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2036 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
2037 devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
2038 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2039 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
2040 0, 0, nullptr, 0, nullptr,
2041 1, &presTrans);
2042 }
2043
2044 // When grabbing a frame, add a readback at the end and skip presenting.
2045 if (frameGrabbing)
2046 addReadback();
2047
2048 VkResult err = devFuncs->vkEndCommandBuffer(image.cmdBuf);
2049 if (err != VK_SUCCESS) {
2050 if (!checkDeviceLost(err))
2051 qWarning("QVulkanWindow: Failed to end frame command buffer: %d", err);
2052 return;
2053 }
2054
2055 // submit draw calls
2056 VkSubmitInfo submitInfo;
2057 memset(&submitInfo, 0, sizeof(submitInfo));
2058 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2059 submitInfo.commandBufferCount = 1;
2060 submitInfo.pCommandBuffers = &image.cmdBuf;
2061 if (frame.imageSemWaitable) {
2062 submitInfo.waitSemaphoreCount = 1;
2063 submitInfo.pWaitSemaphores = &frame.imageSem;
2064 }
2065 if (!frameGrabbing) {
2066 submitInfo.signalSemaphoreCount = 1;
2067 submitInfo.pSignalSemaphores = &frame.drawSem;
2068 }
2069 VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
2070 submitInfo.pWaitDstStageMask = &psf;
2071
2072 Q_ASSERT(!image.cmdFenceWaitable);
2073
2074 err = devFuncs->vkQueueSubmit(gfxQueue, 1, &submitInfo, image.cmdFence);
2075 if (err == VK_SUCCESS) {
2076 frame.imageSemWaitable = false;
2077 image.cmdFenceWaitable = true;
2078 } else {
2079 if (!checkDeviceLost(err))
2080 qWarning("QVulkanWindow: Failed to submit to graphics queue: %d", err);
2081 return;
2082 }
2083
2084 // block and then bail out when grabbing
2085 if (frameGrabbing) {
2086 finishBlockingReadback();
2087 frameGrabbing = false;
2088 // Leave frame.imageAcquired set to true.
2089 // Do not change currentFrame.
2090 emit q->frameGrabbed(frameGrabTargetImage);
2091 return;
2092 }
2093
2094 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
2095 // Submit the swapchain image acquire to the present queue.
2096 submitInfo.pWaitSemaphores = &frame.drawSem;
2097 submitInfo.pSignalSemaphores = &frame.presTransSem;
2098 submitInfo.pCommandBuffers = &image.presTransCmdBuf; // must be USAGE_SIMULTANEOUS
2099 err = devFuncs->vkQueueSubmit(presQueue, 1, &submitInfo, VK_NULL_HANDLE);
2100 if (err != VK_SUCCESS) {
2101 if (!checkDeviceLost(err))
2102 qWarning("QVulkanWindow: Failed to submit to present queue: %d", err);
2103 return;
2104 }
2105 }
2106
2107 // queue present
2108 VkPresentInfoKHR presInfo;
2109 memset(&presInfo, 0, sizeof(presInfo));
2110 presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
2111 presInfo.swapchainCount = 1;
2112 presInfo.pSwapchains = &swapChain;
2113 presInfo.pImageIndices = &currentImage;
2114 presInfo.waitSemaphoreCount = 1;
2115 presInfo.pWaitSemaphores = gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem;
2116
2117 // Do platform-specific WM notification. F.ex. essential on Wayland in
2118 // order to circumvent driver frame callbacks
2119 inst->presentAboutToBeQueued(q);
2120
2121 err = vkQueuePresentKHR(presQueue, &presInfo);
2122 if (err != VK_SUCCESS) {
2123 if (err == VK_ERROR_OUT_OF_DATE_KHR) {
2124 recreateSwapChain();
2125 q->requestUpdate();
2126 return;
2127 } else if (err != VK_SUBOPTIMAL_KHR) {
2128 if (!checkDeviceLost(err))
2129 qWarning("QVulkanWindow: Failed to present: %d", err);
2130 return;
2131 }
2132 }
2133
2134 frame.imageAcquired = false;
2135
2136 inst->presentQueued(q);
2137
2138 currentFrame = (currentFrame + 1) % frameLag;
2139}
2140
2154void QVulkanWindow::frameReady()
2155{
2157 "QVulkanWindow", "frameReady() can only be called from the GUI (main) thread");
2158
2159 Q_D(QVulkanWindow);
2160
2161 if (!d->framePending) {
2162 qWarning("QVulkanWindow: frameReady() called without a corresponding startNextFrame()");
2163 return;
2164 }
2165
2166 d->framePending = false;
2167
2168 d->endFrame();
2169}
2170
2171bool QVulkanWindowPrivate::checkDeviceLost(VkResult err)
2172{
2173 if (err == VK_ERROR_DEVICE_LOST) {
2174 qWarning("QVulkanWindow: Device lost");
2175 if (renderer)
2176 renderer->logicalDeviceLost();
2177 qCDebug(lcGuiVk, "Releasing all resources due to device lost");
2178 releaseSwapChain();
2179 reset();
2180 qCDebug(lcGuiVk, "Restarting");
2181 ensureStarted();
2182 return true;
2183 }
2184 return false;
2185}
2186
2187void QVulkanWindowPrivate::addReadback()
2188{
2189 VkImageCreateInfo imageInfo;
2190 memset(&imageInfo, 0, sizeof(imageInfo));
2191 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2192 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2193 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2194 imageInfo.extent.width = frameGrabTargetImage.width();
2195 imageInfo.extent.height = frameGrabTargetImage.height();
2196 imageInfo.extent.depth = 1;
2197 imageInfo.mipLevels = 1;
2198 imageInfo.arrayLayers = 1;
2199 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2200 imageInfo.tiling = VK_IMAGE_TILING_LINEAR;
2201 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
2202 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2203
2204 VkResult err = devFuncs->vkCreateImage(dev, &imageInfo, nullptr, &frameGrabImage);
2205 if (err != VK_SUCCESS) {
2206 qWarning("QVulkanWindow: Failed to create image for readback: %d", err);
2207 return;
2208 }
2209
2210 VkMemoryRequirements memReq;
2211 devFuncs->vkGetImageMemoryRequirements(dev, frameGrabImage, &memReq);
2212
2213 VkMemoryAllocateInfo allocInfo = {
2214 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
2215 nullptr,
2216 memReq.size,
2217 hostVisibleMemIndex
2218 };
2219
2220 err = devFuncs->vkAllocateMemory(dev, &allocInfo, nullptr, &frameGrabImageMem);
2221 if (err != VK_SUCCESS) {
2222 qWarning("QVulkanWindow: Failed to allocate memory for readback image: %d", err);
2223 return;
2224 }
2225
2226 err = devFuncs->vkBindImageMemory(dev, frameGrabImage, frameGrabImageMem, 0);
2227 if (err != VK_SUCCESS) {
2228 qWarning("QVulkanWindow: Failed to bind readback image memory: %d", err);
2229 return;
2230 }
2231
2232 ImageResources &image(imageRes[currentImage]);
2233
2234 VkImageMemoryBarrier barrier;
2235 memset(&barrier, 0, sizeof(barrier));
2236 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2237 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2238 barrier.subresourceRange.levelCount = barrier.subresourceRange.layerCount = 1;
2239
2240 barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2241 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
2242 barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2243 barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2244 barrier.image = image.image;
2245
2246 devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
2247 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2248 VK_PIPELINE_STAGE_TRANSFER_BIT,
2249 0, 0, nullptr, 0, nullptr,
2250 1, &barrier);
2251
2252 barrier.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2253 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2254 barrier.srcAccessMask = 0;
2255 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2256 barrier.image = frameGrabImage;
2257
2258 devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
2259 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
2260 VK_PIPELINE_STAGE_TRANSFER_BIT,
2261 0, 0, nullptr, 0, nullptr,
2262 1, &barrier);
2263
2264 VkImageCopy copyInfo;
2265 memset(&copyInfo, 0, sizeof(copyInfo));
2266 copyInfo.srcSubresource.aspectMask = copyInfo.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2267 copyInfo.srcSubresource.layerCount = copyInfo.dstSubresource.layerCount = 1;
2268 copyInfo.extent.width = frameGrabTargetImage.width();
2269 copyInfo.extent.height = frameGrabTargetImage.height();
2270 copyInfo.extent.depth = 1;
2271
2272 devFuncs->vkCmdCopyImage(image.cmdBuf, image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
2273 frameGrabImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyInfo);
2274
2275 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2276 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
2277 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2278 barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
2279 barrier.image = frameGrabImage;
2280
2281 devFuncs->vkCmdPipelineBarrier(image.cmdBuf,
2282 VK_PIPELINE_STAGE_TRANSFER_BIT,
2283 VK_PIPELINE_STAGE_HOST_BIT,
2284 0, 0, nullptr, 0, nullptr,
2285 1, &barrier);
2286}
2287
2288void QVulkanWindowPrivate::finishBlockingReadback()
2289{
2290 ImageResources &image(imageRes[currentImage]);
2291
2292 // Block until the current frame is done. Normally this wait would only be
2293 // done in current + concurrentFrameCount().
2294 devFuncs->vkWaitForFences(dev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
2295 devFuncs->vkResetFences(dev, 1, &image.cmdFence);
2296 // will reuse the same image for the next "real" frame, do not wait then
2297 image.cmdFenceWaitable = false;
2298
2299 VkImageSubresource subres = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 };
2300 VkSubresourceLayout layout;
2301 devFuncs->vkGetImageSubresourceLayout(dev, frameGrabImage, &subres, &layout);
2302
2303 uchar *p;
2304 VkResult err = devFuncs->vkMapMemory(dev, frameGrabImageMem, layout.offset, layout.size, 0, reinterpret_cast<void **>(&p));
2305 if (err != VK_SUCCESS) {
2306 qWarning("QVulkanWindow: Failed to map readback image memory after transfer: %d", err);
2307 return;
2308 }
2309
2310 for (int y = 0; y < frameGrabTargetImage.height(); ++y) {
2311 memcpy(frameGrabTargetImage.scanLine(y), p, frameGrabTargetImage.width() * 4);
2312 p += layout.rowPitch;
2313 }
2314
2315 devFuncs->vkUnmapMemory(dev, frameGrabImageMem);
2316
2317 devFuncs->vkDestroyImage(dev, frameGrabImage, nullptr);
2318 frameGrabImage = VK_NULL_HANDLE;
2319 devFuncs->vkFreeMemory(dev, frameGrabImageMem, nullptr);
2320 frameGrabImageMem = VK_NULL_HANDLE;
2321}
2322
2330VkPhysicalDevice QVulkanWindow::physicalDevice() const
2331{
2332 Q_D(const QVulkanWindow);
2333 if (d->physDevIndex < d->physDevs.size())
2334 return d->physDevs[d->physDevIndex];
2335 qWarning("QVulkanWindow: Physical device not available");
2336 return VK_NULL_HANDLE;
2337}
2338
2346const VkPhysicalDeviceProperties *QVulkanWindow::physicalDeviceProperties() const
2347{
2348 Q_D(const QVulkanWindow);
2349 if (d->physDevIndex < d->physDevProps.size())
2350 return &d->physDevProps[d->physDevIndex];
2351 qWarning("QVulkanWindow: Physical device properties not available");
2352 return nullptr;
2353}
2354
2362VkDevice QVulkanWindow::device() const
2363{
2364 Q_D(const QVulkanWindow);
2365 return d->dev;
2366}
2367
2375VkQueue QVulkanWindow::graphicsQueue() const
2376{
2377 Q_D(const QVulkanWindow);
2378 return d->gfxQueue;
2379}
2380
2392uint32_t QVulkanWindow::graphicsQueueFamilyIndex() const
2393{
2394 Q_D(const QVulkanWindow);
2395 return d->gfxQueueFamilyIdx;
2396}
2397
2405VkCommandPool QVulkanWindow::graphicsCommandPool() const
2406{
2407 Q_D(const QVulkanWindow);
2408 return d->cmdPool;
2409}
2410
2421uint32_t QVulkanWindow::hostVisibleMemoryIndex() const
2422{
2423 Q_D(const QVulkanWindow);
2424 return d->hostVisibleMemIndex;
2425}
2426
2439uint32_t QVulkanWindow::deviceLocalMemoryIndex() const
2440{
2441 Q_D(const QVulkanWindow);
2442 return d->deviceLocalMemIndex;
2443}
2444
2463VkRenderPass QVulkanWindow::defaultRenderPass() const
2464{
2465 Q_D(const QVulkanWindow);
2466 return d->defaultRenderPass;
2467}
2468
2478VkFormat QVulkanWindow::colorFormat() const
2479{
2480 Q_D(const QVulkanWindow);
2481 return d->colorFormat;
2482}
2483
2491VkFormat QVulkanWindow::depthStencilFormat() const
2492{
2493 Q_D(const QVulkanWindow);
2494 return d->dsFormat;
2495}
2496
2520QSize QVulkanWindow::swapChainImageSize() const
2521{
2522 Q_D(const QVulkanWindow);
2523 return d->swapChainImageSize;
2524}
2525
2534VkCommandBuffer QVulkanWindow::currentCommandBuffer() const
2535{
2536 Q_D(const QVulkanWindow);
2537 if (!d->framePending) {
2538 qWarning("QVulkanWindow: Attempted to call currentCommandBuffer() without an active frame");
2539 return VK_NULL_HANDLE;
2540 }
2541 return d->imageRes[d->currentImage].cmdBuf;
2542}
2543
2563VkFramebuffer QVulkanWindow::currentFramebuffer() const
2564{
2565 Q_D(const QVulkanWindow);
2566 if (!d->framePending) {
2567 qWarning("QVulkanWindow: Attempted to call currentFramebuffer() without an active frame");
2568 return VK_NULL_HANDLE;
2569 }
2570 return d->imageRes[d->currentImage].fb;
2571}
2572
2594int QVulkanWindow::currentFrame() const
2595{
2596 Q_D(const QVulkanWindow);
2597 if (!d->framePending)
2598 qWarning("QVulkanWindow: Attempted to call currentFrame() without an active frame");
2599 return d->currentFrame;
2600}
2601
2618int QVulkanWindow::concurrentFrameCount() const
2619{
2620 Q_D(const QVulkanWindow);
2621 return d->frameLag;
2622}
2623
2635int QVulkanWindow::swapChainImageCount() const
2636{
2637 Q_D(const QVulkanWindow);
2638 return d->swapChainBufferCount;
2639}
2640
2647int QVulkanWindow::currentSwapChainImageIndex() const
2648{
2649 Q_D(const QVulkanWindow);
2650 if (!d->framePending)
2651 qWarning("QVulkanWindow: Attempted to call currentSwapChainImageIndex() without an active frame");
2652 return d->currentImage;
2653}
2654
2664VkImage QVulkanWindow::swapChainImage(int idx) const
2665{
2666 Q_D(const QVulkanWindow);
2667 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].image : VK_NULL_HANDLE;
2668}
2669
2679VkImageView QVulkanWindow::swapChainImageView(int idx) const
2680{
2681 Q_D(const QVulkanWindow);
2682 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].imageView : VK_NULL_HANDLE;
2683}
2684
2692VkImage QVulkanWindow::depthStencilImage() const
2693{
2694 Q_D(const QVulkanWindow);
2695 return d->dsImage;
2696}
2697
2705VkImageView QVulkanWindow::depthStencilImageView() const
2706{
2707 Q_D(const QVulkanWindow);
2708 return d->dsView;
2709}
2710
2719VkSampleCountFlagBits QVulkanWindow::sampleCountFlagBits() const
2720{
2721 Q_D(const QVulkanWindow);
2722 return d->sampleCount;
2723}
2724
2735VkImage QVulkanWindow::msaaColorImage(int idx) const
2736{
2737 Q_D(const QVulkanWindow);
2738 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].msaaImage : VK_NULL_HANDLE;
2739}
2740
2751VkImageView QVulkanWindow::msaaColorImageView(int idx) const
2752{
2753 Q_D(const QVulkanWindow);
2754 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].msaaImageView : VK_NULL_HANDLE;
2755}
2756
2765bool QVulkanWindow::supportsGrab() const
2766{
2767 Q_D(const QVulkanWindow);
2768 return d->swapChainSupportsReadBack;
2769}
2770
2802QImage QVulkanWindow::grab()
2803{
2804 Q_D(QVulkanWindow);
2805 if (!d->swapChain) {
2806 qWarning("QVulkanWindow: Attempted to call grab() without a swapchain");
2807 return QImage();
2808 }
2809 if (d->framePending) {
2810 qWarning("QVulkanWindow: Attempted to call grab() while a frame is still pending");
2811 return QImage();
2812 }
2813 if (!d->swapChainSupportsReadBack) {
2814 qWarning("QVulkanWindow: Attempted to call grab() with a swapchain that does not support usage as transfer source");
2815 return QImage();
2816 }
2817
2818 d->frameGrabbing = true;
2819 d->beginFrame();
2820
2821 if (d->colorFormat == VK_FORMAT_B8G8R8A8_UNORM)
2822 d->frameGrabTargetImage = std::move(d->frameGrabTargetImage).rgbSwapped();
2823
2824 return d->frameGrabTargetImage;
2825}
2826
2838QMatrix4x4 QVulkanWindow::clipCorrectionMatrix()
2839{
2840 Q_D(QVulkanWindow);
2841 if (d->m_clipCorrect.isIdentity()) {
2842 // NB the ctor takes row-major
2843 d->m_clipCorrect = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
2844 0.0f, -1.0f, 0.0f, 0.0f,
2845 0.0f, 0.0f, 0.5f, 0.5f,
2846 0.0f, 0.0f, 0.0f, 1.0f);
2847 }
2848 return d->m_clipCorrect;
2849}
2850
2852
2853#include "moc_qvulkanwindow.cpp"
\inmodule QtCore
\inmodule QtCore
Definition qbytearray.h:57
static QCoreApplication * instance() noexcept
Returns a pointer to the application's QCoreApplication (or QGuiApplication/QApplication) instance.
\inmodule QtCore
Definition qcoreevent.h:45
@ UpdateRequest
Definition qcoreevent.h:113
@ PlatformSurface
Definition qcoreevent.h:278
Type type() const
Returns the event type.
Definition qcoreevent.h:304
The QExposeEvent class contains event parameters for expose events. \inmodule QtGui.
Definition qevent.h:515
\inmodule QtGui
Definition qimage.h:37
@ Format_RGBA8888
Definition qimage.h:59
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
The QPlatformSurfaceEvent class is used to notify about native platform surface events....
Definition qevent.h:531
The QResizeEvent class contains event parameters for resize events.
Definition qevent.h:548
\inmodule QtCore
Definition qsize.h:25
@ VulkanSurface
Definition qsurface.h:35
static QThread * currentThread()
Definition qthread.cpp:1039
bool singleShot
whether the timer is a single-shot timer
Definition qtimer.h:22
\inmodule QtGui
The QVulkanFunctions class provides cross-platform access to the instance level core Vulkan 1....
The QVulkanInstance class represents a native Vulkan instance, enabling Vulkan rendering onto a QSurf...
\inmodule QtGui
\inmodule QtGui
\inmodule QtGui
Definition qwindow.h:63
virtual bool event(QEvent *) override
Override this to handle any event (ev) sent to the window.
Definition qwindow.cpp:2511
EGLint EGLint * formats
Combined button and popup list for selecting options.
Definition image.cpp:4
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
Flags
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLsizei const GLfloat * v
[13]
GLint GLenum GLsizei GLsizei GLsizei depth
GLboolean r
[2]
GLenum GLenum GLsizei count
GLfloat GLfloat f
GLuint color
[2]
GLbitfield flags
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLint GLsizei GLsizei GLenum format
GLint y
GLboolean reset
GLint void * img
Definition qopenglext.h:233
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
GLint GLfloat GLint stencil
GLenum GLenum colorFormat
GLsizeiptr const void GLenum usage
Definition qopenglext.h:543
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
Int aligned(Int v, Int byteAlign)
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
unsigned char uchar
Definition qtypes.h:32
static struct @397 q_vk_sampleCounts[]
QList< QImage > images
[6]
QVBoxLayout * layout
QFrame frame
[0]
QSvgRenderer * renderer
[0]