diff options
Diffstat (limited to 'src/gui/rhi/qrhivulkan.cpp')
-rw-r--r-- | src/gui/rhi/qrhivulkan.cpp | 2579 |
1 files changed, 1664 insertions, 915 deletions
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index e7e31b1b2d..da8c8fc52a 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -1,46 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Gui module -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qrhivulkan_p_p.h" -#include "qrhivulkanext_p.h" +// 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.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 @@ -49,12 +14,16 @@ #endif QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wsuggest-override") +#if defined(Q_CC_CLANG) && Q_CC_CLANG >= 1100 +QT_WARNING_DISABLE_CLANG("-Wdeprecated-copy") +#endif #include "vk_mem_alloc.h" QT_WARNING_POP #include <qmath.h> #include <QVulkanFunctions> #include <QtGui/qwindow.h> +#include <optional> QT_BEGIN_NAMESPACE @@ -90,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: @@ -104,18 +76,7 @@ QT_BEGIN_NAMESPACE ... QVulkanInstance inst; - #ifndef Q_OS_ANDROID - inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); - #else - inst.setLayers(QByteArrayList() - << "VK_LAYER_GOOGLE_threading" - << "VK_LAYER_LUNARG_parameter_validation" - << "VK_LAYER_LUNARG_object_tracker" - << "VK_LAYER_LUNARG_core_validation" - << "VK_LAYER_LUNARG_image" - << "VK_LAYER_LUNARG_swapchain" - << "VK_LAYER_GOOGLE_unique_objects"); - #endif + inst.setLayers({ "VK_LAYER_KHRONOS_validation" }); // for debugging only, not for release builds inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions()); if (!inst.create()) qFatal("Vulkan not available"); @@ -160,11 +121,21 @@ QT_BEGIN_NAMESPACE in deviceExtensions. This can be relevant when integrating with native Vulkan rendering code. - It is expected that the desired list of instance extensions will be queried - by calling the static function preferredInstanceExtensions() before - initializing a QVulkanInstance. The returned list can be passed to - QVulkanInstance::setExtensions() as-is, because unsupported extensions are - filtered out automatically. + It is expected that the backend's desired list of instance extensions will + be queried by calling the static function preferredInstanceExtensions() + before initializing a QVulkanInstance. The returned list can be safely + passed to QVulkanInstance::setExtensions() as-is, because unsupported + extensions are filtered out automatically. If this is not done, certain + features, such as QRhi::CustomInstanceStepRate may be reported as + unsupported even when the Vulkan implementation on the system has support + for the relevant functionality. + + For full functionality the QVulkanInstance needs to have API 1.1 enabled, + when available. This means calling QVulkanInstance::setApiVersion() with + 1.1 or higher whenever QVulkanInstance::supportedApiVersion() reports that + at least Vulkan 1.1 is supported. If this is not done, certain features, + such as QRhi::RenderTo3DTextureSlice may be reported as unsupported even + when the Vulkan implementation on the system supports Vulkan 1.1 or newer. \section2 Working with existing Vulkan devices @@ -197,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) { @@ -233,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) -{ - return globalVulkanInstance->deviceFunctions(device)->vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); -} - -void VKAPI_PTR wrap_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator) -{ - 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) +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetInstanceProcAddr(VkInstance, const char *pName) { - globalVulkanInstance->deviceFunctions(device)->vkUnmapMemory(device, memory); + return globalVulkanInstance->getInstanceProcAddr(pName); } -VkResult VKAPI_PTR wrap_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges) +static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetDeviceProcAddr(VkDevice device, const char *pName) { - 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) @@ -323,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 { @@ -330,11 +325,15 @@ 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_debug_marker"), QByteArrayLiteral("VK_EXT_vertex_attribute_divisor") }; } @@ -362,20 +361,19 @@ QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *im } } -static bool qvk_debug_filter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, - size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage) +static bool qvk_debug_filter(QVulkanInstance::DebugMessageSeverityFlags severity, + QVulkanInstance::DebugMessageTypeFlags type, + const void *callbackData) { - Q_UNUSED(flags); - Q_UNUSED(objectType); - Q_UNUSED(object); - Q_UNUSED(location); - Q_UNUSED(messageCode); - Q_UNUSED(pLayerPrefix); + Q_UNUSED(severity); + Q_UNUSED(type); +#ifdef VK_EXT_debug_utils + const VkDebugUtilsMessengerCallbackDataEXT *d = static_cast<const VkDebugUtilsMessengerCallbackDataEXT *>(callbackData); // Filter out certain misleading validation layer messages, as per // VulkanMemoryAllocator documentation. - if (strstr(pMessage, "Mapping an image with layout") - && strstr(pMessage, "can result in undefined behavior if this memory is used by the device")) + if (strstr(d->pMessage, "Mapping an image with layout") + && strstr(d->pMessage, "can result in undefined behavior if this memory is used by the device")) { return true; } @@ -386,9 +384,11 @@ static bool qvk_debug_filter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTyp // then move on to another pool. If there is a real error, a qWarning // message is shown by allocateDescriptorSet(), so the validation warning // does not have any value and is just noise. - if (strstr(pMessage, "VUID-VkDescriptorSetAllocateInfo-descriptorPool-00307")) + if (strstr(d->pMessage, "VUID-VkDescriptorSetAllocateInfo-descriptorPool-00307")) return true; - +#else + Q_UNUSED(callbackData); +#endif return false; } @@ -418,11 +418,17 @@ bool QRhiVulkan::create(QRhi::Flags flags) return false; } - globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application + rhiFlags = flags; + qCDebug(QRHI_LOG_INFO, "Initializing QRhi Vulkan backend %p with flags %d", this, int(rhiFlags)); + globalVulkanInstance = inst; // used for function resolving in vkmemalloc callbacks f = inst->functions(); - - rhiFlags = flags; + if (QRHI_LOG_INFO().isEnabled(QtDebugMsg)) { + qCDebug(QRHI_LOG_INFO, "Enabled instance extensions:"); + for (const char *ext : inst->extensions()) + qCDebug(QRHI_LOG_INFO, " %s", ext); + } + caps.debugUtils = inst->extensions().contains(QByteArrayLiteral("VK_EXT_debug_utils")); QList<VkQueueFamilyProperties> queueFamilyProps; auto queryQueueFamilyProps = [this, &queueFamilyProps] { @@ -503,54 +509,129 @@ bool QRhiVulkan::create(QRhi::Flags flags) physDevProperties.deviceType); } + caps.apiVersion = inst->apiVersion(); + + // Check the physical device API version against the instance API version, + // they do not have to match, which means whatever version was set in the + // QVulkanInstance may not be legally used with a given device if the + // physical device has a lower version. + const QVersionNumber physDevApiVersion(VK_VERSION_MAJOR(physDevProperties.apiVersion), + VK_VERSION_MINOR(physDevProperties.apiVersion)); // patch version left out intentionally + if (physDevApiVersion < caps.apiVersion) { + qCDebug(QRHI_LOG_INFO) << "Instance has api version" << caps.apiVersion + << "whereas the chosen physical device has" << physDevApiVersion + << "- restricting to the latter"; + caps.apiVersion = physDevApiVersion; + } + driverInfoStruct.deviceName = QByteArray(physDevProperties.deviceName); driverInfoStruct.deviceId = physDevProperties.deviceID; 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; } } - VkDeviceQueueCreateInfo queueInfo[2]; + VkDeviceQueueCreateInfo queueInfo = {}; const float prio[] = { 0 }; - memset(queueInfo, 0, sizeof(queueInfo)); - queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueInfo[0].queueFamilyIndex = uint32_t(gfxQueueFamilyIdx); - queueInfo[0].queueCount = 1; - queueInfo[0].pQueuePriorities = prio; + queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueInfo.queueFamilyIndex = gfxQueueFamilyIdx; + queueInfo.queueCount = 1; + queueInfo.pQueuePriorities = prio; QList<const char *> devLayers; - if (inst->layers().contains("VK_LAYER_LUNARG_standard_validation")) - devLayers.append("VK_LAYER_LUNARG_standard_validation"); + if (inst->layers().contains("VK_LAYER_KHRONOS_validation")) + devLayers.append("VK_LAYER_KHRONOS_validation"); QVulkanInfoVector<QVulkanExtension> devExts; uint32_t devExtCount = 0; @@ -558,44 +639,56 @@ 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"); - debugMarkersAvailable = false; - if (devExts.contains(VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) { - requestedDevExts.append(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); - debugMarkersAvailable = true; + 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."); + } } - vertexAttribDivisorAvailable = false; + caps.vertexAttribDivisor = false; +#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); - vertexAttribDivisorAvailable = true; + caps.vertexAttribDivisor = 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()); + } } } @@ -605,29 +698,50 @@ bool QRhiVulkan::create(QRhi::Flags flags) qCDebug(QRHI_LOG_INFO, " %s", ext); } - VkDeviceCreateInfo devInfo; - memset(&devInfo, 0, sizeof(devInfo)); + VkDeviceCreateInfo devInfo = {}; devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; devInfo.queueCreateInfoCount = 1; - devInfo.pQueueCreateInfos = queueInfo; - devInfo.enabledLayerCount = uint32_t(devLayers.count()); + devInfo.pQueueCreateInfos = &queueInfo; + 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(); - VkPhysicalDeviceFeatures features; - memset(&features, 0, sizeof(features)); - if (physDevFeatures.wideLines) - features.wideLines = VK_TRUE; - if (physDevFeatures.largePoints) - features.largePoints = VK_TRUE; - if (physDevFeatures.textureCompressionETC2) - features.textureCompressionETC2 = VK_TRUE; - if (physDevFeatures.textureCompressionASTC_LDR) - features.textureCompressionASTC_LDR = VK_TRUE; - if (physDevFeatures.textureCompressionBC) - features.textureCompressionBC = VK_TRUE; - devInfo.pEnabledFeatures = &features; + // Enable all features that are reported as supported, except + // robustness because that potentially affects performance. + // + // Enabling all features mainly serves third-party renderers that may + // use the VkDevice created here. For the record, the backend here + // optionally relies on the following features, meaning just for our + // (QRhi/Quick/Quick 3D) purposes it would be sufficient to + // enable-if-supported only the following: + // + // wideLines, largePoints, fillModeNonSolid, + // tessellationShader, geometryShader + // textureCompressionETC2, textureCompressionASTC_LDR, textureCompressionBC + +#ifdef VK_VERSION_1_1 + physDevFeaturesChainable.features.robustBufferAccess = VK_FALSE; +#endif +#ifdef VK_VERSION_1_3 + physDevFeatures13.robustImageAccess = VK_FALSE; +#endif + +#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 + { + physDevFeatures.robustBufferAccess = VK_FALSE; + devInfo.pEnabledFeatures = &physDevFeatures; + } VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev); if (err != VK_SUCCESS) { @@ -638,12 +752,18 @@ bool QRhiVulkan::create(QRhi::Flags flags) qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); } + vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>( + inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); + vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>( + inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR")); + vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>( + inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfacePresentModesKHR")); + df = inst->deviceFunctions(dev); - VkCommandPoolCreateInfo poolInfo; - memset(&poolInfo, 0, sizeof(poolInfo)); + 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) { @@ -652,18 +772,15 @@ 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(); - hasCompute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; + caps.compute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits; ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment; @@ -671,35 +788,41 @@ bool QRhiVulkan::create(QRhi::Flags flags) // elsewhere states that the minimum bufferOffset is 4... texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment); - hasWideLines = physDevFeatures.wideLines; + caps.wideLines = physDevFeatures.wideLines; + + caps.texture3DSliceAs2D = caps.apiVersion >= QVersionNumber(1, 1); + + caps.tessellation = physDevFeatures.tessellationShader; + caps.geometryShader = physDevFeatures.geometryShader; + + 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 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; - - VmaAllocatorCreateInfo allocatorInfo; - memset(&allocatorInfo, 0, sizeof(allocatorInfo)); + 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 // the allocator's own mutexes. This gives a performance boost. 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) { @@ -718,8 +841,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) else qWarning("Failed to create initial descriptor pool: %d", err); - VkQueryPoolCreateInfo timestampQueryPoolInfo; - memset(×tampQueryPoolInfo, 0, sizeof(timestampQueryPoolInfo)); + VkQueryPoolCreateInfo timestampQueryPoolInfo = {}; timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP; timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2; @@ -731,12 +853,14 @@ bool QRhiVulkan::create(QRhi::Flags flags) timestampQueryPoolMap.resize(QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair timestampQueryPoolMap.fill(false); - if (debugMarkersAvailable) { - vkCmdDebugMarkerBegin = reinterpret_cast<PFN_vkCmdDebugMarkerBeginEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerBeginEXT")); - vkCmdDebugMarkerEnd = reinterpret_cast<PFN_vkCmdDebugMarkerEndEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerEndEXT")); - vkCmdDebugMarkerInsert = reinterpret_cast<PFN_vkCmdDebugMarkerInsertEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerInsertEXT")); - vkDebugMarkerSetObjectName = reinterpret_cast<PFN_vkDebugMarkerSetObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkDebugMarkerSetObjectNameEXT")); +#ifdef VK_EXT_debug_utils + if (caps.debugUtils) { + vkSetDebugUtilsObjectNameEXT = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkSetDebugUtilsObjectNameEXT")); + vkCmdBeginDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdBeginDebugUtilsLabelEXT")); + vkCmdEndDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdEndDebugUtilsLabelEXT")); + vkCmdInsertDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdInsertDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdInsertDebugUtilsLabelEXT")); } +#endif deviceLost = false; @@ -746,6 +870,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) nativeHandlesStruct.gfxQueueIdx = gfxQueueIdx; nativeHandlesStruct.gfxQueue = gfxQueue; nativeHandlesStruct.vmemAllocator = allocator; + nativeHandlesStruct.inst = inst; return true; } @@ -814,8 +939,7 @@ VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool) { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, QVK_STORAGE_BUFFERS_PER_POOL }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, QVK_STORAGE_IMAGES_PER_POOL } }; - VkDescriptorPoolCreateInfo descPoolInfo; - memset(&descPoolInfo, 0, sizeof(descPoolInfo)); + VkDescriptorPoolCreateInfo descPoolInfo = {}; descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; // Do not enable vkFreeDescriptorSets - sets are never freed on their own // (good so no trouble with fragmentation), they just deref their pool @@ -837,7 +961,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); @@ -857,7 +981,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); @@ -900,6 +1024,10 @@ static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture case QRhiTexture::R32F: return VK_FORMAT_R32_SFLOAT; + case QRhiTexture::RGB10A2: + // intentionally A2B10G10R10, not A2R10G10B10 + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case QRhiTexture::D16: return VK_FORMAT_D16_UNORM; case QRhiTexture::D24: @@ -961,12 +1089,11 @@ 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); } } -static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format, QRhiTexture::Flags *flags) +static inline QRhiTexture::Format swapchainReadbackTextureFormat(VkFormat format, QRhiTexture::Flags *flags) { switch (format) { case VK_FORMAT_R8G8B8A8_UNORM: @@ -981,24 +1108,14 @@ static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format if (flags) (*flags) |= QRhiTexture::sRGB; return QRhiTexture::BGRA8; - case VK_FORMAT_R8_UNORM: - return QRhiTexture::R8; - case VK_FORMAT_R8G8_UNORM: - return QRhiTexture::RG8; - case VK_FORMAT_R8_SRGB: - if (flags) - (*flags) |= QRhiTexture::sRGB; - return QRhiTexture::R8; - case VK_FORMAT_R8G8_SRGB: - if (flags) - (*flags) |= QRhiTexture::sRGB; - return QRhiTexture::RG8; - case VK_FORMAT_R16_UNORM: - return QRhiTexture::R16; - case VK_FORMAT_R16G16_UNORM: - return QRhiTexture::RG16; - default: // this cannot assert, must warn and return unknown - qWarning("VkFormat %d is not a recognized uncompressed color format", format); + case VK_FORMAT_R16G16B16A16_SFLOAT: + return QRhiTexture::RGBA16F; + case VK_FORMAT_R32G32B32A32_SFLOAT: + return QRhiTexture::RGBA32F; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + return QRhiTexture::RGB10A2; + default: + qWarning("VkFormat %d cannot be read back", format); break; } return QRhiTexture::UnknownFormat; @@ -1074,8 +1191,7 @@ bool QRhiVulkan::createTransientImage(VkFormat format, VkResult err; for (int i = 0; i < count; ++i) { - VkImageCreateInfo imgInfo; - memset(&imgInfo, 0, sizeof(imgInfo)); + VkImageCreateInfo imgInfo = {}; imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imgInfo.imageType = VK_IMAGE_TYPE_2D; imgInfo.format = format; @@ -1100,8 +1216,7 @@ bool QRhiVulkan::createTransientImage(VkFormat format, df->vkGetImageMemoryRequirements(dev, images[i], &memReq); } - VkMemoryAllocateInfo memInfo; - memset(&memInfo, 0, sizeof(memInfo)); + VkMemoryAllocateInfo memInfo = {}; memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * VkDeviceSize(count); @@ -1129,8 +1244,7 @@ bool QRhiVulkan::createTransientImage(VkFormat format, } ofs += aligned(memReq.size, memReq.alignment); - VkImageViewCreateInfo imgViewInfo; - memset(&imgViewInfo, 0, sizeof(imgViewInfo)); + VkImageViewCreateInfo imgViewInfo = {}; imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; imgViewInfo.image = images[i]; imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -1184,18 +1298,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; } @@ -1203,8 +1317,7 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD { // attachment list layout is color (1), ds (0-1), resolve (0-1) - VkAttachmentDescription attDesc; - memset(&attDesc, 0, sizeof(attDesc)); + VkAttachmentDescription attDesc = {}; attDesc.format = colorFormat; attDesc.samples = samples; attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -1218,6 +1331,7 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); rpD->hasDepthStencil = hasDepthStencil; + rpD->multiViewCount = 0; if (hasDepthStencil) { // clear on load + no store + lazy alloc + transient image should play @@ -1252,8 +1366,7 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD } // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own. - VkSubpassDependency subpassDep; - memset(&subpassDep, 0, sizeof(subpassDep)); + VkSubpassDependency subpassDep = {}; subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; subpassDep.dstSubpass = 0; subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; @@ -1288,29 +1401,63 @@ 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 +}; + bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, const QRhiColorAttachment *firstColorAttachment, const QRhiColorAttachment *lastColorAttachment, bool preserveColor, bool preserveDs, + bool storeDs, QRhiRenderBuffer *depthStencilBuffer, QRhiTexture *depthTexture) { // attachment list layout is color (0-8), ds (0-1), resolve (0-8) + 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; - memset(&attDesc, 0, sizeof(attDesc)); + 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 @@ -1318,31 +1465,41 @@ 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; - VkAttachmentDescription attDesc; - memset(&attDesc, 0, sizeof(attDesc)); + const VkAttachmentStoreOp storeOp = storeDs ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; + VkAttachmentDescription attDesc = {}; attDesc.format = dsFormat; attDesc.samples = samples; attDesc.loadOp = loadOp; 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()) { @@ -1362,9 +1519,8 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, int(srcFormat), int(dstFormat)); } - VkAttachmentDescription attDesc; - memset(&attDesc, 0, sizeof(attDesc)); - attDesc.format = dstFormat; + 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; @@ -1374,14 +1530,14 @@ 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->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 @@ -1392,6 +1548,10 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, VkSubpassDescription subpassDesc; fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD); + MultiViewRenderPassSetupHelper multiViewHelper; + if (!multiViewHelper.prepare(&rpInfo, multiViewCount, caps.multiView)) + return false; + VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); if (err != VK_SUCCESS) { qWarning("Failed to create renderpass: %d", err); @@ -1436,21 +1596,45 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : surfaceCaps.currentTransform; + // This looks odd but matches how platforms work in practice. + // + // On Windows with NVIDIA for example, the only supportedCompositeAlpha + // value reported is OPAQUE, nothing else. Yet transparency works + // regardless, as long as the native window is set up correctly, so that's + // not something we need to handle here. + // + // On Linux with Intel and Mesa and running on xcb reports, on one + // particular system, INHERIT+PRE_MULTIPLIED. Tranparency works, regardless, + // presumably due to setting INHERIT. + // + // On the same setup with Wayland instead of xcb we see + // OPAQUE+PRE_MULTIPLIED reported. Here transparency won't work unless + // PRE_MULTIPLIED is set. + // + // Therefore our rules are: + // - Prefer INHERIT over OPAQUE. + // - Then based on the request, try the requested alpha mode, but if + // that's not reported as supported, try also the other (PRE/POST, + // POST/PRE) as that is better than nothing. This is not different from + // some other backends, e.g. D3D11 with DirectComposition there is also + // no control over being straight or pre-multiplied. Whereas with + // WGL/GLX/EGL we never had that sort of control. + VkCompositeAlphaFlagBitsKHR compositeAlpha = (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasPreMulAlpha) - && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)) - { - compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; - } - - if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasNonPreMulAlpha) - && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)) - { - compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasPreMulAlpha)) { + if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + } else if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasNonPreMulAlpha)) { + if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; } VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -1458,9 +1642,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; @@ -1477,15 +1668,14 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) reuseExisting ? "recycled" : "new", reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode); - VkSwapchainCreateInfoKHR swapChainInfo; - memset(&swapChainInfo, 0, sizeof(swapChainInfo)); + VkSwapchainCreateInfoKHR swapChainInfo = {}; swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapChainInfo.surface = swapChainD->surface; swapChainInfo.minImageCount = reqBufferCount; 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; @@ -1543,12 +1733,13 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) } } - VkFenceCreateInfo fenceInfo; - memset(&fenceInfo, 0, sizeof(fenceInfo)); + VkFenceCreateInfo fenceInfo = {}; 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]; @@ -1557,8 +1748,7 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) image.msaaImageView = msaaViews[i]; } - VkImageViewCreateInfo imgViewInfo; - memset(&imgViewInfo, 0, sizeof(imgViewInfo)); + VkImageViewCreateInfo imgViewInfo = {}; imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; imgViewInfo.image = swapChainImages[i]; imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -1577,11 +1767,40 @@ 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; - VkSemaphoreCreateInfo semInfo; - memset(&semInfo, 0, sizeof(semInfo)); + VkSemaphoreCreateInfo semInfo = {}; semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { @@ -1645,7 +1864,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); @@ -1687,7 +1906,25 @@ void QRhiVulkan::ensureCommandPoolForNewFrame() flags |= VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT; // put all command buffers allocated from this slot's pool to initial state - df->vkResetCommandPool(dev, cmdPool[currentFrameSlot], 0); + 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) @@ -1695,7 +1932,8 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); - QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); + + 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 @@ -1740,32 +1978,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; - // now we have the gpu time for the previous frame for this slot, report it - // (does not matter that it is not for this frame) - QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs)); - } - } else { - qWarning("Failed to query timestamp: %d", err); - } - } - currentFrameSlot = int(swapChainD->currentFrameSlot); currentSwapChain = swapChainD; if (swapChainD->ds) @@ -1779,34 +1991,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 (profilerPrivateOrNull() && swapChainD->bufferCount > 1) { // no timestamps if not having at least 2 frames in flight - for (int i = 0; i < timestampQueryPoolMap.count(); ++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; - } - swapChainD->cbWrapper.cb = frame.cmdBuf; QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); swapChainD->rtWrapper.d.fb = image.fb; - QRHI_PROF_F(beginSwapChainFrame(swapChain)); + 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; + } + } + return QRhi::FrameOpSuccess; } @@ -1815,6 +2049,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; @@ -1822,8 +2060,7 @@ QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFram QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); if (image.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { - VkImageMemoryBarrier presTrans; - memset(&presTrans, 0, sizeof(presTrans)); + VkImageMemoryBarrier presTrans = {}; presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; @@ -1870,14 +2107,9 @@ QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFram frame.imageSemWaitable = false; frame.cmdFenceWaitable = true; - QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - // this must be done before the Present - QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1)); - if (needsPresent) { // add the Present to the queue - VkPresentInfoKHR presInfo; - memset(&presInfo, 0, sizeof(presInfo)); + VkPresentInfoKHR presInfo = {}; presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presInfo.swapchainCount = 1; presInfo.pSwapchains = &swapChainD->sc; @@ -1930,8 +2162,8 @@ void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb) // // With multiple swapchains on the same QRhi things get more convoluted // (and currentFrameSlot strictly alternating is not true anymore) but - // beginNonWrapperFrame() solves that by blocking as necessary so the rest - // here is safe regardless. + // begin(Offscreen)Frame() blocks anyway waiting for its current frame + // slot's previous commands to complete so this here is safe regardless. executeDeferredReleases(); @@ -1945,8 +2177,7 @@ void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb) QRhi::FrameOpResult QRhiVulkan::startPrimaryCommandBuffer(VkCommandBuffer *cb) { if (!*cb) { - VkCommandBufferAllocateInfo cmdBufInfo; - memset(&cmdBufInfo, 0, sizeof(cmdBufInfo)); + VkCommandBufferAllocateInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufInfo.commandPool = cmdPool[currentFrameSlot]; cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -1964,8 +2195,7 @@ QRhi::FrameOpResult QRhiVulkan::startPrimaryCommandBuffer(VkCommandBuffer *cb) } } - VkCommandBufferBeginInfo cmdBufBeginInfo; - memset(&cmdBufBeginInfo, 0, sizeof(cmdBufBeginInfo)); + VkCommandBufferBeginInfo cmdBufBeginInfo = {}; cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; VkResult err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo); @@ -1996,8 +2226,7 @@ QRhi::FrameOpResult QRhiVulkan::endAndSubmitPrimaryCommandBuffer(VkCommandBuffer return QRhi::FrameOpError; } - VkSubmitInfo submitInfo; - memset(&submitInfo, 0, sizeof(submitInfo)); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &cb; @@ -2028,7 +2257,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) { @@ -2042,17 +2271,17 @@ void QRhiVulkan::waitCommandCompletion(int frameSlot) QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags) { // Switch to the next slot manually. Swapchains do not know about this - // which is good. So for example a - unusual but possible - onscreen, - // onscreen, offscreen, onscreen, onscreen, onscreen sequence of - // begin/endFrame leads to 0, 1, 0, 0, 1, 0. This works because the - // offscreen frame is synchronous in the sense that we wait for execution - // to complete in endFrame, and so no resources used in that frame are busy + // which is good. So for example an onscreen, onscreen, offscreen, + // onscreen, onscreen, onscreen sequence of frames leads to 0, 1, 0, 0, 1, + // 0. (no strict alternation anymore) But this is not different from what + // happens when multiple swapchains are involved. Offscreen frames are + // synchronous anyway in the sense that they wait for execution to complete + // in endOffscreenFrame, so no resources used in that frame are busy // anymore in the next frame. + currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT; - // except that this gets complicated with multiple swapchains so make sure - // any pending commands have finished for the frame slot we are going to use - if (swapchains.count() > 1) - waitCommandCompletion(currentFrameSlot); + + waitCommandCompletion(currentFrameSlot); ensureCommandPoolForNewFrame(); @@ -2064,6 +2293,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; } @@ -2077,9 +2324,14 @@ 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; - memset(&fenceInfo, 0, sizeof(fenceInfo)); + VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence); if (err != VK_SUCCESS) { @@ -2100,6 +2352,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; } @@ -2171,6 +2441,9 @@ static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD) { + if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(rtD->description(), rtD->d.currentResIdList)) + rtD->create(); + rtD->lastActiveFrameSlot = currentFrameSlot; rtD->d.rp->lastActiveFrameSlot = currentFrameSlot; QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); @@ -2232,8 +2505,7 @@ VkCommandBuffer QRhiVulkan::startSecondaryCommandBuffer(QVkRenderTargetData *rtD secondaryCb = freeSecondaryCbs[currentFrameSlot].last(); freeSecondaryCbs[currentFrameSlot].removeLast(); } else { - VkCommandBufferAllocateInfo cmdBufInfo; - memset(&cmdBufInfo, 0, sizeof(cmdBufInfo)); + VkCommandBufferAllocateInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdBufInfo.commandPool = cmdPool[currentFrameSlot]; cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; @@ -2246,12 +2518,10 @@ VkCommandBuffer QRhiVulkan::startSecondaryCommandBuffer(QVkRenderTargetData *rtD } } - VkCommandBufferBeginInfo cmdBufBeginInfo; - memset(&cmdBufBeginInfo, 0, sizeof(cmdBufBeginInfo)); + VkCommandBufferBeginInfo cmdBufBeginInfo = {}; cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufBeginInfo.flags = rtD ? VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT : 0; - VkCommandBufferInheritanceInfo cmdBufInheritInfo; - memset(&cmdBufInheritInfo, 0, sizeof(cmdBufInheritInfo)); + VkCommandBufferInheritanceInfo cmdBufInheritInfo = {}; cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; cmdBufInheritInfo.subpass = 0; if (rtD) { @@ -2307,8 +2577,8 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, QVkRenderTargetData *rtD = nullptr; switch (rt->resourceType()) { - case QRhiResource::RenderTarget: - rtD = &QRHI_RES(QVkReferenceRenderTarget, rt)->d; + case QRhiResource::SwapChainRenderTarget: + rtD = &QRHI_RES(QVkSwapChainRenderTarget, rt)->d; rtD->rp->lastActiveFrameSlot = currentFrameSlot; Q_ASSERT(currentSwapChain); currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse = @@ -2333,8 +2603,7 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, // No copy operations or image layout transitions allowed after this point // (up until endPass) as we are going to begin the renderpass. - VkRenderPassBeginInfo rpBeginInfo; - memset(&rpBeginInfo, 0, sizeof(rpBeginInfo)); + VkRenderPassBeginInfo rpBeginInfo = {}; rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; rpBeginInfo.renderPass = rtD->rp->rp; rpBeginInfo.framebuffer = rtD->fb; @@ -2359,17 +2628,19 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, float(colorClearValue.alphaF()) } }; cvs.append(cv); } - rpBeginInfo.clearValueCount = uint32_t(cvs.count()); + 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)); + + cbD->resetCachedState(); } void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -2381,7 +2652,6 @@ void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourc VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last(); cbD->activeSecondaryCbStack.removeLast(); endAndEnqueueSecondaryCommandBuffer(secondaryCb, cbD); - cbD->resetCachedState(); } QVkCommandBuffer::Command &cmd(cbD->commands.get()); @@ -2413,6 +2683,8 @@ void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, if (cbD->passUsesSecondaryCb) cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer()); + + cbD->resetCachedState(); } void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -2424,7 +2696,6 @@ void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch * VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last(); cbD->activeSecondaryCbStack.removeLast(); endAndEnqueueSecondaryCommandBuffer(secondaryCb, cbD); - cbD->resetCachedState(); } cbD->recordingPass = QVkCommandBuffer::NoPass; @@ -2495,9 +2766,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: @@ -2530,8 +2801,7 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) if (accessInThisDispatch && !isNewInThisDispatch) { if (it.key()->resourceType() == QRhiResource::Texture) { QVkTexture *texD = QRHI_RES(QVkTexture, it.key()); - VkImageMemoryBarrier barrier; - memset(&barrier, 0, sizeof(barrier)); + VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; // won't care about subresources, pretend the whole resource was written @@ -2547,8 +2817,7 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) imageBarriers.append(barrier); } else { QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key()); - VkBufferMemoryBarrier barrier; - memset(&barrier, 0, sizeof(barrier)); + VkBufferMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; @@ -2575,12 +2844,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)); @@ -2590,18 +2859,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; @@ -2613,8 +2882,7 @@ void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv) { - VkShaderModuleCreateInfo shaderInfo; - memset(&shaderInfo, 0, sizeof(shaderInfo)); + VkShaderModuleCreateInfo shaderInfo = {}; shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; shaderInfo.codeSize = size_t(spirv.size()); shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData()); @@ -2632,8 +2900,7 @@ bool QRhiVulkan::ensurePipelineCache(const void *initialData, size_t initialData if (pipelineCache) return true; - VkPipelineCacheCreateInfo pipelineCacheInfo; - memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo)); + VkPipelineCacheCreateInfo pipelineCacheInfo = {}; pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; pipelineCacheInfo.initialDataSize = initialDataSize; pipelineCacheInfo.pInitialData = initialData; @@ -2658,12 +2925,11 @@ 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; - memset(&writeInfo, 0, sizeof(writeInfo)); + VkWriteDescriptorSet writeInfo = {}; writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeInfo.dstSet = srbD->descSets[frameSlot]; writeInfo.dstBinding = uint32_t(b->binding); @@ -2683,17 +2949,17 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i bd.ubuf.generation = bufD->generation; VkDescriptorBufferInfo bufInfo; bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; - bufInfo.offset = VkDeviceSize(b->u.ubuf.offset); - bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size); + bufInfo.offset = b->u.ubuf.offset; + 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; case QRhiShaderResourceBinding::SampledTexture: { - const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; ArrayOfImageDesc imageInfo(data->count); @@ -2709,7 +2975,44 @@ 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; + case QRhiShaderResourceBinding::Texture: + { + const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; + writeInfo.descriptorCount = data->count; // arrays of (separate) images are supported + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + ArrayOfImageDesc imageInfo(data->count); + for (int elem = 0; elem < data->count; ++elem) { + QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); + bd.stex.d[elem].texId = texD->m_id; + bd.stex.d[elem].texGeneration = texD->generation; + bd.stex.d[elem].samplerId = 0; + bd.stex.d[elem].samplerGeneration = 0; + imageInfo[elem].sampler = VK_NULL_HANDLE; + imageInfo[elem].imageView = texD->imageView; + imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + bd.stex.count = data->count; + imageInfoIndex = imageInfos.size(); + imageInfos.append(imageInfo); + } + break; + case QRhiShaderResourceBinding::Sampler: + { + QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.texSamplers[0].sampler); + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + bd.stex.d[0].texId = 0; + bd.stex.d[0].texGeneration = 0; + bd.stex.d[0].samplerId = samplerD->m_id; + bd.stex.d[0].samplerGeneration = samplerD->generation; + ArrayOfImageDesc imageInfo(1); + imageInfo[0].sampler = samplerD->sampler; + imageInfo[0].imageView = VK_NULL_HANDLE; + imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + imageInfoIndex = imageInfos.size(); imageInfos.append(imageInfo); } break; @@ -2718,7 +3021,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; @@ -2727,7 +3030,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); } } @@ -2742,9 +3045,9 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i bd.sbuf.generation = bufD->generation; VkDescriptorBufferInfo bufInfo; bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; - bufInfo.offset = VkDeviceSize(b->u.ubuf.offset); - bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size); - bufferInfoIndex = bufferInfos.count(); + bufInfo.offset = b->u.ubuf.offset; + bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size; + bufferInfoIndex = bufferInfos.size(); bufferInfos.append(bufInfo); } break; @@ -2758,7 +3061,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) @@ -2767,7 +3070,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) @@ -2799,8 +3102,7 @@ void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, in return; } - VkBufferMemoryBarrier bufMemBarrier; - memset(&bufMemBarrier, 0, sizeof(bufMemBarrier)); + VkBufferMemoryBarrier bufMemBarrier = {}; bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; @@ -2814,7 +3116,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; @@ -2832,8 +3134,7 @@ void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, return; } - VkImageMemoryBarrier barrier; - memset(&barrier, 0, sizeof(barrier)); + VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.subresourceRange.aspectMask = aspectMaskForTextureFormat(texD->m_format); barrier.subresourceRange.baseMipLevel = 0; @@ -2856,7 +3157,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; @@ -2868,8 +3169,7 @@ void QRhiVulkan::depthStencilExplicitBarrier(QVkCommandBuffer *cbD, QVkRenderBuf { Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); - VkImageMemoryBarrier barrier; - memset(&barrier, 0, sizeof(barrier)); + VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; barrier.subresourceRange.baseMipLevel = 0; @@ -2891,7 +3191,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); } @@ -2903,8 +3203,7 @@ void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, int startLevel, int levelCount) { Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); - VkImageMemoryBarrier barrier; - memset(&barrier, 0, sizeof(barrier)); + VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = uint32_t(startLevel); @@ -2922,7 +3221,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); } @@ -2944,15 +3243,20 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level, qsizetype copySizeBytes = 0; 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; - memset(©Info, 0, sizeof(copyInfo)); + VkBufferImageCopy copyInfo = {}; copyInfo.bufferOffset = *curOfs; copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copyInfo.imageSubresource.mipLevel = uint32_t(level); - copyInfo.imageSubresource.baseArrayLayer = uint32_t(layer); + copyInfo.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(layer); copyInfo.imageSubresource.layerCount = 1; 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(); @@ -3039,10 +3343,15 @@ 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); - QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) { const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]); @@ -3060,16 +3369,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); if (!bufD->stagingBuffers[currentFrameSlot]) { - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); + VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; // must cover the entire buffer - this way multiple, partial updates per frame // are supported even when the staging buffer is reused (Static) - bufferInfo.size = VkDeviceSize(bufD->m_size); + bufferInfo.size = bufD->m_size; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; VmaAllocation allocation; @@ -3077,9 +3384,9 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr); if (err == VK_SUCCESS) { bufD->stagingAllocations[currentFrameSlot] = allocation; - QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size))); } else { - qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err); + qWarning("Failed to create staging buffer of size %u: %d", bufD->m_size, err); + printExtraErrorInfo(err); continue; } } @@ -3091,18 +3398,17 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat qWarning("Failed to map buffer: %d", err); continue; } - memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); + memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), u.data.size()); + vmaFlushAllocation(toVmaAllocator(allocator), a, u.offset, u.data.size()); vmaUnmapMemory(toVmaAllocator(allocator), a); - vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size())); trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - VkBufferCopy copyInfo; - memset(©Info, 0, sizeof(copyInfo)); - copyInfo.srcOffset = VkDeviceSize(u.offset); - copyInfo.dstOffset = VkDeviceSize(u.offset); - copyInfo.size = VkDeviceSize(u.data.size()); + VkBufferCopy copyInfo = {}; + copyInfo.srcOffset = u.offset; + copyInfo.dstOffset = u.offset; + copyInfo.size = u.data.size(); QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; @@ -3128,7 +3434,6 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; bufD->stagingAllocations[currentFrameSlot] = nullptr; releaseQueue.append(e); - QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot)); } } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); @@ -3139,7 +3444,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); if (err == VK_SUCCESS) { u.result->data.resize(u.readSize); - memcpy(u.result->data.data(), reinterpret_cast<char *>(p) + u.offset, size_t(u.readSize)); + memcpy(u.result->data.data(), reinterpret_cast<char *>(p) + u.offset, u.readSize); vmaUnmapMemory(toVmaAllocator(allocator), a); } if (u.result->completed) @@ -3156,32 +3461,29 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat readback.result = u.result; readback.byteSize = u.readSize; - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); + VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = VkDeviceSize(readback.byteSize); + bufferInfo.size = readback.byteSize; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; VmaAllocation allocation; VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr); if (err == VK_SUCCESS) { readback.stagingAlloc = allocation; - QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), bufD, uint(readback.byteSize))); } else { qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); + printExtraErrorInfo(err); continue; } trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - VkBufferCopy copyInfo; - memset(©Info, 0, sizeof(copyInfo)); - copyInfo.srcOffset = VkDeviceSize(u.offset); - copyInfo.size = VkDeviceSize(u.readSize); + VkBufferCopy copyInfo = {}; + copyInfo.srcOffset = u.offset; + copyInfo.size = u.readSize; QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; @@ -3202,22 +3504,20 @@ 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; layer < QRhi::MAX_LAYERS; ++layer) { - for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) + 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 : std::as_const(u.subresDesc[layer][level])) stagingSize += subresUploadByteSize(subresDesc); } } Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]); - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); + VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = stagingSize; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; VmaAllocation allocation; @@ -3225,10 +3525,10 @@ 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; - QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, quint32(stagingSize))); BufferImageCopyList copyInfos; size_t curOfs = 0; @@ -3240,19 +3540,19 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat continue; } - for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { - for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { + 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); @@ -3262,9 +3562,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; @@ -3275,7 +3575,6 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; utexD->stagingAllocations[currentFrameSlot] = nullptr; releaseQueue.append(e); - QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot)); // Similarly to buffers, transitioning away from DST is done later, // when a renderpass using the texture is encountered. @@ -3289,25 +3588,29 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat } QVkTexture *srcD = QRHI_RES(QVkTexture, u.src); QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst); + const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional); + const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional); - VkImageCopy region; - memset(®ion, 0, sizeof(region)); - + VkImageCopy region = {}; region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel()); - region.srcSubresource.baseArrayLayer = uint32_t(u.desc.sourceLayer()); + region.srcSubresource.baseArrayLayer = srcIs3D ? 0 : uint32_t(u.desc.sourceLayer()); region.srcSubresource.layerCount = 1; region.srcOffset.x = u.desc.sourceTopLeft().x(); region.srcOffset.y = u.desc.sourceTopLeft().y(); + if (srcIs3D) + region.srcOffset.z = uint32_t(u.desc.sourceLayer()); region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel()); - region.dstSubresource.baseArrayLayer = uint32_t(u.desc.destinationLayer()); + region.dstSubresource.baseArrayLayer = dstIs3D ? 0 : uint32_t(u.desc.destinationLayer()); region.dstSubresource.layerCount = 1; region.dstOffset.x = u.desc.destinationTopLeft().x(); region.dstOffset.y = u.desc.destinationTopLeft().y(); + if (dstIs3D) + region.dstOffset.z = uint32_t(u.desc.destinationLayer()); const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); @@ -3337,11 +3640,13 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture()); QVkSwapChain *swapChainD = nullptr; + bool is3D = false; if (texD) { if (texD->samples > VK_SAMPLE_COUNT_1_BIT) { qWarning("Multisample texture cannot be read back"); continue; } + is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional); readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); readback.format = texD->m_format; texD->lastActiveFrameSlot = currentFrameSlot; @@ -3353,7 +3658,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat continue; } readback.pixelSize = swapChainD->pixelSize; - readback.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr); + readback.format = swapchainReadbackTextureFormat(swapChainD->colorFormat, nullptr); if (readback.format == QRhiTexture::UnknownFormat) continue; @@ -3363,36 +3668,33 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat textureFormatInfo(readback.format, readback.pixelSize, nullptr, &readback.byteSize, nullptr); // Create a host visible readback buffer. - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); + VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = readback.byteSize; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; VmaAllocation allocation; VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr); if (err == VK_SUCCESS) { readback.stagingAlloc = allocation; - QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), - texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD), - readback.byteSize)); } else { qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); + printExtraErrorInfo(err); continue; } // Copy from the (optimal and not host visible) image into the buffer. - VkBufferImageCopy copyDesc; - memset(©Desc, 0, sizeof(copyDesc)); + VkBufferImageCopy copyDesc = {}; copyDesc.bufferOffset = 0; copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level()); - copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer()); + copyDesc.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(u.rb.layer()); copyDesc.imageSubresource.layerCount = 1; + if (is3D) + copyDesc.imageOffset.z = u.rb.layer(); copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width()); copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height()); copyDesc.imageExtent.depth = 1; @@ -3437,6 +3739,8 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips)); const bool isCube = utexD->m_flags.testFlag(QRhiTexture::CubeMap); + const bool isArray = utexD->m_flags.testFlag(QRhiTexture::TextureArray); + const bool is3D = utexD->m_flags.testFlag(QRhiTexture::ThreeDimensional); VkImageLayout origLayout = utexD->usageState.layout; VkAccessFlags origAccess = utexD->usageState.access; @@ -3444,9 +3748,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat if (!origStage) origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - for (int layer = 0; layer < (isCube ? 6 : 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 ? qMax(1, utexD->m_depth) : 1; for (int level = 1; level < int(utexD->mipLevelCount); ++level) { if (level == 1) { subresourceBarrier(cbD, utexD->image, @@ -3471,9 +3776,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat layer, 1, level, 1); - VkImageBlit region; - memset(®ion, 0, sizeof(region)); - + VkImageBlit region = {}; region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.srcSubresource.mipLevel = uint32_t(level) - 1; region.srcSubresource.baseArrayLayer = uint32_t(layer); @@ -3481,7 +3784,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.srcOffsets[1].x = qMax(1, w); region.srcOffsets[1].y = qMax(1, h); - region.srcOffsets[1].z = 1; + region.srcOffsets[1].z = qMax(1, depth); region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.dstSubresource.mipLevel = uint32_t(level); @@ -3490,7 +3793,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.dstOffsets[1].x = qMax(1, w >> 1); region.dstOffsets[1].y = qMax(1, h >> 1); - region.dstOffsets[1].z = 1; + region.dstOffsets[1].z = qMax(1, depth >> 1); QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::BlitImage; @@ -3503,6 +3806,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat w >>= 1; h >>= 1; + depth >>= 1; } if (utexD->mipLevelCount > 1) { @@ -3543,18 +3847,18 @@ void QRhiVulkan::executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot) qWarning("Failed to map buffer: %d", err); return; } - int changeBegin = -1; - int changeEnd = -1; - for (const QVkBuffer::DynamicUpdate &u : qAsConst(bufD->pendingDynamicUpdates[slot])) { - memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); - if (changeBegin == -1 || u.offset < changeBegin) + quint32 changeBegin = UINT32_MAX; + quint32 changeEnd = 0; + 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 (changeEnd == -1 || u.offset + u.data.size() > changeEnd) + if (u.offset + u.data.size() > changeEnd) changeEnd = u.offset + u.data.size(); } + if (changeBegin < UINT32_MAX && changeBegin < changeEnd) + vmaFlushAllocation(toVmaAllocator(allocator), a, changeBegin, changeEnd - changeBegin); vmaUnmapMemory(toVmaAllocator(allocator), a); - if (changeBegin >= 0) - vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(changeBegin), VkDeviceSize(changeEnd - changeBegin)); bufD->pendingDynamicUpdates[slot].clear(); } @@ -3580,7 +3884,7 @@ static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkD vmaDestroyImage(toVmaAllocator(allocator), e.texture.image, toVmaAllocation(e.texture.allocation)); for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) vmaDestroyBuffer(toVmaAllocator(allocator), e.texture.stagingBuffers[i], toVmaAllocation(e.texture.stagingAllocations[i])); - for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { + for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) { if (e.texture.extraImageViews[i]) df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr); } @@ -3593,7 +3897,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) { @@ -3626,6 +3930,7 @@ 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); break; case QRhiVulkan::DeferredReleaseEntry::RenderPass: df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr); @@ -3648,9 +3953,8 @@ void QRhiVulkan::executeDeferredReleases(bool forced) void QRhiVulkan::finishActiveReadbacks(bool forced) { QVarLengthArray<std::function<void()>, 4> completedCallbacks; - QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - 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; @@ -3667,7 +3971,6 @@ void QRhiVulkan::finishActiveReadbacks(bool forced) } vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a); - QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); if (readback.result->completed) completedCallbacks.append(readback.result->completed); @@ -3676,7 +3979,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); @@ -3684,14 +3987,13 @@ void QRhiVulkan::finishActiveReadbacks(bool forced) VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); if (err == VK_SUCCESS && p) { readback.result->data.resize(readback.byteSize); - memcpy(readback.result->data.data(), p, size_t(readback.byteSize)); + memcpy(readback.result->data.data(), p, readback.byteSize); vmaUnmapMemory(toVmaAllocator(allocator), a); } else { qWarning("Failed to map buffer readback buffer of size %d: %d", readback.byteSize, err); } vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a); - QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); if (readback.result->completed) completedCallbacks.append(readback.result->completed); @@ -3738,33 +4040,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) @@ -3867,17 +4162,23 @@ void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD) cmd.args.drawIndexed.firstInstance); break; case QVkCommandBuffer::Command::DebugMarkerBegin: - cmd.args.debugMarkerBegin.marker.pMarkerName = - cbD->pools.debugMarkerData[cmd.args.debugMarkerBegin.markerNameIndex].constData(); - vkCmdDebugMarkerBegin(cbD->cb, &cmd.args.debugMarkerBegin.marker); +#ifdef VK_EXT_debug_utils + cmd.args.debugMarkerBegin.label.pLabelName = + cbD->pools.debugMarkerData[cmd.args.debugMarkerBegin.labelNameIndex].constData(); + vkCmdBeginDebugUtilsLabelEXT(cbD->cb, &cmd.args.debugMarkerBegin.label); +#endif break; case QVkCommandBuffer::Command::DebugMarkerEnd: - vkCmdDebugMarkerEnd(cbD->cb); +#ifdef VK_EXT_debug_utils + vkCmdEndDebugUtilsLabelEXT(cbD->cb); +#endif break; case QVkCommandBuffer::Command::DebugMarkerInsert: - cmd.args.debugMarkerInsert.marker.pMarkerName = - cbD->pools.debugMarkerData[cmd.args.debugMarkerInsert.markerNameIndex].constData(); - vkCmdDebugMarkerInsert(cbD->cb, &cmd.args.debugMarkerInsert.marker); +#ifdef VK_EXT_debug_utils + cmd.args.debugMarkerInsert.label.pLabelName = + cbD->pools.debugMarkerData[cmd.args.debugMarkerInsert.labelNameIndex].constData(); + vkCmdInsertDebugUtilsLabelEXT(cbD->cb, &cmd.args.debugMarkerInsert.label); +#endif break; case QVkCommandBuffer::Command::TransitionPassResources: recordTransitionPassResources(cbD, cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]); @@ -3923,10 +4224,16 @@ static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::Bu return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; case QRhiPassResourceTracker::BufVertexStage: return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + case QRhiPassResourceTracker::BufTCStage: + return VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT; + case QRhiPassResourceTracker::BufTEStage: + return VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT; case QRhiPassResourceTracker::BufFragmentStage: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; case QRhiPassResourceTracker::BufComputeStage: return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + case QRhiPassResourceTracker::BufGeometryStage: + return VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; default: Q_UNREACHABLE(); break; @@ -3989,6 +4296,10 @@ static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::Te switch (stage) { case QRhiPassResourceTracker::TexVertexStage: return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + case QRhiPassResourceTracker::TexTCStage: + return VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT; + case QRhiPassResourceTracker::TexTEStage: + return VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT; case QRhiPassResourceTracker::TexFragmentStage: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; case QRhiPassResourceTracker::TexColorOutputStage: @@ -3997,6 +4308,8 @@ static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::Te return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; case QRhiPassResourceTracker::TexComputeStage: return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + case QRhiPassResourceTracker::TexGeometryStage: + return VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; default: Q_UNREACHABLE(); break; @@ -4066,8 +4379,7 @@ void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhi if (!accessIsWrite(access)) continue; } - VkBufferMemoryBarrier bufMemBarrier; - memset(&bufMemBarrier, 0, sizeof(bufMemBarrier)); + VkBufferMemoryBarrier bufMemBarrier = {}; bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; @@ -4091,8 +4403,7 @@ void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhi if (!accessIsWrite(access)) continue; } - VkImageMemoryBarrier barrier; - memset(&barrier, 0, sizeof(barrier)); + VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.subresourceRange.aspectMask = aspectMaskForTextureFormat(texD->m_format); barrier.subresourceRange.baseMipLevel = 0; @@ -4117,10 +4428,18 @@ 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); } -QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size) +QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size) { return new QVkBuffer(this, type, usage, size); } @@ -4194,13 +4513,13 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const case QRhi::MultisampleRenderBuffer: return true; case QRhi::DebugMarkers: - return debugMarkersAvailable; + return caps.debugUtils; case QRhi::Timestamps: return timestampValidBits != 0; case QRhi::Instancing: return true; case QRhi::CustomInstanceStepRate: - return vertexAttribDivisorAvailable; + return caps.vertexAttribDivisor; case QRhi::PrimitiveRestart: return true; case QRhi::NonDynamicUniformBuffers: @@ -4214,9 +4533,9 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const case QRhi::ElementIndexUint: return true; case QRhi::Compute: - return hasCompute; + return caps.compute; case QRhi::WideLines: - return hasWideLines; + return caps.wideLines; case QRhi::VertexShaderPointSize: return true; case QRhi::BaseVertex: @@ -4243,9 +4562,38 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::ImageDataStride: return true; - default: - Q_UNREACHABLE(); + case QRhi::RenderBufferImport: return false; + case QRhi::ThreeDimensionalTextures: + return true; + case QRhi::RenderTo3DTextureSlice: + return caps.texture3DSliceAs2D; + case QRhi::TextureArrays: + return true; + case QRhi::Tessellation: + return caps.tessellation; + case QRhi::GeometryShader: + return caps.geometryShader; + case QRhi::TextureArrayRange: + 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; + default: + Q_UNREACHABLE_RETURN(false); } } @@ -4274,9 +4622,16 @@ int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const return int(physDevProperties.limits.maxComputeWorkGroupSize[1]); case QRhi::MaxThreadGroupZ: return int(physDevProperties.limits.maxComputeWorkGroupSize[2]); + case QRhi::TextureArraySizeMax: + return int(physDevProperties.limits.maxImageArrayLayers); + case QRhi::MaxUniformBufferRange: + return int(qMin<uint32_t>(INT_MAX, physDevProperties.limits.maxUniformBufferRange)); + case QRhi::MaxVertexInputs: + return physDevProperties.limits.maxVertexInputAttributes; + case QRhi::MaxVertexOutputs: + return physDevProperties.limits.maxVertexOutputComponents / 4; default: - Q_UNREACHABLE(); - return 0; + Q_UNREACHABLE_RETURN(0); } } @@ -4290,16 +4645,24 @@ QRhiDriverInfo QRhiVulkan::driverInfo() const return driverInfoStruct; } -void QRhiVulkan::sendVMemStatsToProfiler() +QRhiStats QRhiVulkan::statistics() { - QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - if (!rhiP) - return; + QRhiStats result; + result.totalPipelineCreationTime = totalPipelineCreationTime(); + + VmaBudget budgets[VK_MAX_MEMORY_HEAPS]; + vmaGetHeapBudgets(toVmaAllocator(allocator), budgets); - VmaStats stats; - vmaCalculateStats(toVmaAllocator(allocator), &stats); - QRHI_PROF_F(vmemStat(stats.total.blockCount, stats.total.allocationCount, - quint32(stats.total.usedBytes), quint32(stats.total.unusedBytes))); + 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; } bool QRhiVulkan::makeThreadLocalNativeContextCurrent() @@ -4341,7 +4704,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); @@ -4349,7 +4712,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(); } @@ -4361,6 +4724,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); @@ -4374,7 +4738,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; @@ -4382,49 +4746,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; } @@ -4437,7 +4801,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"); } } @@ -4448,10 +4812,11 @@ QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, co return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint); } -QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, const QSize &pixelSize, +QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, + const QSize &pixelSize, int depth, int arraySize, int sampleCount, QRhiTexture::Flags flags) { - return new QVkTexture(this, format, pixelSize, sampleCount, flags); + return new QVkTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags); } QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, @@ -4531,8 +4896,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: @@ -4560,8 +4925,10 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin } break; case QRhiShaderResourceBinding::SampledTexture: + case QRhiShaderResourceBinding::Texture: + case QRhiShaderResourceBinding::Sampler: { - const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; + const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; if (bd.stex.count != data->count) { bd.stex.count = data->count; rewriteDescSet = true; @@ -4569,21 +4936,32 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin for (int elem = 0; elem < data->count; ++elem) { QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler); - texD->lastActiveFrameSlot = currentFrameSlot; - samplerD->lastActiveFrameSlot = currentFrameSlot; - trackedRegisterTexture(&passResTracker, texD, - QRhiPassResourceTracker::TexSample, - QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); - if (texD->generation != bd.stex.d[elem].texGeneration - || texD->m_id != bd.stex.d[elem].texId - || samplerD->generation != bd.stex.d[elem].samplerGeneration - || samplerD->m_id != bd.stex.d[elem].samplerId) + // We use the same code path for both combined and separate + // images and samplers, so tex or sampler (but not both) can be + // null here. + Q_ASSERT(texD || samplerD); + if (texD) { + texD->lastActiveFrameSlot = currentFrameSlot; + trackedRegisterTexture(&passResTracker, texD, + QRhiPassResourceTracker::TexSample, + QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage)); + } + if (samplerD) + samplerD->lastActiveFrameSlot = currentFrameSlot; + const quint64 texId = texD ? texD->m_id : 0; + const uint texGen = texD ? texD->generation : 0; + const quint64 samplerId = samplerD ? samplerD->m_id : 0; + const uint samplerGen = samplerD ? samplerD->generation : 0; + if (texGen != bd.stex.d[elem].texGeneration + || texId != bd.stex.d[elem].texId + || samplerGen != bd.stex.d[elem].samplerGeneration + || samplerId != bd.stex.d[elem].samplerId) { rewriteDescSet = true; - bd.stex.d[elem].texId = texD->m_id; - bd.stex.d[elem].texGeneration = texD->generation; - bd.stex.d[elem].samplerId = samplerD->m_id; - bd.stex.d[elem].samplerGeneration = samplerD->generation; + bd.stex.d[elem].texId = texId; + bd.stex.d[elem].texGeneration = texGen; + bd.stex.d[elem].samplerId = samplerId; + bd.stex.d[elem].samplerGeneration = samplerGen; } } } @@ -4665,8 +5043,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) { @@ -4686,8 +5064,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; @@ -4695,9 +5073,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) { @@ -4756,16 +5134,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()); } } @@ -4814,7 +5192,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()); @@ -4833,9 +5211,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); @@ -4858,7 +5239,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()); @@ -4948,60 +5329,72 @@ void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) { - if (!debugMarkers || !debugMarkersAvailable) +#ifdef VK_EXT_debug_utils + if (!debugMarkers || !caps.debugUtils) return; - VkDebugMarkerMarkerInfoEXT marker; - memset(&marker, 0, sizeof(marker)); - marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT; + VkDebugUtilsLabelEXT label = {}; + label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) { - marker.pMarkerName = name.constData(); - vkCmdDebugMarkerBegin(cbD->activeSecondaryCbStack.last(), &marker); + label.pLabelName = name.constData(); + vkCmdBeginDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last(), &label); } else { QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin; - cmd.args.debugMarkerBegin.marker = marker; - cmd.args.debugMarkerBegin.markerNameIndex = cbD->pools.debugMarkerData.count(); + cmd.args.debugMarkerBegin.label = label; + cmd.args.debugMarkerBegin.labelNameIndex = cbD->pools.debugMarkerData.size(); cbD->pools.debugMarkerData.append(name); } +#else + Q_UNUSED(cb); + Q_UNUSED(name); +#endif } void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb) { - if (!debugMarkers || !debugMarkersAvailable) +#ifdef VK_EXT_debug_utils + if (!debugMarkers || !caps.debugUtils) return; QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) { - vkCmdDebugMarkerEnd(cbD->activeSecondaryCbStack.last()); + vkCmdEndDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last()); } else { QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd; } +#else + Q_UNUSED(cb); +#endif } void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) { - if (!debugMarkers || !debugMarkersAvailable) +#ifdef VK_EXT_debug_utils + if (!debugMarkers || !caps.debugUtils) return; - VkDebugMarkerMarkerInfoEXT marker; - memset(&marker, 0, sizeof(marker)); - marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT; + VkDebugUtilsLabelEXT label = {}; + label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) { - marker.pMarkerName = msg.constData(); - vkCmdDebugMarkerInsert(cbD->activeSecondaryCbStack.last(), &marker); + label.pLabelName = msg.constData(); + vkCmdInsertDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last(), &label); } else { QVkCommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert; - cmd.args.debugMarkerInsert.marker = marker; - cmd.args.debugMarkerInsert.markerNameIndex = cbD->pools.debugMarkerData.count(); + cmd.args.debugMarkerInsert.label = label; + cmd.args.debugMarkerInsert.labelNameIndex = cbD->pools.debugMarkerData.size(); cbD->pools.debugMarkerData.append(msg); } +#else + Q_UNUSED(cb); + Q_UNUSED(msg); +#endif } const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb) @@ -5015,8 +5408,8 @@ static inline QVkRenderTargetData *maybeRenderTargetData(QVkCommandBuffer *cbD) QVkRenderTargetData *rtD = nullptr; if (cbD->recordingPass == QVkCommandBuffer::RenderPass) { switch (cbD->currentTarget->resourceType()) { - case QRhiResource::RenderTarget: - rtD = &QRHI_RES(QVkReferenceRenderTarget, cbD->currentTarget)->d; + case QRhiResource::SwapChainRenderTarget: + rtD = &QRHI_RES(QVkSwapChainRenderTarget, cbD->currentTarget)->d; break; case QRhiResource::TextureRenderTarget: rtD = &QRHI_RES(QVkTextureRenderTarget, cbD->currentTarget)->d; @@ -5082,23 +5475,35 @@ void QRhiVulkan::endExternal(QRhiCommandBuffer *cb) cbD->resetCachedState(); } -void QRhiVulkan::setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot) +double QRhiVulkan::lastCompletedGpuTime(QRhiCommandBuffer *cb) { - if (!debugMarkers || !debugMarkersAvailable || name.isEmpty()) + 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 + if (!debugMarkers || !caps.debugUtils || name.isEmpty()) return; - VkDebugMarkerObjectNameInfoEXT nameInfo; - memset(&nameInfo, 0, sizeof(nameInfo)); - nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT; + VkDebugUtilsObjectNameInfoEXT nameInfo = {}; + nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; nameInfo.objectType = type; - nameInfo.object = object; + nameInfo.objectHandle = object; QByteArray decoratedName = name; if (slot >= 0) { decoratedName += '/'; decoratedName += QByteArray::number(slot); } nameInfo.pObjectName = decoratedName.constData(); - vkDebugMarkerSetObjectName(dev, &nameInfo); + vkSetDebugUtilsObjectNameEXT(dev, &nameInfo); +#else + Q_UNUSED(object); + Q_UNUSED(type); + Q_UNUSED(name); + Q_UNUSED(slot); +#endif } static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage) @@ -5123,8 +5528,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); } } @@ -5138,8 +5542,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); } } @@ -5153,8 +5556,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); } } @@ -5163,13 +5565,18 @@ static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type) switch (type) { case QRhiShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT; + case QRhiShaderStage::TessellationControl: + return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case QRhiShaderStage::TessellationEvaluation: + return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; case QRhiShaderStage::Fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; case QRhiShaderStage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT; + 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); } } @@ -5206,9 +5613,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); } } @@ -5227,9 +5657,10 @@ static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t) return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; case QRhiGraphicsPipeline::Points: return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + 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); } } @@ -5243,8 +5674,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); } } @@ -5256,8 +5686,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); } } @@ -5317,8 +5746,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); } } @@ -5336,8 +5764,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); } } @@ -5361,8 +5788,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); } } @@ -5386,8 +5812,19 @@ 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); + } +} + +static inline VkPolygonMode toVkPolygonMode(QRhiGraphicsPipeline::PolygonMode mode) +{ + switch (mode) { + case QRhiGraphicsPipeline::Fill: + return VK_POLYGON_MODE_FILL; + case QRhiGraphicsPipeline::Line: + return VK_POLYGON_MODE_LINE; + default: + Q_UNREACHABLE_RETURN(VK_POLYGON_MODE_FILL); } } @@ -5409,6 +5846,12 @@ static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBindin case QRhiShaderResourceBinding::SampledTexture: return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + case QRhiShaderResourceBinding::Texture: + return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + + case QRhiShaderResourceBinding::Sampler: + return VK_DESCRIPTOR_TYPE_SAMPLER; + case QRhiShaderResourceBinding::ImageLoad: case QRhiShaderResourceBinding::ImageStore: case QRhiShaderResourceBinding::ImageLoadStore: @@ -5420,8 +5863,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); } } @@ -5434,6 +5876,12 @@ static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding: s |= VK_SHADER_STAGE_FRAGMENT_BIT; if (stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) s |= VK_SHADER_STAGE_COMPUTE_BIT; + if (stage.testFlag(QRhiShaderResourceBinding::TessellationControlStage)) + s |= VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + if (stage.testFlag(QRhiShaderResourceBinding::TessellationEvaluationStage)) + s |= VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + if (stage.testFlag(QRhiShaderResourceBinding::GeometryStage)) + s |= VK_SHADER_STAGE_GEOMETRY_BIT; return VkShaderStageFlags(s); } @@ -5457,12 +5905,11 @@ 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); } } -QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size) +QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size) : QRhiBuffer(rhi, type, usage, size) { for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { @@ -5499,12 +5946,13 @@ void QVkBuffer::destroy() } QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - QRHI_PROF; - QRHI_PROF_F(releaseBuffer(this)); - - rhiD->unregisterResource(this); + // destroy() implementations, unlike other functions, are expected to test + // for m_rhi being null, to allow surviving in case one attempts to destroy + // a (leaked) resource after the QRhi. + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkBuffer::create() @@ -5517,16 +5965,14 @@ bool QVkBuffer::create() return false; } - const int nonZeroSize = m_size <= 0 ? 256 : m_size; + const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size; - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); + VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = uint32_t(nonZeroSize); + bufferInfo.size = nonZeroSize; bufferInfo.usage = toVkBufferUsage(m_usage); - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VmaAllocationCreateInfo allocInfo = {}; if (m_type == Dynamic) { #ifndef Q_OS_DARWIN // not for MoltenVK @@ -5554,19 +6000,17 @@ bool QVkBuffer::create() if (err != VK_SUCCESS) break; allocations[i] = allocation; - rhiD->setObjectName(uint64_t(buffers[i]), VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, m_objectName, + rhiD->setObjectName(uint64_t(buffers[i]), VK_OBJECT_TYPE_BUFFER, m_objectName, m_type == Dynamic ? i : -1); } } 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; } - QRHI_PROF; - QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), m_type != Dynamic ? 1 : QVK_FRAMES_IN_FLIGHT, 0)); - lastActiveFrameSlot = -1; generation += 1; rhiD->registerResource(this); @@ -5615,8 +6059,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, @@ -5656,12 +6100,10 @@ void QVkRenderBuffer::destroy() } QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - QRHI_PROF; - QRHI_PROF_F(releaseRenderBuffer(this)); - - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkRenderBuffer::create() @@ -5673,8 +6115,7 @@ bool QVkRenderBuffer::create() return false; QRHI_RES_RHI(QRhiVulkan); - QRHI_PROF; - samples = rhiD->effectiveSampleCount(m_sampleCount); + samples = rhiD->effectiveSampleCountBits(m_sampleCount); switch (m_type) { case QRhiRenderBuffer::Color: @@ -5682,6 +6123,8 @@ bool QVkRenderBuffer::create() if (!backingTexture) { backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(backingFormat(), m_pixelSize, + 1, + 0, m_sampleCount, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); } else { @@ -5692,7 +6135,6 @@ bool QVkRenderBuffer::create() if (!backingTexture->create()) return false; vkformat = backingTexture->vkformat; - QRHI_PROF_F(newRenderBuffer(this, false, false, samples)); } break; case QRhiRenderBuffer::DepthStencil: @@ -5709,8 +6151,7 @@ bool QVkRenderBuffer::create() { return false; } - rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName); - QRHI_PROF_F(newRenderBuffer(this, true, false, samples)); + rhiD->setObjectName(uint64_t(image), VK_OBJECT_TYPE_IMAGE, m_objectName); break; default: Q_UNREACHABLE(); @@ -5718,6 +6159,7 @@ bool QVkRenderBuffer::create() } lastActiveFrameSlot = -1; + generation += 1; rhiD->registerResource(this); return true; } @@ -5730,15 +6172,15 @@ QRhiTexture::Format QVkRenderBuffer::backingFormat() const return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; } -QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, - int sampleCount, Flags flags) - : QRhiTexture(rhi, format, pixelSize, sampleCount, flags) +QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, + int arraySize, int sampleCount, Flags flags) + : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags) { for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { stagingBuffers[i] = VK_NULL_HANDLE; stagingAllocations[i] = nullptr; } - for (int i = 0; i < QRhi::MAX_LEVELS; ++i) + for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) perLevelImageViews[i] = VK_NULL_HANDLE; } @@ -5768,7 +6210,7 @@ void QVkTexture::destroy() stagingAllocations[i] = nullptr; } - for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { + for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) { e.texture.extraImageViews[i] = perLevelImageViews[i]; perLevelImageViews[i] = VK_NULL_HANDLE; } @@ -5778,12 +6220,10 @@ void QVkTexture::destroy() imageAlloc = nullptr; QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - QRHI_PROF; - QRHI_PROF_F(releaseTexture(this)); - - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkTexture::prepareCreate(QSize *adjustedSize) @@ -5793,6 +6233,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); @@ -5801,27 +6250,64 @@ 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_LEVELS; + 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"); return false; } + if (is3D) { + qWarning("3D texture cannot be multisample"); + return false; + } if (hasMipMaps) { qWarning("Multisample texture cannot have mipmaps"); return false; } } + if (isCube && is3D) { + qWarning("Texture cannot be both cube and 3D"); + return false; + } + if (isArray && is3D) { + qWarning("Texture cannot be both array and 3D"); + return false; + } + 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; + } + if (m_arraySize > 0 && !isArray) { + qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize); + return false; + } + if (m_arraySize < 1 && isArray) { + qWarning("Texture is an array but array size is %d", m_arraySize); + return false; + } usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED; usageState.access = 0; @@ -5839,20 +6325,31 @@ bool QVkTexture::finishCreate() const auto aspectMask = aspectMaskForTextureFormat(m_format); 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; - memset(&viewInfo, 0, sizeof(viewInfo)); + VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; - viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : 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; viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; viewInfo.subresourceRange.aspectMask = aspectMask; viewInfo.subresourceRange.levelCount = mipLevelCount; - viewInfo.subresourceRange.layerCount = isCube ? 6 : 1; + if (isArray && m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) { + viewInfo.subresourceRange.baseArrayLayer = uint32_t(m_arrayRangeStart); + viewInfo.subresourceRange.layerCount = uint32_t(m_arrayRangeLength); + } else { + viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); + } VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView); if (err != VK_SUCCESS) { @@ -5872,21 +6369,42 @@ bool QVkTexture::create() if (!prepareCreate(&size)) return false; + QRHI_RES_RHI(QRhiVulkan); const bool isRenderTarget = m_flags.testFlag(QRhiTexture::RenderTarget); const bool isDepth = isDepthTextureFormat(m_format); 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; - memset(&imageInfo, 0, sizeof(imageInfo)); + VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.flags = isCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0; - imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.flags = 0; + if (isCube) + imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + if (is3D && isRenderTarget) { + // This relies on a Vulkan 1.1 constant. For guaranteed proper behavior + // this also requires that at run time the VkInstance has at least API 1.1 + // enabled. (though it works as expected with some Vulkan (1.2) + // implementations regardless of the requested API version, but f.ex. the + // validation layer complains when using this without enabling >=1.1) + if (!rhiD->caps.texture3DSliceAs2D) + qWarning("QRhiVulkan: Rendering to 3D texture slice may not be functional without API 1.1 on the VkInstance"); +#ifdef VK_VERSION_1_1 + imageInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; +#else + imageInfo.flags |= 0x00000020; +#endif + } + + 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 = 1; + imageInfo.extent.depth = is3D ? qMax(1, m_depth) : 1; imageInfo.mipLevels = mipLevelCount; - imageInfo.arrayLayers = isCube ? 6 : 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; @@ -5905,15 +6423,20 @@ bool QVkTexture::create() if (m_flags.testFlag(QRhiTexture::UsedWithLoadStore)) imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VmaAllocationCreateInfo allocInfo = {}; allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - QRHI_RES_RHI(QRhiVulkan); 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; @@ -5921,10 +6444,7 @@ bool QVkTexture::create() if (!finishCreate()) return false; - rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName); - - QRHI_PROF; - QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : 1, samples)); + rhiD->setObjectName(uint64_t(image), VK_OBJECT_TYPE_IMAGE, m_objectName); owns = true; rhiD->registerResource(this); @@ -5945,9 +6465,6 @@ bool QVkTexture::createFrom(QRhiTexture::NativeTexture src) if (!finishCreate()) return false; - QRHI_PROF; - QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), m_flags.testFlag(CubeMap) ? 6 : 1, samples)); - usageState.layout = VkImageLayout(src.layout); owns = false; @@ -5966,7 +6483,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) @@ -5974,13 +6491,19 @@ VkImageView QVkTexture::imageViewForLevel(int level) const VkImageAspectFlags aspectMask = aspectMaskForTextureFormat(m_format); 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; - memset(&viewInfo, 0, sizeof(viewInfo)); + VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; - viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : 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; @@ -5989,7 +6512,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 : 1; + viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1); VkImageView v = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); @@ -6027,8 +6550,10 @@ void QVkSampler::destroy() sampler = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkSampler::create() @@ -6036,8 +6561,7 @@ bool QVkSampler::create() if (sampler) destroy(); - VkSamplerCreateInfo samplerInfo; - memset(&samplerInfo, 0, sizeof(samplerInfo)); + VkSamplerCreateInfo samplerInfo = {}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = toVkFilter(m_magFilter); samplerInfo.minFilter = toVkFilter(m_minFilter); @@ -6066,6 +6590,7 @@ bool QVkSampler::create() QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi) : QRhiRenderPassDescriptor(rhi) { + serializedFormatData.reserve(32); } QVkRenderPassDescriptor::~QVkRenderPassDescriptor() @@ -6092,9 +6617,10 @@ void QVkRenderPassDescriptor::destroy() rp = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } static inline bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b) @@ -6119,16 +6645,18 @@ 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 (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; @@ -6144,7 +6672,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; @@ -6157,6 +6685,49 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other return true; } +void QVkRenderPassDescriptor::updateSerializedFormat() +{ + serializedFormatData.clear(); + auto p = std::back_inserter(serializedFormatData); + + *p++ = attDescs.size(); + *p++ = colorRefs.size(); + *p++ = resolveRefs.size(); + *p++ = hasDepthStencil; + *p++ = multiViewCount; + + auto serializeAttachmentData = [this, &p](uint32_t attIdx) { + const bool used = attIdx != VK_ATTACHMENT_UNUSED; + const VkAttachmentDescription *a = used ? &attDescs[attIdx] : nullptr; + *p++ = used ? a->format : 0; + *p++ = used ? a->samples : 0; + *p++ = used ? a->loadOp : 0; + *p++ = used ? a->storeOp : 0; + *p++ = used ? a->stencilLoadOp : 0; + *p++ = used ? a->stencilStoreOp : 0; + *p++ = used ? a->initialLayout : 0; + *p++ = used ? a->finalLayout : 0; + }; + + for (int i = 0, ie = colorRefs.size(); i != ie; ++i) { + const uint32_t attIdx = colorRefs[i].attachment; + *p++ = attIdx; + serializeAttachmentData(attIdx); + } + + if (hasDepthStencil) { + const uint32_t attIdx = dsRef.attachment; + *p++ = attIdx; + serializeAttachmentData(attIdx); + } + + for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) { + const uint32_t attIdx = resolveRefs[i].attachment; + *p++ = attIdx; + serializeAttachmentData(attIdx); + } +} + QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescriptor() const { QVkRenderPassDescriptor *rpD = new QVkRenderPassDescriptor(m_rhi); @@ -6167,6 +6738,7 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri rpD->resolveRefs = resolveRefs; rpD->subpassDeps = subpassDeps; rpD->hasDepthStencil = hasDepthStencil; + rpD->multiViewCount = multiViewCount; rpD->dsRef = dsRef; VkRenderPassCreateInfo rpInfo; @@ -6174,6 +6746,12 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri 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); @@ -6181,42 +6759,48 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri return nullptr; } + rpD->updateSerializedFormat(); rhiD->registerResource(rpD); return rpD; } +QVector<quint32> QVkRenderPassDescriptor::serializedFormat() const +{ + return serializedFormatData; +} + const QRhiNativeHandles *QVkRenderPassDescriptor::nativeHandles() { nativeHandlesStruct.renderPass = rp; return &nativeHandlesStruct; } -QVkReferenceRenderTarget::QVkReferenceRenderTarget(QRhiImplementation *rhi) - : QRhiRenderTarget(rhi) +QVkSwapChainRenderTarget::QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain) + : QRhiSwapChainRenderTarget(rhi, swapchain) { } -QVkReferenceRenderTarget::~QVkReferenceRenderTarget() +QVkSwapChainRenderTarget::~QVkSwapChainRenderTarget() { destroy(); } -void QVkReferenceRenderTarget::destroy() +void QVkSwapChainRenderTarget::destroy() { // nothing to do here } -QSize QVkReferenceRenderTarget::pixelSize() const +QSize QVkSwapChainRenderTarget::pixelSize() const { return d.pixelSize; } -float QVkReferenceRenderTarget::devicePixelRatio() const +float QVkSwapChainRenderTarget::devicePixelRatio() const { return d.dpr; } -int QVkReferenceRenderTarget::sampleCount() const +int QVkSwapChainRenderTarget::sampleCount() const { return d.sampleCount; } @@ -6256,10 +6840,14 @@ void QVkTextureRenderTarget::destroy() resrtv[att] = VK_NULL_HANDLE; } - QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); + e.textureRenderTarget.dsv = dsv; + dsv = VK_NULL_HANDLE; - rhiD->unregisterResource(this); + QRHI_RES_RHI(QRhiVulkan); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescriptor() @@ -6273,6 +6861,7 @@ 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.depthStencilBuffer(), m_desc.depthTexture())) { @@ -6281,6 +6870,7 @@ QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescrip } rp->ownsRp = true; + rp->updateSerializedFormat(); rhiD->registerResource(rp); return rp; } @@ -6290,13 +6880,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; @@ -6307,12 +6897,17 @@ bool QVkTextureRenderTarget::create() Q_ASSERT(texD || rbD); if (texD) { Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget)); - VkImageViewCreateInfo viewInfo; - memset(&viewInfo, 0, sizeof(viewInfo)); + 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; @@ -6321,7 +6916,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); @@ -6346,7 +6941,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; @@ -6366,18 +6979,19 @@ 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()); Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget)); d.resolveAttCount += 1; - VkImageViewCreateInfo viewInfo; - memset(&viewInfo, 0, sizeof(viewInfo)); + 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; @@ -6386,7 +7000,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); @@ -6402,8 +7016,7 @@ bool QVkTextureRenderTarget::create() d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); Q_ASSERT(d.rp && d.rp->rp); - VkFramebufferCreateInfo fbInfo; - memset(&fbInfo, 0, sizeof(fbInfo)); + 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); @@ -6418,6 +7031,8 @@ bool QVkTextureRenderTarget::create() return false; } + QRhiRenderTargetAttachmentTracker::updateResIdList<QVkTexture, QVkRenderBuffer>(m_desc, &d.currentResIdList); + lastActiveFrameSlot = -1; rhiD->registerResource(this); return true; @@ -6425,6 +7040,9 @@ bool QVkTextureRenderTarget::create() QSize QVkTextureRenderTarget::pixelSize() const { + if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(m_desc, d.currentResIdList)) + const_cast<QVkTextureRenderTarget *>(this)->create(); + return d.pixelSize; } @@ -6468,9 +7086,10 @@ void QVkShaderResourceBindings::destroy() descSets[i] = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkShaderResourceBindings::create() @@ -6489,16 +7108,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; @@ -6508,13 +7123,12 @@ bool QVkShaderResourceBindings::create() } QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings; - for (const QRhiShaderResourceBinding &binding : qAsConst(sortedBindings)) { - const QRhiShaderResourceBinding::Data *b = binding.data(); - VkDescriptorSetLayoutBinding vkbinding; - memset(&vkbinding, 0, sizeof(vkbinding)); + 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); - if (b->type == QRhiShaderResourceBinding::SampledTexture) + if (b->type == QRhiShaderResourceBinding::SampledTexture || b->type == QRhiShaderResourceBinding::Texture) vkbinding.descriptorCount = b->u.stex.count; else vkbinding.descriptorCount = 1; @@ -6522,10 +7136,9 @@ bool QVkShaderResourceBindings::create() vkbindings.append(vkbinding); } - VkDescriptorSetLayoutCreateInfo layoutInfo; - memset(&layoutInfo, 0, sizeof(layoutInfo)); + 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); @@ -6534,8 +7147,7 @@ bool QVkShaderResourceBindings::create() return false; } - VkDescriptorSetAllocateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); + VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT; VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT]; @@ -6546,7 +7158,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)); } @@ -6557,6 +7169,31 @@ bool QVkShaderResourceBindings::create() return true; } +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(), 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 + // detect changes in the associated data, such as the buffer offset. And + // just like after a create(), a call to updateResources() may lead to now + // specifying a different offset for the same QRhiBuffer for a given binding + // point. The same applies to other type of associated data that is not part + // of the layout, such as the mip level for a StorageImage. Instead of + // 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].size() == sortedBindings.size()); + for (BoundResourceData &bd : boundResourceData[i]) + memset(&bd, 0, sizeof(BoundResourceData)); + } + + generation += 1; +} + QVkGraphicsPipeline::QVkGraphicsPipeline(QRhiImplementation *rhi) : QRhiGraphicsPipeline(rhi) { @@ -6583,9 +7220,10 @@ void QVkGraphicsPipeline::destroy() layout = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkGraphicsPipeline::create() @@ -6594,14 +7232,14 @@ bool QVkGraphicsPipeline::create() destroy(); QRHI_RES_RHI(QRhiVulkan); + rhiD->pipelineCreationStart(); if (!rhiD->sanityCheckGraphicsPipeline(this)) return false; if (!rhiD->ensurePipelineCache()) return false; - VkPipelineLayoutCreateInfo pipelineLayoutInfo; - memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings); @@ -6613,8 +7251,7 @@ bool QVkGraphicsPipeline::create() return false; } - VkGraphicsPipelineCreateInfo pipelineInfo; - memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + VkGraphicsPipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; QVarLengthArray<VkShaderModule, 4> shaders; @@ -6629,8 +7266,7 @@ bool QVkGraphicsPipeline::create() VkShaderModule shader = rhiD->createShader(spirv.shader()); if (shader) { shaders.append(shader); - VkPipelineShaderStageCreateInfo shaderInfo; - memset(&shaderInfo, 0, sizeof(shaderInfo)); + VkPipelineShaderStageCreateInfo shaderInfo = {}; shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderInfo.stage = toVkShaderStage(shaderStage.type()); shaderInfo.module = shader; @@ -6638,11 +7274,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) @@ -6654,9 +7292,12 @@ bool QVkGraphicsPipeline::create() ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE }; if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) { - if (rhiD->vertexAttribDivisorAvailable) { - nonOneStepRates.append({ uint32_t(bindingIndex), uint32_t(it->instanceStepRate()) }); - } else { +#ifdef VK_EXT_vertex_attribute_divisor + if (rhiD->caps.vertexAttribDivisor) { + nonOneStepRates.append({ uint32_t(bindingIndex), it->instanceStepRate() }); + } 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"); @@ -6676,21 +7317,21 @@ bool QVkGraphicsPipeline::create() }; vertexAttributes.append(attributeInfo); } - VkPipelineVertexInputStateCreateInfo vertexInputInfo; - memset(&vertexInputInfo, 0, sizeof(vertexInputInfo)); + 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(); - VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo; +#ifdef VK_EXT_vertex_attribute_divisor + VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo = {}; if (!nonOneStepRates.isEmpty()) { - memset(&divisorInfo, 0, sizeof(divisorInfo)); - 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; @@ -6701,28 +7342,52 @@ bool QVkGraphicsPipeline::create() if (m_flags.testFlag(QRhiGraphicsPipeline::UsesStencilRef)) dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE; - VkPipelineDynamicStateCreateInfo dynamicInfo; - memset(&dynamicInfo, 0, sizeof(dynamicInfo)); + 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; - VkPipelineViewportStateCreateInfo viewportInfo; - memset(&viewportInfo, 0, sizeof(viewportInfo)); + VkPipelineViewportStateCreateInfo viewportInfo = {}; viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportInfo.viewportCount = viewportInfo.scissorCount = 1; pipelineInfo.pViewportState = &viewportInfo; - VkPipelineInputAssemblyStateCreateInfo inputAsmInfo; - memset(&inputAsmInfo, 0, sizeof(inputAsmInfo)); + VkPipelineInputAssemblyStateCreateInfo inputAsmInfo = {}; inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAsmInfo.topology = toVkTopology(m_topology); inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip); pipelineInfo.pInputAssemblyState = &inputAsmInfo; - VkPipelineRasterizationStateCreateInfo rastInfo; - memset(&rastInfo, 0, sizeof(rastInfo)); + VkPipelineTessellationStateCreateInfo tessInfo = {}; +#ifdef VK_VERSION_1_1 + VkPipelineTessellationDomainOriginStateCreateInfo originInfo = {}; +#endif + if (m_topology == Patches) { + tessInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; + tessInfo.patchControlPoints = uint32_t(qMax(1, m_patchControlPointCount)); + + // To be able to use the same tess.evaluation shader with both OpenGL + // and Vulkan, flip the tessellation domain origin to be lower left. + // This allows declaring the winding order in the shader to be CCW and + // still have it working with both APIs. This requires Vulkan 1.1 (or + // VK_KHR_maintenance2 but don't bother with that). +#ifdef VK_VERSION_1_1 + if (rhiD->caps.apiVersion >= QVersionNumber(1, 1)) { + originInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO; + originInfo.domainOrigin = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT; + tessInfo.pNext = &originInfo; + } else { + qWarning("Proper tessellation support requires Vulkan 1.1 or newer, leaving domain origin unset"); + } +#else + qWarning("QRhi was built without Vulkan 1.1 headers, this is not sufficient for proper tessellation support"); +#endif + + pipelineInfo.pTessellationState = &tessInfo; + } + + VkPipelineRasterizationStateCreateInfo rastInfo = {}; rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rastInfo.cullMode = toVkCullMode(m_cullMode); rastInfo.frontFace = toVkFrontFace(m_frontFace); @@ -6731,17 +7396,16 @@ bool QVkGraphicsPipeline::create() rastInfo.depthBiasConstantFactor = float(m_depthBias); rastInfo.depthBiasSlopeFactor = m_slopeScaledDepthBias; } - rastInfo.lineWidth = rhiD->hasWideLines ? m_lineWidth : 1.0f; + rastInfo.lineWidth = rhiD->caps.wideLines ? m_lineWidth : 1.0f; + rastInfo.polygonMode = toVkPolygonMode(m_polygonMode); pipelineInfo.pRasterizationState = &rastInfo; - VkPipelineMultisampleStateCreateInfo msInfo; - memset(&msInfo, 0, sizeof(msInfo)); + 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; - memset(&dsInfo, 0, sizeof(dsInfo)); + VkPipelineDepthStencilStateCreateInfo dsInfo = {}; dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; dsInfo.depthTestEnable = m_depthTest; dsInfo.depthWriteEnable = m_depthWrite; @@ -6757,13 +7421,11 @@ bool QVkGraphicsPipeline::create() } pipelineInfo.pDepthStencilState = &dsInfo; - VkPipelineColorBlendStateCreateInfo blendInfo; - memset(&blendInfo, 0, sizeof(blendInfo)); + 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)) { - VkPipelineColorBlendAttachmentState blend; - memset(&blend, 0, sizeof(blend)); + for (const QRhiGraphicsPipeline::TargetBlend &b : std::as_const(m_targetBlends)) { + VkPipelineColorBlendAttachmentState blend = {}; blend.blendEnable = b.enable; blend.srcColorBlendFactor = toVkBlendFactor(b.srcColor); blend.dstColorBlendFactor = toVkBlendFactor(b.dstColor); @@ -6775,13 +7437,12 @@ bool QVkGraphicsPipeline::create() vktargetBlends.append(blend); } if (vktargetBlends.isEmpty()) { - VkPipelineColorBlendAttachmentState blend; - memset(&blend, 0, sizeof(blend)); + VkPipelineColorBlendAttachmentState blend = {}; blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | 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; @@ -6800,6 +7461,7 @@ bool QVkGraphicsPipeline::create() return false; } + rhiD->pipelineCreationEnd(); lastActiveFrameSlot = -1; generation += 1; rhiD->registerResource(this); @@ -6832,9 +7494,10 @@ void QVkComputePipeline::destroy() layout = VK_NULL_HANDLE; QRHI_RES_RHI(QRhiVulkan); - rhiD->releaseQueue.append(e); - - rhiD->unregisterResource(this); + if (rhiD) { + rhiD->releaseQueue.append(e); + rhiD->unregisterResource(this); + } } bool QVkComputePipeline::create() @@ -6843,11 +7506,11 @@ bool QVkComputePipeline::create() destroy(); QRHI_RES_RHI(QRhiVulkan); + rhiD->pipelineCreationStart(); if (!rhiD->ensurePipelineCache()) return false; - VkPipelineLayoutCreateInfo pipelineLayoutInfo; - memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings); @@ -6859,8 +7522,7 @@ bool QVkComputePipeline::create() return false; } - VkComputePipelineCreateInfo pipelineInfo; - memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + VkComputePipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; pipelineInfo.layout = layout; @@ -6879,8 +7541,7 @@ bool QVkComputePipeline::create() return false; } VkShaderModule shader = rhiD->createShader(spirv.shader()); - VkPipelineShaderStageCreateInfo shaderInfo; - memset(&shaderInfo, 0, sizeof(shaderInfo)); + VkPipelineShaderStageCreateInfo shaderInfo = {}; shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; shaderInfo.module = shader; @@ -6894,6 +7555,7 @@ bool QVkComputePipeline::create() return false; } + rhiD->pipelineCreationEnd(); lastActiveFrameSlot = -1; generation += 1; rhiD->registerResource(this); @@ -6938,7 +7600,8 @@ const QRhiNativeHandles *QVkCommandBuffer::nativeHandles() QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi) : QRhiSwapChain(rhi), - rtWrapper(rhi), + rtWrapper(rhi, this), + rtWrapperRight(rhi, this), cbWrapper(rhi) { } @@ -6954,8 +7617,10 @@ void QVkSwapChain::destroy() return; QRHI_RES_RHI(QRhiVulkan); - rhiD->swapchains.remove(this); - rhiD->releaseSwapChainResources(this); + if (rhiD) { + rhiD->swapchains.remove(this); + rhiD->releaseSwapChainResources(this); + } for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { QVkSwapChain::FrameResources &frame(frameRes[i]); @@ -6965,10 +7630,8 @@ void QVkSwapChain::destroy() surface = lastConnectedSurface = VK_NULL_HANDLE; - QRHI_PROF; - QRHI_PROF_F(releaseSwapChain(this)); - - rhiD->unregisterResource(this); + if (rhiD) + rhiD->unregisterResource(this); } QRhiCommandBuffer *QVkSwapChain::currentFrameCommandBuffer() @@ -6981,6 +7644,11 @@ QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget() return &rtWrapper; } +QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer) +{ + return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight; +} + QSize QVkSwapChain::surfacePixelSize() { if (!ensureSurface()) @@ -6988,8 +7656,7 @@ QSize QVkSwapChain::surfacePixelSize() // The size from the QWindow may not exactly match the surface... so if a // size is reported from the surface, use that. - VkSurfaceCapabilitiesKHR surfaceCaps; - memset(&surfaceCaps, 0, sizeof(surfaceCaps)); + VkSurfaceCapabilitiesKHR surfaceCaps = {}; QRHI_RES_RHI(QRhiVulkan); rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps); VkExtent2D bufferSize = surfaceCaps.currentExtent; @@ -7000,6 +7667,52 @@ QSize QVkSwapChain::surfacePixelSize() return QSize(int(bufferSize.width), int(bufferSize.height)); } +static inline bool hdrFormatMatchesVkSurfaceFormat(QRhiSwapChain::Format f, const VkSurfaceFormatKHR &s) +{ + switch (f) { + case QRhiSwapChain::HDRExtendedSrgbLinear: + return s.format == VK_FORMAT_R16G16B16A16_SFLOAT + && s.colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT; + 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; + } + return false; +} + +bool QVkSwapChain::isFormatSupported(Format f) +{ + if (f == SDR) + return true; + + if (!m_window) { + qWarning("Attempted to call isFormatSupported() without a window set"); + return false; + } + + // we may be called before create so query the surface + VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(m_window); + + QRHI_RES_RHI(QRhiVulkan); + uint32_t formatCount = 0; + rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surf, &formatCount, nullptr); + QVarLengthArray<VkSurfaceFormatKHR, 8> formats(formatCount); + if (formatCount) { + rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surf, &formatCount, formats.data()); + for (uint32_t i = 0; i < formatCount; ++i) { + if (hdrFormatMatchesVkSurfaceFormat(f, formats[i])) + return true; + } + } + + return false; +} + QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor() { // not yet built so cannot rely on data computed in createOrResize() @@ -7019,6 +7732,7 @@ QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor() } rp->ownsRp = true; + rp->updateSerializedFormat(); rhiD->registerResource(rp); return rp; } @@ -7058,27 +7772,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->vkGetPhysicalDeviceSurfaceCapabilitiesKHR) { - rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>( - rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); - rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>( - rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR")); - rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>( - rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfacePresentModesKHR")); - if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR - || !rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR - || !rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR) - { - qWarning("Physical device surface queries not available"); - return false; - } + if (!rhiD->inst->supportsPresent(rhiD->physDev, rhiD->gfxQueueFamilyIdx, m_window)) { + qWarning("Presenting not supported on this window"); + return false; } quint32 formatCount = 0; @@ -7087,16 +7783,23 @@ bool QVkSwapChain::ensureSurface() if (formatCount) rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, formats.data()); + // See if there is a better match than the default BGRA8 format. (but if + // not, we will stick to the default) const bool srgbRequested = m_flags.testFlag(sRGB); for (int i = 0; i < int(formatCount); ++i) { - if (formats[i].format != VK_FORMAT_UNDEFINED && srgbRequested == isSrgbFormat(formats[i].format)) { - colorFormat = formats[i].format; - colorSpace = formats[i].colorSpace; - break; + if (formats[i].format != VK_FORMAT_UNDEFINED) { + 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; + break; + } } } - samples = rhiD->effectiveSampleCount(m_sampleCount); + samples = rhiD->effectiveSampleCountBits(m_sampleCount); quint32 presModeCount = 0; rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr); @@ -7152,6 +7855,7 @@ bool QVkSwapChain::createOrResize() if (!m_renderPassDesc) qWarning("QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()."); + rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp); @@ -7179,8 +7883,7 @@ bool QVkSwapChain::createOrResize() samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE }; - VkFramebufferCreateInfo fbInfo; - memset(&fbInfo, 0, sizeof(fbInfo)); + 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); @@ -7196,10 +7899,56 @@ bool QVkSwapChain::createOrResize() } } - frameCount = 0; + 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); - QRHI_PROF; - QRHI_PROF_F(resizeSwapChain(this, QVK_FRAMES_IN_FLIGHT, samples > VK_SAMPLE_COUNT_1_BIT ? QVK_FRAMES_IN_FLIGHT : 0, samples)); + 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; + } + 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); + 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) rhiD->registerResource(this); |