1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Gui module
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qrhivulkan_p_p.h"
38#include "qrhivulkanext_p.h"
39
40#define VMA_IMPLEMENTATION
41#define VMA_STATIC_VULKAN_FUNCTIONS 0
42#define VMA_RECORDING_ENABLED 0
43#define VMA_DEDICATED_ALLOCATION 0
44#ifdef QT_DEBUG
45#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1
46#endif
47#include "vk_mem_alloc.h"
48
49#include <qmath.h>
50#include <QVulkanFunctions>
51#include <QtGui/qwindow.h>
52
53QT_BEGIN_NAMESPACE
54
55/*
56 Vulkan 1.0 backend. Provides a double-buffered swapchain that throttles the
57 rendering thread to vsync. Textures and "static" buffers are device local,
58 and a separate, host visible staging buffer is used to upload data to them.
59 "Dynamic" buffers are in host visible memory and are duplicated (since there
60 can be 2 frames in flight). This is handled transparently to the application.
61
62 Barriers are generated automatically for each render or compute pass, based
63 on the resources that are used in that pass (in QRhiShaderResourceBindings,
64 vertex inputs, etc.). This implies deferring the recording of the command
65 buffer since the barriers have to be placed at the right place (before the
66 pass), and that can only be done once we know all the things the pass does.
67
68 This in turn has implications for integrating external commands
69 (beginExternal() - direct Vulkan calls - endExternal()) because that is
70 incompatible with this approach by nature. Therefore we support another mode
71 of operation, where each render or compute pass uses one or more secondary
72 command buffers (recorded right away), with each beginExternal() leading to
73 closing the current secondary cb, creating a new secondary cb for the
74 external content, and then starting yet another one in endExternal() for
75 whatever comes afterwards in the pass. This way the primary command buffer
76 only has vkCmdExecuteCommand(s) within a renderpass instance
77 (Begin-EndRenderPass). (i.e. our only subpass is then
78 VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS instead of
79 VK_SUBPASS_CONTENTS_INLINE)
80
81 The command buffer management mode is decided on a per frame basis,
82 controlled by the ExternalContentsInPass flag of beginFrame().
83*/
84
85/*!
86 \class QRhiVulkanInitParams
87 \internal
88 \inmodule QtGui
89 \brief Vulkan specific initialization parameters.
90
91 A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to
92 the user to ensure this is available and initialized. This is typically
93 done in main() similarly to the following:
94
95 \badcode
96 int main(int argc, char **argv)
97 {
98 ...
99
100 QVulkanInstance inst;
101 #ifndef Q_OS_ANDROID
102 inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
103 #else
104 inst.setLayers(QByteArrayList()
105 << "VK_LAYER_GOOGLE_threading"
106 << "VK_LAYER_LUNARG_parameter_validation"
107 << "VK_LAYER_LUNARG_object_tracker"
108 << "VK_LAYER_LUNARG_core_validation"
109 << "VK_LAYER_LUNARG_image"
110 << "VK_LAYER_LUNARG_swapchain"
111 << "VK_LAYER_GOOGLE_unique_objects");
112 #endif
113 inst.setExtensions(QByteArrayList()
114 << "VK_KHR_get_physical_device_properties2");
115 if (!inst.create())
116 qFatal("Vulkan not available");
117
118 ...
119 }
120 \endcode
121
122 The example here has two optional aspects: it enables the
123 \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan
124 validation layers}, when they are available, and also enables the
125 VK_KHR_get_physical_device_properties2 extension (part of Vulkan 1.1), when
126 available. The former is useful during the development phase (remember that
127 QVulkanInstance conveniently redirects messages and warnings to qDebug).
128 Avoid enabling it in production builds, however. The latter is important in
129 order to make QRhi::CustomInstanceStepRate available with Vulkan since
130 VK_EXT_vertex_attribute_divisor (part of Vulkan 1.1) depends on it. It can
131 be omitted when instanced drawing with a non-one step rate is not used.
132
133 Once this is done, a Vulkan-based QRhi can be created by passing the
134 instance and a QWindow with its surface type set to
135 QSurface::VulkanSurface:
136
137 \badcode
138 QRhiVulkanInitParams params;
139 params.inst = vulkanInstance;
140 params.window = window;
141 rhi = QRhi::create(QRhi::Vulkan, &params);
142 \endcode
143
144 The window is optional and can be omitted. This is not recommended however
145 because there is then no way to ensure presenting is supported while
146 choosing a graphics queue.
147
148 \note Even when a window is specified, QRhiSwapChain objects can be created
149 for other windows as well, as long as they all have their
150 QWindow::surfaceType() set to QSurface::VulkanSurface.
151
152 To request additional extensions to be enabled on the Vulkan device, list them
153 in deviceExtensions. This can be relevant when integrating with native Vulkan
154 rendering code.
155
156 \section2 Working with existing Vulkan devices
157
158 When interoperating with another graphics engine, it may be necessary to
159 get a QRhi instance that uses the same Vulkan device. This can be achieved
160 by passing a pointer to a QRhiVulkanNativeHandles to QRhi::create().
161
162 The physical device and device object must then be set to a non-null value.
163 In addition, either the graphics queue family index or the graphics queue
164 object itself is required. Prefer the former, whenever possible since
165 deducing the index is not possible afterwards. Optionally, an existing
166 command pool object can be specified as well, and, also optionally,
167 vmemAllocator can be used to share the same
168 \l{https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator}{Vulkan
169 memory allocator} between two QRhi instances.
170
171 The QRhi does not take ownership of any of the external objects.
172 */
173
174/*!
175 \class QRhiVulkanNativeHandles
176 \internal
177 \inmodule QtGui
178 \brief Collects device, queue, and other Vulkan objects that are used by the QRhi.
179
180 \note Ownership of the Vulkan objects is never transferred.
181 */
182
183/*!
184 \class QRhiVulkanCommandBufferNativeHandles
185 \internal
186 \inmodule QtGui
187 \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer.
188
189 \note The Vulkan command buffer object is only guaranteed to be valid, and
190 in recording state, while recording a frame. That is, between a
191 \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or
192 \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
193 \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
194 */
195
196/*!
197 \class QRhiVulkanRenderPassNativeHandles
198 \internal
199 \inmodule QtGui
200 \brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor.
201 */
202
203template <class Int>
204inline Int aligned(Int v, Int byteAlign)
205{
206 return (v + byteAlign - 1) & ~(byteAlign - 1);
207}
208
209static QVulkanInstance *globalVulkanInstance;
210
211static void VKAPI_PTR wrap_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties)
212{
213 globalVulkanInstance->functions()->vkGetPhysicalDeviceProperties(physicalDevice, pProperties);
214}
215
216static void VKAPI_PTR wrap_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties)
217{
218 globalVulkanInstance->functions()->vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties);
219}
220
221static VkResult VKAPI_PTR wrap_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory)
222{
223 return globalVulkanInstance->deviceFunctions(device)->vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory);
224}
225
226void VKAPI_PTR wrap_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator)
227{
228 globalVulkanInstance->deviceFunctions(device)->vkFreeMemory(device, memory, pAllocator);
229}
230
231VkResult VKAPI_PTR wrap_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData)
232{
233 return globalVulkanInstance->deviceFunctions(device)->vkMapMemory(device, memory, offset, size, flags, ppData);
234}
235
236void VKAPI_PTR wrap_vkUnmapMemory(VkDevice device, VkDeviceMemory memory)
237{
238 globalVulkanInstance->deviceFunctions(device)->vkUnmapMemory(device, memory);
239}
240
241VkResult VKAPI_PTR wrap_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)
242{
243 return globalVulkanInstance->deviceFunctions(device)->vkFlushMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
244}
245
246VkResult VKAPI_PTR wrap_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)
247{
248 return globalVulkanInstance->deviceFunctions(device)->vkInvalidateMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
249}
250
251VkResult VKAPI_PTR wrap_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset)
252{
253 return globalVulkanInstance->deviceFunctions(device)->vkBindBufferMemory(device, buffer, memory, memoryOffset);
254}
255
256VkResult VKAPI_PTR wrap_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset)
257{
258 return globalVulkanInstance->deviceFunctions(device)->vkBindImageMemory(device, image, memory, memoryOffset);
259}
260
261void VKAPI_PTR wrap_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements)
262{
263 globalVulkanInstance->deviceFunctions(device)->vkGetBufferMemoryRequirements(device, buffer, pMemoryRequirements);
264}
265
266void VKAPI_PTR wrap_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements)
267{
268 globalVulkanInstance->deviceFunctions(device)->vkGetImageMemoryRequirements(device, image, pMemoryRequirements);
269}
270
271VkResult VKAPI_PTR wrap_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer)
272{
273 return globalVulkanInstance->deviceFunctions(device)->vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
274}
275
276void VKAPI_PTR wrap_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator)
277{
278 globalVulkanInstance->deviceFunctions(device)->vkDestroyBuffer(device, buffer, pAllocator);
279}
280
281VkResult VKAPI_PTR wrap_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage)
282{
283 return globalVulkanInstance->deviceFunctions(device)->vkCreateImage(device, pCreateInfo, pAllocator, pImage);
284}
285
286void VKAPI_PTR wrap_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator)
287{
288 globalVulkanInstance->deviceFunctions(device)->vkDestroyImage(device, image, pAllocator);
289}
290
291static inline VmaAllocation toVmaAllocation(QVkAlloc a)
292{
293 return reinterpret_cast<VmaAllocation>(a);
294}
295
296static inline VmaAllocator toVmaAllocator(QVkAllocator a)
297{
298 return reinterpret_cast<VmaAllocator>(a);
299}
300
301QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importDevice)
302 : ofr(this)
303{
304 inst = params->inst;
305 maybeWindow = params->window; // may be null
306 requestedDeviceExtensions = params->deviceExtensions;
307
308 importedDevice = importDevice != nullptr;
309 if (importedDevice) {
310 physDev = importDevice->physDev;
311 dev = importDevice->dev;
312 if (physDev && dev) {
313 gfxQueueFamilyIdx = importDevice->gfxQueueFamilyIdx;
314 gfxQueue = importDevice->gfxQueue;
315 if (importDevice->cmdPool) {
316 importedCmdPool = true;
317 cmdPool = importDevice->cmdPool;
318 }
319 if (importDevice->vmemAllocator) {
320 importedAllocator = true;
321 allocator = importDevice->vmemAllocator;
322 }
323 } else {
324 qWarning(msg: "No (physical) Vulkan device is given, cannot import");
325 importedDevice = false;
326 }
327 }
328}
329
330static bool qvk_debug_filter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object,
331 size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage)
332{
333 Q_UNUSED(flags);
334 Q_UNUSED(objectType);
335 Q_UNUSED(object);
336 Q_UNUSED(location);
337 Q_UNUSED(messageCode);
338 Q_UNUSED(pLayerPrefix);
339
340 // Filter out certain misleading validation layer messages, as per
341 // VulkanMemoryAllocator documentation.
342 if (strstr(haystack: pMessage, needle: "Mapping an image with layout")
343 && strstr(haystack: pMessage, needle: "can result in undefined behavior if this memory is used by the device"))
344 {
345 return true;
346 }
347
348 // In certain cases allocateDescriptorSet() will attempt to allocate from a
349 // pool that does not have enough descriptors of a certain type. This makes
350 // the validation layer shout. However, this is not an error since we will
351 // then move on to another pool. If there is a real error, a qWarning
352 // message is shown by allocateDescriptorSet(), so the validation warning
353 // does not have any value and is just noise.
354 if (strstr(haystack: pMessage, needle: "VUID-VkDescriptorSetAllocateInfo-descriptorPool-00307"))
355 return true;
356
357 return false;
358}
359
360bool QRhiVulkan::create(QRhi::Flags flags)
361{
362 Q_UNUSED(flags);
363 Q_ASSERT(inst);
364
365 if (!inst->isValid()) {
366 qWarning(msg: "Vulkan instance is not valid");
367 return false;
368 }
369
370 globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application
371
372 f = inst->functions();
373
374 QVector<VkQueueFamilyProperties> queueFamilyProps;
375 auto queryQueueFamilyProps = [this, &queueFamilyProps] {
376 uint32_t queueCount = 0;
377 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
378 queueFamilyProps.resize(asize: int(queueCount));
379 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
380 };
381
382 if (!importedDevice) {
383 uint32_t physDevCount = 0;
384 f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr);
385 if (!physDevCount) {
386 qWarning(msg: "No physical devices");
387 return false;
388 }
389 QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount);
390 VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data());
391 if (err != VK_SUCCESS || !physDevCount) {
392 qWarning(msg: "Failed to enumerate physical devices: %d", err);
393 return false;
394 }
395
396 int physDevIndex = -1;
397 int requestedPhysDevIndex = -1;
398 if (qEnvironmentVariableIsSet(varName: "QT_VK_PHYSICAL_DEVICE_INDEX"))
399 requestedPhysDevIndex = qEnvironmentVariableIntValue(varName: "QT_VK_PHYSICAL_DEVICE_INDEX");
400
401 if (requestedPhysDevIndex < 0 && flags.testFlag(flag: QRhi::PreferSoftwareRenderer)) {
402 for (int i = 0; i < int(physDevCount); ++i) {
403 f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
404 if (physDevProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
405 requestedPhysDevIndex = i;
406 break;
407 }
408 }
409 }
410
411 for (int i = 0; i < int(physDevCount); ++i) {
412 f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
413 qCDebug(QRHI_LOG_INFO, "Physical device %d: '%s' %d.%d.%d (api %d.%d.%d vendor 0x%X device 0x%X type %d)",
414 i,
415 physDevProperties.deviceName,
416 VK_VERSION_MAJOR(physDevProperties.driverVersion),
417 VK_VERSION_MINOR(physDevProperties.driverVersion),
418 VK_VERSION_PATCH(physDevProperties.driverVersion),
419 VK_VERSION_MAJOR(physDevProperties.apiVersion),
420 VK_VERSION_MINOR(physDevProperties.apiVersion),
421 VK_VERSION_PATCH(physDevProperties.apiVersion),
422 physDevProperties.vendorID,
423 physDevProperties.deviceID,
424 physDevProperties.deviceType);
425 if (physDevIndex < 0 && (requestedPhysDevIndex < 0 || requestedPhysDevIndex == int(i))) {
426 physDevIndex = i;
427 qCDebug(QRHI_LOG_INFO, " using this physical device");
428 }
429 }
430
431 if (physDevIndex < 0) {
432 qWarning(msg: "No matching physical device");
433 return false;
434 }
435 physDev = physDevs[physDevIndex];
436
437 queryQueueFamilyProps();
438
439 gfxQueue = VK_NULL_HANDLE;
440
441 // We only support combined graphics+present queues. When it comes to
442 // compute, only combined graphics+compute queue is used, compute gets
443 // disabled otherwise.
444 gfxQueueFamilyIdx = -1;
445 int computelessGfxQueueCandidateIdx = -1;
446 for (int i = 0; i < queueFamilyProps.count(); ++i) {
447 qCDebug(QRHI_LOG_INFO, "queue family %d: flags=0x%x count=%d",
448 i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount);
449 if (gfxQueueFamilyIdx == -1
450 && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
451 && (!maybeWindow || inst->supportsPresent(physicalDevice: physDev, queueFamilyIndex: uint32_t(i), window: maybeWindow)))
452 {
453 if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
454 gfxQueueFamilyIdx = i;
455 else if (computelessGfxQueueCandidateIdx == -1)
456 computelessGfxQueueCandidateIdx = i;
457 }
458 }
459 if (gfxQueueFamilyIdx == -1) {
460 if (computelessGfxQueueCandidateIdx != -1) {
461 gfxQueueFamilyIdx = computelessGfxQueueCandidateIdx;
462 } else {
463 qWarning(msg: "No graphics (or no graphics+present) queue family found");
464 return false;
465 }
466 }
467
468 VkDeviceQueueCreateInfo queueInfo[2];
469 const float prio[] = { 0 };
470 memset(s: queueInfo, c: 0, n: sizeof(queueInfo));
471 queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
472 queueInfo[0].queueFamilyIndex = uint32_t(gfxQueueFamilyIdx);
473 queueInfo[0].queueCount = 1;
474 queueInfo[0].pQueuePriorities = prio;
475
476 QVector<const char *> devLayers;
477 if (inst->layers().contains(t: "VK_LAYER_LUNARG_standard_validation"))
478 devLayers.append(t: "VK_LAYER_LUNARG_standard_validation");
479
480 QVulkanInfoVector<QVulkanExtension> devExts;
481 uint32_t devExtCount = 0;
482 f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr);
483 if (devExtCount) {
484 QVector<VkExtensionProperties> extProps(devExtCount);
485 f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data());
486 for (const VkExtensionProperties &p : qAsConst(t&: extProps))
487 devExts.append(t: { .name: p.extensionName, .version: p.specVersion });
488 }
489 qCDebug(QRHI_LOG_INFO, "%d device extensions available", devExts.count());
490
491 QVector<const char *> requestedDevExts;
492 requestedDevExts.append(t: "VK_KHR_swapchain");
493
494 debugMarkersAvailable = false;
495 if (devExts.contains(VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) {
496 requestedDevExts.append(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
497 debugMarkersAvailable = true;
498 }
499
500 vertexAttribDivisorAvailable = false;
501 if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
502 if (inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2"))) {
503 requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
504 vertexAttribDivisorAvailable = true;
505 }
506 }
507
508 for (const QByteArray &ext : requestedDeviceExtensions) {
509 if (!ext.isEmpty()) {
510 if (devExts.contains(name: ext))
511 requestedDevExts.append(t: ext.constData());
512 else
513 qWarning(msg: "Device extension %s is not supported", ext.constData());
514 }
515 }
516
517 QByteArrayList envExtList = qgetenv(varName: "QT_VULKAN_DEVICE_EXTENSIONS").split(sep: ';');
518 for (const QByteArray &ext : envExtList) {
519 if (!ext.isEmpty() && !requestedDevExts.contains(t: ext)) {
520 if (devExts.contains(name: ext))
521 requestedDevExts.append(t: ext.constData());
522 else
523 qWarning(msg: "Device extension %s is not supported", ext.constData());
524 }
525 }
526
527 if (QRHI_LOG_INFO().isEnabled(type: QtDebugMsg)) {
528 qCDebug(QRHI_LOG_INFO, "Enabling device extensions:");
529 for (const char *ext : requestedDevExts)
530 qCDebug(QRHI_LOG_INFO, " %s", ext);
531 }
532
533 VkDeviceCreateInfo devInfo;
534 memset(s: &devInfo, c: 0, n: sizeof(devInfo));
535 devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
536 devInfo.queueCreateInfoCount = 1;
537 devInfo.pQueueCreateInfos = queueInfo;
538 devInfo.enabledLayerCount = uint32_t(devLayers.count());
539 devInfo.ppEnabledLayerNames = devLayers.constData();
540 devInfo.enabledExtensionCount = uint32_t(requestedDevExts.count());
541 devInfo.ppEnabledExtensionNames = requestedDevExts.constData();
542
543 err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
544 if (err != VK_SUCCESS) {
545 qWarning(msg: "Failed to create device: %d", err);
546 return false;
547 }
548 }
549
550 df = inst->deviceFunctions(device: dev);
551
552 if (!importedCmdPool) {
553 VkCommandPoolCreateInfo poolInfo;
554 memset(s: &poolInfo, c: 0, n: sizeof(poolInfo));
555 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
556 poolInfo.queueFamilyIndex = uint32_t(gfxQueueFamilyIdx);
557 VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
558 if (err != VK_SUCCESS) {
559 qWarning(msg: "Failed to create command pool: %d", err);
560 return false;
561 }
562 }
563
564 if (gfxQueueFamilyIdx != -1) {
565 if (!gfxQueue)
566 df->vkGetDeviceQueue(dev, uint32_t(gfxQueueFamilyIdx), 0, &gfxQueue);
567
568 if (queueFamilyProps.isEmpty())
569 queryQueueFamilyProps();
570
571 hasCompute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0;
572 timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits;
573 }
574
575 f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties);
576 ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment;
577 // helps little with an optimal offset of 1 (on some drivers) when the spec
578 // elsewhere states that the minimum bufferOffset is 4...
579 texbufAlign = qMax<VkDeviceSize>(a: 4, b: physDevProperties.limits.optimalBufferCopyOffsetAlignment);
580
581 f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures);
582 hasWideLines = physDevFeatures.wideLines;
583
584 if (!importedAllocator) {
585 VmaVulkanFunctions afuncs;
586 afuncs.vkGetPhysicalDeviceProperties = wrap_vkGetPhysicalDeviceProperties;
587 afuncs.vkGetPhysicalDeviceMemoryProperties = wrap_vkGetPhysicalDeviceMemoryProperties;
588 afuncs.vkAllocateMemory = wrap_vkAllocateMemory;
589 afuncs.vkFreeMemory = wrap_vkFreeMemory;
590 afuncs.vkMapMemory = wrap_vkMapMemory;
591 afuncs.vkUnmapMemory = wrap_vkUnmapMemory;
592 afuncs.vkFlushMappedMemoryRanges = wrap_vkFlushMappedMemoryRanges;
593 afuncs.vkInvalidateMappedMemoryRanges = wrap_vkInvalidateMappedMemoryRanges;
594 afuncs.vkBindBufferMemory = wrap_vkBindBufferMemory;
595 afuncs.vkBindImageMemory = wrap_vkBindImageMemory;
596 afuncs.vkGetBufferMemoryRequirements = wrap_vkGetBufferMemoryRequirements;
597 afuncs.vkGetImageMemoryRequirements = wrap_vkGetImageMemoryRequirements;
598 afuncs.vkCreateBuffer = wrap_vkCreateBuffer;
599 afuncs.vkDestroyBuffer = wrap_vkDestroyBuffer;
600 afuncs.vkCreateImage = wrap_vkCreateImage;
601 afuncs.vkDestroyImage = wrap_vkDestroyImage;
602
603 VmaAllocatorCreateInfo allocatorInfo;
604 memset(s: &allocatorInfo, c: 0, n: sizeof(allocatorInfo));
605 // A QRhi is supposed to be used from one single thread only. Disable
606 // the allocator's own mutexes. This gives a performance boost.
607 allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
608 allocatorInfo.physicalDevice = physDev;
609 allocatorInfo.device = dev;
610 allocatorInfo.pVulkanFunctions = &afuncs;
611 VmaAllocator vmaallocator;
612 VkResult err = vmaCreateAllocator(pCreateInfo: &allocatorInfo, pAllocator: &vmaallocator);
613 if (err != VK_SUCCESS) {
614 qWarning(msg: "Failed to create allocator: %d", err);
615 return false;
616 }
617 allocator = vmaallocator;
618 }
619
620 inst->installDebugOutputFilter(filter: qvk_debug_filter);
621
622 VkDescriptorPool pool;
623 VkResult err = createDescriptorPool(pool: &pool);
624 if (err == VK_SUCCESS)
625 descriptorPools.append(t: pool);
626 else
627 qWarning(msg: "Failed to create initial descriptor pool: %d", err);
628
629 VkQueryPoolCreateInfo timestampQueryPoolInfo;
630 memset(s: &timestampQueryPoolInfo, c: 0, n: sizeof(timestampQueryPoolInfo));
631 timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
632 timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
633 timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2;
634 err = df->vkCreateQueryPool(dev, &timestampQueryPoolInfo, nullptr, &timestampQueryPool);
635 if (err != VK_SUCCESS) {
636 qWarning(msg: "Failed to create timestamp query pool: %d", err);
637 return false;
638 }
639 timestampQueryPoolMap.resize(size: QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair
640 timestampQueryPoolMap.fill(aval: false);
641
642 if (debugMarkersAvailable) {
643 vkCmdDebugMarkerBegin = reinterpret_cast<PFN_vkCmdDebugMarkerBeginEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerBeginEXT"));
644 vkCmdDebugMarkerEnd = reinterpret_cast<PFN_vkCmdDebugMarkerEndEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerEndEXT"));
645 vkCmdDebugMarkerInsert = reinterpret_cast<PFN_vkCmdDebugMarkerInsertEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerInsertEXT"));
646 vkDebugMarkerSetObjectName = reinterpret_cast<PFN_vkDebugMarkerSetObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkDebugMarkerSetObjectNameEXT"));
647 }
648
649 deviceLost = false;
650
651 nativeHandlesStruct.physDev = physDev;
652 nativeHandlesStruct.dev = dev;
653 nativeHandlesStruct.gfxQueueFamilyIdx = gfxQueueFamilyIdx;
654 nativeHandlesStruct.gfxQueue = gfxQueue;
655 nativeHandlesStruct.cmdPool = cmdPool;
656 nativeHandlesStruct.vmemAllocator = allocator;
657
658 return true;
659}
660
661void QRhiVulkan::destroy()
662{
663 if (!df)
664 return;
665
666 if (!deviceLost)
667 df->vkDeviceWaitIdle(dev);
668
669 executeDeferredReleases(forced: true);
670 finishActiveReadbacks(forced: true);
671
672 if (ofr.cmdFence) {
673 df->vkDestroyFence(dev, ofr.cmdFence, nullptr);
674 ofr.cmdFence = VK_NULL_HANDLE;
675 }
676
677 if (ofr.cbWrapper.cb) {
678 df->vkFreeCommandBuffers(dev, cmdPool, 1, &ofr.cbWrapper.cb);
679 ofr.cbWrapper.cb = VK_NULL_HANDLE;
680 }
681
682 if (pipelineCache) {
683 df->vkDestroyPipelineCache(dev, pipelineCache, nullptr);
684 pipelineCache = VK_NULL_HANDLE;
685 }
686
687 for (const DescriptorPoolData &pool : descriptorPools)
688 df->vkDestroyDescriptorPool(dev, pool.pool, nullptr);
689
690 descriptorPools.clear();
691
692 if (timestampQueryPool) {
693 df->vkDestroyQueryPool(dev, timestampQueryPool, nullptr);
694 timestampQueryPool = VK_NULL_HANDLE;
695 }
696
697 if (!importedAllocator && allocator) {
698 vmaDestroyAllocator(allocator: toVmaAllocator(a: allocator));
699 allocator = nullptr;
700 }
701
702 if (!importedCmdPool && cmdPool) {
703 df->vkDestroyCommandPool(dev, cmdPool, nullptr);
704 cmdPool = VK_NULL_HANDLE;
705 }
706
707 if (!importedDevice && dev) {
708 df->vkDestroyDevice(dev, nullptr);
709 inst->resetDeviceFunctions(device: dev);
710 dev = VK_NULL_HANDLE;
711 }
712
713 f = nullptr;
714 df = nullptr;
715}
716
717VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool)
718{
719 VkDescriptorPoolSize descPoolSizes[] = {
720 { .type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount: QVK_UNIFORM_BUFFERS_PER_POOL },
721 { .type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .descriptorCount: QVK_UNIFORM_BUFFERS_PER_POOL },
722 { .type: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount: QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL },
723 { .type: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount: QVK_STORAGE_BUFFERS_PER_POOL },
724 { .type: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount: QVK_STORAGE_IMAGES_PER_POOL }
725 };
726 VkDescriptorPoolCreateInfo descPoolInfo;
727 memset(s: &descPoolInfo, c: 0, n: sizeof(descPoolInfo));
728 descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
729 // Do not enable vkFreeDescriptorSets - sets are never freed on their own
730 // (good so no trouble with fragmentation), they just deref their pool
731 // which is then reset at some point (or not).
732 descPoolInfo.flags = 0;
733 descPoolInfo.maxSets = QVK_DESC_SETS_PER_POOL;
734 descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]);
735 descPoolInfo.pPoolSizes = descPoolSizes;
736 return df->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, pool);
737}
738
739bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex)
740{
741 auto tryAllocate = [this, allocInfo, result](int poolIndex) {
742 allocInfo->descriptorPool = descriptorPools[poolIndex].pool;
743 VkResult r = df->vkAllocateDescriptorSets(dev, allocInfo, result);
744 if (r == VK_SUCCESS)
745 descriptorPools[poolIndex].refCount += 1;
746 return r;
747 };
748
749 int lastPoolIdx = descriptorPools.count() - 1;
750 for (int i = lastPoolIdx; i >= 0; --i) {
751 if (descriptorPools[i].refCount == 0) {
752 df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0);
753 descriptorPools[i].allocedDescSets = 0;
754 }
755 if (descriptorPools[i].allocedDescSets + int(allocInfo->descriptorSetCount) <= QVK_DESC_SETS_PER_POOL) {
756 VkResult err = tryAllocate(i);
757 if (err == VK_SUCCESS) {
758 descriptorPools[i].allocedDescSets += allocInfo->descriptorSetCount;
759 *resultPoolIndex = i;
760 return true;
761 }
762 }
763 }
764
765 VkDescriptorPool newPool;
766 VkResult poolErr = createDescriptorPool(pool: &newPool);
767 if (poolErr == VK_SUCCESS) {
768 descriptorPools.append(t: newPool);
769 lastPoolIdx = descriptorPools.count() - 1;
770 VkResult err = tryAllocate(lastPoolIdx);
771 if (err != VK_SUCCESS) {
772 qWarning(msg: "Failed to allocate descriptor set from new pool too, giving up: %d", err);
773 return false;
774 }
775 descriptorPools[lastPoolIdx].allocedDescSets += allocInfo->descriptorSetCount;
776 *resultPoolIndex = lastPoolIdx;
777 return true;
778 } else {
779 qWarning(msg: "Failed to allocate new descriptor pool: %d", poolErr);
780 return false;
781 }
782}
783
784static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
785{
786 const bool srgb = flags.testFlag(flag: QRhiTexture::sRGB);
787 switch (format) {
788 case QRhiTexture::RGBA8:
789 return srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
790 case QRhiTexture::BGRA8:
791 return srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
792 case QRhiTexture::R8:
793 return srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM;
794 case QRhiTexture::R16:
795 return VK_FORMAT_R16_UNORM;
796 case QRhiTexture::RED_OR_ALPHA8:
797 return VK_FORMAT_R8_UNORM;
798
799 case QRhiTexture::RGBA16F:
800 return VK_FORMAT_R16G16B16A16_SFLOAT;
801 case QRhiTexture::RGBA32F:
802 return VK_FORMAT_R32G32B32A32_SFLOAT;
803 case QRhiTexture::R16F:
804 return VK_FORMAT_R16_SFLOAT;
805 case QRhiTexture::R32F:
806 return VK_FORMAT_R32_SFLOAT;
807
808 case QRhiTexture::D16:
809 return VK_FORMAT_D16_UNORM;
810 case QRhiTexture::D32F:
811 return VK_FORMAT_D32_SFLOAT;
812
813 case QRhiTexture::BC1:
814 return srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK : VK_FORMAT_BC1_RGB_UNORM_BLOCK;
815 case QRhiTexture::BC2:
816 return srgb ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK;
817 case QRhiTexture::BC3:
818 return srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK;
819 case QRhiTexture::BC4:
820 return VK_FORMAT_BC4_UNORM_BLOCK;
821 case QRhiTexture::BC5:
822 return VK_FORMAT_BC5_UNORM_BLOCK;
823 case QRhiTexture::BC6H:
824 return VK_FORMAT_BC6H_UFLOAT_BLOCK;
825 case QRhiTexture::BC7:
826 return srgb ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK;
827
828 case QRhiTexture::ETC2_RGB8:
829 return srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
830 case QRhiTexture::ETC2_RGB8A1:
831 return srgb ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
832 case QRhiTexture::ETC2_RGBA8:
833 return srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
834
835 case QRhiTexture::ASTC_4x4:
836 return srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
837 case QRhiTexture::ASTC_5x4:
838 return srgb ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
839 case QRhiTexture::ASTC_5x5:
840 return srgb ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
841 case QRhiTexture::ASTC_6x5:
842 return srgb ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
843 case QRhiTexture::ASTC_6x6:
844 return srgb ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
845 case QRhiTexture::ASTC_8x5:
846 return srgb ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
847 case QRhiTexture::ASTC_8x6:
848 return srgb ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
849 case QRhiTexture::ASTC_8x8:
850 return srgb ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
851 case QRhiTexture::ASTC_10x5:
852 return srgb ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
853 case QRhiTexture::ASTC_10x6:
854 return srgb ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
855 case QRhiTexture::ASTC_10x8:
856 return srgb ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
857 case QRhiTexture::ASTC_10x10:
858 return srgb ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
859 case QRhiTexture::ASTC_12x10:
860 return srgb ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
861 case QRhiTexture::ASTC_12x12:
862 return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
863
864 default:
865 Q_UNREACHABLE();
866 return VK_FORMAT_R8G8B8A8_UNORM;
867 }
868}
869
870static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format, QRhiTexture::Flags *flags)
871{
872 switch (format) {
873 case VK_FORMAT_R8G8B8A8_UNORM:
874 return QRhiTexture::RGBA8;
875 case VK_FORMAT_R8G8B8A8_SRGB:
876 if (flags)
877 (*flags) |= QRhiTexture::sRGB;
878 return QRhiTexture::RGBA8;
879 case VK_FORMAT_B8G8R8A8_UNORM:
880 return QRhiTexture::BGRA8;
881 case VK_FORMAT_B8G8R8A8_SRGB:
882 if (flags)
883 (*flags) |= QRhiTexture::sRGB;
884 return QRhiTexture::BGRA8;
885 case VK_FORMAT_R8_UNORM:
886 return QRhiTexture::R8;
887 case VK_FORMAT_R8_SRGB:
888 if (flags)
889 (*flags) |= QRhiTexture::sRGB;
890 return QRhiTexture::R8;
891 case VK_FORMAT_R16_UNORM:
892 return QRhiTexture::R16;
893 default: // this cannot assert, must warn and return unknown
894 qWarning(msg: "VkFormat %d is not a recognized uncompressed color format", format);
895 break;
896 }
897 return QRhiTexture::UnknownFormat;
898}
899
900static inline bool isDepthTextureFormat(QRhiTexture::Format format)
901{
902 switch (format) {
903 case QRhiTexture::Format::D16:
904 case QRhiTexture::Format::D32F:
905 return true;
906
907 default:
908 return false;
909 }
910}
911
912// Transient images ("render buffers") backed by lazily allocated memory are
913// managed manually without going through vk_mem_alloc since it does not offer
914// any support for such images. This should be ok since in practice there
915// should be very few of such images.
916
917uint32_t QRhiVulkan::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
918{
919 VkPhysicalDeviceMemoryProperties physDevMemProps;
920 f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
921
922 VkMemoryRequirements memReq;
923 df->vkGetImageMemoryRequirements(dev, img, &memReq);
924 uint32_t memTypeIndex = uint32_t(-1);
925
926 if (memReq.memoryTypeBits) {
927 // Find a device local + lazily allocated, or at least device local memtype.
928 const VkMemoryType *memType = physDevMemProps.memoryTypes;
929 bool foundDevLocal = false;
930 for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
931 if (memReq.memoryTypeBits & (1 << i)) {
932 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
933 if (!foundDevLocal) {
934 foundDevLocal = true;
935 memTypeIndex = i;
936 }
937 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
938 memTypeIndex = i;
939 break;
940 }
941 }
942 }
943 }
944 }
945
946 return memTypeIndex;
947}
948
949bool QRhiVulkan::createTransientImage(VkFormat format,
950 const QSize &pixelSize,
951 VkImageUsageFlags usage,
952 VkImageAspectFlags aspectMask,
953 VkSampleCountFlagBits samples,
954 VkDeviceMemory *mem,
955 VkImage *images,
956 VkImageView *views,
957 int count)
958{
959 VkMemoryRequirements memReq;
960 VkResult err;
961
962 for (int i = 0; i < count; ++i) {
963 VkImageCreateInfo imgInfo;
964 memset(s: &imgInfo, c: 0, n: sizeof(imgInfo));
965 imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
966 imgInfo.imageType = VK_IMAGE_TYPE_2D;
967 imgInfo.format = format;
968 imgInfo.extent.width = uint32_t(pixelSize.width());
969 imgInfo.extent.height = uint32_t(pixelSize.height());
970 imgInfo.extent.depth = 1;
971 imgInfo.mipLevels = imgInfo.arrayLayers = 1;
972 imgInfo.samples = samples;
973 imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
974 imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
975 imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
976
977 err = df->vkCreateImage(dev, &imgInfo, nullptr, images + i);
978 if (err != VK_SUCCESS) {
979 qWarning(msg: "Failed to create image: %d", err);
980 return false;
981 }
982
983 // Assume the reqs are the same since the images are same in every way.
984 // Still, call GetImageMemReq for every image, in order to prevent the
985 // validation layer from complaining.
986 df->vkGetImageMemoryRequirements(dev, images[i], &memReq);
987 }
988
989 VkMemoryAllocateInfo memInfo;
990 memset(s: &memInfo, c: 0, n: sizeof(memInfo));
991 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
992 memInfo.allocationSize = aligned(v: memReq.size, byteAlign: memReq.alignment) * VkDeviceSize(count);
993
994 uint32_t startIndex = 0;
995 do {
996 memInfo.memoryTypeIndex = chooseTransientImageMemType(img: images[0], startIndex);
997 if (memInfo.memoryTypeIndex == uint32_t(-1)) {
998 qWarning(msg: "No suitable memory type found");
999 return false;
1000 }
1001 startIndex = memInfo.memoryTypeIndex + 1;
1002 err = df->vkAllocateMemory(dev, &memInfo, nullptr, mem);
1003 if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
1004 qWarning(msg: "Failed to allocate image memory: %d", err);
1005 return false;
1006 }
1007 } while (err != VK_SUCCESS);
1008
1009 VkDeviceSize ofs = 0;
1010 for (int i = 0; i < count; ++i) {
1011 err = df->vkBindImageMemory(dev, images[i], *mem, ofs);
1012 if (err != VK_SUCCESS) {
1013 qWarning(msg: "Failed to bind image memory: %d", err);
1014 return false;
1015 }
1016 ofs += aligned(v: memReq.size, byteAlign: memReq.alignment);
1017
1018 VkImageViewCreateInfo imgViewInfo;
1019 memset(s: &imgViewInfo, c: 0, n: sizeof(imgViewInfo));
1020 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1021 imgViewInfo.image = images[i];
1022 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1023 imgViewInfo.format = format;
1024 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1025 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1026 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1027 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1028 imgViewInfo.subresourceRange.aspectMask = aspectMask;
1029 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1030
1031 err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
1032 if (err != VK_SUCCESS) {
1033 qWarning(msg: "Failed to create image view: %d", err);
1034 return false;
1035 }
1036 }
1037
1038 return true;
1039}
1040
1041VkFormat QRhiVulkan::optimalDepthStencilFormat()
1042{
1043 if (optimalDsFormat != VK_FORMAT_UNDEFINED)
1044 return optimalDsFormat;
1045
1046 const VkFormat dsFormatCandidates[] = {
1047 VK_FORMAT_D24_UNORM_S8_UINT,
1048 VK_FORMAT_D32_SFLOAT_S8_UINT,
1049 VK_FORMAT_D16_UNORM_S8_UINT
1050 };
1051 const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
1052 int dsFormatIdx = 0;
1053 while (dsFormatIdx < dsFormatCandidateCount) {
1054 optimalDsFormat = dsFormatCandidates[dsFormatIdx];
1055 VkFormatProperties fmtProp;
1056 f->vkGetPhysicalDeviceFormatProperties(physDev, optimalDsFormat, &fmtProp);
1057 if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
1058 break;
1059 ++dsFormatIdx;
1060 }
1061 if (dsFormatIdx == dsFormatCandidateCount)
1062 qWarning(msg: "Failed to find an optimal depth-stencil format");
1063
1064 return optimalDsFormat;
1065}
1066
1067bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat)
1068{
1069 // attachment list layout is color (1), ds (0-1), resolve (0-1)
1070
1071 VkAttachmentDescription attDesc;
1072 memset(s: &attDesc, c: 0, n: sizeof(attDesc));
1073 attDesc.format = colorFormat;
1074 attDesc.samples = samples;
1075 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1076 attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
1077 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1078 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1079 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1080 attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1081 rpD->attDescs.append(t: attDesc);
1082
1083 rpD->colorRefs.append(t: { .attachment: 0, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
1084
1085 if (hasDepthStencil) {
1086 // clear on load + no store + lazy alloc + transient image should play
1087 // nicely with tiled GPUs (no physical backing necessary for ds buffer)
1088 memset(s: &attDesc, c: 0, n: sizeof(attDesc));
1089 attDesc.format = optimalDepthStencilFormat();
1090 attDesc.samples = samples;
1091 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1092 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1093 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1094 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1095 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1096 attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1097 rpD->attDescs.append(t: attDesc);
1098
1099 rpD->dsRef = { .attachment: 1, .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
1100 }
1101
1102 if (samples > VK_SAMPLE_COUNT_1_BIT) {
1103 memset(s: &attDesc, c: 0, n: sizeof(attDesc));
1104 attDesc.format = colorFormat;
1105 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
1106 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1107 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1108 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1109 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1110 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1111 attDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1112 rpD->attDescs.append(t: attDesc);
1113
1114 rpD->resolveRefs.append(t: { .attachment: 2, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
1115 }
1116
1117 VkSubpassDescription subpassDesc;
1118 memset(s: &subpassDesc, c: 0, n: sizeof(subpassDesc));
1119 subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1120 subpassDesc.colorAttachmentCount = 1;
1121 subpassDesc.pColorAttachments = rpD->colorRefs.constData();
1122 subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &rpD->dsRef : nullptr;
1123
1124 // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own.
1125 VkSubpassDependency subpassDep;
1126 memset(s: &subpassDep, c: 0, n: sizeof(subpassDep));
1127 subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL;
1128 subpassDep.dstSubpass = 0;
1129 subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1130 subpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1131 subpassDep.srcAccessMask = 0;
1132 subpassDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1133
1134 VkRenderPassCreateInfo rpInfo;
1135 memset(s: &rpInfo, c: 0, n: sizeof(rpInfo));
1136 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1137 rpInfo.attachmentCount = 1;
1138 rpInfo.pAttachments = rpD->attDescs.constData();
1139 rpInfo.subpassCount = 1;
1140 rpInfo.pSubpasses = &subpassDesc;
1141 rpInfo.dependencyCount = 1;
1142 rpInfo.pDependencies = &subpassDep;
1143
1144 if (hasDepthStencil)
1145 rpInfo.attachmentCount += 1;
1146
1147 if (samples > VK_SAMPLE_COUNT_1_BIT) {
1148 rpInfo.attachmentCount += 1;
1149 subpassDesc.pResolveAttachments = rpD->resolveRefs.constData();
1150 }
1151
1152 VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
1153 if (err != VK_SUCCESS) {
1154 qWarning(msg: "Failed to create renderpass: %d", err);
1155 return false;
1156 }
1157
1158 rpD->hasDepthStencil = hasDepthStencil;
1159
1160 return true;
1161}
1162
1163bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
1164 const QRhiColorAttachment *firstColorAttachment,
1165 const QRhiColorAttachment *lastColorAttachment,
1166 bool preserveColor,
1167 bool preserveDs,
1168 QRhiRenderBuffer *depthStencilBuffer,
1169 QRhiTexture *depthTexture)
1170{
1171 // attachment list layout is color (0-8), ds (0-1), resolve (0-8)
1172
1173 for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
1174 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
1175 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
1176 Q_ASSERT(texD || rbD);
1177 const VkFormat vkformat = texD ? texD->vkformat : rbD->vkformat;
1178 const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples;
1179
1180 VkAttachmentDescription attDesc;
1181 memset(s: &attDesc, c: 0, n: sizeof(attDesc));
1182 attDesc.format = vkformat;
1183 attDesc.samples = samples;
1184 attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
1185 attDesc.storeOp = it->resolveTexture() ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
1186 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1187 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1188 // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT
1189 attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
1190 attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1191 rpD->attDescs.append(t: attDesc);
1192
1193 const VkAttachmentReference ref = { .attachment: uint32_t(rpD->attDescs.count() - 1), .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
1194 rpD->colorRefs.append(t: ref);
1195 }
1196
1197 rpD->hasDepthStencil = depthStencilBuffer || depthTexture;
1198 if (rpD->hasDepthStencil) {
1199 const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat
1200 : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat;
1201 const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples
1202 : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples;
1203 const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
1204 const VkAttachmentStoreOp storeOp = depthTexture ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
1205 VkAttachmentDescription attDesc;
1206 memset(s: &attDesc, c: 0, n: sizeof(attDesc));
1207 attDesc.format = dsFormat;
1208 attDesc.samples = samples;
1209 attDesc.loadOp = loadOp;
1210 attDesc.storeOp = storeOp;
1211 attDesc.stencilLoadOp = loadOp;
1212 attDesc.stencilStoreOp = storeOp;
1213 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1214 attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1215 rpD->attDescs.append(t: attDesc);
1216 }
1217 rpD->dsRef = { .attachment: uint32_t(rpD->attDescs.count() - 1), .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
1218
1219 for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
1220 if (it->resolveTexture()) {
1221 QVkTexture *rtexD = QRHI_RES(QVkTexture, it->resolveTexture());
1222 if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
1223 qWarning(msg: "Resolving into a multisample texture is not supported");
1224
1225 VkAttachmentDescription attDesc;
1226 memset(s: &attDesc, c: 0, n: sizeof(attDesc));
1227 attDesc.format = rtexD->vkformat;
1228 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
1229 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
1230 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1231 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1232 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1233 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1234 attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1235 rpD->attDescs.append(t: attDesc);
1236
1237 const VkAttachmentReference ref = { .attachment: uint32_t(rpD->attDescs.count() - 1), .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
1238 rpD->resolveRefs.append(t: ref);
1239 } else {
1240 const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
1241 rpD->resolveRefs.append(t: ref);
1242 }
1243 }
1244
1245 VkSubpassDescription subpassDesc;
1246 memset(s: &subpassDesc, c: 0, n: sizeof(subpassDesc));
1247 subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1248 subpassDesc.colorAttachmentCount = uint32_t(rpD->colorRefs.count());
1249 Q_ASSERT(rpD->colorRefs.count() == rpD->resolveRefs.count());
1250 subpassDesc.pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr;
1251 subpassDesc.pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr;
1252 subpassDesc.pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr;
1253
1254 VkRenderPassCreateInfo rpInfo;
1255 memset(s: &rpInfo, c: 0, n: sizeof(rpInfo));
1256 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1257 rpInfo.attachmentCount = uint32_t(rpD->attDescs.count());
1258 rpInfo.pAttachments = rpD->attDescs.constData();
1259 rpInfo.subpassCount = 1;
1260 rpInfo.pSubpasses = &subpassDesc;
1261 // don't yet know the correct initial/final access and stage stuff for the
1262 // implicit deps at this point, so leave it to the resource tracking to
1263 // generate barriers
1264
1265 VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
1266 if (err != VK_SUCCESS) {
1267 qWarning(msg: "Failed to create renderpass: %d", err);
1268 return false;
1269 }
1270
1271 return true;
1272}
1273
1274bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
1275{
1276 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
1277 if (swapChainD->pixelSize.isEmpty()) {
1278 qWarning(msg: "Surface size is 0, cannot create swapchain");
1279 return false;
1280 }
1281
1282 df->vkDeviceWaitIdle(dev);
1283
1284 if (!vkCreateSwapchainKHR) {
1285 vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
1286 vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
1287 vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
1288 vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
1289 vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
1290 if (!vkCreateSwapchainKHR || !vkDestroySwapchainKHR || !vkGetSwapchainImagesKHR || !vkAcquireNextImageKHR || !vkQueuePresentKHR) {
1291 qWarning(msg: "Swapchain functions not available");
1292 return false;
1293 }
1294 }
1295
1296 VkSurfaceCapabilitiesKHR surfaceCaps;
1297 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, swapChainD->surface, &surfaceCaps);
1298 quint32 reqBufferCount;
1299 if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::MinimalBufferCount)) {
1300 reqBufferCount = qMax<quint32>(a: 2, b: surfaceCaps.minImageCount);
1301 } else {
1302 const quint32 maxBuffers = QVkSwapChain::MAX_BUFFER_COUNT;
1303 if (surfaceCaps.maxImageCount)
1304 reqBufferCount = qMax(a: qMin(a: surfaceCaps.maxImageCount, b: maxBuffers), b: surfaceCaps.minImageCount);
1305 else
1306 reqBufferCount = qMax<quint32>(a: 2, b: surfaceCaps.minImageCount);
1307 }
1308
1309 VkSurfaceTransformFlagBitsKHR preTransform =
1310 (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
1311 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
1312 : surfaceCaps.currentTransform;
1313
1314 VkCompositeAlphaFlagBitsKHR compositeAlpha =
1315 (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
1316 ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
1317 : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1318
1319 if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::SurfaceHasPreMulAlpha)
1320 && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR))
1321 {
1322 compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
1323 }
1324
1325 if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::SurfaceHasNonPreMulAlpha)
1326 && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR))
1327 {
1328 compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
1329 }
1330
1331 VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1332 swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
1333 if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(flag: QRhiSwapChain::UsedAsTransferSource))
1334 usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1335
1336 VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
1337 if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::NoVSync)) {
1338 if (swapChainD->supportedPresentationModes.contains(t: VK_PRESENT_MODE_MAILBOX_KHR))
1339 presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
1340 else if (swapChainD->supportedPresentationModes.contains(t: VK_PRESENT_MODE_IMMEDIATE_KHR))
1341 presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
1342 }
1343
1344 // If the surface is different than before, then passing in the old
1345 // swapchain associated with the old surface can fail the swapchain
1346 // creation. (for example, Android loses the surface when backgrounding and
1347 // restoring applications, and it also enforces failing swapchain creation
1348 // with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR if the old swapchain is provided)
1349 const bool reuseExisting = swapChainD->sc && swapChainD->lastConnectedSurface == swapChainD->surface;
1350
1351 qCDebug(QRHI_LOG_INFO, "Creating %s swapchain of %u buffers, size %dx%d, presentation mode %d",
1352 reuseExisting ? "recycled" : "new",
1353 reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode);
1354
1355 VkSwapchainCreateInfoKHR swapChainInfo;
1356 memset(s: &swapChainInfo, c: 0, n: sizeof(swapChainInfo));
1357 swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1358 swapChainInfo.surface = swapChainD->surface;
1359 swapChainInfo.minImageCount = reqBufferCount;
1360 swapChainInfo.imageFormat = swapChainD->colorFormat;
1361 swapChainInfo.imageColorSpace = swapChainD->colorSpace;
1362 swapChainInfo.imageExtent = VkExtent2D { .width: uint32_t(swapChainD->pixelSize.width()), .height: uint32_t(swapChainD->pixelSize.height()) };
1363 swapChainInfo.imageArrayLayers = 1;
1364 swapChainInfo.imageUsage = usage;
1365 swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1366 swapChainInfo.preTransform = preTransform;
1367 swapChainInfo.compositeAlpha = compositeAlpha;
1368 swapChainInfo.presentMode = presentMode;
1369 swapChainInfo.clipped = true;
1370 swapChainInfo.oldSwapchain = reuseExisting ? swapChainD->sc : VK_NULL_HANDLE;
1371
1372 VkSwapchainKHR newSwapChain;
1373 VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
1374 if (err != VK_SUCCESS) {
1375 qWarning(msg: "Failed to create swapchain: %d", err);
1376 return false;
1377 }
1378
1379 if (swapChainD->sc)
1380 releaseSwapChainResources(swapChain);
1381
1382 swapChainD->sc = newSwapChain;
1383 swapChainD->lastConnectedSurface = swapChainD->surface;
1384
1385 quint32 actualSwapChainBufferCount = 0;
1386 err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, nullptr);
1387 if (err != VK_SUCCESS || actualSwapChainBufferCount == 0) {
1388 qWarning(msg: "Failed to get swapchain images: %d", err);
1389 return false;
1390 }
1391
1392 if (actualSwapChainBufferCount > QVkSwapChain::MAX_BUFFER_COUNT) {
1393 qWarning(msg: "Too many swapchain buffers (%u)", actualSwapChainBufferCount);
1394 return false;
1395 }
1396 if (actualSwapChainBufferCount != reqBufferCount)
1397 qCDebug(QRHI_LOG_INFO, "Actual swapchain buffer count is %u", actualSwapChainBufferCount);
1398 swapChainD->bufferCount = int(actualSwapChainBufferCount);
1399
1400 VkImage swapChainImages[QVkSwapChain::MAX_BUFFER_COUNT];
1401 err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, swapChainImages);
1402 if (err != VK_SUCCESS) {
1403 qWarning(msg: "Failed to get swapchain images: %d", err);
1404 return false;
1405 }
1406
1407 VkImage msaaImages[QVkSwapChain::MAX_BUFFER_COUNT];
1408 VkImageView msaaViews[QVkSwapChain::MAX_BUFFER_COUNT];
1409 if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
1410 if (!createTransientImage(format: swapChainD->colorFormat,
1411 pixelSize: swapChainD->pixelSize,
1412 usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
1413 aspectMask: VK_IMAGE_ASPECT_COLOR_BIT,
1414 samples: swapChainD->samples,
1415 mem: &swapChainD->msaaImageMem,
1416 images: msaaImages,
1417 views: msaaViews,
1418 count: swapChainD->bufferCount))
1419 {
1420 qWarning(msg: "Failed to create transient image for MSAA color buffer");
1421 return false;
1422 }
1423 }
1424
1425 VkFenceCreateInfo fenceInfo;
1426 memset(s: &fenceInfo, c: 0, n: sizeof(fenceInfo));
1427 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1428 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1429
1430 for (int i = 0; i < swapChainD->bufferCount; ++i) {
1431 QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
1432 image.image = swapChainImages[i];
1433 if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
1434 image.msaaImage = msaaImages[i];
1435 image.msaaImageView = msaaViews[i];
1436 }
1437
1438 VkImageViewCreateInfo imgViewInfo;
1439 memset(s: &imgViewInfo, c: 0, n: sizeof(imgViewInfo));
1440 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1441 imgViewInfo.image = swapChainImages[i];
1442 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1443 imgViewInfo.format = swapChainD->colorFormat;
1444 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1445 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1446 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1447 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1448 imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1449 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1450 err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
1451 if (err != VK_SUCCESS) {
1452 qWarning(msg: "Failed to create swapchain image view %d: %d", i, err);
1453 return false;
1454 }
1455
1456 image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone;
1457 }
1458
1459 swapChainD->currentImageIndex = 0;
1460
1461 VkSemaphoreCreateInfo semInfo;
1462 memset(s: &semInfo, c: 0, n: sizeof(semInfo));
1463 semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1464
1465 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
1466 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
1467
1468 frame.imageAcquired = false;
1469 frame.imageSemWaitable = false;
1470
1471 df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.imageFence);
1472 frame.imageFenceWaitable = true; // fence was created in signaled state
1473
1474 df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
1475 df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
1476
1477 err = df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
1478 if (err != VK_SUCCESS) {
1479 qWarning(msg: "Failed to create command buffer fence: %d", err);
1480 return false;
1481 }
1482 frame.cmdFenceWaitable = true; // fence was created in signaled state
1483 }
1484
1485 swapChainD->currentFrameSlot = 0;
1486
1487 return true;
1488}
1489
1490void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain)
1491{
1492 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
1493
1494 if (swapChainD->sc == VK_NULL_HANDLE)
1495 return;
1496
1497 if (!deviceLost)
1498 df->vkDeviceWaitIdle(dev);
1499
1500 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
1501 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
1502 if (frame.cmdFence) {
1503 if (frame.cmdFenceWaitable)
1504 df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
1505 df->vkDestroyFence(dev, frame.cmdFence, nullptr);
1506 frame.cmdFence = VK_NULL_HANDLE;
1507 frame.cmdFenceWaitable = false;
1508 }
1509 if (frame.imageFence) {
1510 if (frame.imageFenceWaitable)
1511 df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX);
1512 df->vkDestroyFence(dev, frame.imageFence, nullptr);
1513 frame.imageFence = VK_NULL_HANDLE;
1514 frame.imageFenceWaitable = false;
1515 }
1516 if (frame.imageSem) {
1517 df->vkDestroySemaphore(dev, frame.imageSem, nullptr);
1518 frame.imageSem = VK_NULL_HANDLE;
1519 }
1520 if (frame.drawSem) {
1521 df->vkDestroySemaphore(dev, frame.drawSem, nullptr);
1522 frame.drawSem = VK_NULL_HANDLE;
1523 }
1524 if (frame.cmdBuf) {
1525 df->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
1526 frame.cmdBuf = VK_NULL_HANDLE;
1527 }
1528 }
1529
1530 for (int i = 0; i < swapChainD->bufferCount; ++i) {
1531 QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
1532 if (image.fb) {
1533 df->vkDestroyFramebuffer(dev, image.fb, nullptr);
1534 image.fb = VK_NULL_HANDLE;
1535 }
1536 if (image.imageView) {
1537 df->vkDestroyImageView(dev, image.imageView, nullptr);
1538 image.imageView = VK_NULL_HANDLE;
1539 }
1540 if (image.msaaImageView) {
1541 df->vkDestroyImageView(dev, image.msaaImageView, nullptr);
1542 image.msaaImageView = VK_NULL_HANDLE;
1543 }
1544 if (image.msaaImage) {
1545 df->vkDestroyImage(dev, image.msaaImage, nullptr);
1546 image.msaaImage = VK_NULL_HANDLE;
1547 }
1548 }
1549
1550 if (swapChainD->msaaImageMem) {
1551 df->vkFreeMemory(dev, swapChainD->msaaImageMem, nullptr);
1552 swapChainD->msaaImageMem = VK_NULL_HANDLE;
1553 }
1554
1555 vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr);
1556 swapChainD->sc = VK_NULL_HANDLE;
1557
1558 // NB! surface and similar must remain intact
1559}
1560
1561QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
1562{
1563 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
1564 const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0;
1565 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]);
1566 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1567
1568 if (!frame.imageAcquired) {
1569 // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate
1570 // (note that we are using FIFO mode -> vsync)
1571 if (frame.imageFenceWaitable) {
1572 df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX);
1573 df->vkResetFences(dev, 1, &frame.imageFence);
1574 frame.imageFenceWaitable = false;
1575 }
1576
1577 // move on to next swapchain image
1578 VkResult err = vkAcquireNextImageKHR(dev, swapChainD->sc, UINT64_MAX,
1579 frame.imageSem, frame.imageFence, &frame.imageIndex);
1580 if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
1581 swapChainD->currentImageIndex = frame.imageIndex;
1582 frame.imageSemWaitable = true;
1583 frame.imageAcquired = true;
1584 frame.imageFenceWaitable = true;
1585 } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
1586 return QRhi::FrameOpSwapChainOutOfDate;
1587 } else {
1588 if (err == VK_ERROR_DEVICE_LOST) {
1589 qWarning(msg: "Device loss detected in vkAcquireNextImageKHR()");
1590 deviceLost = true;
1591 return QRhi::FrameOpDeviceLost;
1592 }
1593 qWarning(msg: "Failed to acquire next swapchain image: %d", err);
1594 return QRhi::FrameOpError;
1595 }
1596 }
1597
1598 // Make sure the previous commands for the same image have finished. (note
1599 // that this is based on the fence from the command buffer submit, nothing
1600 // to do with the Present)
1601 //
1602 // Do this also for any other swapchain's commands with the same frame slot
1603 // While this reduces concurrency, it keeps resource usage safe: swapchain
1604 // A starting its frame 0, followed by swapchain B starting its own frame 0
1605 // will make B wait for A's frame 0 commands, so if a resource is written
1606 // in B's frame or when B checks for pending resource releases, that won't
1607 // mess up A's in-flight commands (as they are not in flight anymore).
1608 waitCommandCompletion(frameSlot: frameResIndex);
1609
1610 // Now is the time to read the timestamps for the previous frame for this slot.
1611 if (frame.timestampQueryIndex >= 0) {
1612 quint64 timestamp[2] = { 0, 0 };
1613 VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2,
1614 2 * sizeof(quint64), timestamp, sizeof(quint64),
1615 VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
1616 timestampQueryPoolMap.clearBit(i: frame.timestampQueryIndex / 2);
1617 frame.timestampQueryIndex = -1;
1618 if (err == VK_SUCCESS) {
1619 quint64 mask = 0;
1620 for (quint64 i = 0; i < timestampValidBits; i += 8)
1621 mask |= 0xFFULL << i;
1622 const quint64 ts0 = timestamp[0] & mask;
1623 const quint64 ts1 = timestamp[1] & mask;
1624 const float nsecsPerTick = physDevProperties.limits.timestampPeriod;
1625 if (!qFuzzyIsNull(f: nsecsPerTick)) {
1626 const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f;
1627 // now we have the gpu time for the previous frame for this slot, report it
1628 // (does not matter that it is not for this frame)
1629 QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs));
1630 }
1631 } else {
1632 qWarning(msg: "Failed to query timestamp: %d", err);
1633 }
1634 }
1635
1636 // build new draw command buffer
1637 QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(cb: &frame.cmdBuf);
1638 if (cbres != QRhi::FrameOpSuccess)
1639 return cbres;
1640
1641 // when profiling is enabled, pick a free query (pair) from the pool
1642 int timestampQueryIdx = -1;
1643 if (profilerPrivateOrNull() && swapChainD->bufferCount > 1) { // no timestamps if not having at least 2 frames in flight
1644 for (int i = 0; i < timestampQueryPoolMap.count(); ++i) {
1645 if (!timestampQueryPoolMap.testBit(i)) {
1646 timestampQueryPoolMap.setBit(i);
1647 timestampQueryIdx = i * 2;
1648 break;
1649 }
1650 }
1651 }
1652 if (timestampQueryIdx >= 0) {
1653 df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2);
1654 // record timestamp at the start of the command buffer
1655 df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1656 timestampQueryPool, uint32_t(timestampQueryIdx));
1657 frame.timestampQueryIndex = timestampQueryIdx;
1658 }
1659
1660 swapChainD->cbWrapper.cb = frame.cmdBuf;
1661 swapChainD->cbWrapper.useSecondaryCb = flags.testFlag(flag: QRhi::ExternalContentsInPass);
1662
1663 QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
1664 swapChainD->rtWrapper.d.fb = image.fb;
1665
1666 currentFrameSlot = int(swapChainD->currentFrameSlot);
1667 currentSwapChain = swapChainD;
1668 if (swapChainD->ds)
1669 swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
1670
1671 QRHI_PROF_F(beginSwapChainFrame(swapChain));
1672
1673 prepareNewFrame(cb: &swapChainD->cbWrapper);
1674
1675 return QRhi::FrameOpSuccess;
1676}
1677
1678QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
1679{
1680 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
1681 Q_ASSERT(currentSwapChain == swapChainD);
1682
1683 recordPrimaryCommandBuffer(cbD: &swapChainD->cbWrapper);
1684
1685 int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0;
1686 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]);
1687 QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
1688
1689 if (image.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) {
1690 VkImageMemoryBarrier presTrans;
1691 memset(s: &presTrans, c: 0, n: sizeof(presTrans));
1692 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1693 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1694 presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1695 presTrans.image = image.image;
1696 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1697 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
1698
1699 if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseNone) {
1700 // was not used at all (no render pass), just transition from undefined to presentable
1701 presTrans.srcAccessMask = 0;
1702 presTrans.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1703 df->vkCmdPipelineBarrier(frame.cmdBuf,
1704 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
1705 0, 0, nullptr, 0, nullptr,
1706 1, &presTrans);
1707 } else if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseTransferSource) {
1708 // was used in a readback as transfer source, go back to presentable layout
1709 presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
1710 presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
1711 df->vkCmdPipelineBarrier(frame.cmdBuf,
1712 VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
1713 0, 0, nullptr, 0, nullptr,
1714 1, &presTrans);
1715 }
1716 image.lastUse = QVkSwapChain::ImageResources::ScImageUseRender;
1717 }
1718
1719 // record another timestamp, when enabled
1720 if (frame.timestampQueryIndex >= 0) {
1721 df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
1722 timestampQueryPool, uint32_t(frame.timestampQueryIndex + 1));
1723 }
1724
1725 // stop recording and submit to the queue
1726 Q_ASSERT(!frame.cmdFenceWaitable);
1727 const bool needsPresent = !flags.testFlag(flag: QRhi::SkipPresent);
1728 QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb: frame.cmdBuf,
1729 cmdFence: frame.cmdFence,
1730 waitSem: frame.imageSemWaitable ? &frame.imageSem : nullptr,
1731 signalSem: needsPresent ? &frame.drawSem : nullptr);
1732 if (submitres != QRhi::FrameOpSuccess)
1733 return submitres;
1734
1735 frame.imageSemWaitable = false;
1736 frame.cmdFenceWaitable = true;
1737
1738 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
1739 // this must be done before the Present
1740 QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
1741
1742 if (needsPresent) {
1743 // add the Present to the queue
1744 VkPresentInfoKHR presInfo;
1745 memset(s: &presInfo, c: 0, n: sizeof(presInfo));
1746 presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1747 presInfo.swapchainCount = 1;
1748 presInfo.pSwapchains = &swapChainD->sc;
1749 presInfo.pImageIndices = &swapChainD->currentImageIndex;
1750 presInfo.waitSemaphoreCount = 1;
1751 presInfo.pWaitSemaphores = &frame.drawSem; // gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem;
1752
1753 // Do platform-specific WM notification. F.ex. essential on Wayland in
1754 // order to circumvent driver frame callbacks
1755 inst->presentAboutToBeQueued(window: swapChainD->window);
1756
1757 VkResult err = vkQueuePresentKHR(gfxQueue, &presInfo);
1758 if (err != VK_SUCCESS) {
1759 if (err == VK_ERROR_OUT_OF_DATE_KHR) {
1760 return QRhi::FrameOpSwapChainOutOfDate;
1761 } else if (err != VK_SUBOPTIMAL_KHR) {
1762 if (err == VK_ERROR_DEVICE_LOST) {
1763 qWarning(msg: "Device loss detected in vkQueuePresentKHR()");
1764 deviceLost = true;
1765 return QRhi::FrameOpDeviceLost;
1766 }
1767 qWarning(msg: "Failed to present: %d", err);
1768 return QRhi::FrameOpError;
1769 }
1770 }
1771
1772 // Do platform-specific WM notification. F.ex. essential on X11 in
1773 // order to prevent glitches on resizing the window.
1774 inst->presentQueued(window: swapChainD->window);
1775
1776 // mark the current swapchain buffer as unused from our side
1777 frame.imageAcquired = false;
1778 // and move on to the next buffer
1779 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
1780 }
1781
1782 swapChainD->frameCount += 1;
1783 currentSwapChain = nullptr;
1784 return QRhi::FrameOpSuccess;
1785}
1786
1787void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb)
1788{
1789 // Now is the time to do things for frame N-F, where N is the current one,
1790 // F is QVK_FRAMES_IN_FLIGHT, because only here it is guaranteed that that
1791 // frame has completed on the GPU (due to the fence wait in beginFrame). To
1792 // decide if something is safe to handle now a simple "lastActiveFrameSlot
1793 // == currentFrameSlot" is sufficient (remember that e.g. with F==2
1794 // currentFrameSlot goes 0, 1, 0, 1, 0, ...)
1795 //
1796 // With multiple swapchains on the same QRhi things get more convoluted
1797 // (and currentFrameSlot strictly alternating is not true anymore) but
1798 // beginNonWrapperFrame() solves that by blocking as necessary so the rest
1799 // here is safe regardless.
1800
1801 executeDeferredReleases();
1802
1803 QRHI_RES(QVkCommandBuffer, cb)->resetState();
1804
1805 finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
1806}
1807
1808QRhi::FrameOpResult QRhiVulkan::startPrimaryCommandBuffer(VkCommandBuffer *cb)
1809{
1810 if (*cb) {
1811 df->vkFreeCommandBuffers(dev, cmdPool, 1, cb);
1812 *cb = VK_NULL_HANDLE;
1813 }
1814
1815 VkCommandBufferAllocateInfo cmdBufInfo;
1816 memset(s: &cmdBufInfo, c: 0, n: sizeof(cmdBufInfo));
1817 cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1818 cmdBufInfo.commandPool = cmdPool;
1819 cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1820 cmdBufInfo.commandBufferCount = 1;
1821
1822 VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, cb);
1823 if (err != VK_SUCCESS) {
1824 if (err == VK_ERROR_DEVICE_LOST) {
1825 qWarning(msg: "Device loss detected in vkAllocateCommandBuffers()");
1826 deviceLost = true;
1827 return QRhi::FrameOpDeviceLost;
1828 }
1829 qWarning(msg: "Failed to allocate frame command buffer: %d", err);
1830 return QRhi::FrameOpError;
1831 }
1832
1833 VkCommandBufferBeginInfo cmdBufBeginInfo;
1834 memset(s: &cmdBufBeginInfo, c: 0, n: sizeof(cmdBufBeginInfo));
1835 cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1836
1837 err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo);
1838 if (err != VK_SUCCESS) {
1839 if (err == VK_ERROR_DEVICE_LOST) {
1840 qWarning(msg: "Device loss detected in vkBeginCommandBuffer()");
1841 deviceLost = true;
1842 return QRhi::FrameOpDeviceLost;
1843 }
1844 qWarning(msg: "Failed to begin frame command buffer: %d", err);
1845 return QRhi::FrameOpError;
1846 }
1847
1848 return QRhi::FrameOpSuccess;
1849}
1850
1851QRhi::FrameOpResult QRhiVulkan::endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence,
1852 VkSemaphore *waitSem, VkSemaphore *signalSem)
1853{
1854 VkResult err = df->vkEndCommandBuffer(cb);
1855 if (err != VK_SUCCESS) {
1856 if (err == VK_ERROR_DEVICE_LOST) {
1857 qWarning(msg: "Device loss detected in vkEndCommandBuffer()");
1858 deviceLost = true;
1859 return QRhi::FrameOpDeviceLost;
1860 }
1861 qWarning(msg: "Failed to end frame command buffer: %d", err);
1862 return QRhi::FrameOpError;
1863 }
1864
1865 VkSubmitInfo submitInfo;
1866 memset(s: &submitInfo, c: 0, n: sizeof(submitInfo));
1867 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1868 submitInfo.commandBufferCount = 1;
1869 submitInfo.pCommandBuffers = &cb;
1870 if (waitSem) {
1871 submitInfo.waitSemaphoreCount = 1;
1872 submitInfo.pWaitSemaphores = waitSem;
1873 }
1874 if (signalSem) {
1875 submitInfo.signalSemaphoreCount = 1;
1876 submitInfo.pSignalSemaphores = signalSem;
1877 }
1878 VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1879 submitInfo.pWaitDstStageMask = &psf;
1880
1881 err = df->vkQueueSubmit(gfxQueue, 1, &submitInfo, cmdFence);
1882 if (err != VK_SUCCESS) {
1883 if (err == VK_ERROR_DEVICE_LOST) {
1884 qWarning(msg: "Device loss detected in vkQueueSubmit()");
1885 deviceLost = true;
1886 return QRhi::FrameOpDeviceLost;
1887 }
1888 qWarning(msg: "Failed to submit to graphics queue: %d", err);
1889 return QRhi::FrameOpError;
1890 }
1891
1892 return QRhi::FrameOpSuccess;
1893}
1894
1895void QRhiVulkan::waitCommandCompletion(int frameSlot)
1896{
1897 for (QVkSwapChain *sc : qAsConst(t&: swapchains)) {
1898 const int frameResIndex = sc->bufferCount > 1 ? frameSlot : 0;
1899 QVkSwapChain::FrameResources &frame(sc->frameRes[frameResIndex]);
1900 if (frame.cmdFenceWaitable) {
1901 df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
1902 df->vkResetFences(dev, 1, &frame.cmdFence);
1903 frame.cmdFenceWaitable = false;
1904 }
1905 }
1906}
1907
1908QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
1909{
1910 QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(cb: &ofr.cbWrapper.cb);
1911 if (cbres != QRhi::FrameOpSuccess)
1912 return cbres;
1913
1914 // Switch to the next slot manually. Swapchains do not know about this
1915 // which is good. So for example a - unusual but possible - onscreen,
1916 // onscreen, offscreen, onscreen, onscreen, onscreen sequence of
1917 // begin/endFrame leads to 0, 1, 0, 0, 1, 0. This works because the
1918 // offscreen frame is synchronous in the sense that we wait for execution
1919 // to complete in endFrame, and so no resources used in that frame are busy
1920 // anymore in the next frame.
1921 currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
1922 // except that this gets complicated with multiple swapchains so make sure
1923 // any pending commands have finished for the frame slot we are going to use
1924 if (swapchains.count() > 1)
1925 waitCommandCompletion(frameSlot: currentFrameSlot);
1926
1927 ofr.cbWrapper.useSecondaryCb = flags.testFlag(flag: QRhi::ExternalContentsInPass);
1928
1929 prepareNewFrame(cb: &ofr.cbWrapper);
1930 ofr.active = true;
1931
1932 *cb = &ofr.cbWrapper;
1933 return QRhi::FrameOpSuccess;
1934}
1935
1936QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags)
1937{
1938 Q_UNUSED(flags);
1939 Q_ASSERT(ofr.active);
1940 ofr.active = false;
1941
1942 recordPrimaryCommandBuffer(cbD: &ofr.cbWrapper);
1943
1944 if (!ofr.cmdFence) {
1945 VkFenceCreateInfo fenceInfo;
1946 memset(s: &fenceInfo, c: 0, n: sizeof(fenceInfo));
1947 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1948 VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence);
1949 if (err != VK_SUCCESS) {
1950 qWarning(msg: "Failed to create command buffer fence: %d", err);
1951 return QRhi::FrameOpError;
1952 }
1953 }
1954
1955 QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb: ofr.cbWrapper.cb, cmdFence: ofr.cmdFence, waitSem: nullptr, signalSem: nullptr);
1956 if (submitres != QRhi::FrameOpSuccess)
1957 return submitres;
1958
1959 // wait for completion
1960 df->vkWaitForFences(dev, 1, &ofr.cmdFence, VK_TRUE, UINT64_MAX);
1961 df->vkResetFences(dev, 1, &ofr.cmdFence);
1962
1963 // Here we know that executing the host-side reads for this (or any
1964 // previous) frame is safe since we waited for completion above.
1965 finishActiveReadbacks(forced: true);
1966
1967 return QRhi::FrameOpSuccess;
1968}
1969
1970QRhi::FrameOpResult QRhiVulkan::finish()
1971{
1972 QVkSwapChain *swapChainD = nullptr;
1973 if (inFrame) {
1974 // There is either a swapchain or an offscreen frame on-going.
1975 // End command recording and submit what we have.
1976 VkCommandBuffer cb;
1977 if (ofr.active) {
1978 Q_ASSERT(!currentSwapChain);
1979 Q_ASSERT(ofr.cbWrapper.recordingPass == QVkCommandBuffer::NoPass);
1980 recordPrimaryCommandBuffer(cbD: &ofr.cbWrapper);
1981 ofr.cbWrapper.resetCommands();
1982 cb = ofr.cbWrapper.cb;
1983 } else {
1984 Q_ASSERT(currentSwapChain);
1985 Q_ASSERT(currentSwapChain->cbWrapper.recordingPass == QVkCommandBuffer::NoPass);
1986 swapChainD = currentSwapChain;
1987 recordPrimaryCommandBuffer(cbD: &swapChainD->cbWrapper);
1988 swapChainD->cbWrapper.resetCommands();
1989 cb = swapChainD->cbWrapper.cb;
1990 }
1991 QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb, VK_NULL_HANDLE, waitSem: nullptr, signalSem: nullptr);
1992 if (submitres != QRhi::FrameOpSuccess)
1993 return submitres;
1994 }
1995
1996 df->vkQueueWaitIdle(gfxQueue);
1997
1998 if (inFrame) {
1999 // Allocate and begin recording on a new command buffer.
2000 if (ofr.active)
2001 startPrimaryCommandBuffer(cb: &ofr.cbWrapper.cb);
2002 else
2003 startPrimaryCommandBuffer(cb: &swapChainD->frameRes[swapChainD->currentFrameSlot].cmdBuf);
2004 }
2005
2006 executeDeferredReleases(forced: true);
2007 finishActiveReadbacks(forced: true);
2008
2009 return QRhi::FrameOpSuccess;
2010}
2011
2012static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkBuffer::UsageState &bufUsage)
2013{
2014 QRhiPassResourceTracker::UsageState u;
2015 u.layout = 0; // unused with buffers
2016 u.access = int(bufUsage.access);
2017 u.stage = int(bufUsage.stage);
2018 return u;
2019}
2020
2021static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkTexture::UsageState &texUsage)
2022{
2023 QRhiPassResourceTracker::UsageState u;
2024 u.layout = texUsage.layout;
2025 u.access = int(texUsage.access);
2026 u.stage = int(texUsage.stage);
2027 return u;
2028}
2029
2030void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD)
2031{
2032 rtD->lastActiveFrameSlot = currentFrameSlot;
2033 rtD->d.rp->lastActiveFrameSlot = currentFrameSlot;
2034 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
2035 for (auto it = rtD->m_desc.cbeginColorAttachments(), itEnd = rtD->m_desc.cendColorAttachments(); it != itEnd; ++it) {
2036 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
2037 QVkTexture *resolveTexD = QRHI_RES(QVkTexture, it->resolveTexture());
2038 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
2039 if (texD) {
2040 trackedRegisterTexture(passResTracker: &passResTracker, texD,
2041 access: QRhiPassResourceTracker::TexColorOutput,
2042 stage: QRhiPassResourceTracker::TexColorOutputStage);
2043 texD->lastActiveFrameSlot = currentFrameSlot;
2044 } else if (rbD) {
2045 // Won't register rbD->backingTexture because it cannot be used for
2046 // anything in a renderpass, its use makes only sense in
2047 // combination with a resolveTexture.
2048 rbD->lastActiveFrameSlot = currentFrameSlot;
2049 }
2050 if (resolveTexD) {
2051 trackedRegisterTexture(passResTracker: &passResTracker, texD: resolveTexD,
2052 access: QRhiPassResourceTracker::TexColorOutput,
2053 stage: QRhiPassResourceTracker::TexColorOutputStage);
2054 resolveTexD->lastActiveFrameSlot = currentFrameSlot;
2055 }
2056 }
2057 if (rtD->m_desc.depthStencilBuffer())
2058 QRHI_RES(QVkRenderBuffer, rtD->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
2059 if (rtD->m_desc.depthTexture()) {
2060 QVkTexture *depthTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthTexture());
2061 trackedRegisterTexture(passResTracker: &passResTracker, texD: depthTexD,
2062 access: QRhiPassResourceTracker::TexDepthOutput,
2063 stage: QRhiPassResourceTracker::TexDepthOutputStage);
2064 depthTexD->lastActiveFrameSlot = currentFrameSlot;
2065 }
2066}
2067
2068void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2069{
2070 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2071 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
2072
2073 enqueueResourceUpdates(cbD, resourceUpdates);
2074}
2075
2076VkCommandBuffer QRhiVulkan::startSecondaryCommandBuffer(QVkRenderTargetData *rtD)
2077{
2078 VkCommandBuffer secondaryCb;
2079
2080 VkCommandBufferAllocateInfo cmdBufInfo;
2081 memset(s: &cmdBufInfo, c: 0, n: sizeof(cmdBufInfo));
2082 cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2083 cmdBufInfo.commandPool = cmdPool;
2084 cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
2085 cmdBufInfo.commandBufferCount = 1;
2086 VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, &secondaryCb);
2087 if (err != VK_SUCCESS) {
2088 qWarning(msg: "Failed to create secondary command buffer: %d", err);
2089 return VK_NULL_HANDLE;
2090 }
2091
2092 VkCommandBufferBeginInfo cmdBufBeginInfo;
2093 memset(s: &cmdBufBeginInfo, c: 0, n: sizeof(cmdBufBeginInfo));
2094 cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2095 cmdBufBeginInfo.flags = rtD ? VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT : 0;
2096 VkCommandBufferInheritanceInfo cmdBufInheritInfo;
2097 memset(s: &cmdBufInheritInfo, c: 0, n: sizeof(cmdBufInheritInfo));
2098 cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
2099 cmdBufInheritInfo.subpass = 0;
2100 if (rtD) {
2101 cmdBufInheritInfo.renderPass = rtD->rp->rp;
2102 cmdBufInheritInfo.framebuffer = rtD->fb;
2103 }
2104 cmdBufBeginInfo.pInheritanceInfo = &cmdBufInheritInfo;
2105
2106 err = df->vkBeginCommandBuffer(secondaryCb, &cmdBufBeginInfo);
2107 if (err != VK_SUCCESS) {
2108 qWarning(msg: "Failed to begin secondary command buffer: %d", err);
2109 df->vkFreeCommandBuffers(dev, cmdPool, 1, &secondaryCb);
2110 return VK_NULL_HANDLE;
2111 }
2112
2113 return secondaryCb;
2114}
2115
2116void QRhiVulkan::endAndEnqueueSecondaryCommandBuffer(VkCommandBuffer cb, QVkCommandBuffer *cbD)
2117{
2118 VkResult err = df->vkEndCommandBuffer(cb);
2119 if (err != VK_SUCCESS)
2120 qWarning(msg: "Failed to end secondary command buffer: %d", err);
2121
2122 QVkCommandBuffer::Command cmd;
2123 cmd.cmd = QVkCommandBuffer::Command::ExecuteSecondary;
2124 cmd.args.executeSecondary.cb = cb;
2125 cbD->commands.append(t: cmd);
2126
2127 deferredReleaseSecondaryCommandBuffer(cb);
2128}
2129
2130void QRhiVulkan::deferredReleaseSecondaryCommandBuffer(VkCommandBuffer cb)
2131{
2132 QRhiVulkan::DeferredReleaseEntry e;
2133 e.type = QRhiVulkan::DeferredReleaseEntry::CommandBuffer;
2134 e.lastActiveFrameSlot = currentFrameSlot;
2135 e.commandBuffer.cb = cb;
2136 releaseQueue.append(t: e);
2137}
2138
2139void QRhiVulkan::beginPass(QRhiCommandBuffer *cb,
2140 QRhiRenderTarget *rt,
2141 const QColor &colorClearValue,
2142 const QRhiDepthStencilClearValue &depthStencilClearValue,
2143 QRhiResourceUpdateBatch *resourceUpdates)
2144{
2145 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2146 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
2147
2148 if (resourceUpdates)
2149 enqueueResourceUpdates(cbD, resourceUpdates);
2150
2151 // Insert a TransitionPassResources into the command stream, pointing to
2152 // the tracker this pass is going to use. That's how we generate the
2153 // barriers later during recording the real VkCommandBuffer, right before
2154 // the vkCmdBeginRenderPass.
2155 enqueueTransitionPassResources(cbD);
2156
2157 QVkRenderTargetData *rtD = nullptr;
2158 switch (rt->resourceType()) {
2159 case QRhiResource::RenderTarget:
2160 rtD = &QRHI_RES(QVkReferenceRenderTarget, rt)->d;
2161 rtD->rp->lastActiveFrameSlot = currentFrameSlot;
2162 Q_ASSERT(currentSwapChain);
2163 currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse =
2164 QVkSwapChain::ImageResources::ScImageUseRender;
2165 break;
2166 case QRhiResource::TextureRenderTarget:
2167 {
2168 QVkTextureRenderTarget *rtTex = QRHI_RES(QVkTextureRenderTarget, rt);
2169 rtD = &rtTex->d;
2170 activateTextureRenderTarget(cbD, rtD: rtTex);
2171 }
2172 break;
2173 default:
2174 Q_UNREACHABLE();
2175 break;
2176 }
2177
2178 cbD->recordingPass = QVkCommandBuffer::RenderPass;
2179 cbD->currentTarget = rt;
2180
2181 // No copy operations or image layout transitions allowed after this point
2182 // (up until endPass) as we are going to begin the renderpass.
2183
2184 VkRenderPassBeginInfo rpBeginInfo;
2185 memset(s: &rpBeginInfo, c: 0, n: sizeof(rpBeginInfo));
2186 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
2187 rpBeginInfo.renderPass = rtD->rp->rp;
2188 rpBeginInfo.framebuffer = rtD->fb;
2189 rpBeginInfo.renderArea.extent.width = uint32_t(rtD->pixelSize.width());
2190 rpBeginInfo.renderArea.extent.height = uint32_t(rtD->pixelSize.height());
2191
2192 QVarLengthArray<VkClearValue, 4> cvs;
2193 for (int i = 0; i < rtD->colorAttCount; ++i) {
2194 VkClearValue cv;
2195 cv.color = { .float32: { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()),
2196 float(colorClearValue.alphaF()) } };
2197 cvs.append(t: cv);
2198 }
2199 for (int i = 0; i < rtD->dsAttCount; ++i) {
2200 VkClearValue cv;
2201 cv.depthStencil = { .depth: depthStencilClearValue.depthClearValue(), .stencil: depthStencilClearValue.stencilClearValue() };
2202 cvs.append(t: cv);
2203 }
2204 for (int i = 0; i < rtD->resolveAttCount; ++i) {
2205 VkClearValue cv;
2206 cv.color = { .float32: { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()),
2207 float(colorClearValue.alphaF()) } };
2208 cvs.append(t: cv);
2209 }
2210 rpBeginInfo.clearValueCount = uint32_t(cvs.count());
2211
2212 QVkCommandBuffer::Command cmd;
2213 cmd.cmd = QVkCommandBuffer::Command::BeginRenderPass;
2214 cmd.args.beginRenderPass.desc = rpBeginInfo;
2215 cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.count();
2216 cbD->pools.clearValue.append(abuf: cvs.constData(), increment: cvs.count());
2217 cbD->commands.append(t: cmd);
2218
2219 if (cbD->useSecondaryCb)
2220 cbD->secondaryCbs.append(t: startSecondaryCommandBuffer(rtD));
2221}
2222
2223void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2224{
2225 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2226 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
2227
2228 if (cbD->useSecondaryCb) {
2229 VkCommandBuffer secondaryCb = cbD->secondaryCbs.last();
2230 cbD->secondaryCbs.removeLast();
2231 endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD);
2232 cbD->resetCachedState();
2233 }
2234
2235 QVkCommandBuffer::Command cmd;
2236 cmd.cmd = QVkCommandBuffer::Command::EndRenderPass;
2237 cbD->commands.append(t: cmd);
2238
2239 cbD->recordingPass = QVkCommandBuffer::NoPass;
2240 cbD->currentTarget = nullptr;
2241
2242 if (resourceUpdates)
2243 enqueueResourceUpdates(cbD, resourceUpdates);
2244}
2245
2246void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2247{
2248 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2249 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
2250
2251 if (resourceUpdates)
2252 enqueueResourceUpdates(cbD, resourceUpdates);
2253
2254 enqueueTransitionPassResources(cbD);
2255
2256 cbD->recordingPass = QVkCommandBuffer::ComputePass;
2257
2258 cbD->computePassState.reset();
2259
2260 if (cbD->useSecondaryCb)
2261 cbD->secondaryCbs.append(t: startSecondaryCommandBuffer());
2262}
2263
2264void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2265{
2266 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2267 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass);
2268
2269 if (cbD->useSecondaryCb) {
2270 VkCommandBuffer secondaryCb = cbD->secondaryCbs.last();
2271 cbD->secondaryCbs.removeLast();
2272 endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD);
2273 cbD->resetCachedState();
2274 }
2275
2276 cbD->recordingPass = QVkCommandBuffer::NoPass;
2277
2278 if (resourceUpdates)
2279 enqueueResourceUpdates(cbD, resourceUpdates);
2280}
2281
2282void QRhiVulkan::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
2283{
2284 QVkComputePipeline *psD = QRHI_RES(QVkComputePipeline, ps);
2285 Q_ASSERT(psD->pipeline);
2286 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2287 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass);
2288
2289 if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
2290 if (cbD->useSecondaryCb) {
2291 df->vkCmdBindPipeline(cbD->secondaryCbs.last(), VK_PIPELINE_BIND_POINT_COMPUTE, psD->pipeline);
2292 } else {
2293 QVkCommandBuffer::Command cmd;
2294 cmd.cmd = QVkCommandBuffer::Command::BindPipeline;
2295 cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
2296 cmd.args.bindPipeline.pipeline = psD->pipeline;
2297 cbD->commands.append(t: cmd);
2298 }
2299
2300 cbD->currentGraphicsPipeline = nullptr;
2301 cbD->currentComputePipeline = ps;
2302 cbD->currentPipelineGeneration = psD->generation;
2303 }
2304
2305 psD->lastActiveFrameSlot = currentFrameSlot;
2306}
2307
2308template<typename T>
2309inline void qrhivk_accumulateComputeResource(T *writtenResources, QRhiResource *resource,
2310 QRhiShaderResourceBinding::Type bindingType,
2311 int loadTypeVal, int storeTypeVal, int loadStoreTypeVal)
2312{
2313 VkAccessFlags access = 0;
2314 if (bindingType == loadTypeVal) {
2315 access = VK_ACCESS_SHADER_READ_BIT;
2316 } else {
2317 access = VK_ACCESS_SHADER_WRITE_BIT;
2318 if (bindingType == loadStoreTypeVal)
2319 access |= VK_ACCESS_SHADER_READ_BIT;
2320 }
2321 auto it = writtenResources->find(resource);
2322 if (it != writtenResources->end())
2323 it->first |= access;
2324 else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal)
2325 writtenResources->insert(resource, { access, true });
2326}
2327
2328void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
2329{
2330 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
2331 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass);
2332
2333 // When there are multiple dispatches, read-after-write and
2334 // write-after-write need a barrier.
2335 QVarLengthArray<VkImageMemoryBarrier, 8> imageBarriers;
2336 QVarLengthArray<VkBufferMemoryBarrier, 8> bufferBarriers;
2337 if (cbD->currentComputeSrb) {
2338 // The key in the writtenResources map indicates that the resource was
2339 // written in a previous dispatch, whereas the value accumulates the
2340 // access mask in the current one.
2341 for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources)
2342 accessAndIsNewFlag = { 0, false };
2343
2344 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, cbD->currentComputeSrb);
2345 const int bindingCount = srbD->m_bindings.count();
2346 for (int i = 0; i < bindingCount; ++i) {
2347 const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(idx: i).data();
2348 switch (b->type) {
2349 case QRhiShaderResourceBinding::ImageLoad:
2350 case QRhiShaderResourceBinding::ImageStore:
2351 case QRhiShaderResourceBinding::ImageLoadStore:
2352 qrhivk_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources,
2353 resource: b->u.simage.tex,
2354 bindingType: b->type,
2355 loadTypeVal: QRhiShaderResourceBinding::ImageLoad,
2356 storeTypeVal: QRhiShaderResourceBinding::ImageStore,
2357 loadStoreTypeVal: QRhiShaderResourceBinding::ImageLoadStore);
2358 break;
2359 case QRhiShaderResourceBinding::BufferLoad:
2360 case QRhiShaderResourceBinding::BufferStore:
2361 case QRhiShaderResourceBinding::BufferLoadStore:
2362 qrhivk_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources,
2363 resource: b->u.sbuf.buf,
2364 bindingType: b->type,
2365 loadTypeVal: QRhiShaderResourceBinding::BufferLoad,
2366 storeTypeVal: QRhiShaderResourceBinding::BufferStore,
2367 loadStoreTypeVal: QRhiShaderResourceBinding::BufferLoadStore);
2368 break;
2369 default:
2370 break;
2371 }
2372 }
2373
2374 for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) {
2375 const int accessInThisDispatch = it->first;
2376 const bool isNewInThisDispatch = it->second;
2377 if (accessInThisDispatch && !isNewInThisDispatch) {
2378 if (it.key()->resourceType() == QRhiResource::Texture) {
2379 QVkTexture *texD = QRHI_RES(QVkTexture, it.key());
2380 VkImageMemoryBarrier barrier;
2381 memset(s: &barrier, c: 0, n: sizeof(barrier));
2382 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2383 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2384 // won't care about subresources, pretend the whole resource was written
2385 barrier.subresourceRange.baseMipLevel = 0;
2386 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
2387 barrier.subresourceRange.baseArrayLayer = 0;
2388 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
2389 barrier.oldLayout = texD->usageState.layout;
2390 barrier.newLayout = texD->usageState.layout;
2391 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
2392 barrier.dstAccessMask = accessInThisDispatch;
2393 barrier.image = texD->image;
2394 imageBarriers.append(t: barrier);
2395 } else {
2396 QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key());
2397 VkBufferMemoryBarrier barrier;
2398 memset(s: &barrier, c: 0, n: sizeof(barrier));
2399 barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
2400 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2401 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2402 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
2403 barrier.dstAccessMask = accessInThisDispatch;
2404 barrier.buffer = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
2405 barrier.size = VK_WHOLE_SIZE;
2406 bufferBarriers.append(t: barrier);
2407 }
2408 }
2409 // Anything that was previously written, but is only read now, can be
2410 // removed from the written list (because that previous write got a
2411 // corresponding barrier now).
2412 if (accessInThisDispatch == VK_ACCESS_SHADER_READ_BIT)
2413 it = cbD->computePassState.writtenResources.erase(it);
2414 else
2415 ++it;
2416 }
2417 }
2418
2419 if (cbD->useSecondaryCb) {
2420 VkCommandBuffer secondaryCb = cbD->secondaryCbs.last();
2421 if (!imageBarriers.isEmpty()) {
2422 df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
2423 0, 0, nullptr,
2424 0, nullptr,
2425 imageBarriers.count(), imageBarriers.constData());
2426 }
2427 if (!bufferBarriers.isEmpty()) {
2428 df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
2429 0, 0, nullptr,
2430 bufferBarriers.count(), bufferBarriers.constData(),
2431 0, nullptr);
2432 }
2433 df->vkCmdDispatch(secondaryCb, uint32_t(x), uint32_t(y), uint32_t(z));
2434 } else {
2435 QVkCommandBuffer::Command cmd;
2436 if (!imageBarriers.isEmpty()) {
2437 cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
2438 cmd.args.imageBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
2439 cmd.args.imageBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
2440 cmd.args.imageBarrier.count = imageBarriers.count();
2441 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count();
2442 cbD->pools.imageBarrier.append(abuf: imageBarriers.constData(), increment: imageBarriers.count());
2443 cbD->commands.append(t: cmd);
2444 }
2445 if (!bufferBarriers.isEmpty()) {
2446 cmd.cmd = QVkCommandBuffer::Command::BufferBarrier;
2447 cmd.args.bufferBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
2448 cmd.args.bufferBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
2449 cmd.args.bufferBarrier.count = bufferBarriers.count();
2450 cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.count();
2451 cbD->pools.bufferBarrier.append(abuf: bufferBarriers.constData(), increment: bufferBarriers.count());
2452 cbD->commands.append(t: cmd);
2453 }
2454 cmd.cmd = QVkCommandBuffer::Command::Dispatch;
2455 cmd.args.dispatch.x = x;
2456 cmd.args.dispatch.y = y;
2457 cmd.args.dispatch.z = z;
2458 cbD->commands.append(t: cmd);
2459 }
2460}
2461
2462VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv)
2463{
2464 VkShaderModuleCreateInfo shaderInfo;
2465 memset(s: &shaderInfo, c: 0, n: sizeof(shaderInfo));
2466 shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
2467 shaderInfo.codeSize = size_t(spirv.size());
2468 shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData());
2469 VkShaderModule shaderModule;
2470 VkResult err = df->vkCreateShaderModule(dev, &shaderInfo, nullptr, &shaderModule);
2471 if (err != VK_SUCCESS) {
2472 qWarning(msg: "Failed to create shader module: %d", err);
2473 return VK_NULL_HANDLE;
2474 }
2475 return shaderModule;
2476}
2477
2478bool QRhiVulkan::ensurePipelineCache()
2479{
2480 if (pipelineCache)
2481 return true;
2482
2483 VkPipelineCacheCreateInfo pipelineCacheInfo;
2484 memset(s: &pipelineCacheInfo, c: 0, n: sizeof(pipelineCacheInfo));
2485 pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
2486 VkResult err = df->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &pipelineCache);
2487 if (err != VK_SUCCESS) {
2488 qWarning(msg: "Failed to create pipeline cache: %d", err);
2489 return false;
2490 }
2491 return true;
2492}
2493
2494void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx)
2495{
2496 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
2497
2498 QVarLengthArray<VkDescriptorBufferInfo, 8> bufferInfos;
2499 using ArrayOfImageDesc = QVarLengthArray<VkDescriptorImageInfo, 8>;
2500 QVarLengthArray<ArrayOfImageDesc, 8> imageInfos;
2501 QVarLengthArray<VkWriteDescriptorSet, 12> writeInfos;
2502 QVarLengthArray<QPair<int, int>, 12> infoIndices;
2503
2504 const bool updateAll = descSetIdx < 0;
2505 int frameSlot = updateAll ? 0 : descSetIdx;
2506 while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) {
2507 srbD->boundResourceData[frameSlot].resize(asize: srbD->sortedBindings.count());
2508 for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
2509 const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(idx: i).data();
2510 QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]);
2511
2512 VkWriteDescriptorSet writeInfo;
2513 memset(s: &writeInfo, c: 0, n: sizeof(writeInfo));
2514 writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
2515 writeInfo.dstSet = srbD->descSets[frameSlot];
2516 writeInfo.dstBinding = uint32_t(b->binding);
2517 writeInfo.descriptorCount = 1;
2518
2519 int bufferInfoIndex = -1;
2520 int imageInfoIndex = -1;
2521
2522 switch (b->type) {
2523 case QRhiShaderResourceBinding::UniformBuffer:
2524 {
2525 writeInfo.descriptorType = b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
2526 : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
2527 QRhiBuffer *buf = b->u.ubuf.buf;
2528 QVkBuffer *bufD = QRHI_RES(QVkBuffer, buf);
2529 bd.ubuf.id = bufD->m_id;
2530 bd.ubuf.generation = bufD->generation;
2531 VkDescriptorBufferInfo bufInfo;
2532 bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0];
2533 bufInfo.offset = VkDeviceSize(b->u.ubuf.offset);
2534 bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size);
2535 // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads
2536 Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset);
2537 bufferInfoIndex = bufferInfos.count();
2538 bufferInfos.append(t: bufInfo);
2539 }
2540 break;
2541 case QRhiShaderResourceBinding::SampledTexture:
2542 {
2543 const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
2544 writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported
2545 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2546 ArrayOfImageDesc imageInfo(data->count);
2547 for (int elem = 0; elem < data->count; ++elem) {
2548 QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
2549 QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler);
2550 bd.stex.d[elem].texId = texD->m_id;
2551 bd.stex.d[elem].texGeneration = texD->generation;
2552 bd.stex.d[elem].samplerId = samplerD->m_id;
2553 bd.stex.d[elem].samplerGeneration = samplerD->generation;
2554 imageInfo[elem].sampler = samplerD->sampler;
2555 imageInfo[elem].imageView = texD->imageView;
2556 imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2557 }
2558 bd.stex.count = data->count;
2559 imageInfoIndex = imageInfos.count();
2560 imageInfos.append(t: imageInfo);
2561 }
2562 break;
2563 case QRhiShaderResourceBinding::ImageLoad:
2564 case QRhiShaderResourceBinding::ImageStore:
2565 case QRhiShaderResourceBinding::ImageLoadStore:
2566 {
2567 QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
2568 VkImageView view = texD->imageViewForLevel(level: b->u.simage.level);
2569 if (view) {
2570 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
2571 bd.simage.id = texD->m_id;
2572 bd.simage.generation = texD->generation;
2573 ArrayOfImageDesc imageInfo(1);
2574 imageInfo[0].sampler = VK_NULL_HANDLE;
2575 imageInfo[0].imageView = view;
2576 imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
2577 imageInfoIndex = imageInfos.count();
2578 imageInfos.append(t: imageInfo);
2579 }
2580 }
2581 break;
2582 case QRhiShaderResourceBinding::BufferLoad:
2583 case QRhiShaderResourceBinding::BufferStore:
2584 case QRhiShaderResourceBinding::BufferLoadStore:
2585 {
2586 QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf);
2587 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
2588 bd.sbuf.id = bufD->m_id;
2589 bd.sbuf.generation = bufD->generation;
2590 VkDescriptorBufferInfo bufInfo;
2591 bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0];
2592 bufInfo.offset = VkDeviceSize(b->u.ubuf.offset);
2593 bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size);
2594 bufferInfoIndex = bufferInfos.count();
2595 bufferInfos.append(t: bufInfo);
2596 }
2597 break;
2598 default:
2599 continue;
2600 }
2601
2602 writeInfos.append(t: writeInfo);
2603 infoIndices.append(t: { bufferInfoIndex, imageInfoIndex });
2604 }
2605 ++frameSlot;
2606 }
2607
2608 for (int i = 0, writeInfoCount = writeInfos.count(); i < writeInfoCount; ++i) {
2609 const int bufferInfoIndex = infoIndices[i].first;
2610 const int imageInfoIndex = infoIndices[i].second;
2611 if (bufferInfoIndex >= 0)
2612 writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex];
2613 else if (imageInfoIndex >= 0)
2614 writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData();
2615 }
2616
2617 df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.count()), writeInfos.constData(), 0, nullptr);
2618}
2619
2620static inline bool accessIsWrite(VkAccessFlags access)
2621{
2622 return (access & VK_ACCESS_SHADER_WRITE_BIT) != 0
2623 || (access & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0
2624 || (access & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) != 0
2625 || (access & VK_ACCESS_TRANSFER_WRITE_BIT) != 0
2626 || (access & VK_ACCESS_HOST_WRITE_BIT) != 0
2627 || (access & VK_ACCESS_MEMORY_WRITE_BIT) != 0;
2628}
2629
2630void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot,
2631 VkAccessFlags access, VkPipelineStageFlags stage)
2632{
2633 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
2634 Q_ASSERT(access && stage);
2635 QVkBuffer::UsageState &s(bufD->usageState[slot]);
2636 if (!s.stage) {
2637 s.access = access;
2638 s.stage = stage;
2639 return;
2640 }
2641
2642 if (s.access == access && s.stage == stage) {
2643 // No need to flood with unnecessary read-after-read barriers.
2644 // Write-after-write is a different matter, however.
2645 if (!accessIsWrite(access))
2646 return;
2647 }
2648
2649 VkBufferMemoryBarrier bufMemBarrier;
2650 memset(s: &bufMemBarrier, c: 0, n: sizeof(bufMemBarrier));
2651 bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
2652 bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2653 bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2654 bufMemBarrier.srcAccessMask = s.access;
2655 bufMemBarrier.dstAccessMask = access;
2656 bufMemBarrier.buffer = bufD->buffers[slot];
2657 bufMemBarrier.size = VK_WHOLE_SIZE;
2658
2659 QVkCommandBuffer::Command cmd;
2660 cmd.cmd = QVkCommandBuffer::Command::BufferBarrier;
2661 cmd.args.bufferBarrier.srcStageMask = s.stage;
2662 cmd.args.bufferBarrier.dstStageMask = stage;
2663 cmd.args.bufferBarrier.count = 1;
2664 cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.count();
2665 cbD->pools.bufferBarrier.append(t: bufMemBarrier);
2666 cbD->commands.append(t: cmd);
2667
2668 s.access = access;
2669 s.stage = stage;
2670}
2671
2672void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD,
2673 VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage)
2674{
2675 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
2676 Q_ASSERT(layout && access && stage);
2677 QVkTexture::UsageState &s(texD->usageState);
2678 if (s.access == access && s.stage == stage && s.layout == layout) {
2679 if (!accessIsWrite(access))
2680 return;
2681 }
2682
2683 VkImageMemoryBarrier barrier;
2684 memset(s: &barrier, c: 0, n: sizeof(barrier));
2685 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2686 barrier.subresourceRange.aspectMask = !isDepthTextureFormat(format: texD->m_format)
2687 ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
2688 barrier.subresourceRange.baseMipLevel = 0;
2689 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
2690 barrier.subresourceRange.baseArrayLayer = 0;
2691 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
2692 barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
2693 barrier.newLayout = layout;
2694 barrier.srcAccessMask = s.access; // may be 0 but that's fine
2695 barrier.dstAccessMask = access;
2696 barrier.image = texD->image;
2697
2698 VkPipelineStageFlags srcStage = s.stage;
2699 // stage mask cannot be 0
2700 if (!srcStage)
2701 srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2702
2703 QVkCommandBuffer::Command cmd;
2704 cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
2705 cmd.args.imageBarrier.srcStageMask = srcStage;
2706 cmd.args.imageBarrier.dstStageMask = stage;
2707 cmd.args.imageBarrier.count = 1;
2708 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count();
2709 cbD->pools.imageBarrier.append(t: barrier);
2710 cbD->commands.append(t: cmd);
2711
2712 s.layout = layout;
2713 s.access = access;
2714 s.stage = stage;
2715}
2716
2717void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image,
2718 VkImageLayout oldLayout, VkImageLayout newLayout,
2719 VkAccessFlags srcAccess, VkAccessFlags dstAccess,
2720 VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage,
2721 int startLayer, int layerCount,
2722 int startLevel, int levelCount)
2723{
2724 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
2725 VkImageMemoryBarrier barrier;
2726 memset(s: &barrier, c: 0, n: sizeof(barrier));
2727 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2728 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2729 barrier.subresourceRange.baseMipLevel = uint32_t(startLevel);
2730 barrier.subresourceRange.levelCount = uint32_t(levelCount);
2731 barrier.subresourceRange.baseArrayLayer = uint32_t(startLayer);
2732 barrier.subresourceRange.layerCount = uint32_t(layerCount);
2733 barrier.oldLayout = oldLayout;
2734 barrier.newLayout = newLayout;
2735 barrier.srcAccessMask = srcAccess;
2736 barrier.dstAccessMask = dstAccess;
2737 barrier.image = image;
2738
2739 QVkCommandBuffer::Command cmd;
2740 cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
2741 cmd.args.imageBarrier.srcStageMask = srcStage;
2742 cmd.args.imageBarrier.dstStageMask = dstStage;
2743 cmd.args.imageBarrier.count = 1;
2744 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count();
2745 cbD->pools.imageBarrier.append(t: barrier);
2746 cbD->commands.append(t: cmd);
2747}
2748
2749VkDeviceSize QRhiVulkan::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
2750{
2751 VkDeviceSize size = 0;
2752 const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
2753 subresDesc.data().size() : subresDesc.image().sizeInBytes();
2754 if (imageSizeBytes > 0)
2755 size += aligned(v: VkDeviceSize(imageSizeBytes), byteAlign: texbufAlign);
2756 return size;
2757}
2758
2759void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
2760 const QRhiTextureSubresourceUploadDescription &subresDesc,
2761 size_t *curOfs, void *mp,
2762 BufferImageCopyList *copyInfos)
2763{
2764 qsizetype copySizeBytes = 0;
2765 qsizetype imageSizeBytes = 0;
2766 const void *src = nullptr;
2767
2768 VkBufferImageCopy copyInfo;
2769 memset(s: &copyInfo, c: 0, n: sizeof(copyInfo));
2770 copyInfo.bufferOffset = *curOfs;
2771 copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2772 copyInfo.imageSubresource.mipLevel = uint32_t(level);
2773 copyInfo.imageSubresource.baseArrayLayer = uint32_t(layer);
2774 copyInfo.imageSubresource.layerCount = 1;
2775 copyInfo.imageExtent.depth = 1;
2776
2777 const QByteArray rawData = subresDesc.data();
2778 const QPoint dp = subresDesc.destinationTopLeft();
2779 QImage image = subresDesc.image();
2780 if (!image.isNull()) {
2781 copySizeBytes = imageSizeBytes = image.sizeInBytes();
2782 QSize size = image.size();
2783 src = image.constBits();
2784 // Scanlines in QImage are 4 byte aligned so bpl must
2785 // be taken into account for bufferRowLength.
2786 int bpc = qMax(a: 1, b: image.depth() / 8);
2787 // this is in pixels, not bytes, to make it more complicated...
2788 copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc);
2789 if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
2790 const int sx = subresDesc.sourceTopLeft().x();
2791 const int sy = subresDesc.sourceTopLeft().y();
2792 if (!subresDesc.sourceSize().isEmpty())
2793 size = subresDesc.sourceSize();
2794 if (image.depth() == 32) {
2795 // The staging buffer will get the full image
2796 // regardless, just adjust the vk
2797 // buffer-to-image copy start offset.
2798 copyInfo.bufferOffset += VkDeviceSize(sy * image.bytesPerLine() + sx * 4);
2799 // bufferRowLength remains set to the original image's width
2800 } else {
2801 image = image.copy(x: sx, y: sy, w: size.width(), h: size.height());
2802 src = image.constBits();
2803 // The staging buffer gets the slice only. The rest of the
2804 // space reserved for this mip will be unused.
2805 copySizeBytes = image.sizeInBytes();
2806 bpc = qMax(a: 1, b: image.depth() / 8);
2807 copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc);
2808 }
2809 }
2810 copyInfo.imageOffset.x = dp.x();
2811 copyInfo.imageOffset.y = dp.y();
2812 copyInfo.imageExtent.width = uint32_t(size.width());
2813 copyInfo.imageExtent.height = uint32_t(size.height());
2814 copyInfos->append(t: copyInfo);
2815 } else if (!rawData.isEmpty() && isCompressedFormat(format: texD->m_format)) {
2816 copySizeBytes = imageSizeBytes = rawData.size();
2817 src = rawData.constData();
2818 QSize size = q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize);
2819 const int subresw = size.width();
2820 const int subresh = size.height();
2821 if (!subresDesc.sourceSize().isEmpty())
2822 size = subresDesc.sourceSize();
2823 const int w = size.width();
2824 const int h = size.height();
2825 QSize blockDim;
2826 compressedFormatInfo(format: texD->m_format, size: QSize(w, h), bpl: nullptr, byteSize: nullptr, blockDim: &blockDim);
2827 // x and y must be multiples of the block width and height
2828 copyInfo.imageOffset.x = aligned(v: dp.x(), byteAlign: blockDim.width());
2829 copyInfo.imageOffset.y = aligned(v: dp.y(), byteAlign: blockDim.height());
2830 // width and height must be multiples of the block width and height
2831 // or x + width and y + height must equal the subresource width and height
2832 copyInfo.imageExtent.width = uint32_t(dp.x() + w == subresw ? w : aligned(v: w, byteAlign: blockDim.width()));
2833 copyInfo.imageExtent.height = uint32_t(dp.y() + h == subresh ? h : aligned(v: h, byteAlign: blockDim.height()));
2834 copyInfos->append(t: copyInfo);
2835 } else if (!rawData.isEmpty()) {
2836 copySizeBytes = imageSizeBytes = rawData.size();
2837 src = rawData.constData();
2838 QSize size = q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize);
2839 if (!subresDesc.sourceSize().isEmpty())
2840 size = subresDesc.sourceSize();
2841 copyInfo.imageOffset.x = dp.x();
2842 copyInfo.imageOffset.y = dp.y();
2843 copyInfo.imageExtent.width = uint32_t(size.width());
2844 copyInfo.imageExtent.height = uint32_t(size.height());
2845 copyInfos->append(t: copyInfo);
2846 } else {
2847 qWarning(msg: "Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
2848 }
2849
2850 memcpy(dest: reinterpret_cast<char *>(mp) + *curOfs, src: src, n: size_t(copySizeBytes));
2851 *curOfs += aligned(v: VkDeviceSize(imageSizeBytes), byteAlign: texbufAlign);
2852}
2853
2854void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
2855{
2856 QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(b: resourceUpdates);
2857 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
2858
2859 for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) {
2860 if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
2861 QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
2862 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
2863 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
2864 bufD->pendingDynamicUpdates[i].append(t: u);
2865 } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
2866 QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
2867 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
2868 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
2869
2870 if (!bufD->stagingBuffers[currentFrameSlot]) {
2871 VkBufferCreateInfo bufferInfo;
2872 memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo));
2873 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
2874 // must cover the entire buffer - this way multiple, partial updates per frame
2875 // are supported even when the staging buffer is reused (Static)
2876 bufferInfo.size = VkDeviceSize(bufD->m_size);
2877 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2878
2879 VmaAllocationCreateInfo allocInfo;
2880 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
2881 allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2882
2883 VmaAllocation allocation;
2884 VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo,
2885 pBuffer: &bufD->stagingBuffers[currentFrameSlot], pAllocation: &allocation, pAllocationInfo: nullptr);
2886 if (err == VK_SUCCESS) {
2887 bufD->stagingAllocations[currentFrameSlot] = allocation;
2888 QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size)));
2889 } else {
2890 qWarning(msg: "Failed to create staging buffer of size %d: %d", bufD->m_size, err);
2891 continue;
2892 }
2893 }
2894
2895 void *p = nullptr;
2896 VmaAllocation a = toVmaAllocation(a: bufD->stagingAllocations[currentFrameSlot]);
2897 VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p);
2898 if (err != VK_SUCCESS) {
2899 qWarning(msg: "Failed to map buffer: %d", err);
2900 continue;
2901 }
2902 memcpy(dest: static_cast<uchar *>(p) + u.offset, src: u.data.constData(), n: size_t(u.data.size()));
2903 vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a);
2904 vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: VkDeviceSize(u.offset), size: VkDeviceSize(u.data.size()));
2905
2906 trackedBufferBarrier(cbD, bufD, slot: 0,
2907 access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT);
2908
2909 VkBufferCopy copyInfo;
2910 memset(s: &copyInfo, c: 0, n: sizeof(copyInfo));
2911 copyInfo.srcOffset = VkDeviceSize(u.offset);
2912 copyInfo.dstOffset = VkDeviceSize(u.offset);
2913 copyInfo.size = VkDeviceSize(u.data.size());
2914
2915 QVkCommandBuffer::Command cmd;
2916 cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
2917 cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot];
2918 cmd.args.copyBuffer.dst = bufD->buffers[0];
2919 cmd.args.copyBuffer.desc = copyInfo;
2920 cbD->commands.append(t: cmd);
2921
2922 // Where's the barrier for read-after-write? (assuming the common case
2923 // of binding this buffer as vertex/index, or, less likely, as uniform
2924 // buffer, in a renderpass later on) That is handled by the pass
2925 // resource tracking: the appropriate pipeline barrier will be
2926 // generated and recorded right before the renderpass, that binds this
2927 // buffer in one of its commands, gets its BeginRenderPass recorded.
2928
2929 bufD->lastActiveFrameSlot = currentFrameSlot;
2930
2931 if (bufD->m_type == QRhiBuffer::Immutable) {
2932 QRhiVulkan::DeferredReleaseEntry e;
2933 e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
2934 e.lastActiveFrameSlot = currentFrameSlot;
2935 e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot];
2936 e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot];
2937 bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
2938 bufD->stagingAllocations[currentFrameSlot] = nullptr;
2939 releaseQueue.append(t: e);
2940 QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot));
2941 }
2942 } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
2943 QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
2944 if (bufD->m_type == QRhiBuffer::Dynamic) {
2945 executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot);
2946 void *p = nullptr;
2947 VmaAllocation a = toVmaAllocation(a: bufD->allocations[currentFrameSlot]);
2948 VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p);
2949 if (err == VK_SUCCESS) {
2950 u.result->data.resize(size: u.readSize);
2951 memcpy(dest: u.result->data.data(), src: reinterpret_cast<char *>(p) + u.offset, n: size_t(u.readSize));
2952 vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a);
2953 }
2954 if (u.result->completed)
2955 u.result->completed();
2956 } else {
2957 // Non-Dynamic buffers may not be host visible, so have to
2958 // create a readback buffer, enqueue a copy from
2959 // bufD->buffers[0] to this buffer, and then once the command
2960 // buffer completes, copy the data out of the host visible
2961 // readback buffer. Quite similar to what we do for texture
2962 // readbacks.
2963 BufferReadback readback;
2964 readback.activeFrameSlot = currentFrameSlot;
2965 readback.result = u.result;
2966 readback.byteSize = u.readSize;
2967
2968 VkBufferCreateInfo bufferInfo;
2969 memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo));
2970 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
2971 bufferInfo.size = VkDeviceSize(readback.byteSize);
2972 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
2973
2974 VmaAllocationCreateInfo allocInfo;
2975 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
2976 allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
2977
2978 VmaAllocation allocation;
2979 VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &readback.stagingBuf, pAllocation: &allocation, pAllocationInfo: nullptr);
2980 if (err == VK_SUCCESS) {
2981 readback.stagingAlloc = allocation;
2982 QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), bufD, uint(readback.byteSize)));
2983 } else {
2984 qWarning(msg: "Failed to create readback buffer of size %u: %d", readback.byteSize, err);
2985 continue;
2986 }
2987
2988 trackedBufferBarrier(cbD, bufD, slot: 0, access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT);
2989
2990 VkBufferCopy copyInfo;
2991 memset(s: &copyInfo, c: 0, n: sizeof(copyInfo));
2992 copyInfo.srcOffset = VkDeviceSize(u.offset);
2993 copyInfo.size = VkDeviceSize(u.readSize);
2994
2995 QVkCommandBuffer::Command cmd;
2996 cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
2997 cmd.args.copyBuffer.src = bufD->buffers[0];
2998 cmd.args.copyBuffer.dst = readback.stagingBuf;
2999 cmd.args.copyBuffer.desc = copyInfo;
3000 cbD->commands.append(t: cmd);
3001
3002 bufD->lastActiveFrameSlot = currentFrameSlot;
3003
3004 activeBufferReadbacks.append(t: readback);
3005 }
3006 }
3007 }
3008
3009 for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
3010 if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
3011 QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
3012 // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos
3013 VkDeviceSize stagingSize = 0;
3014 for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
3015 for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
3016 for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(t: u.subresDesc[layer][level]))
3017 stagingSize += subresUploadByteSize(subresDesc);
3018 }
3019 }
3020
3021 Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]);
3022 VkBufferCreateInfo bufferInfo;
3023 memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo));
3024 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
3025 bufferInfo.size = stagingSize;
3026 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3027
3028 VmaAllocationCreateInfo allocInfo;
3029 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
3030 allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
3031
3032 VmaAllocation allocation;
3033 VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo,
3034 pBuffer: &utexD->stagingBuffers[currentFrameSlot], pAllocation: &allocation, pAllocationInfo: nullptr);
3035 if (err != VK_SUCCESS) {
3036 qWarning(msg: "Failed to create image staging buffer of size %d: %d", int(stagingSize), err);
3037 continue;
3038 }
3039 utexD->stagingAllocations[currentFrameSlot] = allocation;
3040 QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, quint32(stagingSize)));
3041
3042 BufferImageCopyList copyInfos;
3043 size_t curOfs = 0;
3044 void *mp = nullptr;
3045 VmaAllocation a = toVmaAllocation(a: utexD->stagingAllocations[currentFrameSlot]);
3046 err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &mp);
3047 if (err != VK_SUCCESS) {
3048 qWarning(msg: "Failed to map image data: %d", err);
3049 continue;
3050 }
3051
3052 for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
3053 for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
3054 const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]);
3055 if (srd.isEmpty())
3056 continue;
3057 for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(t: srd)) {
3058 prepareUploadSubres(texD: utexD, layer, level,
3059 subresDesc, curOfs: &curOfs, mp, copyInfos: &copyInfos);
3060 }
3061 }
3062 }
3063 vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a);
3064 vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: 0, size: stagingSize);
3065
3066 trackedImageBarrier(cbD, texD: utexD, layout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
3067 access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT);
3068
3069 QVkCommandBuffer::Command cmd;
3070 cmd.cmd = QVkCommandBuffer::Command::CopyBufferToImage;
3071 cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot];
3072 cmd.args.copyBufferToImage.dst = utexD->image;
3073 cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
3074 cmd.args.copyBufferToImage.count = copyInfos.count();
3075 cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.count();
3076 cbD->pools.bufferImageCopy.append(abuf: copyInfos.constData(), increment: copyInfos.count());
3077 cbD->commands.append(t: cmd);
3078
3079 // no reuse of staging, this is intentional
3080 QRhiVulkan::DeferredReleaseEntry e;
3081 e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
3082 e.lastActiveFrameSlot = currentFrameSlot;
3083 e.stagingBuffer.stagingBuffer = utexD->stagingBuffers[currentFrameSlot];
3084 e.stagingBuffer.stagingAllocation = utexD->stagingAllocations[currentFrameSlot];
3085 utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
3086 utexD->stagingAllocations[currentFrameSlot] = nullptr;
3087 releaseQueue.append(t: e);
3088 QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
3089
3090 // Similarly to buffers, transitioning away from DST is done later,
3091 // when a renderpass using the texture is encountered.
3092
3093 utexD->lastActiveFrameSlot = currentFrameSlot;
3094 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
3095 Q_ASSERT(u.src && u.dst);
3096 if (u.src == u.dst) {
3097 qWarning(msg: "Texture copy with matching source and destination is not supported");
3098 continue;
3099 }
3100 QVkTexture *srcD = QRHI_RES(QVkTexture, u.src);
3101 QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst);
3102
3103 VkImageCopy region;
3104 memset(s: &region, c: 0, n: sizeof(region));
3105
3106 region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3107 region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel());
3108 region.srcSubresource.baseArrayLayer = uint32_t(u.desc.sourceLayer());
3109 region.srcSubresource.layerCount = 1;
3110
3111 region.srcOffset.x = u.desc.sourceTopLeft().x();
3112 region.srcOffset.y = u.desc.sourceTopLeft().y();
3113
3114 region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3115 region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel());
3116 region.dstSubresource.baseArrayLayer = uint32_t(u.desc.destinationLayer());
3117 region.dstSubresource.layerCount = 1;
3118
3119 region.dstOffset.x = u.desc.destinationTopLeft().x();
3120 region.dstOffset.y = u.desc.destinationTopLeft().y();
3121
3122 const QSize mipSize = q->sizeForMipLevel(mipLevel: u.desc.sourceLevel(), baseLevelSize: srcD->m_pixelSize);
3123 const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
3124 region.extent.width = uint32_t(copySize.width());
3125 region.extent.height = uint32_t(copySize.height());
3126 region.extent.depth = 1;
3127
3128 trackedImageBarrier(cbD, texD: srcD, layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
3129 access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT);
3130 trackedImageBarrier(cbD, texD: dstD, layout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
3131 access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT);
3132
3133 QVkCommandBuffer::Command cmd;
3134 cmd.cmd = QVkCommandBuffer::Command::CopyImage;
3135 cmd.args.copyImage.src = srcD->image;
3136 cmd.args.copyImage.srcLayout = srcD->usageState.layout;
3137 cmd.args.copyImage.dst = dstD->image;
3138 cmd.args.copyImage.dstLayout = dstD->usageState.layout;
3139 cmd.args.copyImage.desc = region;
3140 cbD->commands.append(t: cmd);
3141
3142 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
3143 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
3144 TextureReadback readback;
3145 readback.activeFrameSlot = currentFrameSlot;
3146 readback.desc = u.rb;
3147 readback.result = u.result;
3148
3149 QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture());
3150 QVkSwapChain *swapChainD = nullptr;
3151 if (texD) {
3152 if (texD->samples > VK_SAMPLE_COUNT_1_BIT) {
3153 qWarning(msg: "Multisample texture cannot be read back");
3154 continue;
3155 }
3156 readback.pixelSize = q->sizeForMipLevel(mipLevel: u.rb.level(), baseLevelSize: texD->m_pixelSize);
3157 readback.format = texD->m_format;
3158 texD->lastActiveFrameSlot = currentFrameSlot;
3159 } else {
3160 Q_ASSERT(currentSwapChain);
3161 swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain);
3162 if (!swapChainD->supportsReadback) {
3163 qWarning(msg: "Swapchain does not support readback");
3164 continue;
3165 }
3166 readback.pixelSize = swapChainD->pixelSize;
3167 readback.format = colorTextureFormatFromVkFormat(format: swapChainD->colorFormat, flags: nullptr);
3168 if (readback.format == QRhiTexture::UnknownFormat)
3169 continue;
3170
3171 // Multisample swapchains need nothing special since resolving
3172 // happens when ending a renderpass.
3173 }
3174 textureFormatInfo(format: readback.format, size: readback.pixelSize, bpl: nullptr, byteSize: &readback.byteSize);
3175
3176 // Create a host visible readback buffer.
3177 VkBufferCreateInfo bufferInfo;
3178 memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo));
3179 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
3180 bufferInfo.size = readback.byteSize;
3181 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
3182
3183 VmaAllocationCreateInfo allocInfo;
3184 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
3185 allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
3186
3187 VmaAllocation allocation;
3188 VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &readback.stagingBuf, pAllocation: &allocation, pAllocationInfo: nullptr);
3189 if (err == VK_SUCCESS) {
3190 readback.stagingAlloc = allocation;
3191 QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf),
3192 texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
3193 readback.byteSize));
3194 } else {
3195 qWarning(msg: "Failed to create readback buffer of size %u: %d", readback.byteSize, err);
3196 continue;
3197 }
3198
3199 // Copy from the (optimal and not host visible) image into the buffer.
3200 VkBufferImageCopy copyDesc;
3201 memset(s: &copyDesc, c: 0, n: sizeof(copyDesc));
3202 copyDesc.bufferOffset = 0;
3203 copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3204 copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level());
3205 copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer());
3206 copyDesc.imageSubresource.layerCount = 1;
3207 copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width());
3208 copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height());
3209 copyDesc.imageExtent.depth = 1;
3210
3211 if (texD) {
3212 trackedImageBarrier(cbD, texD, layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
3213 access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT);
3214 QVkCommandBuffer::Command cmd;
3215 cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
3216 cmd.args.copyImageToBuffer.src = texD->image;
3217 cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout;
3218 cmd.args.copyImageToBuffer.dst = readback.stagingBuf;
3219 cmd.args.copyImageToBuffer.desc = copyDesc;
3220 cbD->commands.append(t: cmd);
3221 } else {
3222 // use the swapchain image
3223 QVkSwapChain::ImageResources &imageRes(swapChainD->imageRes[swapChainD->currentImageIndex]);
3224 VkImage image = imageRes.image;
3225 if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseTransferSource) {
3226 if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) {
3227 qWarning(msg: "Attempted to read back undefined swapchain image content, "
3228 "results are undefined. (do a render pass first)");
3229 }
3230 subresourceBarrier(cbD, image,
3231 oldLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
3232 srcAccess: VK_ACCESS_MEMORY_READ_BIT, dstAccess: VK_ACCESS_TRANSFER_READ_BIT,
3233 srcStage: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT,
3234 startLayer: 0, layerCount: 1,
3235 startLevel: 0, levelCount: 1);
3236 imageRes.lastUse = QVkSwapChain::ImageResources::ScImageUseTransferSource;
3237 }
3238
3239 QVkCommandBuffer::Command cmd;
3240 cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
3241 cmd.args.copyImageToBuffer.src = image;
3242 cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
3243 cmd.args.copyImageToBuffer.dst = readback.stagingBuf;
3244 cmd.args.copyImageToBuffer.desc = copyDesc;
3245 cbD->commands.append(t: cmd);
3246 }
3247
3248 activeTextureReadbacks.append(t: readback);
3249 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
3250 QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
3251 Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips));
3252 int w = utexD->m_pixelSize.width();
3253 int h = utexD->m_pixelSize.height();
3254
3255 VkImageLayout origLayout = utexD->usageState.layout;
3256 VkAccessFlags origAccess = utexD->usageState.access;
3257 VkPipelineStageFlags origStage = utexD->usageState.stage;
3258 if (!origStage)
3259 origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
3260
3261 for (int level = 1; level < int(utexD->mipLevelCount); ++level) {
3262 if (level == 1) {
3263 subresourceBarrier(cbD, image: utexD->image,
3264 oldLayout: origLayout, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
3265 srcAccess: origAccess, dstAccess: VK_ACCESS_TRANSFER_READ_BIT,
3266 srcStage: origStage, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT,
3267 startLayer: u.layer, layerCount: 1,
3268 startLevel: level - 1, levelCount: 1);
3269 } else {
3270 subresourceBarrier(cbD, image: utexD->image,
3271 oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
3272 srcAccess: VK_ACCESS_TRANSFER_WRITE_BIT, dstAccess: VK_ACCESS_TRANSFER_READ_BIT,
3273 srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT,
3274 startLayer: u.layer, layerCount: 1,
3275 startLevel: level - 1, levelCount: 1);
3276 }
3277
3278 subresourceBarrier(cbD, image: utexD->image,
3279 oldLayout: origLayout, newLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
3280 srcAccess: origAccess, dstAccess: VK_ACCESS_TRANSFER_WRITE_BIT,
3281 srcStage: origStage, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT,
3282 startLayer: u.layer, layerCount: 1,
3283 startLevel: level, levelCount: 1);
3284
3285 VkImageBlit region;
3286 memset(s: &region, c: 0, n: sizeof(region));
3287
3288 region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3289 region.srcSubresource.mipLevel = uint32_t(level) - 1;
3290 region.srcSubresource.baseArrayLayer = uint32_t(u.layer);
3291 region.srcSubresource.layerCount = 1;
3292
3293 region.srcOffsets[1].x = qMax(a: 1, b: w);
3294 region.srcOffsets[1].y = qMax(a: 1, b: h);
3295 region.srcOffsets[1].z = 1;
3296
3297 region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3298 region.dstSubresource.mipLevel = uint32_t(level);
3299 region.dstSubresource.baseArrayLayer = uint32_t(u.layer);
3300 region.dstSubresource.layerCount = 1;
3301
3302 region.dstOffsets[1].x = qMax(a: 1, b: w >> 1);
3303 region.dstOffsets[1].y = qMax(a: 1, b: h >> 1);
3304 region.dstOffsets[1].z = 1;
3305
3306 QVkCommandBuffer::Command cmd;
3307 cmd.cmd = QVkCommandBuffer::Command::BlitImage;
3308 cmd.args.blitImage.src = utexD->image;
3309 cmd.args.blitImage.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
3310 cmd.args.blitImage.dst = utexD->image;
3311 cmd.args.blitImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
3312 cmd.args.blitImage.filter = VK_FILTER_LINEAR;
3313 cmd.args.blitImage.desc = region;
3314 cbD->commands.append(t: cmd);
3315
3316 w >>= 1;
3317 h >>= 1;
3318 }
3319
3320 if (utexD->mipLevelCount > 1) {
3321 subresourceBarrier(cbD, image: utexD->image,
3322 oldLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, newLayout: origLayout,
3323 srcAccess: VK_ACCESS_TRANSFER_READ_BIT, dstAccess: origAccess,
3324 srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: origStage,
3325 startLayer: u.layer, layerCount: 1,
3326 startLevel: 0, levelCount: int(utexD->mipLevelCount) - 1);
3327 subresourceBarrier(cbD, image: utexD->image,
3328 oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newLayout: origLayout,
3329 srcAccess: VK_ACCESS_TRANSFER_WRITE_BIT, dstAccess: origAccess,
3330 srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: origStage,
3331 startLayer: u.layer, layerCount: 1,
3332 startLevel: int(utexD->mipLevelCount) - 1, levelCount: 1);
3333 }
3334
3335 utexD->lastActiveFrameSlot = currentFrameSlot;
3336 }
3337 }
3338
3339 ud->free();
3340}
3341
3342void QRhiVulkan::executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot)
3343{
3344 if (bufD->pendingDynamicUpdates[slot].isEmpty())
3345 return;
3346
3347 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
3348 void *p = nullptr;
3349 VmaAllocation a = toVmaAllocation(a: bufD->allocations[slot]);
3350 // The vmaMap/Unmap are basically a no-op when persistently mapped since it
3351 // refcounts; this is great because we don't need to care if the allocation
3352 // was created as persistently mapped or not.
3353 VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p);
3354 if (err != VK_SUCCESS) {
3355 qWarning(msg: "Failed to map buffer: %d", err);
3356 return;
3357 }
3358 int changeBegin = -1;
3359 int changeEnd = -1;
3360 for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(t&: bufD->pendingDynamicUpdates[slot])) {
3361 Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf));
3362 memcpy(dest: static_cast<char *>(p) + u.offset, src: u.data.constData(), n: size_t(u.data.size()));
3363 if (changeBegin == -1 || u.offset < changeBegin)
3364 changeBegin = u.offset;
3365 if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
3366 changeEnd = u.offset + u.data.size();
3367 }
3368 vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a);
3369 if (changeBegin >= 0)
3370 vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: VkDeviceSize(changeBegin), size: VkDeviceSize(changeEnd - changeBegin));
3371
3372 bufD->pendingDynamicUpdates[slot].clear();
3373}
3374
3375static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator)
3376{
3377 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
3378 vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.buffer.buffers[i], allocation: toVmaAllocation(a: e.buffer.allocations[i]));
3379 vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.buffer.stagingBuffers[i], allocation: toVmaAllocation(a: e.buffer.stagingAllocations[i]));
3380 }
3381}
3382
3383static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
3384{
3385 df->vkDestroyImageView(dev, e.renderBuffer.imageView, nullptr);
3386 df->vkDestroyImage(dev, e.renderBuffer.image, nullptr);
3387 df->vkFreeMemory(dev, e.renderBuffer.memory, nullptr);
3388}
3389
3390static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator)
3391{
3392 df->vkDestroyImageView(dev, e.texture.imageView, nullptr);
3393 vmaDestroyImage(allocator: toVmaAllocator(a: allocator), image: e.texture.image, allocation: toVmaAllocation(a: e.texture.allocation));
3394 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
3395 vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.texture.stagingBuffers[i], allocation: toVmaAllocation(a: e.texture.stagingAllocations[i]));
3396 for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
3397 if (e.texture.extraImageViews[i])
3398 df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr);
3399 }
3400}
3401
3402static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
3403{
3404 df->vkDestroySampler(dev, e.sampler.sampler, nullptr);
3405}
3406
3407void QRhiVulkan::executeDeferredReleases(bool forced)
3408{
3409 for (int i = releaseQueue.count() - 1; i >= 0; --i) {
3410 const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]);
3411 if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
3412 switch (e.type) {
3413 case QRhiVulkan::DeferredReleaseEntry::Pipeline:
3414 df->vkDestroyPipeline(dev, e.pipelineState.pipeline, nullptr);
3415 df->vkDestroyPipelineLayout(dev, e.pipelineState.layout, nullptr);
3416 break;
3417 case QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings:
3418 df->vkDestroyDescriptorSetLayout(dev, e.shaderResourceBindings.layout, nullptr);
3419 if (e.shaderResourceBindings.poolIndex >= 0) {
3420 descriptorPools[e.shaderResourceBindings.poolIndex].refCount -= 1;
3421 Q_ASSERT(descriptorPools[e.shaderResourceBindings.poolIndex].refCount >= 0);
3422 }
3423 break;
3424 case QRhiVulkan::DeferredReleaseEntry::Buffer:
3425 qrhivk_releaseBuffer(e, allocator);
3426 break;
3427 case QRhiVulkan::DeferredReleaseEntry::RenderBuffer:
3428 qrhivk_releaseRenderBuffer(e, dev, df);
3429 break;
3430 case QRhiVulkan::DeferredReleaseEntry::Texture:
3431 qrhivk_releaseTexture(e, dev, df, allocator);
3432 break;
3433 case QRhiVulkan::DeferredReleaseEntry::Sampler:
3434 qrhivk_releaseSampler(e, dev, df);
3435 break;
3436 case QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget:
3437 df->vkDestroyFramebuffer(dev, e.textureRenderTarget.fb, nullptr);
3438 for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
3439 df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr);
3440 df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr);
3441 }
3442 break;
3443 case QRhiVulkan::DeferredReleaseEntry::RenderPass:
3444 df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr);
3445 break;
3446 case QRhiVulkan::DeferredReleaseEntry::StagingBuffer:
3447 vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.stagingBuffer.stagingBuffer, allocation: toVmaAllocation(a: e.stagingBuffer.stagingAllocation));
3448 break;
3449 case QRhiVulkan::DeferredReleaseEntry::CommandBuffer:
3450 df->vkFreeCommandBuffers(dev, cmdPool, 1, &e.commandBuffer.cb);
3451 break;
3452 default:
3453 Q_UNREACHABLE();
3454 break;
3455 }
3456 releaseQueue.removeAt(i);
3457 }
3458 }
3459}
3460
3461void QRhiVulkan::finishActiveReadbacks(bool forced)
3462{
3463 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
3464 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
3465
3466 for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) {
3467 const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]);
3468 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
3469 readback.result->format = readback.format;
3470 readback.result->pixelSize = readback.pixelSize;
3471 VmaAllocation a = toVmaAllocation(a: readback.stagingAlloc);
3472 void *p = nullptr;
3473 VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p);
3474 if (err == VK_SUCCESS && p) {
3475 readback.result->data.resize(size: int(readback.byteSize));
3476 memcpy(dest: readback.result->data.data(), src: p, n: readback.byteSize);
3477 vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a);
3478 } else {
3479 qWarning(msg: "Failed to map texture readback buffer of size %u: %d", readback.byteSize, err);
3480 }
3481
3482 vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: readback.stagingBuf, allocation: a);
3483 QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf)));
3484
3485 if (readback.result->completed)
3486 completedCallbacks.append(t: readback.result->completed);
3487
3488 activeTextureReadbacks.removeAt(i);
3489 }
3490 }
3491
3492 for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) {
3493 const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]);
3494 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
3495 VmaAllocation a = toVmaAllocation(a: readback.stagingAlloc);
3496 void *p = nullptr;
3497 VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p);
3498 if (err == VK_SUCCESS && p) {
3499 readback.result->data.resize(size: readback.byteSize);
3500 memcpy(dest: readback.result->data.data(), src: p, n: size_t(readback.byteSize));
3501 vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a);
3502 } else {
3503 qWarning(msg: "Failed to map buffer readback buffer of size %d: %d", readback.byteSize, err);
3504 }
3505
3506 vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: readback.stagingBuf, allocation: a);
3507 QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf)));
3508
3509 if (readback.result->completed)
3510 completedCallbacks.append(t: readback.result->completed);
3511
3512 activeBufferReadbacks.removeAt(i);
3513 }
3514 }
3515
3516 for (auto f : completedCallbacks)
3517 f();
3518}
3519
3520static struct {
3521 VkSampleCountFlagBits mask;
3522 int count;
3523} qvk_sampleCounts[] = {
3524 // keep this sorted by 'count'
3525 { .mask: VK_SAMPLE_COUNT_1_BIT, .count: 1 },
3526 { .mask: VK_SAMPLE_COUNT_2_BIT, .count: 2 },
3527 { .mask: VK_SAMPLE_COUNT_4_BIT, .count: 4 },
3528 { .mask: VK_SAMPLE_COUNT_8_BIT, .count: 8 },
3529 { .mask: VK_SAMPLE_COUNT_16_BIT, .count: 16 },
3530 { .mask: VK_SAMPLE_COUNT_32_BIT, .count: 32 },
3531 { .mask: VK_SAMPLE_COUNT_64_BIT, .count: 64 }
3532};
3533
3534QVector<int> QRhiVulkan::supportedSampleCounts() const
3535{
3536 const VkPhysicalDeviceLimits *limits = &physDevProperties.limits;
3537 VkSampleCountFlags color = limits->framebufferColorSampleCounts;
3538 VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
3539 VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
3540 QVector<int> result;
3541
3542 for (const auto &qvk_sampleCount : qvk_sampleCounts) {
3543 if ((color & qvk_sampleCount.mask)
3544 && (depth & qvk_sampleCount.mask)
3545 && (stencil & qvk_sampleCount.mask))
3546 {
3547 result.append(t: qvk_sampleCount.count);
3548 }
3549 }
3550
3551 return result;
3552}
3553
3554VkSampleCountFlagBits QRhiVulkan::effectiveSampleCount(int sampleCount)
3555{
3556 // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
3557 sampleCount = qBound(min: 1, val: sampleCount, max: 64);
3558
3559 if (!supportedSampleCounts().contains(t: sampleCount)) {
3560 qWarning(msg: "Attempted to set unsupported sample count %d", sampleCount);
3561 return VK_SAMPLE_COUNT_1_BIT;
3562 }
3563
3564 for (const auto &qvk_sampleCount : qvk_sampleCounts) {
3565 if (qvk_sampleCount.count == sampleCount)
3566 return qvk_sampleCount.mask;
3567 }
3568
3569 Q_UNREACHABLE();
3570 return VK_SAMPLE_COUNT_1_BIT;
3571}
3572
3573void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD)
3574{
3575 cbD->passResTrackers.append(t: QRhiPassResourceTracker());
3576 cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1;
3577
3578 QVkCommandBuffer::Command cmd;
3579 cmd.cmd = QVkCommandBuffer::Command::TransitionPassResources;
3580 cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.count() - 1;
3581 cbD->commands.append(t: cmd);
3582}
3583
3584void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD)
3585{
3586 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
3587
3588 for (QVkCommandBuffer::Command &cmd : cbD->commands) {
3589 switch (cmd.cmd) {
3590 case QVkCommandBuffer::Command::CopyBuffer:
3591 df->vkCmdCopyBuffer(cbD->cb, cmd.args.copyBuffer.src, cmd.args.copyBuffer.dst,
3592 1, &cmd.args.copyBuffer.desc);
3593 break;
3594 case QVkCommandBuffer::Command::CopyBufferToImage:
3595 df->vkCmdCopyBufferToImage(cbD->cb, cmd.args.copyBufferToImage.src, cmd.args.copyBufferToImage.dst,
3596 cmd.args.copyBufferToImage.dstLayout,
3597 uint32_t(cmd.args.copyBufferToImage.count),
3598 cbD->pools.bufferImageCopy.constData() + cmd.args.copyBufferToImage.bufferImageCopyIndex);
3599 break;
3600 case QVkCommandBuffer::Command::CopyImage:
3601 df->vkCmdCopyImage(cbD->cb, cmd.args.copyImage.src, cmd.args.copyImage.srcLayout,
3602 cmd.args.copyImage.dst, cmd.args.copyImage.dstLayout,
3603 1, &cmd.args.copyImage.desc);
3604 break;
3605 case QVkCommandBuffer::Command::CopyImageToBuffer:
3606 df->vkCmdCopyImageToBuffer(cbD->cb, cmd.args.copyImageToBuffer.src, cmd.args.copyImageToBuffer.srcLayout,
3607 cmd.args.copyImageToBuffer.dst,
3608 1, &cmd.args.copyImageToBuffer.desc);
3609 break;
3610 case QVkCommandBuffer::Command::ImageBarrier:
3611 df->vkCmdPipelineBarrier(cbD->cb, cmd.args.imageBarrier.srcStageMask, cmd.args.imageBarrier.dstStageMask,
3612 0, 0, nullptr, 0, nullptr,
3613 cmd.args.imageBarrier.count, cbD->pools.imageBarrier.constData() + cmd.args.imageBarrier.index);
3614 break;
3615 case QVkCommandBuffer::Command::BufferBarrier:
3616 df->vkCmdPipelineBarrier(cbD->cb, cmd.args.bufferBarrier.srcStageMask, cmd.args.bufferBarrier.dstStageMask,
3617 0, 0, nullptr,
3618 cmd.args.bufferBarrier.count, cbD->pools.bufferBarrier.constData() + cmd.args.bufferBarrier.index,
3619 0, nullptr);
3620 break;
3621 case QVkCommandBuffer::Command::BlitImage:
3622 df->vkCmdBlitImage(cbD->cb, cmd.args.blitImage.src, cmd.args.blitImage.srcLayout,
3623 cmd.args.blitImage.dst, cmd.args.blitImage.dstLayout,
3624 1, &cmd.args.blitImage.desc,
3625 cmd.args.blitImage.filter);
3626 break;
3627 case QVkCommandBuffer::Command::BeginRenderPass:
3628 cmd.args.beginRenderPass.desc.pClearValues = cbD->pools.clearValue.constData() + cmd.args.beginRenderPass.clearValueIndex;
3629 df->vkCmdBeginRenderPass(cbD->cb, &cmd.args.beginRenderPass.desc,
3630 cbD->useSecondaryCb ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS : VK_SUBPASS_CONTENTS_INLINE);
3631 break;
3632 case QVkCommandBuffer::Command::EndRenderPass:
3633 df->vkCmdEndRenderPass(cbD->cb);
3634 break;
3635 case QVkCommandBuffer::Command::BindPipeline:
3636 df->vkCmdBindPipeline(cbD->cb, cmd.args.bindPipeline.bindPoint, cmd.args.bindPipeline.pipeline);
3637 break;
3638 case QVkCommandBuffer::Command::BindDescriptorSet:
3639 {
3640 const uint32_t *offsets = nullptr;
3641 if (cmd.args.bindDescriptorSet.dynamicOffsetCount > 0)
3642 offsets = cbD->pools.dynamicOffset.constData() + cmd.args.bindDescriptorSet.dynamicOffsetIndex;
3643 df->vkCmdBindDescriptorSets(cbD->cb, cmd.args.bindDescriptorSet.bindPoint,
3644 cmd.args.bindDescriptorSet.pipelineLayout,
3645 0, 1, &cmd.args.bindDescriptorSet.descSet,
3646 uint32_t(cmd.args.bindDescriptorSet.dynamicOffsetCount),
3647 offsets);
3648 }
3649 break;
3650 case QVkCommandBuffer::Command::BindVertexBuffer:
3651 df->vkCmdBindVertexBuffers(cbD->cb, uint32_t(cmd.args.bindVertexBuffer.startBinding),
3652 uint32_t(cmd.args.bindVertexBuffer.count),
3653 cbD->pools.vertexBuffer.constData() + cmd.args.bindVertexBuffer.vertexBufferIndex,
3654 cbD->pools.vertexBufferOffset.constData() + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex);
3655 break;
3656 case QVkCommandBuffer::Command::BindIndexBuffer:
3657 df->vkCmdBindIndexBuffer(cbD->cb, cmd.args.bindIndexBuffer.buf,
3658 cmd.args.bindIndexBuffer.ofs, cmd.args.bindIndexBuffer.type);
3659 break;
3660 case QVkCommandBuffer::Command::SetViewport:
3661 df->vkCmdSetViewport(cbD->cb, 0, 1, &cmd.args.setViewport.viewport);
3662 break;
3663 case QVkCommandBuffer::Command::SetScissor:
3664 df->vkCmdSetScissor(cbD->cb, 0, 1, &cmd.args.setScissor.scissor);
3665 break;
3666 case QVkCommandBuffer::Command::SetBlendConstants:
3667 df->vkCmdSetBlendConstants(cbD->cb, cmd.args.setBlendConstants.c);
3668 break;
3669 case QVkCommandBuffer::Command::SetStencilRef:
3670 df->vkCmdSetStencilReference(cbD->cb, VK_STENCIL_FRONT_AND_BACK, cmd.args.setStencilRef.ref);
3671 break;
3672 case QVkCommandBuffer::Command::Draw:
3673 df->vkCmdDraw(cbD->cb, cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
3674 cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
3675 break;
3676 case QVkCommandBuffer::Command::DrawIndexed:
3677 df->vkCmdDrawIndexed(cbD->cb, cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
3678 cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
3679 cmd.args.drawIndexed.firstInstance);
3680 break;
3681 case QVkCommandBuffer::Command::DebugMarkerBegin:
3682 cmd.args.debugMarkerBegin.marker.pMarkerName =
3683 cbD->pools.debugMarkerData[cmd.args.debugMarkerBegin.markerNameIndex].constData();
3684 vkCmdDebugMarkerBegin(cbD->cb, &cmd.args.debugMarkerBegin.marker);
3685 break;
3686 case QVkCommandBuffer::Command::DebugMarkerEnd:
3687 vkCmdDebugMarkerEnd(cbD->cb);
3688 break;
3689 case QVkCommandBuffer::Command::DebugMarkerInsert:
3690 cmd.args.debugMarkerInsert.marker.pMarkerName =
3691 cbD->pools.debugMarkerData[cmd.args.debugMarkerInsert.markerNameIndex].constData();
3692 vkCmdDebugMarkerInsert(cbD->cb, &cmd.args.debugMarkerInsert.marker);
3693 break;
3694 case QVkCommandBuffer::Command::TransitionPassResources:
3695 recordTransitionPassResources(cbD, tracker: cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]);
3696 break;
3697 case QVkCommandBuffer::Command::Dispatch:
3698 df->vkCmdDispatch(cbD->cb, uint32_t(cmd.args.dispatch.x), uint32_t(cmd.args.dispatch.y), uint32_t(cmd.args.dispatch.z));
3699 break;
3700 case QVkCommandBuffer::Command::ExecuteSecondary:
3701 df->vkCmdExecuteCommands(cbD->cb, 1, &cmd.args.executeSecondary.cb);
3702 break;
3703 default:
3704 break;
3705 }
3706 }
3707}
3708
3709static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::BufferAccess access)
3710{
3711 switch (access) {
3712 case QRhiPassResourceTracker::BufVertexInput:
3713 return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
3714 case QRhiPassResourceTracker::BufIndexRead:
3715 return VK_ACCESS_INDEX_READ_BIT;
3716 case QRhiPassResourceTracker::BufUniformRead:
3717 return VK_ACCESS_UNIFORM_READ_BIT;
3718 case QRhiPassResourceTracker::BufStorageLoad:
3719 return VK_ACCESS_SHADER_READ_BIT;
3720 case QRhiPassResourceTracker::BufStorageStore:
3721 return VK_ACCESS_SHADER_WRITE_BIT;
3722 case QRhiPassResourceTracker::BufStorageLoadStore:
3723 return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
3724 default:
3725 Q_UNREACHABLE();
3726 break;
3727 }
3728 return 0;
3729}
3730
3731static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::BufferStage stage)
3732{
3733 switch (stage) {
3734 case QRhiPassResourceTracker::BufVertexInputStage:
3735 return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
3736 case QRhiPassResourceTracker::BufVertexStage:
3737 return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
3738 case QRhiPassResourceTracker::BufFragmentStage:
3739 return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
3740 case QRhiPassResourceTracker::BufComputeStage:
3741 return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
3742 default:
3743 Q_UNREACHABLE();
3744 break;
3745 }
3746 return 0;
3747}
3748
3749static inline QVkBuffer::UsageState toVkBufferUsageState(QRhiPassResourceTracker::UsageState usage)
3750{
3751 QVkBuffer::UsageState u;
3752 u.access = VkAccessFlags(usage.access);
3753 u.stage = VkPipelineStageFlags(usage.stage);
3754 return u;
3755}
3756
3757static inline VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess access)
3758{
3759 switch (access) {
3760 case QRhiPassResourceTracker::TexSample:
3761 return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3762 case QRhiPassResourceTracker::TexColorOutput:
3763 return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3764 case QRhiPassResourceTracker::TexDepthOutput:
3765 return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
3766 case QRhiPassResourceTracker::TexStorageLoad:
3767 case QRhiPassResourceTracker::TexStorageStore:
3768 case QRhiPassResourceTracker::TexStorageLoadStore:
3769 return VK_IMAGE_LAYOUT_GENERAL;
3770 default:
3771 Q_UNREACHABLE();
3772 break;
3773 }
3774 return VK_IMAGE_LAYOUT_GENERAL;
3775}
3776
3777static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess access)
3778{
3779 switch (access) {
3780 case QRhiPassResourceTracker::TexSample:
3781 return VK_ACCESS_SHADER_READ_BIT;
3782 case QRhiPassResourceTracker::TexColorOutput:
3783 return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
3784 case QRhiPassResourceTracker::TexDepthOutput:
3785 return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
3786 case QRhiPassResourceTracker::TexStorageLoad:
3787 return VK_ACCESS_SHADER_READ_BIT;
3788 case QRhiPassResourceTracker::TexStorageStore:
3789 return VK_ACCESS_SHADER_WRITE_BIT;
3790 case QRhiPassResourceTracker::TexStorageLoadStore:
3791 return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
3792 default:
3793 Q_UNREACHABLE();
3794 break;
3795 }
3796 return 0;
3797}
3798
3799static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::TextureStage stage)
3800{
3801 switch (stage) {
3802 case QRhiPassResourceTracker::TexVertexStage:
3803 return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
3804 case QRhiPassResourceTracker::TexFragmentStage:
3805 return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
3806 case QRhiPassResourceTracker::TexColorOutputStage:
3807 return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
3808 case QRhiPassResourceTracker::TexDepthOutputStage:
3809 return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
3810 case QRhiPassResourceTracker::TexComputeStage:
3811 return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
3812 default:
3813 Q_UNREACHABLE();
3814 break;
3815 }
3816 return 0;
3817}
3818
3819static inline QVkTexture::UsageState toVkTextureUsageState(QRhiPassResourceTracker::UsageState usage)
3820{
3821 QVkTexture::UsageState u;
3822 u.layout = VkImageLayout(usage.layout);
3823 u.access = VkAccessFlags(usage.access);
3824 u.stage = VkPipelineStageFlags(usage.stage);
3825 return u;
3826}
3827
3828void QRhiVulkan::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker,
3829 QVkBuffer *bufD,
3830 int slot,
3831 QRhiPassResourceTracker::BufferAccess access,
3832 QRhiPassResourceTracker::BufferStage stage)
3833{
3834 QVkBuffer::UsageState &u(bufD->usageState[slot]);
3835 passResTracker->registerBuffer(buf: bufD, slot, access: &access, stage: &stage, state: toPassTrackerUsageState(bufUsage: u));
3836 u.access = toVkAccess(access);
3837 u.stage = toVkPipelineStage(stage);
3838}
3839
3840void QRhiVulkan::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker,
3841 QVkTexture *texD,
3842 QRhiPassResourceTracker::TextureAccess access,
3843 QRhiPassResourceTracker::TextureStage stage)
3844{
3845 QVkTexture::UsageState &u(texD->usageState);
3846 passResTracker->registerTexture(tex: texD, access: &access, stage: &stage, state: toPassTrackerUsageState(texUsage: u));
3847 u.layout = toVkLayout(access);
3848 u.access = toVkAccess(access);
3849 u.stage = toVkPipelineStage(stage);
3850}
3851
3852void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker)
3853{
3854 if (tracker.isEmpty())
3855 return;
3856
3857 for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) {
3858 QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key());
3859 VkAccessFlags access = toVkAccess(access: it->access);
3860 VkPipelineStageFlags stage = toVkPipelineStage(stage: it->stage);
3861 QVkBuffer::UsageState s = toVkBufferUsageState(usage: it->stateAtPassBegin);
3862 if (!s.stage)
3863 continue;
3864 if (s.access == access && s.stage == stage) {
3865 if (!accessIsWrite(access))
3866 continue;
3867 }
3868 VkBufferMemoryBarrier bufMemBarrier;
3869 memset(s: &bufMemBarrier, c: 0, n: sizeof(bufMemBarrier));
3870 bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
3871 bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3872 bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3873 bufMemBarrier.srcAccessMask = s.access;
3874 bufMemBarrier.dstAccessMask = access;
3875 bufMemBarrier.buffer = bufD->buffers[it->slot];
3876 bufMemBarrier.size = VK_WHOLE_SIZE;
3877 df->vkCmdPipelineBarrier(cbD->cb, s.stage, stage, 0,
3878 0, nullptr,
3879 1, &bufMemBarrier,
3880 0, nullptr);
3881 }
3882
3883 for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) {
3884 QVkTexture *texD = QRHI_RES(QVkTexture, it.key());
3885 VkImageLayout layout = toVkLayout(access: it->access);
3886 VkAccessFlags access = toVkAccess(access: it->access);
3887 VkPipelineStageFlags stage = toVkPipelineStage(stage: it->stage);
3888 QVkTexture::UsageState s = toVkTextureUsageState(usage: it->stateAtPassBegin);
3889 if (s.access == access && s.stage == stage && s.layout == layout) {
3890 if (!accessIsWrite(access))
3891 continue;
3892 }
3893 VkImageMemoryBarrier barrier;
3894 memset(s: &barrier, c: 0, n: sizeof(barrier));
3895 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
3896 barrier.subresourceRange.aspectMask = !isDepthTextureFormat(format: texD->m_format)
3897 ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
3898 barrier.subresourceRange.baseMipLevel = 0;
3899 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
3900 barrier.subresourceRange.baseArrayLayer = 0;
3901 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
3902 barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
3903 barrier.newLayout = layout;
3904 barrier.srcAccessMask = s.access; // may be 0 but that's fine
3905 barrier.dstAccessMask = access;
3906 barrier.image = texD->image;
3907 VkPipelineStageFlags srcStage = s.stage;
3908 // stage mask cannot be 0
3909 if (!srcStage)
3910 srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
3911 df->vkCmdPipelineBarrier(cbD->cb, srcStage, stage, 0,
3912 0, nullptr,
3913 0, nullptr,
3914 1, &barrier);
3915 }
3916}
3917
3918QRhiSwapChain *QRhiVulkan::createSwapChain()
3919{
3920 return new QVkSwapChain(this);
3921}
3922
3923QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
3924{
3925 return new QVkBuffer(this, type, usage, size);
3926}
3927
3928int QRhiVulkan::ubufAlignment() const
3929{
3930 return int(ubufAlign); // typically 256 (bytes)
3931}
3932
3933bool QRhiVulkan::isYUpInFramebuffer() const
3934{
3935 return false;
3936}
3937
3938bool QRhiVulkan::isYUpInNDC() const
3939{
3940 return false;
3941}
3942
3943bool QRhiVulkan::isClipDepthZeroToOne() const
3944{
3945 return true;
3946}
3947
3948QMatrix4x4 QRhiVulkan::clipSpaceCorrMatrix() const
3949{
3950 // See https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/
3951
3952 static QMatrix4x4 m;
3953 if (m.isIdentity()) {
3954 // NB the ctor takes row-major
3955 m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
3956 0.0f, -1.0f, 0.0f, 0.0f,
3957 0.0f, 0.0f, 0.5f, 0.5f,
3958 0.0f, 0.0f, 0.0f, 1.0f);
3959 }
3960 return m;
3961}
3962
3963bool QRhiVulkan::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
3964{
3965 // Note that with some SDKs the validation layer gives an odd warning about
3966 // BC not being supported, even when our check here succeeds. Not much we
3967 // can do about that.
3968 if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7) {
3969 if (!physDevFeatures.textureCompressionBC)
3970 return false;
3971 }
3972
3973 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8) {
3974 if (!physDevFeatures.textureCompressionETC2)
3975 return false;
3976 }
3977
3978 if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12) {
3979 if (!physDevFeatures.textureCompressionASTC_LDR)
3980 return false;
3981 }
3982
3983 VkFormat vkformat = toVkTextureFormat(format, flags);
3984 VkFormatProperties props;
3985 f->vkGetPhysicalDeviceFormatProperties(physDev, vkformat, &props);
3986 return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
3987}
3988
3989bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
3990{
3991 switch (feature) {
3992 case QRhi::MultisampleTexture:
3993 return true;
3994 case QRhi::MultisampleRenderBuffer:
3995 return true;
3996 case QRhi::DebugMarkers:
3997 return debugMarkersAvailable;
3998 case QRhi::Timestamps:
3999 return timestampValidBits != 0;
4000 case QRhi::Instancing:
4001 return true;
4002 case QRhi::CustomInstanceStepRate:
4003 return vertexAttribDivisorAvailable;
4004 case QRhi::PrimitiveRestart:
4005 return true;
4006 case QRhi::NonDynamicUniformBuffers:
4007 return true;
4008 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
4009 return true;
4010 case QRhi::NPOTTextureRepeat:
4011 return true;
4012 case QRhi::RedOrAlpha8IsRed:
4013 return true;
4014 case QRhi::ElementIndexUint:
4015 return true;
4016 case QRhi::Compute:
4017 return hasCompute;
4018 case QRhi::WideLines:
4019 return hasWideLines;
4020 case QRhi::VertexShaderPointSize:
4021 return true;
4022 case QRhi::BaseVertex:
4023 return true;
4024 case QRhi::BaseInstance:
4025 return true;
4026 case QRhi::TriangleFanTopology:
4027 return true;
4028 case QRhi::ReadBackNonUniformBuffer:
4029 return true;
4030 case QRhi::ReadBackNonBaseMipLevel:
4031 return true;
4032 case QRhi::TexelFetch:
4033 return true;
4034 default:
4035 Q_UNREACHABLE();
4036 return false;
4037 }
4038}
4039
4040int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const
4041{
4042 switch (limit) {
4043 case QRhi::TextureSizeMin:
4044 return 1;
4045 case QRhi::TextureSizeMax:
4046 return int(physDevProperties.limits.maxImageDimension2D);
4047 case QRhi::MaxColorAttachments:
4048 return int(physDevProperties.limits.maxColorAttachments);
4049 case QRhi::FramesInFlight:
4050 return QVK_FRAMES_IN_FLIGHT;
4051 case QRhi::MaxAsyncReadbackFrames:
4052 return QVK_FRAMES_IN_FLIGHT;
4053 default:
4054 Q_UNREACHABLE();
4055 return 0;
4056 }
4057}
4058
4059const QRhiNativeHandles *QRhiVulkan::nativeHandles()
4060{
4061 return &nativeHandlesStruct;
4062}
4063
4064void QRhiVulkan::sendVMemStatsToProfiler()
4065{
4066 QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
4067 if (!rhiP)
4068 return;
4069
4070 VmaStats stats;
4071 vmaCalculateStats(allocator: toVmaAllocator(a: allocator), pStats: &stats);
4072 QRHI_PROF_F(vmemStat(stats.total.blockCount, stats.total.allocationCount,
4073 quint32(stats.total.usedBytes), quint32(stats.total.unusedBytes)));
4074}
4075
4076bool QRhiVulkan::makeThreadLocalNativeContextCurrent()
4077{
4078 // not applicable
4079 return false;
4080}
4081
4082void QRhiVulkan::releaseCachedResources()
4083{
4084 // nothing to do here
4085}
4086
4087bool QRhiVulkan::isDeviceLost() const
4088{
4089 return deviceLost;
4090}
4091
4092QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
4093 int sampleCount, QRhiRenderBuffer::Flags flags)
4094{
4095 return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags);
4096}
4097
4098QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
4099 int sampleCount, QRhiTexture::Flags flags)
4100{
4101 return new QVkTexture(this, format, pixelSize, sampleCount, flags);
4102}
4103
4104QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
4105 QRhiSampler::Filter mipmapMode,
4106 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
4107{
4108 return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
4109}
4110
4111QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
4112 QRhiTextureRenderTarget::Flags flags)
4113{
4114 return new QVkTextureRenderTarget(this, desc, flags);
4115}
4116
4117QRhiGraphicsPipeline *QRhiVulkan::createGraphicsPipeline()
4118{
4119 return new QVkGraphicsPipeline(this);
4120}
4121
4122QRhiComputePipeline *QRhiVulkan::createComputePipeline()
4123{
4124 return new QVkComputePipeline(this);
4125}
4126
4127QRhiShaderResourceBindings *QRhiVulkan::createShaderResourceBindings()
4128{
4129 return new QVkShaderResourceBindings(this);
4130}
4131
4132void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
4133{
4134 QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, ps);
4135 Q_ASSERT(psD->pipeline);
4136 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4137 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4138
4139 if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
4140 if (cbD->useSecondaryCb) {
4141 df->vkCmdBindPipeline(cbD->secondaryCbs.last(), VK_PIPELINE_BIND_POINT_GRAPHICS, psD->pipeline);
4142 } else {
4143 QVkCommandBuffer::Command cmd;
4144 cmd.cmd = QVkCommandBuffer::Command::BindPipeline;
4145 cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
4146 cmd.args.bindPipeline.pipeline = psD->pipeline;
4147 cbD->commands.append(t: cmd);
4148 }
4149
4150 cbD->currentGraphicsPipeline = ps;
4151 cbD->currentComputePipeline = nullptr;
4152 cbD->currentPipelineGeneration = psD->generation;
4153 }
4154
4155 psD->lastActiveFrameSlot = currentFrameSlot;
4156}
4157
4158void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
4159 int dynamicOffsetCount,
4160 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
4161{
4162 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4163 Q_ASSERT(cbD->recordingPass != QVkCommandBuffer::NoPass);
4164 QVkGraphicsPipeline *gfxPsD = QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline);
4165 QVkComputePipeline *compPsD = QRHI_RES(QVkComputePipeline, cbD->currentComputePipeline);
4166
4167 if (!srb) {
4168 if (gfxPsD)
4169 srb = gfxPsD->m_shaderResourceBindings;
4170 else
4171 srb = compPsD->m_shaderResourceBindings;
4172 }
4173
4174 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
4175 bool hasSlottedResourceInSrb = false;
4176 bool hasDynamicOffsetInSrb = false;
4177
4178 for (const QRhiShaderResourceBinding &binding : qAsConst(t&: srbD->sortedBindings)) {
4179 const QRhiShaderResourceBinding::Data *b = binding.data();
4180 switch (b->type) {
4181 case QRhiShaderResourceBinding::UniformBuffer:
4182 if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->m_type == QRhiBuffer::Dynamic)
4183 hasSlottedResourceInSrb = true;
4184 if (b->u.ubuf.hasDynamicOffset)
4185 hasDynamicOffsetInSrb = true;
4186 break;
4187 default:
4188 break;
4189 }
4190 }
4191
4192 const int descSetIdx = hasSlottedResourceInSrb ? currentFrameSlot : 0;
4193 bool rewriteDescSet = false;
4194
4195 // Do host writes and mark referenced shader resources as in-use.
4196 // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects.
4197 for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
4198 const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(idx: i).data();
4199 QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[descSetIdx][i]);
4200 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
4201 switch (b->type) {
4202 case QRhiShaderResourceBinding::UniformBuffer:
4203 {
4204 QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.ubuf.buf);
4205 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
4206
4207 if (bufD->m_type == QRhiBuffer::Dynamic)
4208 executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot);
4209
4210 bufD->lastActiveFrameSlot = currentFrameSlot;
4211 trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot: bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
4212 access: QRhiPassResourceTracker::BufUniformRead,
4213 stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage));
4214
4215 // Check both the "local" id (the generation counter) and the
4216 // global id. The latter is relevant when a newly allocated
4217 // QRhiResource ends up with the same pointer as a previous one.
4218 // (and that previous one could have been in an srb...)
4219 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
4220 rewriteDescSet = true;
4221 bd.ubuf.id = bufD->m_id;
4222 bd.ubuf.generation = bufD->generation;
4223 }
4224 }
4225 break;
4226 case QRhiShaderResourceBinding::SampledTexture:
4227 {
4228 const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex;
4229 if (bd.stex.count != data->count) {
4230 bd.stex.count = data->count;
4231 rewriteDescSet = true;
4232 }
4233 for (int elem = 0; elem < data->count; ++elem) {
4234 QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
4235 QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler);
4236 texD->lastActiveFrameSlot = currentFrameSlot;
4237 samplerD->lastActiveFrameSlot = currentFrameSlot;
4238 trackedRegisterTexture(passResTracker: &passResTracker, texD,
4239 access: QRhiPassResourceTracker::TexSample,
4240 stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage));
4241 if (texD->generation != bd.stex.d[elem].texGeneration
4242 || texD->m_id != bd.stex.d[elem].texId
4243 || samplerD->generation != bd.stex.d[elem].samplerGeneration
4244 || samplerD->m_id != bd.stex.d[elem].samplerId)
4245 {
4246 rewriteDescSet = true;
4247 bd.stex.d[elem].texId = texD->m_id;
4248 bd.stex.d[elem].texGeneration = texD->generation;
4249 bd.stex.d[elem].samplerId = samplerD->m_id;
4250 bd.stex.d[elem].samplerGeneration = samplerD->generation;
4251 }
4252 }
4253 }
4254 break;
4255 case QRhiShaderResourceBinding::ImageLoad:
4256 case QRhiShaderResourceBinding::ImageStore:
4257 case QRhiShaderResourceBinding::ImageLoadStore:
4258 {
4259 QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
4260 Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore));
4261 texD->lastActiveFrameSlot = currentFrameSlot;
4262 QRhiPassResourceTracker::TextureAccess access;
4263 if (b->type == QRhiShaderResourceBinding::ImageLoad)
4264 access = QRhiPassResourceTracker::TexStorageLoad;
4265 else if (b->type == QRhiShaderResourceBinding::ImageStore)
4266 access = QRhiPassResourceTracker::TexStorageStore;
4267 else
4268 access = QRhiPassResourceTracker::TexStorageLoadStore;
4269 trackedRegisterTexture(passResTracker: &passResTracker, texD,
4270 access,
4271 stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage));
4272
4273 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
4274 rewriteDescSet = true;
4275 bd.simage.id = texD->m_id;
4276 bd.simage.generation = texD->generation;
4277 }
4278 }
4279 break;
4280 case QRhiShaderResourceBinding::BufferLoad:
4281 case QRhiShaderResourceBinding::BufferStore:
4282 case QRhiShaderResourceBinding::BufferLoadStore:
4283 {
4284 QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf);
4285 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
4286
4287 if (bufD->m_type == QRhiBuffer::Dynamic)
4288 executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot);
4289
4290 bufD->lastActiveFrameSlot = currentFrameSlot;
4291 QRhiPassResourceTracker::BufferAccess access;
4292 if (b->type == QRhiShaderResourceBinding::BufferLoad)
4293 access = QRhiPassResourceTracker::BufStorageLoad;
4294 else if (b->type == QRhiShaderResourceBinding::BufferStore)
4295 access = QRhiPassResourceTracker::BufStorageStore;
4296 else
4297 access = QRhiPassResourceTracker::BufStorageLoadStore;
4298 trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot: bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
4299 access,
4300 stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage));
4301
4302 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
4303 rewriteDescSet = true;
4304 bd.sbuf.id = bufD->m_id;
4305 bd.sbuf.generation = bufD->generation;
4306 }
4307 }
4308 break;
4309 default:
4310 Q_UNREACHABLE();
4311 break;
4312 }
4313 }
4314
4315 // write descriptor sets, if needed
4316 if (rewriteDescSet)
4317 updateShaderResourceBindings(srb, descSetIdx);
4318
4319 // make sure the descriptors for the correct slot will get bound.
4320 // also, dynamic offsets always need a bind.
4321 const bool forceRebind = (hasSlottedResourceInSrb && cbD->currentDescSetSlot != descSetIdx) || hasDynamicOffsetInSrb;
4322
4323 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
4324
4325 if (forceRebind || rewriteDescSet || srbChanged || cbD->currentSrbGeneration != srbD->generation) {
4326 QVarLengthArray<uint32_t, 4> dynOfs;
4327 if (hasDynamicOffsetInSrb) {
4328 // Filling out dynOfs based on the sorted bindings is important
4329 // because dynOfs has to be ordered based on the binding numbers,
4330 // and neither srb nor dynamicOffsets has any such ordering
4331 // requirement.
4332 for (const QRhiShaderResourceBinding &binding : qAsConst(t&: srbD->sortedBindings)) {
4333 const QRhiShaderResourceBinding::Data *b = binding.data();
4334 if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) {
4335 uint32_t offset = 0;
4336 for (int i = 0; i < dynamicOffsetCount; ++i) {
4337 const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
4338 if (dynOfs.first == b->binding) {
4339 offset = dynOfs.second;
4340 break;
4341 }
4342 }
4343 dynOfs.append(t: offset); // use 0 if dynamicOffsets did not contain this binding
4344 }
4345 }
4346 }
4347
4348 if (cbD->useSecondaryCb) {
4349 df->vkCmdBindDescriptorSets(cbD->secondaryCbs.last(),
4350 gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE,
4351 gfxPsD ? gfxPsD->layout : compPsD->layout,
4352 0, 1, &srbD->descSets[descSetIdx],
4353 uint32_t(dynOfs.count()),
4354 dynOfs.count() ? dynOfs.constData() : nullptr);
4355 } else {
4356 QVkCommandBuffer::Command cmd;
4357 cmd.cmd = QVkCommandBuffer::Command::BindDescriptorSet;
4358 cmd.args.bindDescriptorSet.bindPoint = gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS
4359 : VK_PIPELINE_BIND_POINT_COMPUTE;
4360 cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->layout;
4361 cmd.args.bindDescriptorSet.descSet = srbD->descSets[descSetIdx];
4362 cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.count();
4363 cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.count();
4364 cbD->pools.dynamicOffset.append(abuf: dynOfs.constData(), increment: dynOfs.count());
4365 cbD->commands.append(t: cmd);
4366 }
4367
4368 if (gfxPsD) {
4369 cbD->currentGraphicsSrb = srb;
4370 cbD->currentComputeSrb = nullptr;
4371 } else {
4372 cbD->currentGraphicsSrb = nullptr;
4373 cbD->currentComputeSrb = srb;
4374 }
4375 cbD->currentSrbGeneration = srbD->generation;
4376 cbD->currentDescSetSlot = descSetIdx;
4377 }
4378
4379 srbD->lastActiveFrameSlot = currentFrameSlot;
4380}
4381
4382void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb,
4383 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
4384 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
4385{
4386 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4387 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4388 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
4389
4390 bool needsBindVBuf = false;
4391 for (int i = 0; i < bindingCount; ++i) {
4392 const int inputSlot = startBinding + i;
4393 QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
4394 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
4395 bufD->lastActiveFrameSlot = currentFrameSlot;
4396 if (bufD->m_type == QRhiBuffer::Dynamic)
4397 executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot);
4398
4399 const VkBuffer vkvertexbuf = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
4400 if (cbD->currentVertexBuffers[inputSlot] != vkvertexbuf
4401 || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
4402 {
4403 needsBindVBuf = true;
4404 cbD->currentVertexBuffers[inputSlot] = vkvertexbuf;
4405 cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
4406 }
4407 }
4408
4409 if (needsBindVBuf) {
4410 QVarLengthArray<VkBuffer, 4> bufs;
4411 QVarLengthArray<VkDeviceSize, 4> ofs;
4412 for (int i = 0; i < bindingCount; ++i) {
4413 QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
4414 const int slot = bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
4415 bufs.append(t: bufD->buffers[slot]);
4416 ofs.append(t: bindings[i].second);
4417 trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot,
4418 access: QRhiPassResourceTracker::BufVertexInput,
4419 stage: QRhiPassResourceTracker::BufVertexInputStage);
4420 }
4421
4422 if (cbD->useSecondaryCb) {
4423 df->vkCmdBindVertexBuffers(cbD->secondaryCbs.last(), uint32_t(startBinding),
4424 uint32_t(bufs.count()), bufs.constData(), ofs.constData());
4425 } else {
4426 QVkCommandBuffer::Command cmd;
4427 cmd.cmd = QVkCommandBuffer::Command::BindVertexBuffer;
4428 cmd.args.bindVertexBuffer.startBinding = startBinding;
4429 cmd.args.bindVertexBuffer.count = bufs.count();
4430 cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.count();
4431 cbD->pools.vertexBuffer.append(abuf: bufs.constData(), increment: bufs.count());
4432 cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.count();
4433 cbD->pools.vertexBufferOffset.append(abuf: ofs.constData(), increment: ofs.count());
4434 cbD->commands.append(t: cmd);
4435 }
4436 }
4437
4438 if (indexBuf) {
4439 QVkBuffer *ibufD = QRHI_RES(QVkBuffer, indexBuf);
4440 Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
4441 ibufD->lastActiveFrameSlot = currentFrameSlot;
4442 if (ibufD->m_type == QRhiBuffer::Dynamic)
4443 executeBufferHostWritesForSlot(bufD: ibufD, slot: currentFrameSlot);
4444
4445 const int slot = ibufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
4446 const VkBuffer vkindexbuf = ibufD->buffers[slot];
4447 const VkIndexType type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? VK_INDEX_TYPE_UINT16
4448 : VK_INDEX_TYPE_UINT32;
4449
4450 if (cbD->currentIndexBuffer != vkindexbuf
4451 || cbD->currentIndexOffset != indexOffset
4452 || cbD->currentIndexFormat != type)
4453 {
4454 cbD->currentIndexBuffer = vkindexbuf;
4455 cbD->currentIndexOffset = indexOffset;
4456 cbD->currentIndexFormat = type;
4457
4458 if (cbD->useSecondaryCb) {
4459 df->vkCmdBindIndexBuffer(cbD->secondaryCbs.last(), vkindexbuf, indexOffset, type);
4460 } else {
4461 QVkCommandBuffer::Command cmd;
4462 cmd.cmd = QVkCommandBuffer::Command::BindIndexBuffer;
4463 cmd.args.bindIndexBuffer.buf = vkindexbuf;
4464 cmd.args.bindIndexBuffer.ofs = indexOffset;
4465 cmd.args.bindIndexBuffer.type = type;
4466 cbD->commands.append(t: cmd);
4467 }
4468
4469 trackedRegisterBuffer(passResTracker: &passResTracker, bufD: ibufD, slot,
4470 access: QRhiPassResourceTracker::BufIndexRead,
4471 stage: QRhiPassResourceTracker::BufVertexInputStage);
4472 }
4473 }
4474}
4475
4476void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
4477{
4478 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4479 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4480 const QSize outputSize = cbD->currentTarget->pixelSize();
4481
4482 // x,y is top-left in VkViewport but bottom-left in QRhiViewport
4483 float x, y, w, h;
4484 if (!qrhi_toTopLeftRenderTargetRect(outputSize, r: viewport.viewport(), x: &x, y: &y, w: &w, h: &h))
4485 return;
4486
4487 QVkCommandBuffer::Command cmd;
4488 VkViewport *vp = &cmd.args.setViewport.viewport;
4489 vp->x = x;
4490 vp->y = y;
4491 vp->width = w;
4492 vp->height = h;
4493 vp->minDepth = viewport.minDepth();
4494 vp->maxDepth = viewport.maxDepth();
4495
4496 if (cbD->useSecondaryCb) {
4497 df->vkCmdSetViewport(cbD->secondaryCbs.last(), 0, 1, vp);
4498 } else {
4499 cmd.cmd = QVkCommandBuffer::Command::SetViewport;
4500 cbD->commands.append(t: cmd);
4501 }
4502
4503 if (!QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesScissor)) {
4504 VkRect2D *s = &cmd.args.setScissor.scissor;
4505 s->offset.x = int32_t(x);
4506 s->offset.y = int32_t(y);
4507 s->extent.width = uint32_t(w);
4508 s->extent.height = uint32_t(h);
4509 if (cbD->useSecondaryCb) {
4510 df->vkCmdSetScissor(cbD->secondaryCbs.last(), 0, 1, s);
4511 } else {
4512 cmd.cmd = QVkCommandBuffer::Command::SetScissor;
4513 cbD->commands.append(t: cmd);
4514 }
4515 }
4516}
4517
4518void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
4519{
4520 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4521 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4522 Q_ASSERT(QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
4523 const QSize outputSize = cbD->currentTarget->pixelSize();
4524
4525 // x,y is top-left in VkRect2D but bottom-left in QRhiScissor
4526 int x, y, w, h;
4527 if (!qrhi_toTopLeftRenderTargetRect(outputSize, r: scissor.scissor(), x: &x, y: &y, w: &w, h: &h))
4528 return;
4529
4530 QVkCommandBuffer::Command cmd;
4531 VkRect2D *s = &cmd.args.setScissor.scissor;
4532 s->offset.x = x;
4533 s->offset.y = y;
4534 s->extent.width = uint32_t(w);
4535 s->extent.height = uint32_t(h);
4536
4537 if (cbD->useSecondaryCb) {
4538 df->vkCmdSetScissor(cbD->secondaryCbs.last(), 0, 1, s);
4539 } else {
4540 cmd.cmd = QVkCommandBuffer::Command::SetScissor;
4541 cbD->commands.append(t: cmd);
4542 }
4543}
4544
4545void QRhiVulkan::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
4546{
4547 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4548 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4549
4550 if (cbD->useSecondaryCb) {
4551 float constants[] = { float(c.redF()), float(c.greenF()), float(c.blueF()), float(c.alphaF()) };
4552 df->vkCmdSetBlendConstants(cbD->secondaryCbs.last(), constants);
4553 } else {
4554 QVkCommandBuffer::Command cmd;
4555 cmd.cmd = QVkCommandBuffer::Command::SetBlendConstants;
4556 cmd.args.setBlendConstants.c[0] = float(c.redF());
4557 cmd.args.setBlendConstants.c[1] = float(c.greenF());
4558 cmd.args.setBlendConstants.c[2] = float(c.blueF());
4559 cmd.args.setBlendConstants.c[3] = float(c.alphaF());
4560 cbD->commands.append(t: cmd);
4561 }
4562}
4563
4564void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
4565{
4566 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4567 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4568
4569 if (cbD->useSecondaryCb) {
4570 df->vkCmdSetStencilReference(cbD->secondaryCbs.last(), VK_STENCIL_FRONT_AND_BACK, refValue);
4571 } else {
4572 QVkCommandBuffer::Command cmd;
4573 cmd.cmd = QVkCommandBuffer::Command::SetStencilRef;
4574 cmd.args.setStencilRef.ref = refValue;
4575 cbD->commands.append(t: cmd);
4576 }
4577}
4578
4579void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
4580 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
4581{
4582 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4583 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4584
4585 if (cbD->useSecondaryCb) {
4586 df->vkCmdDraw(cbD->secondaryCbs.last(), vertexCount, instanceCount, firstVertex, firstInstance);
4587 } else {
4588 QVkCommandBuffer::Command cmd;
4589 cmd.cmd = QVkCommandBuffer::Command::Draw;
4590 cmd.args.draw.vertexCount = vertexCount;
4591 cmd.args.draw.instanceCount = instanceCount;
4592 cmd.args.draw.firstVertex = firstVertex;
4593 cmd.args.draw.firstInstance = firstInstance;
4594 cbD->commands.append(t: cmd);
4595 }
4596}
4597
4598void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
4599 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
4600{
4601 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4602 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
4603
4604 if (cbD->useSecondaryCb) {
4605 df->vkCmdDrawIndexed(cbD->secondaryCbs.last(), indexCount, instanceCount,
4606 firstIndex, vertexOffset, firstInstance);
4607 } else {
4608 QVkCommandBuffer::Command cmd;
4609 cmd.cmd = QVkCommandBuffer::Command::DrawIndexed;
4610 cmd.args.drawIndexed.indexCount = indexCount;
4611 cmd.args.drawIndexed.instanceCount = instanceCount;
4612 cmd.args.drawIndexed.firstIndex = firstIndex;
4613 cmd.args.drawIndexed.vertexOffset = vertexOffset;
4614 cmd.args.drawIndexed.firstInstance = firstInstance;
4615 cbD->commands.append(t: cmd);
4616 }
4617}
4618
4619void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
4620{
4621 if (!debugMarkers || !debugMarkersAvailable)
4622 return;
4623
4624 VkDebugMarkerMarkerInfoEXT marker;
4625 memset(s: &marker, c: 0, n: sizeof(marker));
4626 marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
4627
4628 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4629 if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->useSecondaryCb) {
4630 marker.pMarkerName = name.constData();
4631 vkCmdDebugMarkerBegin(cbD->secondaryCbs.last(), &marker);
4632 } else {
4633 QVkCommandBuffer::Command cmd;
4634 cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin;
4635 cmd.args.debugMarkerBegin.marker = marker;
4636 cmd.args.debugMarkerBegin.markerNameIndex = cbD->pools.debugMarkerData.count();
4637 cbD->pools.debugMarkerData.append(t: name);
4638 cbD->commands.append(t: cmd);
4639 }
4640}
4641
4642void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb)
4643{
4644 if (!debugMarkers || !debugMarkersAvailable)
4645 return;
4646
4647 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4648 if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->useSecondaryCb) {
4649 vkCmdDebugMarkerEnd(cbD->secondaryCbs.last());
4650 } else {
4651 QVkCommandBuffer::Command cmd;
4652 cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd;
4653 cbD->commands.append(t: cmd);
4654 }
4655}
4656
4657void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
4658{
4659 if (!debugMarkers || !debugMarkersAvailable)
4660 return;
4661
4662 VkDebugMarkerMarkerInfoEXT marker;
4663 memset(s: &marker, c: 0, n: sizeof(marker));
4664 marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
4665
4666 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4667 if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->useSecondaryCb) {
4668 marker.pMarkerName = msg.constData();
4669 vkCmdDebugMarkerInsert(cbD->secondaryCbs.last(), &marker);
4670 } else {
4671 QVkCommandBuffer::Command cmd;
4672 cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert;
4673 cmd.args.debugMarkerInsert.marker = marker;
4674 cmd.args.debugMarkerInsert.markerNameIndex = cbD->pools.debugMarkerData.count();
4675 cbD->pools.debugMarkerData.append(t: msg);
4676 cbD->commands.append(t: cmd);
4677 }
4678}
4679
4680const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb)
4681{
4682 return QRHI_RES(QVkCommandBuffer, cb)->nativeHandles();
4683}
4684
4685static inline QVkRenderTargetData *maybeRenderTargetData(QVkCommandBuffer *cbD)
4686{
4687 Q_ASSERT(cbD->currentTarget);
4688 QVkRenderTargetData *rtD = nullptr;
4689 if (cbD->recordingPass == QVkCommandBuffer::RenderPass) {
4690 switch (cbD->currentTarget->resourceType()) {
4691 case QRhiResource::RenderTarget:
4692 rtD = &QRHI_RES(QVkReferenceRenderTarget, cbD->currentTarget)->d;
4693 break;
4694 case QRhiResource::TextureRenderTarget:
4695 rtD = &QRHI_RES(QVkTextureRenderTarget, cbD->currentTarget)->d;
4696 break;
4697 default:
4698 Q_UNREACHABLE();
4699 break;
4700 }
4701 }
4702 return rtD;
4703}
4704
4705void QRhiVulkan::beginExternal(QRhiCommandBuffer *cb)
4706{
4707 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4708
4709 // When not in a pass, it is simple: record what we have (but do not
4710 // submit), the cb can then be used to record more external commands.
4711 if (cbD->recordingPass == QVkCommandBuffer::NoPass) {
4712 recordPrimaryCommandBuffer(cbD);
4713 cbD->resetCommands();
4714 return;
4715 }
4716
4717 // Otherwise, inside a pass, have a secondary command buffer (with
4718 // RENDER_PASS_CONTINUE). Using the main one is not acceptable since we
4719 // cannot just record at this stage, that would mess up the resource
4720 // tracking and commands like TransitionPassResources.
4721
4722 if (cbD->inExternal)
4723 return;
4724
4725 if (!cbD->useSecondaryCb) {
4726 qWarning(msg: "beginExternal() within a pass is only supported with secondary command buffers. "
4727 "This can be enabled by passing QRhi::ExternalContentsInPass to beginFrame().");
4728 return;
4729 }
4730
4731 VkCommandBuffer secondaryCb = cbD->secondaryCbs.last();
4732 cbD->secondaryCbs.removeLast();
4733 endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD);
4734
4735 VkCommandBuffer extCb = startSecondaryCommandBuffer(rtD: maybeRenderTargetData(cbD));
4736 if (extCb) {
4737 cbD->secondaryCbs.append(t: extCb);
4738 cbD->inExternal = true;
4739 }
4740}
4741
4742void QRhiVulkan::endExternal(QRhiCommandBuffer *cb)
4743{
4744 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
4745
4746 if (cbD->recordingPass == QVkCommandBuffer::NoPass) {
4747 Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1);
4748 } else if (cbD->inExternal) {
4749 VkCommandBuffer extCb = cbD->secondaryCbs.last();
4750 cbD->secondaryCbs.removeLast();
4751 endAndEnqueueSecondaryCommandBuffer(cb: extCb, cbD);
4752 cbD->secondaryCbs.append(t: startSecondaryCommandBuffer(rtD: maybeRenderTargetData(cbD)));
4753 }
4754
4755 cbD->resetCachedState();
4756}
4757
4758void QRhiVulkan::setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot)
4759{
4760 if (!debugMarkers || !debugMarkersAvailable || name.isEmpty())
4761 return;
4762
4763 VkDebugMarkerObjectNameInfoEXT nameInfo;
4764 memset(s: &nameInfo, c: 0, n: sizeof(nameInfo));
4765 nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
4766 nameInfo.objectType = type;
4767 nameInfo.object = object;
4768 QByteArray decoratedName = name;
4769 if (slot >= 0) {
4770 decoratedName += '/';
4771 decoratedName += QByteArray::number(slot);
4772 }
4773 nameInfo.pObjectName = decoratedName.constData();
4774 vkDebugMarkerSetObjectName(dev, &nameInfo);
4775}
4776
4777static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage)
4778{
4779 int u = 0;
4780 if (usage.testFlag(flag: QRhiBuffer::VertexBuffer))
4781 u |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
4782 if (usage.testFlag(flag: QRhiBuffer::IndexBuffer))
4783 u |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
4784 if (usage.testFlag(flag: QRhiBuffer::UniformBuffer))
4785 u |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
4786 if (usage.testFlag(flag: QRhiBuffer::StorageBuffer))
4787 u |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
4788 return VkBufferUsageFlagBits(u);
4789}
4790
4791static inline VkFilter toVkFilter(QRhiSampler::Filter f)
4792{
4793 switch (f) {
4794 case QRhiSampler::Nearest:
4795 return VK_FILTER_NEAREST;
4796 case QRhiSampler::Linear:
4797 return VK_FILTER_LINEAR;
4798 default:
4799 Q_UNREACHABLE();
4800 return VK_FILTER_NEAREST;
4801 }
4802}
4803
4804static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f)
4805{
4806 switch (f) {
4807 case QRhiSampler::None:
4808 return VK_SAMPLER_MIPMAP_MODE_NEAREST;
4809 case QRhiSampler::Nearest:
4810 return VK_SAMPLER_MIPMAP_MODE_NEAREST;
4811 case QRhiSampler::Linear:
4812 return VK_SAMPLER_MIPMAP_MODE_LINEAR;
4813 default:
4814 Q_UNREACHABLE();
4815 return VK_SAMPLER_MIPMAP_MODE_NEAREST;
4816 }
4817}
4818
4819static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m)
4820{
4821 switch (m) {
4822 case QRhiSampler::Repeat:
4823 return VK_SAMPLER_ADDRESS_MODE_REPEAT;
4824 case QRhiSampler::ClampToEdge:
4825 return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
4826 case QRhiSampler::Mirror:
4827 return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
4828 default:
4829 Q_UNREACHABLE();
4830 return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
4831 }
4832}
4833
4834static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type)
4835{
4836 switch (type) {
4837 case QRhiShaderStage::Vertex:
4838 return VK_SHADER_STAGE_VERTEX_BIT;
4839 case QRhiShaderStage::Fragment:
4840 return VK_SHADER_STAGE_FRAGMENT_BIT;
4841 case QRhiShaderStage::Compute:
4842 return VK_SHADER_STAGE_COMPUTE_BIT;
4843 default:
4844 Q_UNREACHABLE();
4845 return VK_SHADER_STAGE_VERTEX_BIT;
4846 }
4847}
4848
4849static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format)
4850{
4851 switch (format) {
4852 case QRhiVertexInputAttribute::Float4:
4853 return VK_FORMAT_R32G32B32A32_SFLOAT;
4854 case QRhiVertexInputAttribute::Float3:
4855 return VK_FORMAT_R32G32B32_SFLOAT;
4856 case QRhiVertexInputAttribute::Float2:
4857 return VK_FORMAT_R32G32_SFLOAT;
4858 case QRhiVertexInputAttribute::Float:
4859 return VK_FORMAT_R32_SFLOAT;
4860 case QRhiVertexInputAttribute::UNormByte4:
4861 return VK_FORMAT_R8G8B8A8_UNORM;
4862 case QRhiVertexInputAttribute::UNormByte2:
4863 return VK_FORMAT_R8G8_UNORM;
4864 case QRhiVertexInputAttribute::UNormByte:
4865 return VK_FORMAT_R8_UNORM;
4866 default:
4867 Q_UNREACHABLE();
4868 return VK_FORMAT_R32G32B32A32_SFLOAT;
4869 }
4870}
4871
4872static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t)
4873{
4874 switch (t) {
4875 case QRhiGraphicsPipeline::Triangles:
4876 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
4877 case QRhiGraphicsPipeline::TriangleStrip:
4878 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
4879 case QRhiGraphicsPipeline::TriangleFan:
4880 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
4881 case QRhiGraphicsPipeline::Lines:
4882 return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
4883 case QRhiGraphicsPipeline::LineStrip:
4884 return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
4885 case QRhiGraphicsPipeline::Points:
4886 return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
4887 default:
4888 Q_UNREACHABLE();
4889 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
4890 }
4891}
4892
4893static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c)
4894{
4895 switch (c) {
4896 case QRhiGraphicsPipeline::None:
4897 return VK_CULL_MODE_NONE;
4898 case QRhiGraphicsPipeline::Front:
4899 return VK_CULL_MODE_FRONT_BIT;
4900 case QRhiGraphicsPipeline::Back:
4901 return VK_CULL_MODE_BACK_BIT;
4902 default:
4903 Q_UNREACHABLE();
4904 return VK_CULL_MODE_NONE;
4905 }
4906}
4907
4908static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f)
4909{
4910 switch (f) {
4911 case QRhiGraphicsPipeline::CCW:
4912 return VK_FRONT_FACE_COUNTER_CLOCKWISE;
4913 case QRhiGraphicsPipeline::CW:
4914 return VK_FRONT_FACE_CLOCKWISE;
4915 default:
4916 Q_UNREACHABLE();
4917 return VK_FRONT_FACE_COUNTER_CLOCKWISE;
4918 }
4919}
4920
4921static inline VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c)
4922{
4923 int f = 0;
4924 if (c.testFlag(flag: QRhiGraphicsPipeline::R))
4925 f |= VK_COLOR_COMPONENT_R_BIT;
4926 if (c.testFlag(flag: QRhiGraphicsPipeline::G))
4927 f |= VK_COLOR_COMPONENT_G_BIT;
4928 if (c.testFlag(flag: QRhiGraphicsPipeline::B))
4929 f |= VK_COLOR_COMPONENT_B_BIT;
4930 if (c.testFlag(flag: QRhiGraphicsPipeline::A))
4931 f |= VK_COLOR_COMPONENT_A_BIT;
4932 return VkColorComponentFlags(f);
4933}
4934
4935static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
4936{
4937 switch (f) {
4938 case QRhiGraphicsPipeline::Zero:
4939 return VK_BLEND_FACTOR_ZERO;
4940 case QRhiGraphicsPipeline::One:
4941 return VK_BLEND_FACTOR_ONE;
4942 case QRhiGraphicsPipeline::SrcColor:
4943 return VK_BLEND_FACTOR_SRC_COLOR;
4944 case QRhiGraphicsPipeline::OneMinusSrcColor:
4945 return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
4946 case QRhiGraphicsPipeline::DstColor:
4947 return VK_BLEND_FACTOR_DST_COLOR;
4948 case QRhiGraphicsPipeline::OneMinusDstColor:
4949 return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
4950 case QRhiGraphicsPipeline::SrcAlpha:
4951 return VK_BLEND_FACTOR_SRC_ALPHA;
4952 case QRhiGraphicsPipeline::OneMinusSrcAlpha:
4953 return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4954 case QRhiGraphicsPipeline::DstAlpha:
4955 return VK_BLEND_FACTOR_DST_ALPHA;
4956 case QRhiGraphicsPipeline::OneMinusDstAlpha:
4957 return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
4958 case QRhiGraphicsPipeline::ConstantColor:
4959 return VK_BLEND_FACTOR_CONSTANT_COLOR;
4960 case QRhiGraphicsPipeline::OneMinusConstantColor:
4961 return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
4962 case QRhiGraphicsPipeline::ConstantAlpha:
4963 return VK_BLEND_FACTOR_CONSTANT_ALPHA;
4964 case QRhiGraphicsPipeline::OneMinusConstantAlpha:
4965 return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
4966 case QRhiGraphicsPipeline::SrcAlphaSaturate:
4967 return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
4968 case QRhiGraphicsPipeline::Src1Color:
4969 return VK_BLEND_FACTOR_SRC1_COLOR;
4970 case QRhiGraphicsPipeline::OneMinusSrc1Color:
4971 return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
4972 case QRhiGraphicsPipeline::Src1Alpha:
4973 return VK_BLEND_FACTOR_SRC1_ALPHA;
4974 case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
4975 return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
4976 default:
4977 Q_UNREACHABLE();
4978 return VK_BLEND_FACTOR_ZERO;
4979 }
4980}
4981
4982static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op)
4983{
4984 switch (op) {
4985 case QRhiGraphicsPipeline::Add:
4986 return VK_BLEND_OP_ADD;
4987 case QRhiGraphicsPipeline::Subtract:
4988 return VK_BLEND_OP_SUBTRACT;
4989 case QRhiGraphicsPipeline::ReverseSubtract:
4990 return VK_BLEND_OP_REVERSE_SUBTRACT;
4991 case QRhiGraphicsPipeline::Min:
4992 return VK_BLEND_OP_MIN;
4993 case QRhiGraphicsPipeline::Max:
4994 return VK_BLEND_OP_MAX;
4995 default:
4996 Q_UNREACHABLE();
4997 return VK_BLEND_OP_ADD;
4998 }
4999}
5000
5001static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op)
5002{
5003 switch (op) {
5004 case QRhiGraphicsPipeline::Never:
5005 return VK_COMPARE_OP_NEVER;
5006 case QRhiGraphicsPipeline::Less:
5007 return VK_COMPARE_OP_LESS;
5008 case QRhiGraphicsPipeline::Equal:
5009 return VK_COMPARE_OP_EQUAL;
5010 case QRhiGraphicsPipeline::LessOrEqual:
5011 return VK_COMPARE_OP_LESS_OR_EQUAL;
5012 case QRhiGraphicsPipeline::Greater:
5013 return VK_COMPARE_OP_GREATER;
5014 case QRhiGraphicsPipeline::NotEqual:
5015 return VK_COMPARE_OP_NOT_EQUAL;
5016 case QRhiGraphicsPipeline::GreaterOrEqual:
5017 return VK_COMPARE_OP_GREATER_OR_EQUAL;
5018 case QRhiGraphicsPipeline::Always:
5019 return VK_COMPARE_OP_ALWAYS;
5020 default:
5021 Q_UNREACHABLE();
5022 return VK_COMPARE_OP_ALWAYS;
5023 }
5024}
5025
5026static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op)
5027{
5028 switch (op) {
5029 case QRhiGraphicsPipeline::StencilZero:
5030 return VK_STENCIL_OP_ZERO;
5031 case QRhiGraphicsPipeline::Keep:
5032 return VK_STENCIL_OP_KEEP;
5033 case QRhiGraphicsPipeline::Replace:
5034 return VK_STENCIL_OP_REPLACE;
5035 case QRhiGraphicsPipeline::IncrementAndClamp:
5036 return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
5037 case QRhiGraphicsPipeline::DecrementAndClamp:
5038 return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
5039 case QRhiGraphicsPipeline::Invert:
5040 return VK_STENCIL_OP_INVERT;
5041 case QRhiGraphicsPipeline::IncrementAndWrap:
5042 return VK_STENCIL_OP_INCREMENT_AND_WRAP;
5043 case QRhiGraphicsPipeline::DecrementAndWrap:
5044 return VK_STENCIL_OP_DECREMENT_AND_WRAP;
5045 default:
5046 Q_UNREACHABLE();
5047 return VK_STENCIL_OP_KEEP;
5048 }
5049}
5050
5051static inline void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src)
5052{
5053 dst->failOp = toVkStencilOp(op: src.failOp);
5054 dst->passOp = toVkStencilOp(op: src.passOp);
5055 dst->depthFailOp = toVkStencilOp(op: src.depthFailOp);
5056 dst->compareOp = toVkCompareOp(op: src.compareOp);
5057}
5058
5059static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBinding::Data *b)
5060{
5061 switch (b->type) {
5062 case QRhiShaderResourceBinding::UniformBuffer:
5063 return b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
5064 : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
5065
5066 case QRhiShaderResourceBinding::SampledTexture:
5067 return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
5068
5069 case QRhiShaderResourceBinding::ImageLoad:
5070 case QRhiShaderResourceBinding::ImageStore:
5071 case QRhiShaderResourceBinding::ImageLoadStore:
5072 return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
5073
5074 case QRhiShaderResourceBinding::BufferLoad:
5075 case QRhiShaderResourceBinding::BufferStore:
5076 case QRhiShaderResourceBinding::BufferLoadStore:
5077 return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
5078
5079 default:
5080 Q_UNREACHABLE();
5081 return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
5082 }
5083}
5084
5085static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage)
5086{
5087 int s = 0;
5088 if (stage.testFlag(flag: QRhiShaderResourceBinding::VertexStage))
5089 s |= VK_SHADER_STAGE_VERTEX_BIT;
5090 if (stage.testFlag(flag: QRhiShaderResourceBinding::FragmentStage))
5091 s |= VK_SHADER_STAGE_FRAGMENT_BIT;
5092 if (stage.testFlag(flag: QRhiShaderResourceBinding::ComputeStage))
5093 s |= VK_SHADER_STAGE_COMPUTE_BIT;
5094 return VkShaderStageFlags(s);
5095}
5096
5097static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op)
5098{
5099 switch (op) {
5100 case QRhiSampler::Never:
5101 return VK_COMPARE_OP_NEVER;
5102 case QRhiSampler::Less:
5103 return VK_COMPARE_OP_LESS;
5104 case QRhiSampler::Equal:
5105 return VK_COMPARE_OP_EQUAL;
5106 case QRhiSampler::LessOrEqual:
5107 return VK_COMPARE_OP_LESS_OR_EQUAL;
5108 case QRhiSampler::Greater:
5109 return VK_COMPARE_OP_GREATER;
5110 case QRhiSampler::NotEqual:
5111 return VK_COMPARE_OP_NOT_EQUAL;
5112 case QRhiSampler::GreaterOrEqual:
5113 return VK_COMPARE_OP_GREATER_OR_EQUAL;
5114 case QRhiSampler::Always:
5115 return VK_COMPARE_OP_ALWAYS;
5116 default:
5117 Q_UNREACHABLE();
5118 return VK_COMPARE_OP_NEVER;
5119 }
5120}
5121
5122QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
5123 : QRhiBuffer(rhi, type, usage, size)
5124{
5125 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
5126 buffers[i] = stagingBuffers[i] = VK_NULL_HANDLE;
5127 allocations[i] = stagingAllocations[i] = nullptr;
5128 }
5129}
5130
5131QVkBuffer::~QVkBuffer()
5132{
5133 release();
5134}
5135
5136void QVkBuffer::release()
5137{
5138 if (!buffers[0])
5139 return;
5140
5141 QRhiVulkan::DeferredReleaseEntry e;
5142 e.type = QRhiVulkan::DeferredReleaseEntry::Buffer;
5143 e.lastActiveFrameSlot = lastActiveFrameSlot;
5144
5145 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
5146 e.buffer.buffers[i] = buffers[i];
5147 e.buffer.allocations[i] = allocations[i];
5148 e.buffer.stagingBuffers[i] = stagingBuffers[i];
5149 e.buffer.stagingAllocations[i] = stagingAllocations[i];
5150
5151 buffers[i] = VK_NULL_HANDLE;
5152 allocations[i] = nullptr;
5153 stagingBuffers[i] = VK_NULL_HANDLE;
5154 stagingAllocations[i] = nullptr;
5155 pendingDynamicUpdates[i].clear();
5156 }
5157
5158 QRHI_RES_RHI(QRhiVulkan);
5159 rhiD->releaseQueue.append(t: e);
5160
5161 QRHI_PROF;
5162 QRHI_PROF_F(releaseBuffer(this));
5163
5164 rhiD->unregisterResource(res: this);
5165}
5166
5167bool QVkBuffer::build()
5168{
5169 if (buffers[0])
5170 release();
5171
5172 if (m_usage.testFlag(flag: QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
5173 qWarning(msg: "StorageBuffer cannot be combined with Dynamic");
5174 return false;
5175 }
5176
5177 const int nonZeroSize = m_size <= 0 ? 256 : m_size;
5178
5179 VkBufferCreateInfo bufferInfo;
5180 memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo));
5181 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
5182 bufferInfo.size = uint32_t(nonZeroSize);
5183 bufferInfo.usage = toVkBufferUsage(usage: m_usage);
5184
5185 VmaAllocationCreateInfo allocInfo;
5186 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
5187
5188 if (m_type == Dynamic) {
5189#ifndef Q_OS_DARWIN // not for MoltenVK
5190 // Keep mapped all the time. Essential f.ex. with some mobile GPUs,
5191 // where mapping and unmapping an entire allocation every time updating
5192 // a suballocated buffer presents a significant perf. hit.
5193 allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
5194#endif
5195 // host visible, frequent changes
5196 allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
5197 } else {
5198 allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
5199 bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
5200 }
5201
5202 QRHI_RES_RHI(QRhiVulkan);
5203 VkResult err = VK_SUCCESS;
5204 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
5205 buffers[i] = VK_NULL_HANDLE;
5206 allocations[i] = nullptr;
5207 usageState[i].access = usageState[i].stage = 0;
5208 if (i == 0 || m_type == Dynamic) {
5209 VmaAllocation allocation;
5210 err = vmaCreateBuffer(allocator: toVmaAllocator(a: rhiD->allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &buffers[i], pAllocation: &allocation, pAllocationInfo: nullptr);
5211 if (err != VK_SUCCESS)
5212 break;
5213 allocations[i] = allocation;
5214 rhiD->setObjectName(object: uint64_t(buffers[i]), type: VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, name: m_objectName,
5215 slot: m_type == Dynamic ? i : -1);
5216 }
5217 }
5218
5219 if (err != VK_SUCCESS) {
5220 qWarning(msg: "Failed to create buffer: %d", err);
5221 return false;
5222 }
5223
5224 QRHI_PROF;
5225 QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), m_type != Dynamic ? 1 : QVK_FRAMES_IN_FLIGHT, 0));
5226
5227 lastActiveFrameSlot = -1;
5228 generation += 1;
5229 rhiD->registerResource(res: this);
5230 return true;
5231}
5232
5233QRhiBuffer::NativeBuffer QVkBuffer::nativeBuffer()
5234{
5235 if (m_type == Dynamic) {
5236 QRHI_RES_RHI(QRhiVulkan);
5237 NativeBuffer b;
5238 Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QVK_FRAMES_IN_FLIGHT));
5239 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
5240 rhiD->executeBufferHostWritesForSlot(bufD: this, slot: i);
5241 b.objects[i] = &buffers[i];
5242 }
5243 b.slotCount = QVK_FRAMES_IN_FLIGHT;
5244 return b;
5245 }
5246 return { .objects: { &buffers[0] }, .slotCount: 1 };
5247}
5248
5249QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
5250 int sampleCount, Flags flags)
5251 : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
5252{
5253}
5254
5255QVkRenderBuffer::~QVkRenderBuffer()
5256{
5257 release();
5258 delete backingTexture;
5259}
5260
5261void QVkRenderBuffer::release()
5262{
5263 if (!memory && !backingTexture)
5264 return;
5265
5266 QRhiVulkan::DeferredReleaseEntry e;
5267 e.type = QRhiVulkan::DeferredReleaseEntry::RenderBuffer;
5268 e.lastActiveFrameSlot = lastActiveFrameSlot;
5269
5270 e.renderBuffer.memory = memory;
5271 e.renderBuffer.image = image;
5272 e.renderBuffer.imageView = imageView;
5273
5274 memory = VK_NULL_HANDLE;
5275 image = VK_NULL_HANDLE;
5276 imageView = VK_NULL_HANDLE;
5277
5278 if (backingTexture) {
5279 Q_ASSERT(backingTexture->lastActiveFrameSlot == -1);
5280 backingTexture->lastActiveFrameSlot = e.lastActiveFrameSlot;
5281 backingTexture->release();
5282 }
5283
5284 QRHI_RES_RHI(QRhiVulkan);
5285 rhiD->releaseQueue.append(t: e);
5286
5287 QRHI_PROF;
5288 QRHI_PROF_F(releaseRenderBuffer(this));
5289
5290 rhiD->unregisterResource(res: this);
5291}
5292
5293bool QVkRenderBuffer::build()
5294{
5295 if (memory || backingTexture)
5296 release();
5297
5298 if (m_pixelSize.isEmpty())
5299 return false;
5300
5301 QRHI_RES_RHI(QRhiVulkan);
5302 QRHI_PROF;
5303 samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount);
5304
5305 switch (m_type) {
5306 case QRhiRenderBuffer::Color:
5307 {
5308 if (!backingTexture) {
5309 backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(QRhiTexture::RGBA8,
5310 m_pixelSize,
5311 m_sampleCount,
5312 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
5313 } else {
5314 backingTexture->setPixelSize(m_pixelSize);
5315 backingTexture->setSampleCount(m_sampleCount);
5316 }
5317 backingTexture->setName(m_objectName);
5318 if (!backingTexture->build())
5319 return false;
5320 vkformat = backingTexture->vkformat;
5321 QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
5322 }
5323 break;
5324 case QRhiRenderBuffer::DepthStencil:
5325 vkformat = rhiD->optimalDepthStencilFormat();
5326 if (!rhiD->createTransientImage(format: vkformat,
5327 pixelSize: m_pixelSize,
5328 usage: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
5329 aspectMask: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
5330 samples,
5331 mem: &memory,
5332 images: &image,
5333 views: &imageView,
5334 count: 1))
5335 {
5336 return false;
5337 }
5338 rhiD->setObjectName(object: uint64_t(image), type: VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name: m_objectName);
5339 QRHI_PROF_F(newRenderBuffer(this, true, false, samples));
5340 break;
5341 default:
5342 Q_UNREACHABLE();
5343 break;
5344 }
5345
5346 lastActiveFrameSlot = -1;
5347 rhiD->registerResource(res: this);
5348 return true;
5349}
5350
5351QRhiTexture::Format QVkRenderBuffer::backingFormat() const
5352{
5353 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
5354}
5355
5356QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
5357 int sampleCount, Flags flags)
5358 : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
5359{
5360 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
5361 stagingBuffers[i] = VK_NULL_HANDLE;
5362 stagingAllocations[i] = nullptr;
5363 }
5364 for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
5365 perLevelImageViews[i] = VK_NULL_HANDLE;
5366}
5367
5368QVkTexture::~QVkTexture()
5369{
5370 release();
5371}
5372
5373void QVkTexture::release()
5374{
5375 if (!image)
5376 return;
5377
5378 QRhiVulkan::DeferredReleaseEntry e;
5379 e.type = QRhiVulkan::DeferredReleaseEntry::Texture;
5380 e.lastActiveFrameSlot = lastActiveFrameSlot;
5381
5382 e.texture.image = owns ? image : VK_NULL_HANDLE;
5383 e.texture.imageView = imageView;
5384 e.texture.allocation = owns ? imageAlloc : nullptr;
5385
5386 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
5387 e.texture.stagingBuffers[i] = stagingBuffers[i];
5388 e.texture.stagingAllocations[i] = stagingAllocations[i];
5389
5390 stagingBuffers[i] = VK_NULL_HANDLE;
5391 stagingAllocations[i] = nullptr;
5392 }
5393
5394 for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
5395 e.texture.extraImageViews[i] = perLevelImageViews[i];
5396 perLevelImageViews[i] = VK_NULL_HANDLE;
5397 }
5398
5399 image = VK_NULL_HANDLE;
5400 imageView = VK_NULL_HANDLE;
5401 imageAlloc = nullptr;
5402
5403 QRHI_RES_RHI(QRhiVulkan);
5404 rhiD->releaseQueue.append(t: e);
5405
5406 QRHI_PROF;
5407 QRHI_PROF_F(releaseTexture(this));
5408
5409 rhiD->unregisterResource(res: this);
5410}
5411
5412bool QVkTexture::prepareBuild(QSize *adjustedSize)
5413{
5414 if (image)
5415 release();
5416
5417 QRHI_RES_RHI(QRhiVulkan);
5418 vkformat = toVkTextureFormat(format: m_format, flags: m_flags);
5419 VkFormatProperties props;
5420 rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props);
5421 const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
5422 if (!canSampleOptimal) {
5423 qWarning(msg: "Texture sampling with optimal tiling for format %d not supported", vkformat);
5424 return false;
5425 }
5426
5427 const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
5428 const bool isCube = m_flags.testFlag(flag: CubeMap);
5429 const bool hasMipMaps = m_flags.testFlag(flag: MipMapped);
5430
5431 mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
5432 const int maxLevels = QRhi::MAX_LEVELS;
5433 if (mipLevelCount > maxLevels) {
5434 qWarning(msg: "Too many mip levels (%d, max is %d), truncating mip chain", mipLevelCount, maxLevels);
5435 mipLevelCount = maxLevels;
5436 }
5437 samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount);
5438 if (samples > VK_SAMPLE_COUNT_1_BIT) {
5439 if (isCube) {
5440 qWarning(msg: "Cubemap texture cannot be multisample");
5441 return false;
5442 }
5443 if (hasMipMaps) {
5444 qWarning(msg: "Multisample texture cannot have mipmaps");
5445 return false;
5446 }
5447 }
5448
5449 usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
5450 usageState.access = 0;
5451 usageState.stage = 0;
5452
5453 if (adjustedSize)
5454 *adjustedSize = size;
5455
5456 return true;
5457}
5458
5459bool QVkTexture::finishBuild()
5460{
5461 QRHI_RES_RHI(QRhiVulkan);
5462
5463 const bool isDepth = isDepthTextureFormat(format: m_format);
5464 const bool isCube = m_flags.testFlag(flag: CubeMap);
5465
5466 VkImageViewCreateInfo viewInfo;
5467 memset(s: &viewInfo, c: 0, n: sizeof(viewInfo));
5468 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
5469 viewInfo.image = image;
5470 viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D;
5471 viewInfo.format = vkformat;
5472 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
5473 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
5474 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
5475 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
5476 viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
5477 viewInfo.subresourceRange.levelCount = mipLevelCount;
5478 viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
5479
5480 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView);
5481 if (err != VK_SUCCESS) {
5482 qWarning(msg: "Failed to create image view: %d", err);
5483 return false;
5484 }
5485
5486 lastActiveFrameSlot = -1;
5487 generation += 1;
5488
5489 return true;
5490}
5491
5492bool QVkTexture::build()
5493{
5494 QSize size;
5495 if (!prepareBuild(adjustedSize: &size))
5496 return false;
5497
5498 const bool isRenderTarget = m_flags.testFlag(flag: QRhiTexture::RenderTarget);
5499 const bool isDepth = isDepthTextureFormat(format: m_format);
5500 const bool isCube = m_flags.testFlag(flag: CubeMap);
5501
5502 VkImageCreateInfo imageInfo;
5503 memset(s: &imageInfo, c: 0, n: sizeof(imageInfo));
5504 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
5505 imageInfo.flags = isCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0;
5506 imageInfo.imageType = VK_IMAGE_TYPE_2D;
5507 imageInfo.format = vkformat;
5508 imageInfo.extent.width = uint32_t(size.width());
5509 imageInfo.extent.height = uint32_t(size.height());
5510 imageInfo.extent.depth = 1;
5511 imageInfo.mipLevels = mipLevelCount;
5512 imageInfo.arrayLayers = isCube ? 6 : 1;
5513 imageInfo.samples = samples;
5514 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
5515 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
5516
5517 imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
5518 if (isRenderTarget) {
5519 if (isDepth)
5520 imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
5521 else
5522 imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
5523 }
5524 if (m_flags.testFlag(flag: QRhiTexture::UsedAsTransferSource))
5525 imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
5526 if (m_flags.testFlag(flag: QRhiTexture::UsedWithGenerateMips))
5527 imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
5528 if (m_flags.testFlag(flag: QRhiTexture::UsedWithLoadStore))
5529 imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
5530
5531 VmaAllocationCreateInfo allocInfo;
5532 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
5533 allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
5534
5535 QRHI_RES_RHI(QRhiVulkan);
5536 VmaAllocation allocation;
5537 VkResult err = vmaCreateImage(allocator: toVmaAllocator(a: rhiD->allocator), pImageCreateInfo: &imageInfo, pAllocationCreateInfo: &allocInfo, pImage: &image, pAllocation: &allocation, pAllocationInfo: nullptr);
5538 if (err != VK_SUCCESS) {
5539 qWarning(msg: "Failed to create image: %d", err);
5540 return false;
5541 }
5542 imageAlloc = allocation;
5543
5544 if (!finishBuild())
5545 return false;
5546
5547 rhiD->setObjectName(object: uint64_t(image), type: VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name: m_objectName);
5548
5549 QRHI_PROF;
5550 QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : 1, samples));
5551
5552 owns = true;
5553 rhiD->registerResource(res: this);
5554 return true;
5555}
5556
5557bool QVkTexture::buildFrom(QRhiTexture::NativeTexture src)
5558{
5559 auto *img = static_cast<const VkImage*>(src.object);
5560 if (!img || !*img)
5561 return false;
5562
5563 if (!prepareBuild())
5564 return false;
5565
5566 image = *img;
5567
5568 if (!finishBuild())
5569 return false;
5570
5571 QRHI_PROF;
5572 QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), m_flags.testFlag(CubeMap) ? 6 : 1, samples));
5573
5574 usageState.layout = VkImageLayout(src.layout);
5575
5576 owns = false;
5577 QRHI_RES_RHI(QRhiVulkan);
5578 rhiD->registerResource(res: this);
5579 return true;
5580}
5581
5582QRhiTexture::NativeTexture QVkTexture::nativeTexture()
5583{
5584 return {.object: &image, .layout: usageState.layout};
5585}
5586
5587void QVkTexture::setNativeLayout(int layout)
5588{
5589 usageState.layout = VkImageLayout(layout);
5590}
5591
5592VkImageView QVkTexture::imageViewForLevel(int level)
5593{
5594 Q_ASSERT(level >= 0 && level < int(mipLevelCount));
5595 if (perLevelImageViews[level] != VK_NULL_HANDLE)
5596 return perLevelImageViews[level];
5597
5598 const bool isDepth = isDepthTextureFormat(format: m_format);
5599 const bool isCube = m_flags.testFlag(flag: CubeMap);
5600
5601 VkImageViewCreateInfo viewInfo;
5602 memset(s: &viewInfo, c: 0, n: sizeof(viewInfo));
5603 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
5604 viewInfo.image = image;
5605 viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D;
5606 viewInfo.format = vkformat;
5607 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
5608 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
5609 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
5610 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
5611 viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
5612 viewInfo.subresourceRange.baseMipLevel = uint32_t(level);
5613 viewInfo.subresourceRange.levelCount = 1;
5614 viewInfo.subresourceRange.baseArrayLayer = 0;
5615 viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
5616
5617 VkImageView v = VK_NULL_HANDLE;
5618 QRHI_RES_RHI(QRhiVulkan);
5619 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &v);
5620 if (err != VK_SUCCESS) {
5621 qWarning(msg: "Failed to create image view: %d", err);
5622 return VK_NULL_HANDLE;
5623 }
5624
5625 perLevelImageViews[level] = v;
5626 return v;
5627}
5628
5629QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
5630 AddressMode u, AddressMode v, AddressMode w)
5631 : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w)
5632{
5633}
5634
5635QVkSampler::~QVkSampler()
5636{
5637 release();
5638}
5639
5640void QVkSampler::release()
5641{
5642 if (!sampler)
5643 return;
5644
5645 QRhiVulkan::DeferredReleaseEntry e;
5646 e.type = QRhiVulkan::DeferredReleaseEntry::Sampler;
5647 e.lastActiveFrameSlot = lastActiveFrameSlot;
5648
5649 e.sampler.sampler = sampler;
5650 sampler = VK_NULL_HANDLE;
5651
5652 QRHI_RES_RHI(QRhiVulkan);
5653 rhiD->releaseQueue.append(t: e);
5654 rhiD->unregisterResource(res: this);
5655}
5656
5657bool QVkSampler::build()
5658{
5659 if (sampler)
5660 release();
5661
5662 VkSamplerCreateInfo samplerInfo;
5663 memset(s: &samplerInfo, c: 0, n: sizeof(samplerInfo));
5664 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
5665 samplerInfo.magFilter = toVkFilter(f: m_magFilter);
5666 samplerInfo.minFilter = toVkFilter(f: m_minFilter);
5667 samplerInfo.mipmapMode = toVkMipmapMode(f: m_mipmapMode);
5668 samplerInfo.addressModeU = toVkAddressMode(m: m_addressU);
5669 samplerInfo.addressModeV = toVkAddressMode(m: m_addressV);
5670 samplerInfo.addressModeW = toVkAddressMode(m: m_addressW);
5671 samplerInfo.maxAnisotropy = 1.0f;
5672 samplerInfo.compareEnable = m_compareOp != Never;
5673 samplerInfo.compareOp = toVkTextureCompareOp(op: m_compareOp);
5674 samplerInfo.maxLod = m_mipmapMode == None ? 0.25f : 1000.0f;
5675
5676 QRHI_RES_RHI(QRhiVulkan);
5677 VkResult err = rhiD->df->vkCreateSampler(rhiD->dev, &samplerInfo, nullptr, &sampler);
5678 if (err != VK_SUCCESS) {
5679 qWarning(msg: "Failed to create sampler: %d", err);
5680 return false;
5681 }
5682
5683 lastActiveFrameSlot = -1;
5684 generation += 1;
5685 rhiD->registerResource(res: this);
5686 return true;
5687}
5688
5689QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi)
5690 : QRhiRenderPassDescriptor(rhi)
5691{
5692}
5693
5694QVkRenderPassDescriptor::~QVkRenderPassDescriptor()
5695{
5696 release();
5697}
5698
5699void QVkRenderPassDescriptor::release()
5700{
5701 if (!rp)
5702 return;
5703
5704 if (!ownsRp) {
5705 rp = VK_NULL_HANDLE;
5706 return;
5707 }
5708
5709 QRhiVulkan::DeferredReleaseEntry e;
5710 e.type = QRhiVulkan::DeferredReleaseEntry::RenderPass;
5711 e.lastActiveFrameSlot = lastActiveFrameSlot;
5712
5713 e.renderPass.rp = rp;
5714
5715 rp = VK_NULL_HANDLE;
5716
5717 QRHI_RES_RHI(QRhiVulkan);
5718 rhiD->releaseQueue.append(t: e);
5719
5720 rhiD->unregisterResource(res: this);
5721}
5722
5723static inline bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b)
5724{
5725 return a.format == b.format
5726 && a.samples == b.samples
5727 && a.loadOp == b.loadOp
5728 && a.storeOp == b.storeOp
5729 && a.stencilLoadOp == b.stencilLoadOp
5730 && a.stencilStoreOp == b.stencilStoreOp
5731 && a.initialLayout == b.initialLayout
5732 && a.finalLayout == b.finalLayout;
5733}
5734
5735bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
5736{
5737 if (!other)
5738 return false;
5739
5740 const QVkRenderPassDescriptor *o = QRHI_RES(const QVkRenderPassDescriptor, other);
5741
5742 if (attDescs.count() != o->attDescs.count())
5743 return false;
5744 if (colorRefs.count() != o->colorRefs.count())
5745 return false;
5746 if (resolveRefs.count() != o->resolveRefs.count())
5747 return false;
5748 if (hasDepthStencil != o->hasDepthStencil)
5749 return false;
5750
5751 for (int i = 0, ie = colorRefs.count(); i != ie; ++i) {
5752 const uint32_t attIdx = colorRefs[i].attachment;
5753 if (attIdx != o->colorRefs[i].attachment)
5754 return false;
5755 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx]))
5756 return false;
5757 }
5758
5759 if (hasDepthStencil) {
5760 const uint32_t attIdx = dsRef.attachment;
5761 if (attIdx != o->dsRef.attachment)
5762 return false;
5763 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx]))
5764 return false;
5765 }
5766
5767 for (int i = 0, ie = resolveRefs.count(); i != ie; ++i) {
5768 const uint32_t attIdx = resolveRefs[i].attachment;
5769 if (attIdx != o->resolveRefs[i].attachment)
5770 return false;
5771 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx]))
5772 return false;
5773 }
5774
5775 return true;
5776}
5777
5778const QRhiNativeHandles *QVkRenderPassDescriptor::nativeHandles()
5779{
5780 nativeHandlesStruct.renderPass = rp;
5781 return &nativeHandlesStruct;
5782}
5783
5784QVkReferenceRenderTarget::QVkReferenceRenderTarget(QRhiImplementation *rhi)
5785 : QRhiRenderTarget(rhi)
5786{
5787}
5788
5789QVkReferenceRenderTarget::~QVkReferenceRenderTarget()
5790{
5791 release();
5792}
5793
5794void QVkReferenceRenderTarget::release()
5795{
5796 // nothing to do here
5797}
5798
5799QSize QVkReferenceRenderTarget::pixelSize() const
5800{
5801 return d.pixelSize;
5802}
5803
5804float QVkReferenceRenderTarget::devicePixelRatio() const
5805{
5806 return d.dpr;
5807}
5808
5809int QVkReferenceRenderTarget::sampleCount() const
5810{
5811 return d.sampleCount;
5812}
5813
5814QVkTextureRenderTarget::QVkTextureRenderTarget(QRhiImplementation *rhi,
5815 const QRhiTextureRenderTargetDescription &desc,
5816 Flags flags)
5817 : QRhiTextureRenderTarget(rhi, desc, flags)
5818{
5819 for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
5820 rtv[att] = VK_NULL_HANDLE;
5821 resrtv[att] = VK_NULL_HANDLE;
5822 }
5823}
5824
5825QVkTextureRenderTarget::~QVkTextureRenderTarget()
5826{
5827 release();
5828}
5829
5830void QVkTextureRenderTarget::release()
5831{
5832 if (!d.fb)
5833 return;
5834
5835 QRhiVulkan::DeferredReleaseEntry e;
5836 e.type = QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget;
5837 e.lastActiveFrameSlot = lastActiveFrameSlot;
5838
5839 e.textureRenderTarget.fb = d.fb;
5840 d.fb = VK_NULL_HANDLE;
5841
5842 for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
5843 e.textureRenderTarget.rtv[att] = rtv[att];
5844 e.textureRenderTarget.resrtv[att] = resrtv[att];
5845 rtv[att] = VK_NULL_HANDLE;
5846 resrtv[att] = VK_NULL_HANDLE;
5847 }
5848
5849 QRHI_RES_RHI(QRhiVulkan);
5850 rhiD->releaseQueue.append(t: e);
5851
5852 rhiD->unregisterResource(res: this);
5853}
5854
5855QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescriptor()
5856{
5857 // not yet built so cannot rely on data computed in build()
5858
5859 QRHI_RES_RHI(QRhiVulkan);
5860 QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
5861 if (!rhiD->createOffscreenRenderPass(rpD: rp,
5862 firstColorAttachment: m_desc.cbeginColorAttachments(),
5863 lastColorAttachment: m_desc.cendColorAttachments(),
5864 preserveColor: m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveColorContents),
5865 preserveDs: m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveDepthStencilContents),
5866 depthStencilBuffer: m_desc.depthStencilBuffer(),
5867 depthTexture: m_desc.depthTexture()))
5868 {
5869 delete rp;
5870 return nullptr;
5871 }
5872
5873 rp->ownsRp = true;
5874 rhiD->registerResource(res: rp);
5875 return rp;
5876}
5877
5878bool QVkTextureRenderTarget::build()
5879{
5880 if (d.fb)
5881 release();
5882
5883 const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments();
5884 Q_ASSERT(hasColorAttachments || m_desc.depthTexture());
5885 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
5886 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
5887
5888 QRHI_RES_RHI(QRhiVulkan);
5889 QVarLengthArray<VkImageView, 8> views;
5890
5891 d.colorAttCount = 0;
5892 int attIndex = 0;
5893 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
5894 d.colorAttCount += 1;
5895 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
5896 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
5897 Q_ASSERT(texD || rbD);
5898 if (texD) {
5899 Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget));
5900 VkImageViewCreateInfo viewInfo;
5901 memset(s: &viewInfo, c: 0, n: sizeof(viewInfo));
5902 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
5903 viewInfo.image = texD->image;
5904 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
5905 viewInfo.format = texD->vkformat;
5906 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
5907 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
5908 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
5909 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
5910 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
5911 viewInfo.subresourceRange.baseMipLevel = uint32_t(it->level());
5912 viewInfo.subresourceRange.levelCount = 1;
5913 viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->layer());
5914 viewInfo.subresourceRange.layerCount = 1;
5915 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[attIndex]);
5916 if (err != VK_SUCCESS) {
5917 qWarning(msg: "Failed to create render target image view: %d", err);
5918 return false;
5919 }
5920 views.append(t: rtv[attIndex]);
5921 if (attIndex == 0) {
5922 d.pixelSize = texD->pixelSize();
5923 d.sampleCount = texD->samples;
5924 }
5925 } else if (rbD) {
5926 Q_ASSERT(rbD->backingTexture);
5927 views.append(t: rbD->backingTexture->imageView);
5928 if (attIndex == 0) {
5929 d.pixelSize = rbD->pixelSize();
5930 d.sampleCount = rbD->samples;
5931 }
5932 }
5933 }
5934 d.dpr = 1;
5935
5936 if (hasDepthStencil) {
5937 if (m_desc.depthTexture()) {
5938 QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture());
5939 views.append(t: depthTexD->imageView);
5940 if (d.colorAttCount == 0) {
5941 d.pixelSize = depthTexD->pixelSize();
5942 d.sampleCount = depthTexD->samples;
5943 }
5944 } else {
5945 QVkRenderBuffer *depthRbD = QRHI_RES(QVkRenderBuffer, m_desc.depthStencilBuffer());
5946 views.append(t: depthRbD->imageView);
5947 if (d.colorAttCount == 0) {
5948 d.pixelSize = depthRbD->pixelSize();
5949 d.sampleCount = depthRbD->samples;
5950 }
5951 }
5952 d.dsAttCount = 1;
5953 } else {
5954 d.dsAttCount = 0;
5955 }
5956
5957 d.resolveAttCount = 0;
5958 attIndex = 0;
5959 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
5960 if (it->resolveTexture()) {
5961 QVkTexture *resTexD = QRHI_RES(QVkTexture, it->resolveTexture());
5962 Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
5963 d.resolveAttCount += 1;
5964
5965 VkImageViewCreateInfo viewInfo;
5966 memset(s: &viewInfo, c: 0, n: sizeof(viewInfo));
5967 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
5968 viewInfo.image = resTexD->image;
5969 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
5970 viewInfo.format = resTexD->vkformat;
5971 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
5972 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
5973 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
5974 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
5975 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
5976 viewInfo.subresourceRange.baseMipLevel = uint32_t(it->resolveLevel());
5977 viewInfo.subresourceRange.levelCount = 1;
5978 viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->resolveLayer());
5979 viewInfo.subresourceRange.layerCount = 1;
5980 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[attIndex]);
5981 if (err != VK_SUCCESS) {
5982 qWarning(msg: "Failed to create render target resolve image view: %d", err);
5983 return false;
5984 }
5985 views.append(t: resrtv[attIndex]);
5986 }
5987 }
5988
5989 if (!m_renderPassDesc)
5990 qWarning(msg: "QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
5991
5992 d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
5993 Q_ASSERT(d.rp && d.rp->rp);
5994
5995 VkFramebufferCreateInfo fbInfo;
5996 memset(s: &fbInfo, c: 0, n: sizeof(fbInfo));
5997 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
5998 fbInfo.renderPass = d.rp->rp;
5999 fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount);
6000 fbInfo.pAttachments = views.constData();
6001 fbInfo.width = uint32_t(d.pixelSize.width());
6002 fbInfo.height = uint32_t(d.pixelSize.height());
6003 fbInfo.layers = 1;
6004
6005 VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &d.fb);
6006 if (err != VK_SUCCESS) {
6007 qWarning(msg: "Failed to create framebuffer: %d", err);
6008 return false;
6009 }
6010
6011 lastActiveFrameSlot = -1;
6012 rhiD->registerResource(res: this);
6013 return true;
6014}
6015
6016QSize QVkTextureRenderTarget::pixelSize() const
6017{
6018 return d.pixelSize;
6019}
6020
6021float QVkTextureRenderTarget::devicePixelRatio() const
6022{
6023 return d.dpr;
6024}
6025
6026int QVkTextureRenderTarget::sampleCount() const
6027{
6028 return d.sampleCount;
6029}
6030
6031QVkShaderResourceBindings::QVkShaderResourceBindings(QRhiImplementation *rhi)
6032 : QRhiShaderResourceBindings(rhi)
6033{
6034}
6035
6036QVkShaderResourceBindings::~QVkShaderResourceBindings()
6037{
6038 release();
6039}
6040
6041void QVkShaderResourceBindings::release()
6042{
6043 if (!layout)
6044 return;
6045
6046 sortedBindings.clear();
6047
6048 QRhiVulkan::DeferredReleaseEntry e;
6049 e.type = QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings;
6050 e.lastActiveFrameSlot = lastActiveFrameSlot;
6051
6052 e.shaderResourceBindings.poolIndex = poolIndex;
6053 e.shaderResourceBindings.layout = layout;
6054
6055 poolIndex = -1;
6056 layout = VK_NULL_HANDLE;
6057 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
6058 descSets[i] = VK_NULL_HANDLE;
6059
6060 QRHI_RES_RHI(QRhiVulkan);
6061 rhiD->releaseQueue.append(t: e);
6062
6063 rhiD->unregisterResource(res: this);
6064}
6065
6066bool QVkShaderResourceBindings::build()
6067{
6068 if (layout)
6069 release();
6070
6071 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
6072 descSets[i] = VK_NULL_HANDLE;
6073
6074 sortedBindings.clear();
6075 std::copy(first: m_bindings.cbegin(), last: m_bindings.cend(), result: std::back_inserter(x&: sortedBindings));
6076 std::sort(first: sortedBindings.begin(), last: sortedBindings.end(),
6077 comp: [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
6078 {
6079 return a.data()->binding < b.data()->binding;
6080 });
6081
6082 QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings;
6083 for (const QRhiShaderResourceBinding &binding : qAsConst(t&: sortedBindings)) {
6084 const QRhiShaderResourceBinding::Data *b = binding.data();
6085 VkDescriptorSetLayoutBinding vkbinding;
6086 memset(s: &vkbinding, c: 0, n: sizeof(vkbinding));
6087 vkbinding.binding = uint32_t(b->binding);
6088 vkbinding.descriptorType = toVkDescriptorType(b);
6089 if (b->type == QRhiShaderResourceBinding::SampledTexture)
6090 vkbinding.descriptorCount = b->u.stex.count;
6091 else
6092 vkbinding.descriptorCount = 1;
6093 vkbinding.stageFlags = toVkShaderStageFlags(stage: b->stage);
6094 vkbindings.append(t: vkbinding);
6095 }
6096
6097 VkDescriptorSetLayoutCreateInfo layoutInfo;
6098 memset(s: &layoutInfo, c: 0, n: sizeof(layoutInfo));
6099 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
6100 layoutInfo.bindingCount = uint32_t(vkbindings.count());
6101 layoutInfo.pBindings = vkbindings.constData();
6102
6103 QRHI_RES_RHI(QRhiVulkan);
6104 VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout);
6105 if (err != VK_SUCCESS) {
6106 qWarning(msg: "Failed to create descriptor set layout: %d", err);
6107 return false;
6108 }
6109
6110 VkDescriptorSetAllocateInfo allocInfo;
6111 memset(s: &allocInfo, c: 0, n: sizeof(allocInfo));
6112 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
6113 allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT;
6114 VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT];
6115 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
6116 layouts[i] = layout;
6117 allocInfo.pSetLayouts = layouts;
6118 if (!rhiD->allocateDescriptorSet(allocInfo: &allocInfo, result: descSets, resultPoolIndex: &poolIndex))
6119 return false;
6120
6121 rhiD->updateShaderResourceBindings(srb: this);
6122
6123 lastActiveFrameSlot = -1;
6124 generation += 1;
6125 rhiD->registerResource(res: this);
6126 return true;
6127}
6128
6129QVkGraphicsPipeline::QVkGraphicsPipeline(QRhiImplementation *rhi)
6130 : QRhiGraphicsPipeline(rhi)
6131{
6132}
6133
6134QVkGraphicsPipeline::~QVkGraphicsPipeline()
6135{
6136 release();
6137}
6138
6139void QVkGraphicsPipeline::release()
6140{
6141 if (!pipeline && !layout)
6142 return;
6143
6144 QRhiVulkan::DeferredReleaseEntry e;
6145 e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline;
6146 e.lastActiveFrameSlot = lastActiveFrameSlot;
6147
6148 e.pipelineState.pipeline = pipeline;
6149 e.pipelineState.layout = layout;
6150
6151 pipeline = VK_NULL_HANDLE;
6152 layout = VK_NULL_HANDLE;
6153
6154 QRHI_RES_RHI(QRhiVulkan);
6155 rhiD->releaseQueue.append(t: e);
6156
6157 rhiD->unregisterResource(res: this);
6158}
6159
6160bool QVkGraphicsPipeline::build()
6161{
6162 if (pipeline)
6163 release();
6164
6165 QRHI_RES_RHI(QRhiVulkan);
6166 if (!rhiD->sanityCheckGraphicsPipeline(ps: this))
6167 return false;
6168
6169 if (!rhiD->ensurePipelineCache())
6170 return false;
6171
6172 VkPipelineLayoutCreateInfo pipelineLayoutInfo;
6173 memset(s: &pipelineLayoutInfo, c: 0, n: sizeof(pipelineLayoutInfo));
6174 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
6175 pipelineLayoutInfo.setLayoutCount = 1;
6176 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
6177 Q_ASSERT(m_shaderResourceBindings && srbD->layout);
6178 pipelineLayoutInfo.pSetLayouts = &srbD->layout;
6179 VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
6180 if (err != VK_SUCCESS) {
6181 qWarning(msg: "Failed to create pipeline layout: %d", err);
6182 return false;
6183 }
6184
6185 VkGraphicsPipelineCreateInfo pipelineInfo;
6186 memset(s: &pipelineInfo, c: 0, n: sizeof(pipelineInfo));
6187 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
6188
6189 QVarLengthArray<VkShaderModule, 4> shaders;
6190 QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos;
6191 for (const QRhiShaderStage &shaderStage : m_shaderStages) {
6192 const QShader bakedShader = shaderStage.shader();
6193 const QShaderCode spirv = bakedShader.shader(key: { QShader::SpirvShader, 100, shaderStage.shaderVariant() });
6194 if (spirv.shader().isEmpty()) {
6195 qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
6196 return false;
6197 }
6198 VkShaderModule shader = rhiD->createShader(spirv: spirv.shader());
6199 if (shader) {
6200 shaders.append(t: shader);
6201 VkPipelineShaderStageCreateInfo shaderInfo;
6202 memset(s: &shaderInfo, c: 0, n: sizeof(shaderInfo));
6203 shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
6204 shaderInfo.stage = toVkShaderStage(type: shaderStage.type());
6205 shaderInfo.module = shader;
6206 shaderInfo.pName = spirv.entryPoint().constData();
6207 shaderStageCreateInfos.append(t: shaderInfo);
6208 }
6209 }
6210 pipelineInfo.stageCount = uint32_t(shaderStageCreateInfos.count());
6211 pipelineInfo.pStages = shaderStageCreateInfos.constData();
6212
6213 QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings;
6214 QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates;
6215 int bindingIndex = 0;
6216 for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings();
6217 it != itEnd; ++it, ++bindingIndex)
6218 {
6219 VkVertexInputBindingDescription bindingInfo = {
6220 .binding: uint32_t(bindingIndex),
6221 .stride: it->stride(),
6222 .inputRate: it->classification() == QRhiVertexInputBinding::PerVertex
6223 ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE
6224 };
6225 if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) {
6226 if (rhiD->vertexAttribDivisorAvailable) {
6227 nonOneStepRates.append(t: { .binding: uint32_t(bindingIndex), .divisor: uint32_t(it->instanceStepRate()) });
6228 } else {
6229 qWarning(msg: "QRhiVulkan: Instance step rates other than 1 not supported without "
6230 "VK_EXT_vertex_attribute_divisor on the device and "
6231 "VK_KHR_get_physical_device_properties2 on the instance");
6232 }
6233 }
6234 vertexBindings.append(t: bindingInfo);
6235 }
6236 QVarLengthArray<VkVertexInputAttributeDescription, 4> vertexAttributes;
6237 for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
6238 it != itEnd; ++it)
6239 {
6240 VkVertexInputAttributeDescription attributeInfo = {
6241 .location: uint32_t(it->location()),
6242 .binding: uint32_t(it->binding()),
6243 .format: toVkAttributeFormat(format: it->format()),
6244 .offset: it->offset()
6245 };
6246 vertexAttributes.append(t: attributeInfo);
6247 }
6248 VkPipelineVertexInputStateCreateInfo vertexInputInfo;
6249 memset(s: &vertexInputInfo, c: 0, n: sizeof(vertexInputInfo));
6250 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
6251 vertexInputInfo.vertexBindingDescriptionCount = uint32_t(vertexBindings.count());
6252 vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData();
6253 vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.count());
6254 vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData();
6255 VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo;
6256 if (!nonOneStepRates.isEmpty()) {
6257 memset(s: &divisorInfo, c: 0, n: sizeof(divisorInfo));
6258 divisorInfo.sType = VkStructureType(1000190001); // VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
6259 divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.count());
6260 divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData();
6261 vertexInputInfo.pNext = &divisorInfo;
6262 }
6263 pipelineInfo.pVertexInputState = &vertexInputInfo;
6264
6265 QVarLengthArray<VkDynamicState, 8> dynEnable;
6266 dynEnable << VK_DYNAMIC_STATE_VIEWPORT;
6267 dynEnable << VK_DYNAMIC_STATE_SCISSOR; // ignore UsesScissor - Vulkan requires a scissor for the viewport always
6268 if (m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesBlendConstants))
6269 dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS;
6270 if (m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesStencilRef))
6271 dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE;
6272
6273 VkPipelineDynamicStateCreateInfo dynamicInfo;
6274 memset(s: &dynamicInfo, c: 0, n: sizeof(dynamicInfo));
6275 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
6276 dynamicInfo.dynamicStateCount = uint32_t(dynEnable.count());
6277 dynamicInfo.pDynamicStates = dynEnable.constData();
6278 pipelineInfo.pDynamicState = &dynamicInfo;
6279
6280 VkPipelineViewportStateCreateInfo viewportInfo;
6281 memset(s: &viewportInfo, c: 0, n: sizeof(viewportInfo));
6282 viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
6283 viewportInfo.viewportCount = viewportInfo.scissorCount = 1;
6284 pipelineInfo.pViewportState = &viewportInfo;
6285
6286 VkPipelineInputAssemblyStateCreateInfo inputAsmInfo;
6287 memset(s: &inputAsmInfo, c: 0, n: sizeof(inputAsmInfo));
6288 inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
6289 inputAsmInfo.topology = toVkTopology(t: m_topology);
6290 inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip);
6291 pipelineInfo.pInputAssemblyState = &inputAsmInfo;
6292
6293 VkPipelineRasterizationStateCreateInfo rastInfo;
6294 memset(s: &rastInfo, c: 0, n: sizeof(rastInfo));
6295 rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
6296 rastInfo.cullMode = toVkCullMode(c: m_cullMode);
6297 rastInfo.frontFace = toVkFrontFace(f: m_frontFace);
6298 if (m_depthBias != 0 || !qFuzzyIsNull(f: m_slopeScaledDepthBias)) {
6299 rastInfo.depthBiasEnable = true;
6300 rastInfo.depthBiasConstantFactor = float(m_depthBias);
6301 rastInfo.depthBiasSlopeFactor = m_slopeScaledDepthBias;
6302 }
6303 rastInfo.lineWidth = rhiD->hasWideLines ? m_lineWidth : 1.0f;
6304 pipelineInfo.pRasterizationState = &rastInfo;
6305
6306 VkPipelineMultisampleStateCreateInfo msInfo;
6307 memset(s: &msInfo, c: 0, n: sizeof(msInfo));
6308 msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
6309 msInfo.rasterizationSamples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount);
6310 pipelineInfo.pMultisampleState = &msInfo;
6311
6312 VkPipelineDepthStencilStateCreateInfo dsInfo;
6313 memset(s: &dsInfo, c: 0, n: sizeof(dsInfo));
6314 dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
6315 dsInfo.depthTestEnable = m_depthTest;
6316 dsInfo.depthWriteEnable = m_depthWrite;
6317 dsInfo.depthCompareOp = toVkCompareOp(op: m_depthOp);
6318 dsInfo.stencilTestEnable = m_stencilTest;
6319 if (m_stencilTest) {
6320 fillVkStencilOpState(dst: &dsInfo.front, src: m_stencilFront);
6321 dsInfo.front.compareMask = m_stencilReadMask;
6322 dsInfo.front.writeMask = m_stencilWriteMask;
6323 fillVkStencilOpState(dst: &dsInfo.back, src: m_stencilBack);
6324 dsInfo.back.compareMask = m_stencilReadMask;
6325 dsInfo.back.writeMask = m_stencilWriteMask;
6326 }
6327 pipelineInfo.pDepthStencilState = &dsInfo;
6328
6329 VkPipelineColorBlendStateCreateInfo blendInfo;
6330 memset(s: &blendInfo, c: 0, n: sizeof(blendInfo));
6331 blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
6332 QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends;
6333 for (const QRhiGraphicsPipeline::TargetBlend &b : qAsConst(t&: m_targetBlends)) {
6334 VkPipelineColorBlendAttachmentState blend;
6335 memset(s: &blend, c: 0, n: sizeof(blend));
6336 blend.blendEnable = b.enable;
6337 blend.srcColorBlendFactor = toVkBlendFactor(f: b.srcColor);
6338 blend.dstColorBlendFactor = toVkBlendFactor(f: b.dstColor);
6339 blend.colorBlendOp = toVkBlendOp(op: b.opColor);
6340 blend.srcAlphaBlendFactor = toVkBlendFactor(f: b.srcAlpha);
6341 blend.dstAlphaBlendFactor = toVkBlendFactor(f: b.dstAlpha);
6342 blend.alphaBlendOp = toVkBlendOp(op: b.opAlpha);
6343 blend.colorWriteMask = toVkColorComponents(c: b.colorWrite);
6344 vktargetBlends.append(t: blend);
6345 }
6346 if (vktargetBlends.isEmpty()) {
6347 VkPipelineColorBlendAttachmentState blend;
6348 memset(s: &blend, c: 0, n: sizeof(blend));
6349 blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
6350 | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
6351 vktargetBlends.append(t: blend);
6352 }
6353 blendInfo.attachmentCount = uint32_t(vktargetBlends.count());
6354 blendInfo.pAttachments = vktargetBlends.constData();
6355 pipelineInfo.pColorBlendState = &blendInfo;
6356
6357 pipelineInfo.layout = layout;
6358
6359 Q_ASSERT(m_renderPassDesc && QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp);
6360 pipelineInfo.renderPass = QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp;
6361
6362 err = rhiD->df->vkCreateGraphicsPipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
6363
6364 for (VkShaderModule shader : shaders)
6365 rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
6366
6367 if (err != VK_SUCCESS) {
6368 qWarning(msg: "Failed to create graphics pipeline: %d", err);
6369 return false;
6370 }
6371
6372 lastActiveFrameSlot = -1;
6373 generation += 1;
6374 rhiD->registerResource(res: this);
6375 return true;
6376}
6377
6378QVkComputePipeline::QVkComputePipeline(QRhiImplementation *rhi)
6379 : QRhiComputePipeline(rhi)
6380{
6381}
6382
6383QVkComputePipeline::~QVkComputePipeline()
6384{
6385 release();
6386}
6387
6388void QVkComputePipeline::release()
6389{
6390 if (!pipeline && !layout)
6391 return;
6392
6393 QRhiVulkan::DeferredReleaseEntry e;
6394 e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline;
6395 e.lastActiveFrameSlot = lastActiveFrameSlot;
6396
6397 e.pipelineState.pipeline = pipeline;
6398 e.pipelineState.layout = layout;
6399
6400 pipeline = VK_NULL_HANDLE;
6401 layout = VK_NULL_HANDLE;
6402
6403 QRHI_RES_RHI(QRhiVulkan);
6404 rhiD->releaseQueue.append(t: e);
6405
6406 rhiD->unregisterResource(res: this);
6407}
6408
6409bool QVkComputePipeline::build()
6410{
6411 if (pipeline)
6412 release();
6413
6414 QRHI_RES_RHI(QRhiVulkan);
6415 if (!rhiD->ensurePipelineCache())
6416 return false;
6417
6418 VkPipelineLayoutCreateInfo pipelineLayoutInfo;
6419 memset(s: &pipelineLayoutInfo, c: 0, n: sizeof(pipelineLayoutInfo));
6420 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
6421 pipelineLayoutInfo.setLayoutCount = 1;
6422 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
6423 Q_ASSERT(m_shaderResourceBindings && srbD->layout);
6424 pipelineLayoutInfo.pSetLayouts = &srbD->layout;
6425 VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
6426 if (err != VK_SUCCESS) {
6427 qWarning(msg: "Failed to create pipeline layout: %d", err);
6428 return false;
6429 }
6430
6431 VkComputePipelineCreateInfo pipelineInfo;
6432 memset(s: &pipelineInfo, c: 0, n: sizeof(pipelineInfo));
6433 pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
6434 pipelineInfo.layout = layout;
6435
6436 if (m_shaderStage.type() != QRhiShaderStage::Compute) {
6437 qWarning(msg: "Compute pipeline requires a compute shader stage");
6438 return false;
6439 }
6440 const QShader bakedShader = m_shaderStage.shader();
6441 const QShaderCode spirv = bakedShader.shader(key: { QShader::SpirvShader, 100, m_shaderStage.shaderVariant() });
6442 if (spirv.shader().isEmpty()) {
6443 qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
6444 return false;
6445 }
6446 if (bakedShader.stage() != QShader::ComputeStage) {
6447 qWarning() << bakedShader << "is not a compute shader";
6448 return false;
6449 }
6450 VkShaderModule shader = rhiD->createShader(spirv: spirv.shader());
6451 VkPipelineShaderStageCreateInfo shaderInfo;
6452 memset(s: &shaderInfo, c: 0, n: sizeof(shaderInfo));
6453 shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
6454 shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
6455 shaderInfo.module = shader;
6456 shaderInfo.pName = spirv.entryPoint().constData();
6457 pipelineInfo.stage = shaderInfo;
6458
6459 err = rhiD->df->vkCreateComputePipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
6460 rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
6461 if (err != VK_SUCCESS) {
6462 qWarning(msg: "Failed to create graphics pipeline: %d", err);
6463 return false;
6464 }
6465
6466 lastActiveFrameSlot = -1;
6467 generation += 1;
6468 rhiD->registerResource(res: this);
6469 return true;
6470}
6471
6472QVkCommandBuffer::QVkCommandBuffer(QRhiImplementation *rhi)
6473 : QRhiCommandBuffer(rhi)
6474{
6475 resetState();
6476}
6477
6478QVkCommandBuffer::~QVkCommandBuffer()
6479{
6480 release();
6481}
6482
6483void QVkCommandBuffer::release()
6484{
6485 // nothing to do here, cb is not owned by us
6486}
6487
6488const QRhiNativeHandles *QVkCommandBuffer::nativeHandles()
6489{
6490 // Ok this is messy but no other way has been devised yet. Outside
6491 // begin(Compute)Pass - end(Compute)Pass it is simple - just return the
6492 // primary VkCommandBuffer. Inside, however, we need to provide the current
6493 // secondary command buffer (typically the one started by beginExternal(),
6494 // in case we are between beginExternal - endExternal inside a pass).
6495
6496 if (useSecondaryCb && !secondaryCbs.isEmpty())
6497 nativeHandlesStruct.commandBuffer = secondaryCbs.last();
6498 else
6499 nativeHandlesStruct.commandBuffer = cb;
6500
6501 return &nativeHandlesStruct;
6502}
6503
6504QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi)
6505 : QRhiSwapChain(rhi),
6506 rtWrapper(rhi),
6507 cbWrapper(rhi)
6508{
6509}
6510
6511QVkSwapChain::~QVkSwapChain()
6512{
6513 release();
6514}
6515
6516void QVkSwapChain::release()
6517{
6518 if (sc == VK_NULL_HANDLE)
6519 return;
6520
6521 QRHI_RES_RHI(QRhiVulkan);
6522 rhiD->swapchains.remove(value: this);
6523 rhiD->releaseSwapChainResources(swapChain: this);
6524 surface = lastConnectedSurface = VK_NULL_HANDLE;
6525
6526 QRHI_PROF;
6527 QRHI_PROF_F(releaseSwapChain(this));
6528
6529 rhiD->unregisterResource(res: this);
6530}
6531
6532QRhiCommandBuffer *QVkSwapChain::currentFrameCommandBuffer()
6533{
6534 return &cbWrapper;
6535}
6536
6537QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget()
6538{
6539 return &rtWrapper;
6540}
6541
6542QSize QVkSwapChain::surfacePixelSize()
6543{
6544 if (!ensureSurface())
6545 return QSize();
6546
6547 // The size from the QWindow may not exactly match the surface... so if a
6548 // size is reported from the surface, use that.
6549 VkSurfaceCapabilitiesKHR surfaceCaps;
6550 memset(s: &surfaceCaps, c: 0, n: sizeof(surfaceCaps));
6551 QRHI_RES_RHI(QRhiVulkan);
6552 rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps);
6553 VkExtent2D bufferSize = surfaceCaps.currentExtent;
6554 if (bufferSize.width == uint32_t(-1)) {
6555 Q_ASSERT(bufferSize.height == uint32_t(-1));
6556 return m_window->size() * m_window->devicePixelRatio();
6557 }
6558 return QSize(int(bufferSize.width), int(bufferSize.height));
6559}
6560
6561QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor()
6562{
6563 // not yet built so cannot rely on data computed in buildOrResize()
6564
6565 if (!ensureSurface()) // make sure sampleCount and colorFormat reflect what was requested
6566 return nullptr;
6567
6568 QRHI_RES_RHI(QRhiVulkan);
6569 QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
6570 if (!rhiD->createDefaultRenderPass(rpD: rp,
6571 hasDepthStencil: m_depthStencil != nullptr,
6572 samples,
6573 colorFormat))
6574 {
6575 delete rp;
6576 return nullptr;
6577 }
6578
6579 rp->ownsRp = true;
6580 rhiD->registerResource(res: rp);
6581 return rp;
6582}
6583
6584static inline bool isSrgbFormat(VkFormat format)
6585{
6586 switch (format) {
6587 case VK_FORMAT_R8_SRGB:
6588 case VK_FORMAT_R8G8_SRGB:
6589 case VK_FORMAT_R8G8B8_SRGB:
6590 case VK_FORMAT_B8G8R8_SRGB:
6591 case VK_FORMAT_R8G8B8A8_SRGB:
6592 case VK_FORMAT_B8G8R8A8_SRGB:
6593 case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
6594 return true;
6595 default:
6596 return false;
6597 }
6598}
6599
6600bool QVkSwapChain::ensureSurface()
6601{
6602 // Do nothing when already done, however window may change so check the
6603 // surface is still the same. Some of the queries below are very expensive
6604 // with some implementations so it is important to do the rest only once
6605 // per surface.
6606
6607 Q_ASSERT(m_window);
6608 VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(window: m_window);
6609 if (!surf) {
6610 qWarning(msg: "Failed to get surface for window");
6611 return false;
6612 }
6613 if (surface == surf)
6614 return true;
6615
6616 surface = surf;
6617
6618 QRHI_RES_RHI(QRhiVulkan);
6619 if (rhiD->gfxQueueFamilyIdx != -1) {
6620 if (!rhiD->inst->supportsPresent(physicalDevice: rhiD->physDev, queueFamilyIndex: uint32_t(rhiD->gfxQueueFamilyIdx), window: m_window)) {
6621 qWarning(msg: "Presenting not supported on this window");
6622 return false;
6623 }
6624 }
6625
6626 if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR) {
6627 rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
6628 rhiD->inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
6629 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
6630 rhiD->inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfaceFormatsKHR"));
6631 rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(
6632 rhiD->inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfacePresentModesKHR"));
6633 if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR
6634 || !rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR
6635 || !rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR)
6636 {
6637 qWarning(msg: "Physical device surface queries not available");
6638 return false;
6639 }
6640 }
6641
6642 quint32 formatCount = 0;
6643 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, nullptr);
6644 QVector<VkSurfaceFormatKHR> formats(formatCount);
6645 if (formatCount)
6646 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, formats.data());
6647
6648 const bool srgbRequested = m_flags.testFlag(flag: sRGB);
6649 for (int i = 0; i < int(formatCount); ++i) {
6650 if (formats[i].format != VK_FORMAT_UNDEFINED && srgbRequested == isSrgbFormat(format: formats[i].format)) {
6651 colorFormat = formats[i].format;
6652 colorSpace = formats[i].colorSpace;
6653 break;
6654 }
6655 }
6656
6657 samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount);
6658
6659 quint32 presModeCount = 0;
6660 rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr);
6661 QVector<VkPresentModeKHR> presModes(presModeCount);
6662 rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, presModes.data());
6663 supportedPresentationModes = presModes;
6664
6665 return true;
6666}
6667
6668bool QVkSwapChain::buildOrResize()
6669{
6670 QRHI_RES_RHI(QRhiVulkan);
6671 const bool needsRegistration = !window || window != m_window;
6672
6673 // Can be called multiple times due to window resizes - that is not the
6674 // same as a simple release+build (as with other resources). Thus no
6675 // release() here. See recreateSwapChain().
6676
6677 // except if the window actually changes
6678 if (window && window != m_window)
6679 release();
6680
6681 window = m_window;
6682 m_currentPixelSize = surfacePixelSize();
6683 pixelSize = m_currentPixelSize;
6684
6685 if (!rhiD->recreateSwapChain(swapChain: this)) {
6686 qWarning(msg: "Failed to create new swapchain");
6687 return false;
6688 }
6689
6690 if (needsRegistration)
6691 rhiD->swapchains.insert(value: this);
6692
6693 if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
6694 qWarning(msg: "Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
6695 m_depthStencil->sampleCount(), m_sampleCount);
6696 }
6697 if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
6698 if (m_depthStencil->flags().testFlag(flag: QRhiRenderBuffer::UsedWithSwapChainOnly)) {
6699 m_depthStencil->setPixelSize(pixelSize);
6700 if (!m_depthStencil->build())
6701 qWarning(msg: "Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
6702 pixelSize.width(), pixelSize.height());
6703 } else {
6704 qWarning(msg: "Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
6705 m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
6706 pixelSize.width(), pixelSize.height());
6707 }
6708 }
6709
6710 if (!m_renderPassDesc)
6711 qWarning(msg: "QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
6712
6713 rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
6714 Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp);
6715
6716 rtWrapper.d.pixelSize = pixelSize;
6717 rtWrapper.d.dpr = float(window->devicePixelRatio());
6718 rtWrapper.d.sampleCount = samples;
6719 rtWrapper.d.colorAttCount = 1;
6720 if (m_depthStencil) {
6721 rtWrapper.d.dsAttCount = 1;
6722 ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
6723 } else {
6724 rtWrapper.d.dsAttCount = 0;
6725 ds = nullptr;
6726 }
6727 if (samples > VK_SAMPLE_COUNT_1_BIT)
6728 rtWrapper.d.resolveAttCount = 1;
6729 else
6730 rtWrapper.d.resolveAttCount = 0;
6731
6732 for (int i = 0; i < bufferCount; ++i) {
6733 QVkSwapChain::ImageResources &image(imageRes[i]);
6734 VkImageView views[3] = { // color, ds, resolve
6735 samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView,
6736 ds ? ds->imageView : VK_NULL_HANDLE,
6737 samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE
6738 };
6739
6740 VkFramebufferCreateInfo fbInfo;
6741 memset(s: &fbInfo, c: 0, n: sizeof(fbInfo));
6742 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
6743 fbInfo.renderPass = rtWrapper.d.rp->rp;
6744 fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount);
6745 fbInfo.pAttachments = views;
6746 fbInfo.width = uint32_t(pixelSize.width());
6747 fbInfo.height = uint32_t(pixelSize.height());
6748 fbInfo.layers = 1;
6749
6750 VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb);
6751 if (err != VK_SUCCESS) {
6752 qWarning(msg: "Failed to create framebuffer: %d", err);
6753 return false;
6754 }
6755 }
6756
6757 frameCount = 0;
6758
6759 QRHI_PROF;
6760 QRHI_PROF_F(resizeSwapChain(this, QVK_FRAMES_IN_FLIGHT, samples > VK_SAMPLE_COUNT_1_BIT ? QVK_FRAMES_IN_FLIGHT : 0, samples));
6761
6762 if (needsRegistration)
6763 rhiD->registerResource(res: this);
6764
6765 return true;
6766}
6767
6768QT_END_NAMESPACE
6769

source code of qtbase/src/gui/rhi/qrhivulkan.cpp