diff options
Diffstat (limited to 'src/gui/rhi/qrhivulkan.cpp')
-rw-r--r-- | src/gui/rhi/qrhivulkan.cpp | 1625 |
1 files changed, 1162 insertions, 463 deletions
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index bd6a168e31..f0b51146cc 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -1,10 +1,11 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qrhivulkan_p_p.h" -#include "qrhivulkanext_p.h" +#include "qrhivulkan_p.h" +#include <qpa/qplatformvulkaninstance.h> #define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 #define VMA_STATIC_VULKAN_FUNCTIONS 0 #define VMA_RECORDING_ENABLED 0 #define VMA_DEDICATED_ALLOCATION 0 @@ -22,6 +23,7 @@ QT_WARNING_POP #include <qmath.h> #include <QVulkanFunctions> #include <QtGui/qwindow.h> +#include <optional> QT_BEGIN_NAMESPACE @@ -57,10 +59,13 @@ QT_BEGIN_NAMESPACE /*! \class QRhiVulkanInitParams - \internal \inmodule QtGui + \since 6.6 \brief Vulkan specific initialization parameters. + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to the user to ensure this is available and initialized. This is typically done in main() similarly to the following: @@ -163,34 +168,121 @@ QT_BEGIN_NAMESPACE */ /*! + \variable QRhiVulkanInitParams::inst + + The QVulkanInstance that has already been successfully + \l{QVulkanInstance::create()}{created}, required. +*/ + +/*! + \variable QRhiVulkanInitParams::window + + Optional, but recommended when targeting a QWindow. +*/ + +/*! + \variable QRhiVulkanInitParams::deviceExtensions + + Optional, empty by default. The list of Vulkan device extensions to enable. + Unsupported extensions are ignored gracefully. +*/ + +/*! \class QRhiVulkanNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Collects device, queue, and other Vulkan objects that are used by the QRhi. \note Ownership of the Vulkan objects is never transferred. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! + \variable QRhiVulkanNativeHandles::physDev + + When different from \nullptr, specifies the Vulkan physical device to use. +*/ + +/*! + \variable QRhiVulkanNativeHandles::dev + + When wanting to import not just a physical device, but also use an already + existing VkDevice, set this and the graphics queue index and family index. +*/ + +/*! + \variable QRhiVulkanNativeHandles::gfxQueueFamilyIdx + + Graphics queue family index. +*/ + +/*! + \variable QRhiVulkanNativeHandles::gfxQueueIdx + + Graphics queue index. +*/ + +/*! + \variable QRhiVulkanNativeHandles::vmemAllocator + + Relevant only when importing an existing memory allocator object, + leave it set to \nullptr otherwise. +*/ + +/*! + \variable QRhiVulkanNativeHandles::gfxQueue + + Output only, not used by QRhi::create(), only set by the + QRhi::nativeHandles() accessor. The graphics VkQueue used by the QRhi. +*/ + +/*! + \variable QRhiVulkanNativeHandles::inst + + Output only, not used by QRhi::create(), only set by the + QRhi::nativeHandles() accessor. The QVulkanInstance used by the QRhi. +*/ + +/*! \class QRhiVulkanCommandBufferNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer. \note The Vulkan command buffer object is only guaranteed to be valid, and in recording state, while recording a frame. That is, between a \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} - - \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair. + \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ /*! + \variable QRhiVulkanCommandBufferNativeHandles::commandBuffer + + The VkCommandBuffer object. +*/ + +/*! \class QRhiVulkanRenderPassNativeHandles - \internal \inmodule QtGui + \since 6.6 \brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. */ +/*! + \variable QRhiVulkanRenderPassNativeHandles::renderPass + + The VkRenderPass object. +*/ + template <class Int> inline Int aligned(Int v, Int byteAlign) { @@ -199,84 +291,14 @@ inline Int aligned(Int v, Int byteAlign) static QVulkanInstance *globalVulkanInstance; -static void VKAPI_PTR wrap_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties) -{ - globalVulkanInstance->functions()->vkGetPhysicalDeviceProperties(physicalDevice, pProperties); -} - -static void VKAPI_PTR wrap_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties) -{ - globalVulkanInstance->functions()->vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); -} - -static VkResult VKAPI_PTR wrap_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetInstanceProcAddr(VkInstance, const char *pName) { - return globalVulkanInstance->deviceFunctions(device)->vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + return globalVulkanInstance->getInstanceProcAddr(pName); } -void VKAPI_PTR wrap_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator) +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetDeviceProcAddr(VkDevice device, const char *pName) { - globalVulkanInstance->deviceFunctions(device)->vkFreeMemory(device, memory, pAllocator); -} - -VkResult VKAPI_PTR wrap_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData) -{ - return globalVulkanInstance->deviceFunctions(device)->vkMapMemory(device, memory, offset, size, flags, ppData); -} - -void VKAPI_PTR wrap_vkUnmapMemory(VkDevice device, VkDeviceMemory memory) -{ - globalVulkanInstance->deviceFunctions(device)->vkUnmapMemory(device, memory); -} - -VkResult VKAPI_PTR wrap_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges) -{ - return globalVulkanInstance->deviceFunctions(device)->vkFlushMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges); -} - -VkResult VKAPI_PTR wrap_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges) -{ - return globalVulkanInstance->deviceFunctions(device)->vkInvalidateMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges); -} - -VkResult VKAPI_PTR wrap_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) -{ - return globalVulkanInstance->deviceFunctions(device)->vkBindBufferMemory(device, buffer, memory, memoryOffset); -} - -VkResult VKAPI_PTR wrap_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) -{ - return globalVulkanInstance->deviceFunctions(device)->vkBindImageMemory(device, image, memory, memoryOffset); -} - -void VKAPI_PTR wrap_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements) -{ - globalVulkanInstance->deviceFunctions(device)->vkGetBufferMemoryRequirements(device, buffer, pMemoryRequirements); -} - -void VKAPI_PTR wrap_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements) -{ - globalVulkanInstance->deviceFunctions(device)->vkGetImageMemoryRequirements(device, image, pMemoryRequirements); -} - -VkResult VKAPI_PTR wrap_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) -{ - return globalVulkanInstance->deviceFunctions(device)->vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer); -} - -void VKAPI_PTR wrap_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator) -{ - globalVulkanInstance->deviceFunctions(device)->vkDestroyBuffer(device, buffer, pAllocator); -} - -VkResult VKAPI_PTR wrap_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage) -{ - return globalVulkanInstance->deviceFunctions(device)->vkCreateImage(device, pCreateInfo, pAllocator, pImage); -} - -void VKAPI_PTR wrap_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator) -{ - globalVulkanInstance->deviceFunctions(device)->vkDestroyImage(device, image, pAllocator); + return globalVulkanInstance->functions()->vkGetDeviceProcAddr(device, pName); } static inline VmaAllocation toVmaAllocation(QVkAlloc a) @@ -289,6 +311,13 @@ static inline VmaAllocator toVmaAllocator(QVkAllocator a) return reinterpret_cast<VmaAllocator>(a); } +/*! + \return the list of instance extensions that are expected to be enabled on + the QVulkanInstance that is used for the Vulkan-based QRhi. + + The returned list can be safely passed to QVulkanInstance::setExtensions() + as-is, because unsupported extensions are filtered out automatically. + */ QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions() { return { @@ -296,11 +325,18 @@ QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions() }; } +/*! + \return the list of device extensions that are expected to be enabled on the + \c VkDevice when creating a Vulkan-based QRhi with an externally created + \c VkDevice object. + */ QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice() { return { QByteArrayLiteral("VK_KHR_swapchain"), - QByteArrayLiteral("VK_EXT_vertex_attribute_divisor") + QByteArrayLiteral("VK_EXT_vertex_attribute_divisor"), + QByteArrayLiteral("VK_KHR_create_renderpass2"), + QByteArrayLiteral("VK_KHR_depth_stencil_resolve") }; } @@ -394,6 +430,8 @@ bool QRhiVulkan::create(QRhi::Flags flags) for (const char *ext : inst->extensions()) qCDebug(QRHI_LOG_INFO, " %s", ext); } + + caps = {}; caps.debugUtils = inst->extensions().contains(QByteArrayLiteral("VK_EXT_debug_utils")); QList<VkQueueFamilyProperties> queueFamilyProps; @@ -495,32 +533,93 @@ bool QRhiVulkan::create(QRhi::Flags flags) driverInfoStruct.vendorId = physDevProperties.vendorID; driverInfoStruct.deviceType = toRhiDeviceType(physDevProperties.deviceType); - f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures); + bool featuresQueried = false; +#ifdef VK_VERSION_1_1 + VkPhysicalDeviceFeatures2 physDevFeaturesChainable = {}; + physDevFeaturesChainable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; +#endif + + // Vulkan >=1.2 headers at build time, >=1.2 implementation at run time +#ifdef VK_VERSION_1_2 + if (!featuresQueried) { + // Vulkan11Features, Vulkan12Features, etc. are only in Vulkan 1.2 and newer. + if (caps.apiVersion >= QVersionNumber(1, 2)) { + physDevFeatures11IfApi12OrNewer = {}; + physDevFeatures11IfApi12OrNewer.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + physDevFeatures12 = {}; + physDevFeatures12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; +#ifdef VK_VERSION_1_3 + physDevFeatures13 = {}; + physDevFeatures13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; +#endif + physDevFeaturesChainable.pNext = &physDevFeatures11IfApi12OrNewer; + physDevFeatures11IfApi12OrNewer.pNext = &physDevFeatures12; +#ifdef VK_VERSION_1_3 + if (caps.apiVersion >= QVersionNumber(1, 3)) + physDevFeatures12.pNext = &physDevFeatures13; +#endif + f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable); + memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures)); + featuresQueried = true; + } + } +#endif // VK_VERSION_1_2 + + // Vulkan >=1.1 headers at build time, 1.1 implementation at run time +#ifdef VK_VERSION_1_1 + if (!featuresQueried) { + // Vulkan versioning nightmares: if the runtime API version is 1.1, + // there is no Vulkan11Features (introduced in 1.2+, the headers might + // have the types and structs, but the Vulkan implementation version at + // run time is what matters). But there are individual feature structs. + // For multiview, it is important to get this right since at the time of + // writing Quest 3 Android is a Vulkan 1.1 implementation at run time on + // the headset. + if (caps.apiVersion == QVersionNumber(1, 1)) { + multiviewFeaturesIfApi11 = {}; + multiviewFeaturesIfApi11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + physDevFeaturesChainable.pNext = &multiviewFeaturesIfApi11; + f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable); + memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures)); + featuresQueried = true; + } + } +#endif + + if (!featuresQueried) { + // If the API version at run time is 1.0 (or we are building with + // ancient 1.0 headers), then do the Vulkan 1.0 query. + f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures); + featuresQueried = true; + } // Choose queue and create device, unless the device was specified in importParams. if (!importedDevice) { // We only support combined graphics+present queues. When it comes to // compute, only combined graphics+compute queue is used, compute gets // disabled otherwise. - gfxQueueFamilyIdx = -1; - int computelessGfxQueueCandidateIdx = -1; + std::optional<uint32_t> gfxQueueFamilyIdxOpt; + std::optional<uint32_t> computelessGfxQueueCandidateIdxOpt; queryQueueFamilyProps(); - for (int i = 0; i < queueFamilyProps.count(); ++i) { - qCDebug(QRHI_LOG_INFO, "queue family %d: flags=0x%x count=%d", + const uint32_t queueFamilyCount = uint32_t(queueFamilyProps.size()); + for (uint32_t i = 0; i < queueFamilyCount; ++i) { + qCDebug(QRHI_LOG_INFO, "queue family %u: flags=0x%x count=%u", i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount); - if (gfxQueueFamilyIdx == -1 + if (!gfxQueueFamilyIdxOpt.has_value() && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) - && (!maybeWindow || inst->supportsPresent(physDev, uint32_t(i), maybeWindow))) + && (!maybeWindow || inst->supportsPresent(physDev, i, maybeWindow))) { if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) - gfxQueueFamilyIdx = i; - else if (computelessGfxQueueCandidateIdx == -1) - computelessGfxQueueCandidateIdx = i; + gfxQueueFamilyIdxOpt = i; + else if (!computelessGfxQueueCandidateIdxOpt.has_value()) + computelessGfxQueueCandidateIdxOpt = i; } } - if (gfxQueueFamilyIdx == -1) { - if (computelessGfxQueueCandidateIdx != -1) { - gfxQueueFamilyIdx = computelessGfxQueueCandidateIdx; + if (gfxQueueFamilyIdxOpt.has_value()) { + gfxQueueFamilyIdx = gfxQueueFamilyIdxOpt.value(); + } else { + if (computelessGfxQueueCandidateIdxOpt.has_value()) { + gfxQueueFamilyIdx = computelessGfxQueueCandidateIdxOpt.value(); } else { qWarning("No graphics (or no graphics+present) queue family found"); return false; @@ -530,7 +629,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) VkDeviceQueueCreateInfo queueInfo = {}; const float prio[] = { 0 }; queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueInfo.queueFamilyIndex = uint32_t(gfxQueueFamilyIdx); + queueInfo.queueFamilyIndex = gfxQueueFamilyIdx; queueInfo.queueCount = 1; queueInfo.pQueuePriorities = prio; @@ -544,38 +643,69 @@ bool QRhiVulkan::create(QRhi::Flags flags) if (devExtCount) { QList<VkExtensionProperties> extProps(devExtCount); f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data()); - for (const VkExtensionProperties &p : qAsConst(extProps)) + for (const VkExtensionProperties &p : std::as_const(extProps)) devExts.append({ p.extensionName, p.specVersion }); } - qCDebug(QRHI_LOG_INFO, "%d device extensions available", int(devExts.count())); + qCDebug(QRHI_LOG_INFO, "%d device extensions available", int(devExts.size())); QList<const char *> requestedDevExts; requestedDevExts.append("VK_KHR_swapchain"); - caps.vertexAttribDivisor = false; + const bool hasPhysDevProp2 = inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2")); + + if (devExts.contains(QByteArrayLiteral("VK_KHR_portability_subset"))) { + if (hasPhysDevProp2) { + requestedDevExts.append("VK_KHR_portability_subset"); + } else { + qWarning("VK_KHR_portability_subset should be enabled on the device " + "but the instance does not have VK_KHR_get_physical_device_properties2 enabled. " + "Expect problems."); + } + } + +#ifdef VK_EXT_vertex_attribute_divisor if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) { - if (inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2"))) { + if (hasPhysDevProp2) { requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME); caps.vertexAttribDivisor = true; } } +#endif + +#ifdef VK_KHR_create_renderpass2 + if (devExts.contains(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) { + requestedDevExts.append(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); + caps.renderPass2KHR = true; + } +#endif + +#ifdef VK_KHR_depth_stencil_resolve + if (devExts.contains(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME)) { + requestedDevExts.append(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); + caps.depthStencilResolveKHR = true; + } +#endif for (const QByteArray &ext : requestedDeviceExtensions) { - if (!ext.isEmpty()) { - if (devExts.contains(ext)) + if (!ext.isEmpty() && !requestedDevExts.contains(ext)) { + if (devExts.contains(ext)) { requestedDevExts.append(ext.constData()); - else - qWarning("Device extension %s is not supported", ext.constData()); + } else { + qWarning("Device extension %s requested in QRhiVulkanInitParams is not supported", + ext.constData()); + } } } QByteArrayList envExtList = qgetenv("QT_VULKAN_DEVICE_EXTENSIONS").split(';'); for (const QByteArray &ext : envExtList) { if (!ext.isEmpty() && !requestedDevExts.contains(ext)) { - if (devExts.contains(ext)) + if (devExts.contains(ext)) { requestedDevExts.append(ext.constData()); - else - qWarning("Device extension %s is not supported", ext.constData()); + } else { + qWarning("Device extension %s requested in QT_VULKAN_DEVICE_EXTENSIONS is not supported", + ext.constData()); + } } } @@ -589,9 +719,9 @@ bool QRhiVulkan::create(QRhi::Flags flags) devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; devInfo.queueCreateInfoCount = 1; devInfo.pQueueCreateInfos = &queueInfo; - devInfo.enabledLayerCount = uint32_t(devLayers.count()); + devInfo.enabledLayerCount = uint32_t(devLayers.size()); devInfo.ppEnabledLayerNames = devLayers.constData(); - devInfo.enabledExtensionCount = uint32_t(requestedDevExts.count()); + devInfo.enabledExtensionCount = uint32_t(requestedDevExts.size()); devInfo.ppEnabledExtensionNames = requestedDevExts.constData(); // Enable all features that are reported as supported, except @@ -607,42 +737,27 @@ bool QRhiVulkan::create(QRhi::Flags flags) // tessellationShader, geometryShader // textureCompressionETC2, textureCompressionASTC_LDR, textureCompressionBC -#ifdef VK_VERSION_1_2 // Vulkan11Features is only in Vulkan 1.2 - VkPhysicalDeviceFeatures2 physDevFeatures2 = {}; - physDevFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - - VkPhysicalDeviceVulkan11Features features11 = {}; - features11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; - VkPhysicalDeviceVulkan12Features features12 = {}; - features12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; -#ifdef VK_VERSION_1_3 - VkPhysicalDeviceVulkan13Features features13 = {}; - features13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; +#ifdef VK_VERSION_1_1 + physDevFeaturesChainable.features.robustBufferAccess = VK_FALSE; #endif - - if (caps.apiVersion >= QVersionNumber(1, 2)) { - physDevFeatures2.pNext = &features11; - features11.pNext = &features12; #ifdef VK_VERSION_1_3 - if (caps.apiVersion >= QVersionNumber(1, 3)) - features12.pNext = &features13; + physDevFeatures13.robustImageAccess = VK_FALSE; #endif - f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeatures2); - physDevFeatures2.features.robustBufferAccess = VK_FALSE; -#ifdef VK_VERSION_1_3 - features13.robustImageAccess = VK_FALSE; +#ifdef VK_VERSION_1_1 + if (caps.apiVersion >= QVersionNumber(1, 1)) { + // For a >=1.2 implementation at run time, this will enable all + // (1.0-1.3) features reported as supported, except the ones we turn + // off explicitly above. For a 1.1 implementation at run time, this + // only enables the 1.0 and multiview features reported as + // supported. We will not be bothering with the Vulkan 1.1 + // individual feature struct nonsense. + devInfo.pNext = &physDevFeaturesChainable; + } else #endif - - devInfo.pNext = &physDevFeatures2; - } -#endif // VK_VERSION_1_2 - - VkPhysicalDeviceFeatures features; - if (!devInfo.pNext) { - memcpy(&features, &physDevFeatures, sizeof(features)); - features.robustBufferAccess = VK_FALSE; - devInfo.pEnabledFeatures = &features; + { + physDevFeatures.robustBufferAccess = VK_FALSE; + devInfo.pEnabledFeatures = &physDevFeatures; } VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev); @@ -652,6 +767,13 @@ bool QRhiVulkan::create(QRhi::Flags flags) } } else { qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); + + // Here we have no way to tell if the extensions got enabled or not. + // Pretend it's all there and supported. If getProcAddress fails, we'll + // handle that gracefully. + caps.vertexAttribDivisor = true; + caps.renderPass2KHR = true; + caps.depthStencilResolveKHR = true; } vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>( @@ -660,19 +782,12 @@ bool QRhiVulkan::create(QRhi::Flags flags) inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR")); vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>( inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfacePresentModesKHR")); - if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR - || !vkGetPhysicalDeviceSurfaceFormatsKHR - || !vkGetPhysicalDeviceSurfacePresentModesKHR) - { - qWarning("Physical device surface queries not available"); - return false; - } df = inst->deviceFunctions(dev); VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = uint32_t(gfxQueueFamilyIdx); + poolInfo.queueFamilyIndex = gfxQueueFamilyIdx; for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool[i]); if (err != VK_SUCCESS) { @@ -681,13 +796,10 @@ bool QRhiVulkan::create(QRhi::Flags flags) } } - if (gfxQueueFamilyIdx < 0) { - // this is when importParams is faulty and did not specify the queue family index - qWarning("No queue family index provided"); - return false; - } + qCDebug(QRHI_LOG_INFO, "Using queue family index %u and queue index %u", + gfxQueueFamilyIdx, gfxQueueIdx); - df->vkGetDeviceQueue(dev, uint32_t(gfxQueueFamilyIdx), gfxQueueIdx, &gfxQueue); + df->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, gfxQueueIdx, &gfxQueue); if (queueFamilyProps.isEmpty()) queryQueueFamilyProps(); @@ -709,24 +821,32 @@ bool QRhiVulkan::create(QRhi::Flags flags) caps.nonFillPolygonMode = physDevFeatures.fillModeNonSolid; +#ifdef VK_VERSION_1_2 + if (caps.apiVersion >= QVersionNumber(1, 2)) + caps.multiView = physDevFeatures11IfApi12OrNewer.multiview; +#endif + +#ifdef VK_VERSION_1_1 + if (caps.apiVersion == QVersionNumber(1, 1)) + caps.multiView = multiviewFeaturesIfApi11.multiview; +#endif + + // With Vulkan 1.2 renderpass2 and depth_stencil_resolve are core, but we + // have to support the case of 1.1 + extensions, in particular for the Quest + // 3 (Android, Vulkan 1.1 at the time of writing). Therefore, always rely on + // the KHR extension for now. +#ifdef VK_KHR_create_renderpass2 + if (caps.renderPass2KHR) { + vkCreateRenderPass2KHR = reinterpret_cast<PFN_vkCreateRenderPass2KHR>(f->vkGetDeviceProcAddr(dev, "vkCreateRenderPass2KHR")); + if (!vkCreateRenderPass2KHR) // handle it gracefully, the caps flag may be incorrect when using an imported VkDevice + caps.renderPass2KHR = false; + } +#endif + if (!importedAllocator) { - VmaVulkanFunctions afuncs; - afuncs.vkGetPhysicalDeviceProperties = wrap_vkGetPhysicalDeviceProperties; - afuncs.vkGetPhysicalDeviceMemoryProperties = wrap_vkGetPhysicalDeviceMemoryProperties; - afuncs.vkAllocateMemory = wrap_vkAllocateMemory; - afuncs.vkFreeMemory = wrap_vkFreeMemory; - afuncs.vkMapMemory = wrap_vkMapMemory; - afuncs.vkUnmapMemory = wrap_vkUnmapMemory; - afuncs.vkFlushMappedMemoryRanges = wrap_vkFlushMappedMemoryRanges; - afuncs.vkInvalidateMappedMemoryRanges = wrap_vkInvalidateMappedMemoryRanges; - afuncs.vkBindBufferMemory = wrap_vkBindBufferMemory; - afuncs.vkBindImageMemory = wrap_vkBindImageMemory; - afuncs.vkGetBufferMemoryRequirements = wrap_vkGetBufferMemoryRequirements; - afuncs.vkGetImageMemoryRequirements = wrap_vkGetImageMemoryRequirements; - afuncs.vkCreateBuffer = wrap_vkCreateBuffer; - afuncs.vkDestroyBuffer = wrap_vkDestroyBuffer; - afuncs.vkCreateImage = wrap_vkCreateImage; - afuncs.vkDestroyImage = wrap_vkDestroyImage; + VmaVulkanFunctions funcs = {}; + funcs.vkGetInstanceProcAddr = wrap_vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = wrap_vkGetDeviceProcAddr; VmaAllocatorCreateInfo allocatorInfo = {}; // A QRhi is supposed to be used from one single thread only. Disable @@ -734,7 +854,11 @@ bool QRhiVulkan::create(QRhi::Flags flags) allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; allocatorInfo.physicalDevice = physDev; allocatorInfo.device = dev; - allocatorInfo.pVulkanFunctions = &afuncs; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.instance = inst->vkInstance(); + allocatorInfo.vulkanApiVersion = VK_MAKE_VERSION(caps.apiVersion.majorVersion(), + caps.apiVersion.minorVersion(), + caps.apiVersion.microVersion()); VmaAllocator vmaallocator; VkResult err = vmaCreateAllocator(&allocatorInfo, &vmaallocator); if (err != VK_SUCCESS) { @@ -782,6 +906,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) nativeHandlesStruct.gfxQueueIdx = gfxQueueIdx; nativeHandlesStruct.gfxQueue = gfxQueue; nativeHandlesStruct.vmemAllocator = allocator; + nativeHandlesStruct.inst = inst; return true; } @@ -872,7 +997,7 @@ bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, V return r; }; - int lastPoolIdx = descriptorPools.count() - 1; + int lastPoolIdx = descriptorPools.size() - 1; for (int i = lastPoolIdx; i >= 0; --i) { if (descriptorPools[i].refCount == 0) { df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0); @@ -892,7 +1017,7 @@ bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, V VkResult poolErr = createDescriptorPool(&newPool); if (poolErr == VK_SUCCESS) { descriptorPools.append(newPool); - lastPoolIdx = descriptorPools.count() - 1; + lastPoolIdx = descriptorPools.size() - 1; VkResult err = tryAllocate(lastPoolIdx); if (err != VK_SUCCESS) { qWarning("Failed to allocate descriptor set from new pool too, giving up: %d", err); @@ -1000,8 +1125,7 @@ static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK; default: - Q_UNREACHABLE(); - return VK_FORMAT_R8G8B8A8_UNORM; + Q_UNREACHABLE_RETURN(VK_FORMAT_R8G8B8A8_UNORM); } } @@ -1210,18 +1334,18 @@ static void fillRenderPassCreateInfo(VkRenderPassCreateInfo *rpInfo, { memset(subpassDesc, 0, sizeof(VkSubpassDescription)); subpassDesc->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpassDesc->colorAttachmentCount = uint32_t(rpD->colorRefs.count()); + subpassDesc->colorAttachmentCount = uint32_t(rpD->colorRefs.size()); subpassDesc->pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr; subpassDesc->pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr; subpassDesc->pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr; memset(rpInfo, 0, sizeof(VkRenderPassCreateInfo)); rpInfo->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - rpInfo->attachmentCount = uint32_t(rpD->attDescs.count()); + rpInfo->attachmentCount = uint32_t(rpD->attDescs.size()); rpInfo->pAttachments = rpD->attDescs.constData(); rpInfo->subpassCount = 1; rpInfo->pSubpasses = subpassDesc; - rpInfo->dependencyCount = uint32_t(rpD->subpassDeps.count()); + rpInfo->dependencyCount = uint32_t(rpD->subpassDeps.size()); rpInfo->pDependencies = !rpD->subpassDeps.isEmpty() ? rpD->subpassDeps.constData() : nullptr; } @@ -1243,6 +1367,8 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); rpD->hasDepthStencil = hasDepthStencil; + rpD->hasDepthStencilResolve = false; + rpD->multiViewCount = 0; if (hasDepthStencil) { // clear on load + no store + lazy alloc + transient image should play @@ -1312,28 +1438,190 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD return true; } +struct MultiViewRenderPassSetupHelper +{ + bool prepare(VkRenderPassCreateInfo *rpInfo, int multiViewCount, bool multiViewCap) + { + if (multiViewCount < 2) + return true; + if (!multiViewCap) { + qWarning("Cannot create multiview render pass without support for the Vulkan 1.1 multiview feature"); + return false; + } +#ifdef VK_VERSION_1_1 + uint32_t allViewsMask = 0; + for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i) + allViewsMask |= (1 << i); + multiViewMask = allViewsMask; + multiViewCorrelationMask = allViewsMask; + multiViewInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO; + multiViewInfo.subpassCount = 1; + multiViewInfo.pViewMasks = &multiViewMask; + multiViewInfo.correlationMaskCount = 1; + multiViewInfo.pCorrelationMasks = &multiViewCorrelationMask; + rpInfo->pNext = &multiViewInfo; +#endif + return true; + } + +#ifdef VK_VERSION_1_1 + VkRenderPassMultiviewCreateInfo multiViewInfo = {}; + uint32_t multiViewMask = 0; + uint32_t multiViewCorrelationMask = 0; +#endif +}; + +#ifdef VK_KHR_create_renderpass2 +// Effectively converts a VkRenderPassCreateInfo into a VkRenderPassCreateInfo2, +// adding depth-stencil resolve support. Assumes a single subpass and no subpass +// dependencies. +struct RenderPass2SetupHelper +{ + bool prepare(VkRenderPassCreateInfo2 *rpInfo2, const VkRenderPassCreateInfo *rpInfo, const QVkRenderPassDescriptor *rpD, int multiViewCount) { + *rpInfo2 = {}; + + viewMask = 0; + if (multiViewCount >= 2) { + for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i) + viewMask |= (1 << i); + } + + attDescs2.resize(rpInfo->attachmentCount); + for (qsizetype i = 0; i < attDescs2.count(); ++i) { + VkAttachmentDescription2KHR &att2(attDescs2[i]); + const VkAttachmentDescription &att(rpInfo->pAttachments[i]); + att2 = {}; + att2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; + att2.flags = att.flags; + att2.format = att.format; + att2.samples = att.samples; + att2.loadOp = att.loadOp; + att2.storeOp = att.storeOp; + att2.stencilLoadOp = att.stencilLoadOp; + att2.stencilStoreOp = att.stencilStoreOp; + att2.initialLayout = att.initialLayout; + att2.finalLayout = att.finalLayout; + } + + attRefs2.clear(); + subpass2 = {}; + subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR; + const VkSubpassDescription &subpassDesc(rpInfo->pSubpasses[0]); + subpass2.flags = subpassDesc.flags; + subpass2.pipelineBindPoint = subpassDesc.pipelineBindPoint; + if (multiViewCount >= 2) + subpass2.viewMask = viewMask; + + // color attachment refs + qsizetype startIndex = attRefs2.count(); + for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) { + attRefs2.append({}); + VkAttachmentReference2KHR &attref2(attRefs2.last()); + const VkAttachmentReference &attref(subpassDesc.pColorAttachments[j]); + attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; + attref2.attachment = attref.attachment; + attref2.layout = attref.layout; + attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + subpass2.colorAttachmentCount = subpassDesc.colorAttachmentCount; + subpass2.pColorAttachments = attRefs2.constData() + startIndex; + + // color resolve refs + if (subpassDesc.pResolveAttachments) { + startIndex = attRefs2.count(); + for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) { + attRefs2.append({}); + VkAttachmentReference2KHR &attref2(attRefs2.last()); + const VkAttachmentReference &attref(subpassDesc.pResolveAttachments[j]); + attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; + attref2.attachment = attref.attachment; + attref2.layout = attref.layout; + attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + subpass2.pResolveAttachments = attRefs2.constData() + startIndex; + } + + // depth-stencil ref + if (subpassDesc.pDepthStencilAttachment) { + startIndex = attRefs2.count(); + attRefs2.append({}); + VkAttachmentReference2KHR &attref2(attRefs2.last()); + const VkAttachmentReference &attref(*subpassDesc.pDepthStencilAttachment); + attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; + attref2.attachment = attref.attachment; + attref2.layout = attref.layout; + attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + subpass2.pDepthStencilAttachment = attRefs2.constData() + startIndex; + } + + // depth-stencil resolve ref +#ifdef VK_KHR_depth_stencil_resolve + dsResolveDesc = {}; + if (rpD->hasDepthStencilResolve) { + startIndex = attRefs2.count(); + attRefs2.append({}); + VkAttachmentReference2KHR &attref2(attRefs2.last()); + attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; + attref2.attachment = rpD->dsResolveRef.attachment; + attref2.layout = rpD->dsResolveRef.layout; + attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + dsResolveDesc.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR; + dsResolveDesc.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; + dsResolveDesc.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; + dsResolveDesc.pDepthStencilResolveAttachment = attRefs2.constData() + startIndex; + subpass2.pNext = &dsResolveDesc; + } +#endif + + rpInfo2->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR; + rpInfo2->pNext = nullptr; // the 1.1 VkRenderPassMultiviewCreateInfo is part of the '2' structs + rpInfo2->flags = rpInfo->flags; + rpInfo2->attachmentCount = rpInfo->attachmentCount; + rpInfo2->pAttachments = attDescs2.constData(); + rpInfo2->subpassCount = 1; + rpInfo2->pSubpasses = &subpass2; + if (multiViewCount >= 2) { + rpInfo2->correlatedViewMaskCount = 1; + rpInfo2->pCorrelatedViewMasks = &viewMask; + } + return true; + } + + QVarLengthArray<VkAttachmentDescription2KHR, 8> attDescs2; + QVarLengthArray<VkAttachmentReference2KHR, 8> attRefs2; + VkSubpassDescription2KHR subpass2; +#ifdef VK_KHR_depth_stencil_resolve + VkSubpassDescriptionDepthStencilResolveKHR dsResolveDesc; +#endif + uint32_t viewMask; +}; +#endif // VK_KHR_create_renderpass2 + bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, const QRhiColorAttachment *firstColorAttachment, const QRhiColorAttachment *lastColorAttachment, bool preserveColor, bool preserveDs, + bool storeDs, QRhiRenderBuffer *depthStencilBuffer, - QRhiTexture *depthTexture) + QRhiTexture *depthTexture, + QRhiTexture *depthResolveTexture) { - // attachment list layout is color (0-8), ds (0-1), resolve (0-8) + // attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1) + int multiViewCount = 0; for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) { QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); Q_ASSERT(texD || rbD); - const VkFormat vkformat = texD ? texD->vkformat : rbD->vkformat; + const VkFormat vkformat = texD ? texD->viewFormat : rbD->vkformat; const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples; VkAttachmentDescription attDesc = {}; attDesc.format = vkformat; attDesc.samples = samples; attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR; - attDesc.storeOp = it->resolveTexture() ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; + attDesc.storeOp = (it->resolveTexture() && !preserveColor) ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT @@ -1341,18 +1629,29 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; rpD->attDescs.append(attDesc); - const VkAttachmentReference ref = { uint32_t(rpD->attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + const VkAttachmentReference ref = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; rpD->colorRefs.append(ref); + + if (it->multiViewCount() >= 2) { + if (multiViewCount > 0 && multiViewCount != it->multiViewCount()) + qWarning("Inconsistent multiViewCount in color attachment set"); + else + multiViewCount = it->multiViewCount(); + } else if (multiViewCount > 0) { + qWarning("Mixing non-multiview color attachments within a multiview render pass"); + } } + Q_ASSERT(multiViewCount == 0 || multiViewCount >= 2); + rpD->multiViewCount = uint32_t(multiViewCount); rpD->hasDepthStencil = depthStencilBuffer || depthTexture; if (rpD->hasDepthStencil) { - const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat + const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->viewFormat : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat; const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples; const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR; - const VkAttachmentStoreOp storeOp = depthTexture ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; + const VkAttachmentStoreOp storeOp = storeDs ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; VkAttachmentDescription attDesc = {}; attDesc.format = dsFormat; attDesc.samples = samples; @@ -1360,11 +1659,11 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, attDesc.storeOp = storeOp; attDesc.stencilLoadOp = loadOp; attDesc.stencilStoreOp = storeOp; - attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.initialLayout = preserveDs ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; rpD->attDescs.append(attDesc); } - rpD->dsRef = { uint32_t(rpD->attDescs.count() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) { if (it->resolveTexture()) { @@ -1385,7 +1684,7 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, } VkAttachmentDescription attDesc = {}; - attDesc.format = dstFormat; + attDesc.format = rtexD->viewFormat; attDesc.samples = VK_SAMPLE_COUNT_1_BIT; attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -1395,14 +1694,39 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; rpD->attDescs.append(attDesc); - const VkAttachmentReference ref = { uint32_t(rpD->attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + const VkAttachmentReference ref = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; rpD->resolveRefs.append(ref); } else { const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; rpD->resolveRefs.append(ref); } } - Q_ASSERT(rpD->colorRefs.count() == rpD->resolveRefs.count()); + Q_ASSERT(rpD->colorRefs.size() == rpD->resolveRefs.size()); + + rpD->hasDepthStencilResolve = rpD->hasDepthStencil && depthResolveTexture; + if (rpD->hasDepthStencilResolve) { + QVkTexture *rtexD = QRHI_RES(QVkTexture, depthResolveTexture); + if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT) + qWarning("Resolving into a multisample depth texture is not supported"); + + QVkTexture *texD = QRHI_RES(QVkTexture, depthResolveTexture); + if (texD->vkformat != rtexD->vkformat) { + qWarning("Multisample resolve between different depth-stencil formats (%d and %d) is not supported.", + int(texD->vkformat), int(rtexD->vkformat)); + } + + VkAttachmentDescription attDesc = {}; + attDesc.format = rtexD->viewFormat; + attDesc.samples = VK_SAMPLE_COUNT_1_BIT; + attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored + attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attDesc.stencilLoadOp = attDesc.loadOp; + attDesc.stencilStoreOp = attDesc.storeOp; + attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + rpD->attDescs.append(attDesc); + } + rpD->dsResolveRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; // rpD->subpassDeps stays empty: don't yet know the correct initial/final // access and stage stuff for the implicit deps at this point, so leave it @@ -1413,10 +1737,35 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, VkSubpassDescription subpassDesc; fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD); - VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); - if (err != VK_SUCCESS) { - qWarning("Failed to create renderpass: %d", err); + MultiViewRenderPassSetupHelper multiViewHelper; + if (!multiViewHelper.prepare(&rpInfo, multiViewCount, caps.multiView)) return false; + +#ifdef VK_KHR_create_renderpass2 + if (rpD->hasDepthStencilResolve && caps.renderPass2KHR) { + // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1. + VkRenderPassCreateInfo2KHR rpInfo2; + RenderPass2SetupHelper rp2Helper; + if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount)) + return false; + + VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp); + if (err != VK_SUCCESS) { + qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err); + return false; + } + } else +#endif + { + if (rpD->hasDepthStencilResolve) { + qWarning("Resolving multisample depth-stencil buffers is not supported without " + "VK_KHR_depth_stencil_resolve and VK_KHR_create_renderpass2"); + } + VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); + if (err != VK_SUCCESS) { + qWarning("Failed to create renderpass: %d", err); + return false; + } } return true; @@ -1503,9 +1852,16 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource)) usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + const bool stereo = bool(swapChainD->m_window) && (swapChainD->m_window->format().stereo()) + && surfaceCaps.maxImageArrayLayers > 1; + swapChainD->stereo = stereo; + VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; if (swapChainD->m_flags.testFlag(QRhiSwapChain::NoVSync)) { - if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR)) + // Stereo has a weird bug, when using VK_PRESENT_MODE_MAILBOX_KHR, + // black screen is shown, but there is no validation error. + // Detected on Windows, with NVidia RTX A series (at least 4000 and 6000) driver 535.98 + if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR) && !stereo) presentMode = VK_PRESENT_MODE_MAILBOX_KHR; else if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_IMMEDIATE_KHR)) presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; @@ -1529,7 +1885,7 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) swapChainInfo.imageFormat = swapChainD->colorFormat; swapChainInfo.imageColorSpace = swapChainD->colorSpace; swapChainInfo.imageExtent = VkExtent2D { uint32_t(swapChainD->pixelSize.width()), uint32_t(swapChainD->pixelSize.height()) }; - swapChainInfo.imageArrayLayers = 1; + swapChainInfo.imageArrayLayers = stereo ? 2u : 1u; swapChainInfo.imageUsage = usage; swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapChainInfo.preTransform = preTransform; @@ -1591,7 +1947,9 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - swapChainD->imageRes.resize(swapChainD->bufferCount); + // Double up for stereo + swapChainD->imageRes.resize(swapChainD->bufferCount * (stereo ? 2u : 1u)); + for (int i = 0; i < swapChainD->bufferCount; ++i) { QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]); image.image = swapChainImages[i]; @@ -1619,6 +1977,36 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone; } + if (stereo) { + for (int i = 0; i < swapChainD->bufferCount; ++i) { + QVkSwapChain::ImageResources &image(swapChainD->imageRes[i + swapChainD->bufferCount]); + image.image = swapChainImages[i]; + if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) { + image.msaaImage = msaaImages[i]; + image.msaaImageView = msaaViews[i]; + } + + VkImageViewCreateInfo imgViewInfo = {}; + imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imgViewInfo.image = swapChainImages[i]; + imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imgViewInfo.format = swapChainD->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.baseArrayLayer = 1; + imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; + err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView); + if (err != VK_SUCCESS) { + qWarning("Failed to create swapchain image view %d: %d", i, err); + return false; + } + + image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone; + } + } swapChainD->currentImageIndex = 0; @@ -1686,7 +2074,7 @@ void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain) } } - for (int i = 0; i < swapChainD->bufferCount; ++i) { + for (int i = 0; i < swapChainD->bufferCount * (swapChainD->stereo ? 2 : 1); ++i) { QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]); if (image.fb) { df->vkDestroyFramebuffer(dev, image.fb, nullptr); @@ -1731,12 +2119,32 @@ void QRhiVulkan::ensureCommandPoolForNewFrame() df->vkResetCommandPool(dev, cmdPool[currentFrameSlot], flags); } +double QRhiVulkan::elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok) +{ + quint64 mask = 0; + for (quint64 i = 0; i < timestampValidBits; i += 8) + mask |= 0xFFULL << i; + const quint64 ts0 = timestamp[0] & mask; + const quint64 ts1 = timestamp[1] & mask; + const float nsecsPerTick = physDevProperties.limits.timestampPeriod; + if (!qFuzzyIsNull(nsecsPerTick)) { + const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f; + const double elapsedSec = elapsedMs / 1000.0; + *ok = true; + return elapsedSec; + } + *ok = false; + return 0; +} + QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) { QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); + inst->handle()->beginFrame(swapChainD->window); + if (!frame.imageAcquired) { // 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) @@ -1780,30 +2188,6 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin // mess up A's in-flight commands (as they are not in flight anymore). waitCommandCompletion(frameResIndex); - // Now is the time to read the timestamps for the previous frame for this slot. - if (frame.timestampQueryIndex >= 0) { - quint64 timestamp[2] = { 0, 0 }; - VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2, - 2 * sizeof(quint64), timestamp, sizeof(quint64), - VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); - timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2); - frame.timestampQueryIndex = -1; - if (err == VK_SUCCESS) { - quint64 mask = 0; - for (quint64 i = 0; i < timestampValidBits; i += 8) - mask |= 0xFFULL << i; - const quint64 ts0 = timestamp[0] & mask; - const quint64 ts1 = timestamp[1] & mask; - const float nsecsPerTick = physDevProperties.limits.timestampPeriod; - if (!qFuzzyIsNull(nsecsPerTick)) { - const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f; - runGpuFrameTimeCallbacks(elapsedMs); - } - } else { - qWarning("Failed to query timestamp: %d", err); - } - } - currentFrameSlot = int(swapChainD->currentFrameSlot); currentSwapChain = swapChainD; if (swapChainD->ds) @@ -1817,32 +2201,56 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin if (cbres != QRhi::FrameOpSuccess) return cbres; - // when profiling is enabled, pick a free query (pair) from the pool - int timestampQueryIdx = -1; - if (hasGpuFrameTimeCallback() && swapChainD->bufferCount > 1) { // no timestamps if not having at least 2 frames in flight - for (int i = 0; i < timestampQueryPoolMap.count(); ++i) { + swapChainD->cbWrapper.cb = frame.cmdBuf; + + QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); + swapChainD->rtWrapper.d.fb = image.fb; + + if (swapChainD->stereo) { + QVkSwapChain::ImageResources &image( + swapChainD->imageRes[swapChainD->currentImageIndex + swapChainD->bufferCount]); + swapChainD->rtWrapperRight.d.fb = image.fb; + } + + prepareNewFrame(&swapChainD->cbWrapper); + + // Read the timestamps for the previous frame for this slot. + if (frame.timestampQueryIndex >= 0) { + quint64 timestamp[2] = { 0, 0 }; + VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2, + 2 * sizeof(quint64), timestamp, sizeof(quint64), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2); + frame.timestampQueryIndex = -1; + if (err == VK_SUCCESS) { + bool ok = false; + const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, &ok); + if (ok) + swapChainD->cbWrapper.lastGpuTime = elapsedSec; + } else { + qWarning("Failed to query timestamp: %d", err); + } + } + + // No timestamps if the client did not opt in, or when not having at least 2 frames in flight. + if (rhiFlags.testFlag(QRhi::EnableTimestamps) && swapChainD->bufferCount > 1) { + int timestampQueryIdx = -1; + for (int i = 0; i < timestampQueryPoolMap.size(); ++i) { if (!timestampQueryPoolMap.testBit(i)) { timestampQueryPoolMap.setBit(i); timestampQueryIdx = i * 2; break; } } - } - if (timestampQueryIdx >= 0) { - df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2); - // record timestamp at the start of the command buffer - df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - timestampQueryPool, uint32_t(timestampQueryIdx)); - frame.timestampQueryIndex = timestampQueryIdx; + if (timestampQueryIdx >= 0) { + df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2); + // record timestamp at the start of the command buffer + df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + timestampQueryPool, uint32_t(timestampQueryIdx)); + frame.timestampQueryIndex = timestampQueryIdx; + } } - swapChainD->cbWrapper.cb = frame.cmdBuf; - - QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); - swapChainD->rtWrapper.d.fb = image.fb; - - prepareNewFrame(&swapChainD->cbWrapper); - return QRhi::FrameOpSuccess; } @@ -1851,6 +2259,10 @@ QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFram QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); Q_ASSERT(currentSwapChain == swapChainD); + auto cleanup = qScopeGuard([this, swapChainD] { + inst->handle()->endFrame(swapChainD->window); + }); + recordPrimaryCommandBuffer(&swapChainD->cbWrapper); int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; @@ -2055,7 +2467,7 @@ QRhi::FrameOpResult QRhiVulkan::endAndSubmitPrimaryCommandBuffer(VkCommandBuffer void QRhiVulkan::waitCommandCompletion(int frameSlot) { - for (QVkSwapChain *sc : qAsConst(swapchains)) { + for (QVkSwapChain *sc : std::as_const(swapchains)) { const int frameResIndex = sc->bufferCount > 1 ? frameSlot : 0; QVkSwapChain::FrameResources &frame(sc->frameRes[frameResIndex]); if (frame.cmdFenceWaitable) { @@ -2091,6 +2503,24 @@ QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi prepareNewFrame(cbWrapper); ofr.active = true; + if (rhiFlags.testFlag(QRhi::EnableTimestamps)) { + int timestampQueryIdx = -1; + for (int i = 0; i < timestampQueryPoolMap.size(); ++i) { + if (!timestampQueryPoolMap.testBit(i)) { + timestampQueryPoolMap.setBit(i); + timestampQueryIdx = i * 2; + break; + } + } + if (timestampQueryIdx >= 0) { + df->vkCmdResetQueryPool(cbWrapper->cb, timestampQueryPool, uint32_t(timestampQueryIdx), 2); + // record timestamp at the start of the command buffer + df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + timestampQueryPool, uint32_t(timestampQueryIdx)); + ofr.timestampQueryIndex = timestampQueryIdx; + } + } + *cb = cbWrapper; return QRhi::FrameOpSuccess; } @@ -2104,6 +2534,12 @@ QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags) QVkCommandBuffer *cbWrapper(ofr.cbWrapper[currentFrameSlot]); recordPrimaryCommandBuffer(cbWrapper); + // record another timestamp, when enabled + if (ofr.timestampQueryIndex >= 0) { + df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + timestampQueryPool, uint32_t(ofr.timestampQueryIndex + 1)); + } + if (!ofr.cmdFence) { VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; @@ -2126,6 +2562,24 @@ QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags) // previous) frame is safe since we waited for completion above. finishActiveReadbacks(true); + // Read the timestamps, if we wrote them. + if (ofr.timestampQueryIndex >= 0) { + quint64 timestamp[2] = { 0, 0 }; + VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(ofr.timestampQueryIndex), 2, + 2 * sizeof(quint64), timestamp, sizeof(quint64), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + timestampQueryPoolMap.clearBit(ofr.timestampQueryIndex / 2); + ofr.timestampQueryIndex = -1; + if (err == VK_SUCCESS) { + bool ok = false; + const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, &ok); + if (ok) + cbWrapper->lastGpuTime = elapsedSec; + } else { + qWarning("Failed to query timestamp: %d", err); + } + } + return QRhi::FrameOpSuccess; } @@ -2243,6 +2697,13 @@ void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRe QRhiPassResourceTracker::TexDepthOutputStage); depthTexD->lastActiveFrameSlot = currentFrameSlot; } + if (rtD->m_desc.depthResolveTexture()) { + QVkTexture *depthResolveTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthResolveTexture()); + trackedRegisterTexture(&passResTracker, depthResolveTexD, + QRhiPassResourceTracker::TexDepthOutput, + QRhiPassResourceTracker::TexDepthOutputStage); + depthResolveTexD->lastActiveFrameSlot = currentFrameSlot; + } } void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -2384,14 +2845,19 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, float(colorClearValue.alphaF()) } }; cvs.append(cv); } - rpBeginInfo.clearValueCount = uint32_t(cvs.count()); + for (int i = 0; i < rtD->dsResolveAttCount; ++i) { + VkClearValue cv; + cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() }; + cvs.append(cv); + } + rpBeginInfo.clearValueCount = uint32_t(cvs.size()); QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::BeginRenderPass; cmd.args.beginRenderPass.desc = rpBeginInfo; - cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.count(); + cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.size(); cmd.args.beginRenderPass.useSecondaryCb = cbD->passUsesSecondaryCb; - cbD->pools.clearValue.append(cvs.constData(), cvs.count()); + cbD->pools.clearValue.append(cvs.constData(), cvs.size()); if (cbD->passUsesSecondaryCb) cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer(rtD)); @@ -2522,9 +2988,9 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) accessAndIsNewFlag = { 0, false }; QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, cbD->currentComputeSrb); - const int bindingCount = srbD->m_bindings.count(); + const int bindingCount = srbD->m_bindings.size(); for (int i = 0; i < bindingCount; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(i).data(); + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings.at(i)); switch (b->type) { case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: @@ -2600,12 +3066,12 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, - imageBarriers.count(), imageBarriers.constData()); + imageBarriers.size(), imageBarriers.constData()); } if (!bufferBarriers.isEmpty()) { df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, - bufferBarriers.count(), bufferBarriers.constData(), + bufferBarriers.size(), bufferBarriers.constData(), 0, nullptr); } df->vkCmdDispatch(secondaryCb, uint32_t(x), uint32_t(y), uint32_t(z)); @@ -2615,18 +3081,18 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; cmd.args.imageBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; cmd.args.imageBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - cmd.args.imageBarrier.count = imageBarriers.count(); - cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); - cbD->pools.imageBarrier.append(imageBarriers.constData(), imageBarriers.count()); + cmd.args.imageBarrier.count = imageBarriers.size(); + cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); + cbD->pools.imageBarrier.append(imageBarriers.constData(), imageBarriers.size()); } if (!bufferBarriers.isEmpty()) { QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::BufferBarrier; cmd.args.bufferBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; cmd.args.bufferBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - cmd.args.bufferBarrier.count = bufferBarriers.count(); - cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.count(); - cbD->pools.bufferBarrier.append(bufferBarriers.constData(), bufferBarriers.count()); + cmd.args.bufferBarrier.count = bufferBarriers.size(); + cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.size(); + cbD->pools.bufferBarrier.append(bufferBarriers.constData(), bufferBarriers.size()); } QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::Dispatch; @@ -2681,8 +3147,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i const bool updateAll = descSetIdx < 0; int frameSlot = updateAll ? 0 : descSetIdx; while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) { - for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data(); + for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) { + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i)); QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]); VkWriteDescriptorSet writeInfo = {}; @@ -2709,7 +3175,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size; // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset); - bufferInfoIndex = bufferInfos.count(); + bufferInfoIndex = bufferInfos.size(); bufferInfos.append(bufInfo); } break; @@ -2731,7 +3197,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } bd.stex.count = data->count; - imageInfoIndex = imageInfos.count(); + imageInfoIndex = imageInfos.size(); imageInfos.append(imageInfo); } break; @@ -2752,7 +3218,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } bd.stex.count = data->count; - imageInfoIndex = imageInfos.count(); + imageInfoIndex = imageInfos.size(); imageInfos.append(imageInfo); } break; @@ -2768,7 +3234,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i imageInfo[0].sampler = samplerD->sampler; imageInfo[0].imageView = VK_NULL_HANDLE; imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - imageInfoIndex = imageInfos.count(); + imageInfoIndex = imageInfos.size(); imageInfos.append(imageInfo); } break; @@ -2777,7 +3243,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i case QRhiShaderResourceBinding::ImageLoadStore: { QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); - VkImageView view = texD->imageViewForLevel(b->u.simage.level); + VkImageView view = texD->perLevelImageViewForLoadStore(b->u.simage.level); if (view) { writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; bd.simage.id = texD->m_id; @@ -2786,7 +3252,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i imageInfo[0].sampler = VK_NULL_HANDLE; imageInfo[0].imageView = view; imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - imageInfoIndex = imageInfos.count(); + imageInfoIndex = imageInfos.size(); imageInfos.append(imageInfo); } } @@ -2803,7 +3269,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; bufInfo.offset = b->u.ubuf.offset; bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size; - bufferInfoIndex = bufferInfos.count(); + bufferInfoIndex = bufferInfos.size(); bufferInfos.append(bufInfo); } break; @@ -2817,7 +3283,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i ++frameSlot; } - for (int i = 0, writeInfoCount = writeInfos.count(); i < writeInfoCount; ++i) { + for (int i = 0, writeInfoCount = writeInfos.size(); i < writeInfoCount; ++i) { const int bufferInfoIndex = infoIndices[i].first; const int imageInfoIndex = infoIndices[i].second; if (bufferInfoIndex >= 0) @@ -2826,7 +3292,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData(); } - df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.count()), writeInfos.constData(), 0, nullptr); + df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.size()), writeInfos.constData(), 0, nullptr); } static inline bool accessIsWrite(VkAccessFlags access) @@ -2872,7 +3338,7 @@ void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, in cmd.args.bufferBarrier.srcStageMask = s.stage; cmd.args.bufferBarrier.dstStageMask = stage; cmd.args.bufferBarrier.count = 1; - cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.count(); + cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.size(); cbD->pools.bufferBarrier.append(bufMemBarrier); s.access = access; @@ -2913,7 +3379,7 @@ void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, cmd.args.imageBarrier.srcStageMask = srcStage; cmd.args.imageBarrier.dstStageMask = stage; cmd.args.imageBarrier.count = 1; - cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); + cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); cbD->pools.imageBarrier.append(barrier); s.layout = layout; @@ -2947,7 +3413,7 @@ void QRhiVulkan::depthStencilExplicitBarrier(QVkCommandBuffer *cbD, QVkRenderBuf cmd.args.imageBarrier.srcStageMask = stages; cmd.args.imageBarrier.dstStageMask = stages; cmd.args.imageBarrier.count = 1; - cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); + cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); cbD->pools.imageBarrier.append(barrier); } @@ -2977,7 +3443,7 @@ void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, cmd.args.imageBarrier.srcStageMask = srcStage; cmd.args.imageBarrier.dstStageMask = dstStage; cmd.args.imageBarrier.count = 1; - cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); + cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); cbD->pools.imageBarrier.append(barrier); } @@ -3000,6 +3466,7 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level, qsizetype imageSizeBytes = 0; const void *src = nullptr; const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + const bool is1D = texD->m_flags.testFlag(QRhiTexture::OneDimensional); VkBufferImageCopy copyInfo = {}; copyInfo.bufferOffset = *curOfs; @@ -3010,6 +3477,8 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level, copyInfo.imageExtent.depth = 1; if (is3D) copyInfo.imageOffset.z = uint32_t(layer); + if (is1D) + copyInfo.imageOffset.y = uint32_t(layer); const QByteArray rawData = subresDesc.data(); const QPoint dp = subresDesc.destinationTopLeft(); @@ -3096,6 +3565,12 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level, } } +void QRhiVulkan::printExtraErrorInfo(VkResult err) +{ + if (err == VK_ERROR_OUT_OF_DEVICE_MEMORY) + qWarning() << "Out of device memory, current allocator statistics are" << statistics(); +} + void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates) { QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); @@ -3133,6 +3608,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat bufD->stagingAllocations[currentFrameSlot] = allocation; } else { qWarning("Failed to create staging buffer of size %u: %d", bufD->m_size, err); + printExtraErrorInfo(err); continue; } } @@ -3145,8 +3621,8 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat continue; } memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), u.data.size()); - vmaUnmapMemory(toVmaAllocator(allocator), a); vmaFlushAllocation(toVmaAllocator(allocator), a, u.offset, u.data.size()); + vmaUnmapMemory(toVmaAllocator(allocator), a); trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); @@ -3221,6 +3697,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat readback.stagingAlloc = allocation; } else { qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); + printExtraErrorInfo(err); continue; } @@ -3249,9 +3726,9 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos VkDeviceSize stagingSize = 0; - for (int layer = 0, maxLayer = u.subresDesc.count(); layer < maxLayer; ++layer) { + for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) { for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level])) stagingSize += subresUploadByteSize(subresDesc); } } @@ -3270,6 +3747,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat &utexD->stagingBuffers[currentFrameSlot], &allocation, nullptr); if (err != VK_SUCCESS) { qWarning("Failed to create image staging buffer of size %d: %d", int(stagingSize), err); + printExtraErrorInfo(err); continue; } utexD->stagingAllocations[currentFrameSlot] = allocation; @@ -3284,19 +3762,19 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat continue; } - for (int layer = 0, maxLayer = u.subresDesc.count(); layer < maxLayer; ++layer) { + for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) { for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { const QList<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]); if (srd.isEmpty()) continue; - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(srd)) { + for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(srd)) { prepareUploadSubres(utexD, layer, level, subresDesc, &curOfs, mp, ©Infos); } } } - vmaUnmapMemory(toVmaAllocator(allocator), a); vmaFlushAllocation(toVmaAllocator(allocator), a, 0, stagingSize); + vmaUnmapMemory(toVmaAllocator(allocator), a); trackedImageBarrier(cbD, utexD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); @@ -3306,9 +3784,9 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot]; cmd.args.copyBufferToImage.dst = utexD->image; cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - cmd.args.copyBufferToImage.count = copyInfos.count(); - cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.count(); - cbD->pools.bufferImageCopy.append(copyInfos.constData(), copyInfos.count()); + cmd.args.copyBufferToImage.count = copyInfos.size(); + cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.size(); + cbD->pools.bufferImageCopy.append(copyInfos.constData(), copyInfos.size()); // no reuse of staging, this is intentional QRhiVulkan::DeferredReleaseEntry e; @@ -3426,6 +3904,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat readback.stagingAlloc = allocation; } else { qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); + printExtraErrorInfo(err); continue; } @@ -3491,10 +3970,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat if (!origStage) origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - for (int layer = 0; layer < (isCube ? 6 : (isArray ? utexD->m_arraySize : 1)); ++layer) { + for (int layer = 0; layer < (isCube ? 6 : (isArray ? qMax(0, utexD->m_arraySize) : 1)); ++layer) { int w = utexD->m_pixelSize.width(); int h = utexD->m_pixelSize.height(); - int depth = is3D ? utexD->m_depth : 1; + int depth = is3D ? qMax(1, utexD->m_depth) : 1; for (int level = 1; level < int(utexD->mipLevelCount); ++level) { if (level == 1) { subresourceBarrier(cbD, utexD->image, @@ -3592,16 +4071,16 @@ void QRhiVulkan::executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot) } quint32 changeBegin = UINT32_MAX; quint32 changeEnd = 0; - for (const QVkBuffer::DynamicUpdate &u : qAsConst(bufD->pendingDynamicUpdates[slot])) { + for (const QVkBuffer::DynamicUpdate &u : std::as_const(bufD->pendingDynamicUpdates[slot])) { memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size()); if (u.offset < changeBegin) changeBegin = u.offset; if (u.offset + u.data.size() > changeEnd) changeEnd = u.offset + u.data.size(); } - vmaUnmapMemory(toVmaAllocator(allocator), a); if (changeBegin < UINT32_MAX && changeBegin < changeEnd) vmaFlushAllocation(toVmaAllocator(allocator), a, changeBegin, changeEnd - changeBegin); + vmaUnmapMemory(toVmaAllocator(allocator), a); bufD->pendingDynamicUpdates[slot].clear(); } @@ -3640,7 +4119,7 @@ static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkD void QRhiVulkan::executeDeferredReleases(bool forced) { - for (int i = releaseQueue.count() - 1; i >= 0; --i) { + for (int i = releaseQueue.size() - 1; i >= 0; --i) { const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]); if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) { switch (e.type) { @@ -3673,6 +4152,8 @@ void QRhiVulkan::executeDeferredReleases(bool forced) df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr); df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr); } + df->vkDestroyImageView(dev, e.textureRenderTarget.dsv, nullptr); + df->vkDestroyImageView(dev, e.textureRenderTarget.resdsv, nullptr); break; case QRhiVulkan::DeferredReleaseEntry::RenderPass: df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr); @@ -3696,7 +4177,7 @@ void QRhiVulkan::finishActiveReadbacks(bool forced) { QVarLengthArray<std::function<void()>, 4> completedCallbacks; - for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) { + for (int i = activeTextureReadbacks.size() - 1; i >= 0; --i) { const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]); if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { readback.result->format = readback.format; @@ -3721,7 +4202,7 @@ void QRhiVulkan::finishActiveReadbacks(bool forced) } } - for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) { + for (int i = activeBufferReadbacks.size() - 1; i >= 0; --i) { const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]); if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { VmaAllocation a = toVmaAllocation(readback.stagingAlloc); @@ -3782,33 +4263,26 @@ QList<int> QRhiVulkan::supportedSampleCounts() const return result; } -VkSampleCountFlagBits QRhiVulkan::effectiveSampleCount(int sampleCount) +VkSampleCountFlagBits QRhiVulkan::effectiveSampleCountBits(int sampleCount) { - // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1. - sampleCount = qBound(1, sampleCount, 64); - - if (!supportedSampleCounts().contains(sampleCount)) { - qWarning("Attempted to set unsupported sample count %d", sampleCount); - return VK_SAMPLE_COUNT_1_BIT; - } + const int s = effectiveSampleCount(sampleCount); for (const auto &qvk_sampleCount : qvk_sampleCounts) { - if (qvk_sampleCount.count == sampleCount) + if (qvk_sampleCount.count == s) return qvk_sampleCount.mask; } - Q_UNREACHABLE(); - return VK_SAMPLE_COUNT_1_BIT; + Q_UNREACHABLE_RETURN(VK_SAMPLE_COUNT_1_BIT); } void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD) { cbD->passResTrackers.append(QRhiPassResourceTracker()); - cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1; + cbD->currentPassResTrackerIndex = cbD->passResTrackers.size() - 1; QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::TransitionPassResources; - cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.count() - 1; + cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.size() - 1; } void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD) @@ -4177,6 +4651,14 @@ void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhi QRhiSwapChain *QRhiVulkan::createSwapChain() { + if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR + || !vkGetPhysicalDeviceSurfaceFormatsKHR + || !vkGetPhysicalDeviceSurfacePresentModesKHR) + { + qWarning("Physical device surface queries not available"); + return nullptr; + } + return new QVkSwapChain(this); } @@ -4319,9 +4801,24 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::NonFillPolygonMode: return caps.nonFillPolygonMode; + case QRhi::OneDimensionalTextures: + return true; + case QRhi::OneDimensionalTextureMipmaps: + return true; + case QRhi::HalfAttributes: + return true; + case QRhi::RenderToOneDimensionalTexture: + return true; + case QRhi::ThreeDimensionalTextureMipmaps: + return true; + case QRhi::MultiView: + return caps.multiView; + case QRhi::TextureViewFormat: + return true; + case QRhi::ResolveDepthStencil: + return caps.renderPass2KHR && caps.depthStencilResolveKHR; default: - Q_UNREACHABLE(); - return false; + Q_UNREACHABLE_RETURN(false); } } @@ -4359,8 +4856,7 @@ int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const case QRhi::MaxVertexOutputs: return physDevProperties.limits.maxVertexOutputComponents / 4; default: - Q_UNREACHABLE(); - return 0; + Q_UNREACHABLE_RETURN(0); } } @@ -4376,15 +4872,20 @@ QRhiDriverInfo QRhiVulkan::driverInfo() const QRhiStats QRhiVulkan::statistics() { - VmaStats stats; - vmaCalculateStats(toVmaAllocator(allocator), &stats); - QRhiStats result; result.totalPipelineCreationTime = totalPipelineCreationTime(); - result.blockCount = stats.total.blockCount; - result.allocCount = stats.total.allocationCount; - result.usedBytes = stats.total.usedBytes; - result.unusedBytes = stats.total.unusedBytes; + + VmaBudget budgets[VK_MAX_MEMORY_HEAPS]; + vmaGetHeapBudgets(toVmaAllocator(allocator), budgets); + + uint32_t count = toVmaAllocator(allocator)->GetMemoryHeapCount(); + for (uint32_t i = 0; i < count; ++i) { + const VmaStatistics &stats(budgets[i].statistics); + result.blockCount += stats.blockCount; + result.allocCount += stats.allocationCount; + result.usedBytes += stats.allocationBytes; + result.unusedBytes += stats.blockBytes - stats.allocationBytes; + } return result; } @@ -4428,7 +4929,7 @@ QByteArray QRhiVulkan::pipelineCacheData() size_t dataSize = 0; VkResult err = df->vkGetPipelineCacheData(dev, pipelineCache, &dataSize, nullptr); if (err != VK_SUCCESS) { - qWarning("Failed to get pipeline cache data size: %d", err); + qCDebug(QRHI_LOG_INFO, "Failed to get pipeline cache data size: %d", err); return QByteArray(); } const size_t headerSize = sizeof(QVkPipelineCacheDataHeader); @@ -4436,7 +4937,7 @@ QByteArray QRhiVulkan::pipelineCacheData() data.resize(dataOffset + dataSize); err = df->vkGetPipelineCacheData(dev, pipelineCache, &dataSize, data.data() + dataOffset); if (err != VK_SUCCESS) { - qWarning("Failed to get pipeline cache data of %d bytes: %d", int(dataSize), err); + qCDebug(QRHI_LOG_INFO, "Failed to get pipeline cache data of %d bytes: %d", int(dataSize), err); return QByteArray(); } @@ -4448,6 +4949,7 @@ QByteArray QRhiVulkan::pipelineCacheData() header.deviceId = physDevProperties.deviceID; header.dataSize = quint32(dataSize); header.uuidSize = VK_UUID_SIZE; + header.reserved = 0; memcpy(data.data(), &header, headerSize); memcpy(data.data() + headerSize, physDevProperties.pipelineCacheUUID, VK_UUID_SIZE); @@ -4461,7 +4963,7 @@ void QRhiVulkan::setPipelineCacheData(const QByteArray &data) const size_t headerSize = sizeof(QVkPipelineCacheDataHeader); if (data.size() < qsizetype(headerSize)) { - qWarning("setPipelineCacheData: Invalid blob size"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size"); return; } QVkPipelineCacheDataHeader header; @@ -4469,49 +4971,49 @@ void QRhiVulkan::setPipelineCacheData(const QByteArray &data) const quint32 rhiId = pipelineCacheRhiId(); if (header.rhiId != rhiId) { - qWarning("setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)", - rhiId, header.rhiId); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)", + rhiId, header.rhiId); return; } const quint32 arch = quint32(sizeof(void*)); if (header.arch != arch) { - qWarning("setPipelineCacheData: Architecture does not match (%u, %u)", + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)", arch, header.arch); return; } if (header.driverVersion != physDevProperties.driverVersion) { - qWarning("setPipelineCacheData: driverVersion does not match (%u, %u)", - physDevProperties.driverVersion, header.driverVersion); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: driverVersion does not match (%u, %u)", + physDevProperties.driverVersion, header.driverVersion); return; } if (header.vendorId != physDevProperties.vendorID) { - qWarning("setPipelineCacheData: vendorID does not match (%u, %u)", - physDevProperties.vendorID, header.vendorId); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: vendorID does not match (%u, %u)", + physDevProperties.vendorID, header.vendorId); return; } if (header.deviceId != physDevProperties.deviceID) { - qWarning("setPipelineCacheData: deviceID does not match (%u, %u)", - physDevProperties.deviceID, header.deviceId); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: deviceID does not match (%u, %u)", + physDevProperties.deviceID, header.deviceId); return; } if (header.uuidSize != VK_UUID_SIZE) { - qWarning("setPipelineCacheData: VK_UUID_SIZE does not match (%u, %u)", - quint32(VK_UUID_SIZE), header.uuidSize); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: VK_UUID_SIZE does not match (%u, %u)", + quint32(VK_UUID_SIZE), header.uuidSize); return; } if (data.size() < qsizetype(headerSize + VK_UUID_SIZE)) { - qWarning("setPipelineCacheData: Invalid blob, no uuid"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, no uuid"); return; } if (memcmp(data.constData() + headerSize, physDevProperties.pipelineCacheUUID, VK_UUID_SIZE)) { - qWarning("setPipelineCacheData: pipelineCacheUUID does not match"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: pipelineCacheUUID does not match"); return; } const size_t dataOffset = headerSize + VK_UUID_SIZE; if (data.size() < qsizetype(dataOffset + header.dataSize)) { - qWarning("setPipelineCacheData: Invalid blob, data missing"); + qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, data missing"); return; } @@ -4524,7 +5026,7 @@ void QRhiVulkan::setPipelineCacheData(const QByteArray &data) qCDebug(QRHI_LOG_INFO, "Created pipeline cache with initial data of %d bytes", int(header.dataSize)); } else { - qWarning("Failed to create pipeline cache with initial data specified"); + qCDebug(QRHI_LOG_INFO, "Failed to create pipeline cache with initial data specified"); } } @@ -4619,8 +5121,8 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin // Do host writes and mark referenced shader resources as in-use. // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects. - for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings[i].data(); + for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) { + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings[i]); QVkShaderResourceBindings::BoundResourceData &bd(descSetBd[i]); switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: @@ -4766,8 +5268,8 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin // because dynOfs has to be ordered based on the binding numbers, // and neither srb nor dynamicOffsets has any such ordering // requirement. - for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) { + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding); if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) { uint32_t offset = 0; for (int i = 0; i < dynamicOffsetCount; ++i) { @@ -4787,8 +5289,8 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE, gfxPsD ? gfxPsD->layout : compPsD->layout, 0, 1, &srbD->descSets[descSetIdx], - uint32_t(dynOfs.count()), - dynOfs.count() ? dynOfs.constData() : nullptr); + uint32_t(dynOfs.size()), + dynOfs.size() ? dynOfs.constData() : nullptr); } else { QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::BindDescriptorSet; @@ -4796,9 +5298,9 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin : VK_PIPELINE_BIND_POINT_COMPUTE; cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->layout; cmd.args.bindDescriptorSet.descSet = srbD->descSets[descSetIdx]; - cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.count(); - cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.count(); - cbD->pools.dynamicOffset.append(dynOfs.constData(), dynOfs.count()); + cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.size(); + cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.size(); + cbD->pools.dynamicOffset.append(dynOfs.constData(), dynOfs.size()); } if (gfxPsD) { @@ -4857,16 +5359,16 @@ void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb, if (cbD->passUsesSecondaryCb) { df->vkCmdBindVertexBuffers(cbD->activeSecondaryCbStack.last(), uint32_t(startBinding), - uint32_t(bufs.count()), bufs.constData(), ofs.constData()); + uint32_t(bufs.size()), bufs.constData(), ofs.constData()); } else { QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::BindVertexBuffer; cmd.args.bindVertexBuffer.startBinding = startBinding; - cmd.args.bindVertexBuffer.count = bufs.count(); - cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.count(); - cbD->pools.vertexBuffer.append(bufs.constData(), bufs.count()); - cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.count(); - cbD->pools.vertexBufferOffset.append(ofs.constData(), ofs.count()); + cmd.args.bindVertexBuffer.count = bufs.size(); + cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.size(); + cbD->pools.vertexBuffer.append(bufs.constData(), bufs.size()); + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.size(); + cbD->pools.vertexBufferOffset.append(ofs.constData(), ofs.size()); } } @@ -4915,7 +5417,7 @@ void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport // x,y is top-left in VkViewport but bottom-left in QRhiViewport float x, y, w, h; - if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h)) + if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h)) return; QVkCommandBuffer::Command &cmd(cbD->commands.get()); @@ -4934,9 +5436,12 @@ void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport cmd.cmd = QVkCommandBuffer::Command::SetViewport; } - if (!QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) { + if (cbD->currentGraphicsPipeline + && !QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline) + ->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) { QVkCommandBuffer::Command &cmd(cbD->commands.get()); VkRect2D *s = &cmd.args.setScissor.scissor; + qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, viewport.viewport(), &x, &y, &w, &h); s->offset.x = int32_t(x); s->offset.y = int32_t(y); s->extent.width = uint32_t(w); @@ -4959,7 +5464,7 @@ void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) // x,y is top-left in VkRect2D but bottom-left in QRhiScissor int x, y, w, h; - if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h)) + if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h)) return; QVkCommandBuffer::Command &cmd(cbD->commands.get()); @@ -5064,7 +5569,7 @@ void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin; cmd.args.debugMarkerBegin.label = label; - cmd.args.debugMarkerBegin.labelNameIndex = cbD->pools.debugMarkerData.count(); + cmd.args.debugMarkerBegin.labelNameIndex = cbD->pools.debugMarkerData.size(); cbD->pools.debugMarkerData.append(name); } #else @@ -5108,7 +5613,7 @@ void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert; cmd.args.debugMarkerInsert.label = label; - cmd.args.debugMarkerInsert.labelNameIndex = cbD->pools.debugMarkerData.count(); + cmd.args.debugMarkerInsert.labelNameIndex = cbD->pools.debugMarkerData.size(); cbD->pools.debugMarkerData.append(msg); } #else @@ -5195,6 +5700,12 @@ void QRhiVulkan::endExternal(QRhiCommandBuffer *cb) cbD->resetCachedState(); } +double QRhiVulkan::lastCompletedGpuTime(QRhiCommandBuffer *cb) +{ + QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); + return cbD->lastGpuTime; +} + void QRhiVulkan::setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot) { #ifdef VK_EXT_debug_utils @@ -5242,8 +5753,7 @@ static inline VkFilter toVkFilter(QRhiSampler::Filter f) case QRhiSampler::Linear: return VK_FILTER_LINEAR; default: - Q_UNREACHABLE(); - return VK_FILTER_NEAREST; + Q_UNREACHABLE_RETURN(VK_FILTER_NEAREST); } } @@ -5257,8 +5767,7 @@ static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f) case QRhiSampler::Linear: return VK_SAMPLER_MIPMAP_MODE_LINEAR; default: - Q_UNREACHABLE(); - return VK_SAMPLER_MIPMAP_MODE_NEAREST; + Q_UNREACHABLE_RETURN(VK_SAMPLER_MIPMAP_MODE_NEAREST); } } @@ -5272,8 +5781,7 @@ static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m) case QRhiSampler::Mirror: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; default: - Q_UNREACHABLE(); - return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + Q_UNREACHABLE_RETURN(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); } } @@ -5293,8 +5801,7 @@ static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type) case QRhiShaderStage::Geometry: return VK_SHADER_STAGE_GEOMETRY_BIT; default: - Q_UNREACHABLE(); - return VK_SHADER_STAGE_VERTEX_BIT; + Q_UNREACHABLE_RETURN(VK_SHADER_STAGE_VERTEX_BIT); } } @@ -5331,9 +5838,32 @@ static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format form return VK_FORMAT_R32G32_SINT; case QRhiVertexInputAttribute::SInt: return VK_FORMAT_R32_SINT; + case QRhiVertexInputAttribute::Half4: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case QRhiVertexInputAttribute::Half3: + return VK_FORMAT_R16G16B16_SFLOAT; + case QRhiVertexInputAttribute::Half2: + return VK_FORMAT_R16G16_SFLOAT; + case QRhiVertexInputAttribute::Half: + return VK_FORMAT_R16_SFLOAT; + case QRhiVertexInputAttribute::UShort4: + return VK_FORMAT_R16G16B16A16_UINT; + case QRhiVertexInputAttribute::UShort3: + return VK_FORMAT_R16G16B16_UINT; + case QRhiVertexInputAttribute::UShort2: + return VK_FORMAT_R16G16_UINT; + case QRhiVertexInputAttribute::UShort: + return VK_FORMAT_R16_UINT; + case QRhiVertexInputAttribute::SShort4: + return VK_FORMAT_R16G16B16A16_SINT; + case QRhiVertexInputAttribute::SShort3: + return VK_FORMAT_R16G16B16_SINT; + case QRhiVertexInputAttribute::SShort2: + return VK_FORMAT_R16G16_SINT; + case QRhiVertexInputAttribute::SShort: + return VK_FORMAT_R16_SINT; default: - Q_UNREACHABLE(); - return VK_FORMAT_R32G32B32A32_SFLOAT; + Q_UNREACHABLE_RETURN(VK_FORMAT_R32G32B32A32_SFLOAT); } } @@ -5355,8 +5885,7 @@ static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t) case QRhiGraphicsPipeline::Patches: return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; default: - Q_UNREACHABLE(); - return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + Q_UNREACHABLE_RETURN(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); } } @@ -5370,8 +5899,7 @@ static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c) case QRhiGraphicsPipeline::Back: return VK_CULL_MODE_BACK_BIT; default: - Q_UNREACHABLE(); - return VK_CULL_MODE_NONE; + Q_UNREACHABLE_RETURN(VK_CULL_MODE_NONE); } } @@ -5383,8 +5911,7 @@ static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f) case QRhiGraphicsPipeline::CW: return VK_FRONT_FACE_CLOCKWISE; default: - Q_UNREACHABLE(); - return VK_FRONT_FACE_COUNTER_CLOCKWISE; + Q_UNREACHABLE_RETURN(VK_FRONT_FACE_COUNTER_CLOCKWISE); } } @@ -5444,8 +5971,7 @@ static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f) case QRhiGraphicsPipeline::OneMinusSrc1Alpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; default: - Q_UNREACHABLE(); - return VK_BLEND_FACTOR_ZERO; + Q_UNREACHABLE_RETURN(VK_BLEND_FACTOR_ZERO); } } @@ -5463,8 +5989,7 @@ static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op) case QRhiGraphicsPipeline::Max: return VK_BLEND_OP_MAX; default: - Q_UNREACHABLE(); - return VK_BLEND_OP_ADD; + Q_UNREACHABLE_RETURN(VK_BLEND_OP_ADD); } } @@ -5488,8 +6013,7 @@ static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op) case QRhiGraphicsPipeline::Always: return VK_COMPARE_OP_ALWAYS; default: - Q_UNREACHABLE(); - return VK_COMPARE_OP_ALWAYS; + Q_UNREACHABLE_RETURN(VK_COMPARE_OP_ALWAYS); } } @@ -5513,8 +6037,7 @@ static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op) case QRhiGraphicsPipeline::DecrementAndWrap: return VK_STENCIL_OP_DECREMENT_AND_WRAP; default: - Q_UNREACHABLE(); - return VK_STENCIL_OP_KEEP; + Q_UNREACHABLE_RETURN(VK_STENCIL_OP_KEEP); } } @@ -5526,8 +6049,7 @@ static inline VkPolygonMode toVkPolygonMode(QRhiGraphicsPipeline::PolygonMode mo case QRhiGraphicsPipeline::Line: return VK_POLYGON_MODE_LINE; default: - Q_UNREACHABLE(); - return VK_POLYGON_MODE_FILL; + Q_UNREACHABLE_RETURN(VK_POLYGON_MODE_FILL); } } @@ -5566,8 +6088,7 @@ static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBindin return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; default: - Q_UNREACHABLE(); - return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + Q_UNREACHABLE_RETURN(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); } } @@ -5609,8 +6130,7 @@ static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op) case QRhiSampler::Always: return VK_COMPARE_OP_ALWAYS; default: - Q_UNREACHABLE(); - return VK_COMPARE_OP_NEVER; + Q_UNREACHABLE_RETURN(VK_COMPARE_OP_NEVER); } } @@ -5711,7 +6231,8 @@ bool QVkBuffer::create() } if (err != VK_SUCCESS) { - qWarning("Failed to create buffer: %d", err); + qWarning("Failed to create buffer of size %u: %d", nonZeroSize, err); + rhiD->printExtraErrorInfo(err); return false; } @@ -5763,8 +6284,8 @@ void QVkBuffer::endFullDynamicBufferUpdateForCurrentFrame() QRHI_RES_RHI(QRhiVulkan); const int slot = rhiD->currentFrameSlot; VmaAllocation a = toVmaAllocation(allocations[slot]); - vmaUnmapMemory(toVmaAllocator(rhiD->allocator), a); vmaFlushAllocation(toVmaAllocator(rhiD->allocator), a, 0, m_size); + vmaUnmapMemory(toVmaAllocator(rhiD->allocator), a); } QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, @@ -5819,7 +6340,7 @@ bool QVkRenderBuffer::create() return false; QRHI_RES_RHI(QRhiVulkan); - samples = rhiD->effectiveSampleCount(m_sampleCount); + samples = rhiD->effectiveSampleCountBits(m_sampleCount); switch (m_type) { case QRhiRenderBuffer::Color: @@ -5937,6 +6458,15 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize) QRHI_RES_RHI(QRhiVulkan); vkformat = toVkTextureFormat(m_format, m_flags); + if (m_writeViewFormat.format != UnknownFormat) + viewFormat = toVkTextureFormat(m_writeViewFormat.format, m_writeViewFormat.srgb ? sRGB : Flags()); + else + viewFormat = vkformat; + if (m_readViewFormat.format != UnknownFormat) + viewFormatForSampling = toVkTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags()); + else + viewFormatForSampling = vkformat; + VkFormatProperties props; rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props); const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); @@ -5945,19 +6475,22 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize) return false; } - const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; const bool isCube = m_flags.testFlag(CubeMap); const bool isArray = m_flags.testFlag(TextureArray); const bool is3D = m_flags.testFlag(ThreeDimensional); + const bool is1D = m_flags.testFlag(OneDimensional); const bool hasMipMaps = m_flags.testFlag(MipMapped); + const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1) + : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize); + mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1); const int maxLevels = QRhi::MAX_MIP_LEVELS; if (mipLevelCount > maxLevels) { qWarning("Too many mip levels (%d, max is %d), truncating mip chain", mipLevelCount, maxLevels); mipLevelCount = maxLevels; } - samples = rhiD->effectiveSampleCount(m_sampleCount); + samples = rhiD->effectiveSampleCountBits(m_sampleCount); if (samples > VK_SAMPLE_COUNT_1_BIT) { if (isCube) { qWarning("Cubemap texture cannot be multisample"); @@ -5980,12 +6513,18 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize) qWarning("Texture cannot be both array and 3D"); return false; } - m_depth = qMax(1, m_depth); + if (isCube && is1D) { + qWarning("Texture cannot be both cube and 1D"); + return false; + } + if (is1D && is3D) { + qWarning("Texture cannot be both 1D and 3D"); + return false; + } if (m_depth > 1 && !is3D) { qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth); return false; } - m_arraySize = qMax(0, m_arraySize); if (m_arraySize > 0 && !isArray) { qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); return false; @@ -6013,14 +6552,17 @@ bool QVkTexture::finishCreate() const bool isCube = m_flags.testFlag(CubeMap); const bool isArray = m_flags.testFlag(TextureArray); const bool is3D = m_flags.testFlag(ThreeDimensional); + const bool is1D = m_flags.testFlag(OneDimensional); VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; - viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE - : (is3D ? VK_IMAGE_VIEW_TYPE_3D - : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D)); - viewInfo.format = vkformat; + viewInfo.viewType = isCube + ? VK_IMAGE_VIEW_TYPE_CUBE + : (is3D ? VK_IMAGE_VIEW_TYPE_3D + : (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D) + : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D))); + viewInfo.format = viewFormatForSampling; viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; @@ -6031,7 +6573,7 @@ bool QVkTexture::finishCreate() viewInfo.subresourceRange.baseArrayLayer = uint32_t(m_arrayRangeStart); viewInfo.subresourceRange.layerCount = uint32_t(m_arrayRangeLength); } else { - viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? m_arraySize : 1); + viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); } VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView); @@ -6058,6 +6600,7 @@ bool QVkTexture::create() const bool isCube = m_flags.testFlag(CubeMap); const bool isArray = m_flags.testFlag(TextureArray); const bool is3D = m_flags.testFlag(ThreeDimensional); + const bool is1D = m_flags.testFlag(OneDimensional); VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; @@ -6080,13 +6623,13 @@ bool QVkTexture::create() #endif } - imageInfo.imageType = is3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D; + imageInfo.imageType = is1D ? VK_IMAGE_TYPE_1D : is3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D; imageInfo.format = vkformat; imageInfo.extent.width = uint32_t(size.width()); imageInfo.extent.height = uint32_t(size.height()); - imageInfo.extent.depth = is3D ? m_depth : 1; + imageInfo.extent.depth = is3D ? qMax(1, m_depth) : 1; imageInfo.mipLevels = mipLevelCount; - imageInfo.arrayLayers = isCube ? 6 : (isArray ? m_arraySize : 1); + imageInfo.arrayLayers = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); imageInfo.samples = samples; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; @@ -6111,7 +6654,14 @@ bool QVkTexture::create() VmaAllocation allocation; VkResult err = vmaCreateImage(toVmaAllocator(rhiD->allocator), &imageInfo, &allocInfo, &image, &allocation, nullptr); if (err != VK_SUCCESS) { - qWarning("Failed to create image: %d", err); + qWarning("Failed to create image (with VkImageCreateInfo %ux%u depth %u vkformat 0x%X mips %u layers %u vksamples 0x%X): %d", + imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth, + int(imageInfo.format), + imageInfo.mipLevels, + imageInfo.arrayLayers, + int(imageInfo.samples), + err); + rhiD->printExtraErrorInfo(err); return false; } imageAlloc = allocation; @@ -6158,7 +6708,7 @@ void QVkTexture::setNativeLayout(int layout) usageState.layout = VkImageLayout(layout); } -VkImageView QVkTexture::imageViewForLevel(int level) +VkImageView QVkTexture::perLevelImageViewForLoadStore(int level) { Q_ASSERT(level >= 0 && level < int(mipLevelCount)); if (perLevelImageViews[level] != VK_NULL_HANDLE) @@ -6168,14 +6718,17 @@ VkImageView QVkTexture::imageViewForLevel(int level) const bool isCube = m_flags.testFlag(CubeMap); const bool isArray = m_flags.testFlag(TextureArray); const bool is3D = m_flags.testFlag(ThreeDimensional); + const bool is1D = m_flags.testFlag(OneDimensional); VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; - viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE - : (is3D ? VK_IMAGE_VIEW_TYPE_3D - : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D)); - viewInfo.format = vkformat; + viewInfo.viewType = isCube + ? VK_IMAGE_VIEW_TYPE_CUBE + : (is3D ? VK_IMAGE_VIEW_TYPE_3D + : (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D) + : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D))); + viewInfo.format = viewFormat; // this is writeViewFormat, regardless of Load, Store, or LoadStore; intentional viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; @@ -6184,7 +6737,7 @@ VkImageView QVkTexture::imageViewForLevel(int level) viewInfo.subresourceRange.baseMipLevel = uint32_t(level); viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? m_arraySize : 1); + viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); VkImageView v = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); @@ -6262,7 +6815,7 @@ bool QVkSampler::create() QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi) : QRhiRenderPassDescriptor(rhi) { - serializedFormatData.reserve(32); + serializedFormatData.reserve(64); } QVkRenderPassDescriptor::~QVkRenderPassDescriptor() @@ -6317,16 +6870,20 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other const QVkRenderPassDescriptor *o = QRHI_RES(const QVkRenderPassDescriptor, other); - if (attDescs.count() != o->attDescs.count()) + if (attDescs.size() != o->attDescs.size()) return false; - if (colorRefs.count() != o->colorRefs.count()) + if (colorRefs.size() != o->colorRefs.size()) return false; - if (resolveRefs.count() != o->resolveRefs.count()) + if (resolveRefs.size() != o->resolveRefs.size()) return false; if (hasDepthStencil != o->hasDepthStencil) return false; + if (hasDepthStencilResolve != o->hasDepthStencilResolve) + return false; + if (multiViewCount != o->multiViewCount) + return false; - for (int i = 0, ie = colorRefs.count(); i != ie; ++i) { + for (int i = 0, ie = colorRefs.size(); i != ie; ++i) { const uint32_t attIdx = colorRefs[i].attachment; if (attIdx != o->colorRefs[i].attachment) return false; @@ -6342,7 +6899,7 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other return false; } - for (int i = 0, ie = resolveRefs.count(); i != ie; ++i) { + for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) { const uint32_t attIdx = resolveRefs[i].attachment; if (attIdx != o->resolveRefs[i].attachment) return false; @@ -6350,6 +6907,14 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other return false; } + if (hasDepthStencilResolve) { + const uint32_t attIdx = dsResolveRef.attachment; + if (attIdx != o->dsResolveRef.attachment) + return false; + if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx])) + return false; + } + // subpassDeps is not included return true; @@ -6360,10 +6925,12 @@ void QVkRenderPassDescriptor::updateSerializedFormat() serializedFormatData.clear(); auto p = std::back_inserter(serializedFormatData); - *p++ = attDescs.count(); - *p++ = colorRefs.count(); - *p++ = resolveRefs.count(); + *p++ = attDescs.size(); + *p++ = colorRefs.size(); + *p++ = resolveRefs.size(); *p++ = hasDepthStencil; + *p++ = hasDepthStencilResolve; + *p++ = multiViewCount; auto serializeAttachmentData = [this, &p](uint32_t attIdx) { const bool used = attIdx != VK_ATTACHMENT_UNUSED; @@ -6378,7 +6945,7 @@ void QVkRenderPassDescriptor::updateSerializedFormat() *p++ = used ? a->finalLayout : 0; }; - for (int i = 0, ie = colorRefs.count(); i != ie; ++i) { + for (int i = 0, ie = colorRefs.size(); i != ie; ++i) { const uint32_t attIdx = colorRefs[i].attachment; *p++ = attIdx; serializeAttachmentData(attIdx); @@ -6390,11 +6957,17 @@ void QVkRenderPassDescriptor::updateSerializedFormat() serializeAttachmentData(attIdx); } - for (int i = 0, ie = resolveRefs.count(); i != ie; ++i) { + for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) { const uint32_t attIdx = resolveRefs[i].attachment; *p++ = attIdx; serializeAttachmentData(attIdx); } + + if (hasDepthStencilResolve) { + const uint32_t attIdx = dsResolveRef.attachment; + *p++ = attIdx; + serializeAttachmentData(attIdx); + } } QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescriptor() const @@ -6407,13 +6980,22 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri rpD->resolveRefs = resolveRefs; rpD->subpassDeps = subpassDeps; rpD->hasDepthStencil = hasDepthStencil; + rpD->hasDepthStencilResolve = hasDepthStencilResolve; + rpD->multiViewCount = multiViewCount; rpD->dsRef = dsRef; + rpD->dsResolveRef = dsResolveRef; VkRenderPassCreateInfo rpInfo; VkSubpassDescription subpassDesc; fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD); QRHI_RES_RHI(QRhiVulkan); + MultiViewRenderPassSetupHelper multiViewHelper; + if (!multiViewHelper.prepare(&rpInfo, multiViewCount, rhiD->caps.multiView)) { + delete rpD; + return nullptr; + } + VkResult err = rhiD->df->vkCreateRenderPass(rhiD->dev, &rpInfo, nullptr, &rpD->rp); if (err != VK_SUCCESS) { qWarning("Failed to create renderpass: %d", err); @@ -6502,6 +7084,11 @@ void QVkTextureRenderTarget::destroy() resrtv[att] = VK_NULL_HANDLE; } + e.textureRenderTarget.dsv = dsv; + dsv = VK_NULL_HANDLE; + e.textureRenderTarget.resdsv = resdsv; + resdsv = VK_NULL_HANDLE; + QRHI_RES_RHI(QRhiVulkan); if (rhiD) { rhiD->releaseQueue.append(e); @@ -6520,8 +7107,10 @@ QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescrip m_desc.cendColorAttachments(), m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents), m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents), + m_desc.depthTexture() && !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(), m_desc.depthStencilBuffer(), - m_desc.depthTexture())) + m_desc.depthTexture(), + m_desc.depthResolveTexture())) { delete rp; return nullptr; @@ -6538,13 +7127,13 @@ bool QVkTextureRenderTarget::create() if (d.fb) destroy(); - const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); - Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); + Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture()); Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); QRHI_RES_RHI(QRhiVulkan); QVarLengthArray<VkImageView, 8> views; + d.multiViewCount = 0; d.colorAttCount = 0; int attIndex = 0; @@ -6555,11 +7144,17 @@ bool QVkTextureRenderTarget::create() Q_ASSERT(texD || rbD); if (texD) { Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget)); + const bool is1D = texD->flags().testFlag(QRhiTexture::OneDimensional); + const bool isMultiView = it->multiViewCount() >= 2; + if (isMultiView && d.multiViewCount == 0) + d.multiViewCount = it->multiViewCount(); VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = texD->image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = texD->vkformat; + viewInfo.viewType = is1D ? VK_IMAGE_VIEW_TYPE_1D + : (isMultiView ? VK_IMAGE_VIEW_TYPE_2D_ARRAY + : VK_IMAGE_VIEW_TYPE_2D); + viewInfo.format = texD->viewFormat; viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; @@ -6568,7 +7163,7 @@ bool QVkTextureRenderTarget::create() viewInfo.subresourceRange.baseMipLevel = uint32_t(it->level()); viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->layer()); - viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.layerCount = uint32_t(isMultiView ? it->multiViewCount() : 1); VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[attIndex]); if (err != VK_SUCCESS) { qWarning("Failed to create render target image view: %d", err); @@ -6593,7 +7188,25 @@ bool QVkTextureRenderTarget::create() if (hasDepthStencil) { if (m_desc.depthTexture()) { QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture()); - views.append(depthTexD->imageView); + // need a dedicated view just because viewFormat may differ from vkformat + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = depthTexD->image; + viewInfo.viewType = d.multiViewCount > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = depthTexD->viewFormat; + viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount); + VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &dsv); + if (err != VK_SUCCESS) { + qWarning("Failed to create depth-stencil image view for rt: %d", err); + return false; + } + views.append(dsv); if (d.colorAttCount == 0) { d.pixelSize = depthTexD->pixelSize(); d.sampleCount = depthTexD->samples; @@ -6613,6 +7226,7 @@ bool QVkTextureRenderTarget::create() d.resolveAttCount = 0; attIndex = 0; + Q_ASSERT(d.multiViewCount == 0 || d.multiViewCount >= 2); for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { if (it->resolveTexture()) { QVkTexture *resTexD = QRHI_RES(QVkTexture, it->resolveTexture()); @@ -6622,8 +7236,9 @@ bool QVkTextureRenderTarget::create() VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = resTexD->image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = resTexD->vkformat; + viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY + : VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = resTexD->viewFormat; viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; @@ -6632,7 +7247,7 @@ bool QVkTextureRenderTarget::create() viewInfo.subresourceRange.baseMipLevel = uint32_t(it->resolveLevel()); viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->resolveLayer()); - viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount); VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[attIndex]); if (err != VK_SUCCESS) { qWarning("Failed to create render target resolve image view: %d", err); @@ -6642,6 +7257,36 @@ bool QVkTextureRenderTarget::create() } } + if (m_desc.depthResolveTexture()) { + QVkTexture *resTexD = QRHI_RES(QVkTexture, m_desc.depthResolveTexture()); + Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget)); + + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = resTexD->image; + viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY + : VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = resTexD->viewFormat; + viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount); + VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resdsv); + if (err != VK_SUCCESS) { + qWarning("Failed to create render target depth resolve image view: %d", err); + return false; + } + views.append(resdsv); + d.dsResolveAttCount = 1; + } else { + d.dsResolveAttCount = 0; + } + if (!m_renderPassDesc) qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()."); @@ -6651,7 +7296,7 @@ bool QVkTextureRenderTarget::create() VkFramebufferCreateInfo fbInfo = {}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = d.rp->rp; - fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount); + fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount + d.dsResolveAttCount); fbInfo.pAttachments = views.constData(); fbInfo.width = uint32_t(d.pixelSize.width()); fbInfo.height = uint32_t(d.pixelSize.height()); @@ -6740,16 +7385,12 @@ bool QVkShaderResourceBindings::create() sortedBindings.clear(); std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings)); - std::sort(sortedBindings.begin(), sortedBindings.end(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); hasSlottedResource = false; hasDynamicOffset = false; - for (const QRhiShaderResourceBinding &binding : qAsConst(sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + for (const QRhiShaderResourceBinding &binding : std::as_const(sortedBindings)) { + const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding); if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.buf) { if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->type() == QRhiBuffer::Dynamic) hasSlottedResource = true; @@ -6759,8 +7400,8 @@ bool QVkShaderResourceBindings::create() } QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings; - for (const QRhiShaderResourceBinding &binding : qAsConst(sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); + for (const QRhiShaderResourceBinding &binding : std::as_const(sortedBindings)) { + const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding); VkDescriptorSetLayoutBinding vkbinding = {}; vkbinding.binding = uint32_t(b->binding); vkbinding.descriptorType = toVkDescriptorType(b); @@ -6774,7 +7415,7 @@ bool QVkShaderResourceBindings::create() VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - layoutInfo.bindingCount = uint32_t(vkbindings.count()); + layoutInfo.bindingCount = uint32_t(vkbindings.size()); layoutInfo.pBindings = vkbindings.constData(); VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout); @@ -6794,7 +7435,7 @@ bool QVkShaderResourceBindings::create() return false; for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { - boundResourceData[i].resize(sortedBindings.count()); + boundResourceData[i].resize(sortedBindings.size()); for (BoundResourceData &bd : boundResourceData[i]) memset(&bd, 0, sizeof(BoundResourceData)); } @@ -6809,13 +7450,8 @@ void QVkShaderResourceBindings::updateResources(UpdateFlags flags) { sortedBindings.clear(); std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings)); - if (!flags.testFlag(BindingsAreSorted)) { - std::sort(sortedBindings.begin(), sortedBindings.end(), - [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) - { - return a.data()->binding < b.data()->binding; - }); - } + if (!flags.testFlag(BindingsAreSorted)) + std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan); // Reset the state tracking table too - it can deal with assigning a // different QRhiBuffer/Texture/Sampler for a binding point, but it cannot @@ -6827,7 +7463,7 @@ void QVkShaderResourceBindings::updateResources(UpdateFlags flags) // complicating the checks in setShaderResources(), reset the table here // just like we do in create(). for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { - Q_ASSERT(boundResourceData[i].count() == sortedBindings.count()); + Q_ASSERT(boundResourceData[i].size() == sortedBindings.size()); for (BoundResourceData &bd : boundResourceData[i]) memset(&bd, 0, sizeof(BoundResourceData)); } @@ -6915,11 +7551,13 @@ bool QVkGraphicsPipeline::create() shaderStageCreateInfos.append(shaderInfo); } } - pipelineInfo.stageCount = uint32_t(shaderStageCreateInfos.count()); + pipelineInfo.stageCount = uint32_t(shaderStageCreateInfos.size()); pipelineInfo.pStages = shaderStageCreateInfos.constData(); QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings; +#ifdef VK_EXT_vertex_attribute_divisor QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates; +#endif int bindingIndex = 0; for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings(); it != itEnd; ++it, ++bindingIndex) @@ -6931,9 +7569,12 @@ bool QVkGraphicsPipeline::create() ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE }; if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) { +#ifdef VK_EXT_vertex_attribute_divisor if (rhiD->caps.vertexAttribDivisor) { nonOneStepRates.append({ uint32_t(bindingIndex), it->instanceStepRate() }); - } else { + } else +#endif + { qWarning("QRhiVulkan: Instance step rates other than 1 not supported without " "VK_EXT_vertex_attribute_divisor on the device and " "VK_KHR_get_physical_device_properties2 on the instance"); @@ -6955,17 +7596,19 @@ bool QVkGraphicsPipeline::create() } VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = uint32_t(vertexBindings.count()); + vertexInputInfo.vertexBindingDescriptionCount = uint32_t(vertexBindings.size()); vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData(); - vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.count()); + vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.size()); vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData(); +#ifdef VK_EXT_vertex_attribute_divisor VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo = {}; if (!nonOneStepRates.isEmpty()) { - divisorInfo.sType = VkStructureType(1000190001); // VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT - divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.count()); + divisorInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT; + divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.size()); divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData(); vertexInputInfo.pNext = &divisorInfo; } +#endif pipelineInfo.pVertexInputState = &vertexInputInfo; QVarLengthArray<VkDynamicState, 8> dynEnable; @@ -6978,7 +7621,7 @@ bool QVkGraphicsPipeline::create() VkPipelineDynamicStateCreateInfo dynamicInfo = {}; dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicInfo.dynamicStateCount = uint32_t(dynEnable.count()); + dynamicInfo.dynamicStateCount = uint32_t(dynEnable.size()); dynamicInfo.pDynamicStates = dynEnable.constData(); pipelineInfo.pDynamicState = &dynamicInfo; @@ -7036,7 +7679,7 @@ bool QVkGraphicsPipeline::create() VkPipelineMultisampleStateCreateInfo msInfo = {}; msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - msInfo.rasterizationSamples = rhiD->effectiveSampleCount(m_sampleCount); + msInfo.rasterizationSamples = rhiD->effectiveSampleCountBits(m_sampleCount); pipelineInfo.pMultisampleState = &msInfo; VkPipelineDepthStencilStateCreateInfo dsInfo = {}; @@ -7058,7 +7701,7 @@ bool QVkGraphicsPipeline::create() VkPipelineColorBlendStateCreateInfo blendInfo = {}; blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends; - for (const QRhiGraphicsPipeline::TargetBlend &b : qAsConst(m_targetBlends)) { + for (const QRhiGraphicsPipeline::TargetBlend &b : std::as_const(m_targetBlends)) { VkPipelineColorBlendAttachmentState blend = {}; blend.blendEnable = b.enable; blend.srcColorBlendFactor = toVkBlendFactor(b.srcColor); @@ -7076,7 +7719,7 @@ bool QVkGraphicsPipeline::create() | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; vktargetBlends.append(blend); } - blendInfo.attachmentCount = uint32_t(vktargetBlends.count()); + blendInfo.attachmentCount = uint32_t(vktargetBlends.size()); blendInfo.pAttachments = vktargetBlends.constData(); pipelineInfo.pColorBlendState = &blendInfo; @@ -7235,6 +7878,7 @@ const QRhiNativeHandles *QVkCommandBuffer::nativeHandles() QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi) : QRhiSwapChain(rhi), rtWrapper(rhi, this), + rtWrapperRight(rhi, this), cbWrapper(rhi) { } @@ -7277,6 +7921,11 @@ QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget() return &rtWrapper; } +QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer) +{ + return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight; +} + QSize QVkSwapChain::surfacePixelSize() { if (!ensureSurface()) @@ -7304,6 +7953,9 @@ static inline bool hdrFormatMatchesVkSurfaceFormat(QRhiSwapChain::Format f, cons case QRhiSwapChain::HDR10: return (s.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || s.format == VK_FORMAT_A2R10G10B10_UNORM_PACK32) && s.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT; + case QRhiSwapChain::HDRExtendedDisplayP3Linear: + return s.format == VK_FORMAT_R16G16B16A16_SFLOAT + && s.colorSpace == VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT; default: break; } @@ -7397,11 +8049,9 @@ bool QVkSwapChain::ensureSurface() surface = surf; QRHI_RES_RHI(QRhiVulkan); - if (rhiD->gfxQueueFamilyIdx != -1) { - if (!rhiD->inst->supportsPresent(rhiD->physDev, uint32_t(rhiD->gfxQueueFamilyIdx), m_window)) { - qWarning("Presenting not supported on this window"); - return false; - } + if (!rhiD->inst->supportsPresent(rhiD->physDev, rhiD->gfxQueueFamilyIdx, m_window)) { + qWarning("Presenting not supported on this window"); + return false; } quint32 formatCount = 0; @@ -7415,11 +8065,9 @@ bool QVkSwapChain::ensureSurface() const bool srgbRequested = m_flags.testFlag(sRGB); for (int i = 0; i < int(formatCount); ++i) { if (formats[i].format != VK_FORMAT_UNDEFINED) { - bool ok = false; - if (m_format == SDR) - ok = srgbRequested == isSrgbFormat(formats[i].format); - else - ok = hdrFormatMatchesVkSurfaceFormat(m_format, formats[i]); + bool ok = srgbRequested == isSrgbFormat(formats[i].format); + if (m_format != SDR) + ok &= hdrFormatMatchesVkSurfaceFormat(m_format, formats[i]); if (ok) { colorFormat = formats[i].format; colorSpace = formats[i].colorSpace; @@ -7428,7 +8076,7 @@ bool QVkSwapChain::ensureSurface() } } - samples = rhiD->effectiveSampleCount(m_sampleCount); + samples = rhiD->effectiveSampleCountBits(m_sampleCount); quint32 presModeCount = 0; rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr); @@ -7499,6 +8147,7 @@ bool QVkSwapChain::createOrResize() rtWrapper.d.dsAttCount = 0; ds = nullptr; } + rtWrapper.d.dsResolveAttCount = 0; if (samples > VK_SAMPLE_COUNT_1_BIT) rtWrapper.d.resolveAttCount = 1; else @@ -7515,7 +8164,7 @@ bool QVkSwapChain::createOrResize() VkFramebufferCreateInfo fbInfo = {}; fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbInfo.renderPass = rtWrapper.d.rp->rp; - fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount); + fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount + rtWrapper.d.dsResolveAttCount); fbInfo.pAttachments = views; fbInfo.width = uint32_t(pixelSize.width()); fbInfo.height = uint32_t(pixelSize.height()); @@ -7528,6 +8177,56 @@ bool QVkSwapChain::createOrResize() } } + if (stereo) { + rtWrapperRight.setRenderPassDescriptor( + m_renderPassDesc); // for the public getter in QRhiRenderTarget + rtWrapperRight.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); + Q_ASSERT(rtWrapperRight.d.rp && rtWrapperRight.d.rp->rp); + + rtWrapperRight.d.pixelSize = pixelSize; + rtWrapperRight.d.dpr = float(window->devicePixelRatio()); + rtWrapperRight.d.sampleCount = samples; + rtWrapperRight.d.colorAttCount = 1; + if (m_depthStencil) { + rtWrapperRight.d.dsAttCount = 1; + ds = QRHI_RES(QVkRenderBuffer, m_depthStencil); + } else { + rtWrapperRight.d.dsAttCount = 0; + ds = nullptr; + } + rtWrapperRight.d.dsResolveAttCount = 0; + if (samples > VK_SAMPLE_COUNT_1_BIT) + rtWrapperRight.d.resolveAttCount = 1; + else + rtWrapperRight.d.resolveAttCount = 0; + + for (int i = 0; i < bufferCount; ++i) { + QVkSwapChain::ImageResources &image(imageRes[i + bufferCount]); + VkImageView views[3] = { + // color, ds, resolve + samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView, + ds ? ds->imageView : VK_NULL_HANDLE, + samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE + }; + + VkFramebufferCreateInfo fbInfo = {}; + fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbInfo.renderPass = rtWrapperRight.d.rp->rp; + fbInfo.attachmentCount = uint32_t(rtWrapperRight.d.colorAttCount + rtWrapperRight.d.dsAttCount + + rtWrapperRight.d.resolveAttCount + rtWrapperRight.d.dsResolveAttCount); + fbInfo.pAttachments = views; + fbInfo.width = uint32_t(pixelSize.width()); + fbInfo.height = uint32_t(pixelSize.height()); + fbInfo.layers = 1; + + VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb); + if (err != VK_SUCCESS) { + qWarning("Failed to create framebuffer: %d", err); + return false; + } + } + } + frameCount = 0; if (needsRegistration) |