Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qrhivulkan.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qrhivulkan_p.h"
6#include <qpa/qplatformvulkaninstance.h>
7
8#define VMA_IMPLEMENTATION
9#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
10#define VMA_STATIC_VULKAN_FUNCTIONS 0
11#define VMA_RECORDING_ENABLED 0
12#define VMA_DEDICATED_ALLOCATION 0
14Q_STATIC_LOGGING_CATEGORY(QRHI_LOG_VMA, "qt.rhi.vma")
15QT_END_NAMESPACE
16#define VMA_ASSERT(expr) Q_ASSERT(expr)
17#ifdef QT_DEBUG
18#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1
19#define VMA_DEBUG_LOG(str) QT_PREPEND_NAMESPACE(qDebug)(QT_PREPEND_NAMESPACE(QRHI_LOG_VMA), (str))
20#define VMA_DEBUG_LOG_FORMAT(format, ...) QT_PREPEND_NAMESPACE(qDebug)(QT_PREPEND_NAMESPACE(QRHI_LOG_VMA), format, __VA_ARGS__)
21#endif
22template<typename... Args>
23static void debugVmaLeak(const char *format, Args&&... args)
24{
25#ifndef QT_NO_DEBUG
26 // debug builds: just do it always
27 static bool leakCheck = true;
28#else
29 // release builds: opt-in
30 static bool leakCheck = QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue)("QT_RHI_LEAK_CHECK");
31#endif
32 if (leakCheck)
33 QT_PREPEND_NAMESPACE(qWarning)(QT_PREPEND_NAMESPACE(QRHI_LOG_VMA), format, std::forward<Args>(args)...);
34}
35#define VMA_LEAK_LOG_FORMAT(format, ...) debugVmaLeak(format, __VA_ARGS__)
36QT_WARNING_PUSH
37QT_WARNING_DISABLE_GCC("-Wsuggest-override")
38QT_WARNING_DISABLE_GCC("-Wundef")
39QT_WARNING_DISABLE_CLANG("-Wundef")
40#if defined(Q_CC_CLANG) && Q_CC_CLANG >= 1100
41QT_WARNING_DISABLE_CLANG("-Wdeprecated-copy")
42#endif
43#include "vk_mem_alloc.h"
44QT_WARNING_POP
45
46#include <qmath.h>
47#include <QVulkanFunctions>
48#include <QtGui/qwindow.h>
49#include <private/qvulkandefaultinstance_p.h>
50#include <optional>
51
52QT_BEGIN_NAMESPACE
53
54/*
55 Vulkan 1.0 backend. Provides a double-buffered swapchain that throttles the
56 rendering thread to vsync. Textures and "static" buffers are device local,
57 and a separate, host visible staging buffer is used to upload data to them.
58 "Dynamic" buffers are in host visible memory and are duplicated (since there
59 can be 2 frames in flight). This is handled transparently to the application.
60
61 Barriers are generated automatically for each render or compute pass, based
62 on the resources that are used in that pass (in QRhiShaderResourceBindings,
63 vertex inputs, etc.). This implies deferring the recording of the command
64 buffer since the barriers have to be placed at the right place (before the
65 pass), and that can only be done once we know all the things the pass does.
66
67 This in turn has implications for integrating external commands
68 (beginExternal() - direct Vulkan calls - endExternal()) because that is
69 incompatible with this approach by nature. Therefore we support another mode
70 of operation, where each render or compute pass uses one or more secondary
71 command buffers (recorded right away), with each beginExternal() leading to
72 closing the current secondary cb, creating a new secondary cb for the
73 external content, and then starting yet another one in endExternal() for
74 whatever comes afterwards in the pass. This way the primary command buffer
75 only has vkCmdExecuteCommand(s) within a renderpass instance
76 (Begin-EndRenderPass). (i.e. our only subpass is then
77 VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS instead of
78 VK_SUBPASS_CONTENTS_INLINE)
79
80 The command buffer management mode is decided on a per frame basis,
81 controlled by the ExternalContentsInPass flag of beginFrame().
82*/
83
84/*!
85 \class QRhiVulkanInitParams
86 \inmodule QtGuiPrivate
87 \inheaderfile rhi/qrhi.h
88 \since 6.6
89 \brief Vulkan specific initialization parameters.
90
91 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
92 for details.
93
94 A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to
95 the user to ensure this is available and initialized. This is typically
96 done in main() similarly to the following:
97
98 \badcode
99 int main(int argc, char **argv)
100 {
101 ...
102
103 QVulkanInstance inst;
104 inst.setLayers({ "VK_LAYER_KHRONOS_validation" }); // for debugging only, not for release builds
105 inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
106 if (!inst.create())
107 qFatal("Vulkan not available");
108
109 ...
110 }
111 \endcode
112
113 This example enables the
114 \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan
115 validation layers}, when they are available, and also enables the
116 instance-level extensions QRhi reports as desirable (such as,
117 VK_KHR_get_physical_device_properties2), as long as they are supported by
118 the Vulkan implementation at run time.
119
120 The former is optional, and is useful during the development phase
121 QVulkanInstance conveniently redirects messages and warnings to qDebug.
122 Avoid enabling it in production builds, however. The latter is strongly
123 recommended, and is important in order to make certain features functional
124 (for example, QRhi::CustomInstanceStepRate).
125
126 Once this is done, a Vulkan-based QRhi can be created by passing the
127 instance and a QWindow with its surface type set to
128 QSurface::VulkanSurface:
129
130 \badcode
131 QRhiVulkanInitParams params;
132 params.inst = vulkanInstance;
133 params.window = window;
134 rhi = QRhi::create(QRhi::Vulkan, &params);
135 \endcode
136
137 The window is optional and can be omitted. This is not recommended however
138 because there is then no way to ensure presenting is supported while
139 choosing a graphics queue.
140
141 \note Even when a window is specified, QRhiSwapChain objects can be created
142 for other windows as well, as long as they all have their
143 QWindow::surfaceType() set to QSurface::VulkanSurface.
144
145 To request additional extensions to be enabled on the Vulkan device, list them
146 in deviceExtensions. This can be relevant when integrating with native Vulkan
147 rendering code.
148
149 It is expected that the backend's desired list of instance extensions will
150 be queried by calling the static function preferredInstanceExtensions()
151 before initializing a QVulkanInstance. The returned list can be safely
152 passed to QVulkanInstance::setExtensions() as-is, because unsupported
153 extensions are filtered out automatically. If this is not done, certain
154 features, such as QRhi::CustomInstanceStepRate may be reported as
155 unsupported even when the Vulkan implementation on the system has support
156 for the relevant functionality.
157
158 For full functionality the QVulkanInstance needs to have API 1.1 enabled,
159 when available. This means calling QVulkanInstance::setApiVersion() with
160 1.1 or higher whenever QVulkanInstance::supportedApiVersion() reports that
161 at least Vulkan 1.1 is supported. If this is not done, certain features,
162 such as QRhi::RenderTo3DTextureSlice may be reported as unsupported even
163 when the Vulkan implementation on the system supports Vulkan 1.1 or newer.
164
165 \section2 Working with existing Vulkan devices
166
167 When interoperating with another graphics engine, it may be necessary to
168 get a QRhi instance that uses the same Vulkan device. This can be achieved
169 by passing a pointer to a QRhiVulkanNativeHandles to QRhi::create().
170
171 The physical device must always be set to a non-null value. If the
172 intention is to just specify a physical device, but leave the rest of the
173 VkDevice and queue creation to QRhi, then no other members need to be
174 filled out in the struct. For example, this is the case when working with
175 OpenXR.
176
177 To adopt an existing \c VkDevice, the device field must be set to a
178 non-null value as well. In addition, the graphics queue family index is
179 required. The queue index is optional, as the default of 0 is often
180 suitable.
181
182 Optionally, an existing command pool object can be specified as well. Also
183 optionally, vmemAllocator can be used to share the same
184 \l{https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator}{Vulkan
185 memory allocator} between two QRhi instances.
186
187 The QRhi does not take ownership of any of the external objects.
188
189 Applications are encouraged to query the list of desired device extensions
190 by calling the static function preferredExtensionsForImportedDevice(), and
191 enable them on the VkDevice. Otherwise certain QRhi features may not be
192 available.
193 */
194
195/*!
196 \variable QRhiVulkanInitParams::inst
197
198 The QVulkanInstance that has already been successfully
199 \l{QVulkanInstance::create()}{created}, required.
200*/
201
202/*!
203 \variable QRhiVulkanInitParams::window
204
205 Optional, but recommended when targeting a QWindow.
206*/
207
208/*!
209 \variable QRhiVulkanInitParams::deviceExtensions
210
211 Optional, empty by default. The list of Vulkan device extensions to enable.
212 Unsupported extensions are ignored gracefully.
213*/
214
215/*!
216 \class QRhiVulkanNativeHandles
217 \inmodule QtGuiPrivate
218 \inheaderfile rhi/qrhi.h
219 \since 6.6
220 \brief Collects device, queue, and other Vulkan objects that are used by the QRhi.
221
222 \note Ownership of the Vulkan objects is never transferred.
223
224 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
225 for details.
226 */
227
228/*!
229 \variable QRhiVulkanNativeHandles::physDev
230
231 When different from \nullptr, specifies the Vulkan physical device to use.
232*/
233
234/*!
235 \variable QRhiVulkanNativeHandles::dev
236
237 When wanting to import not just a physical device, but also use an already
238 existing VkDevice, set this and the graphics queue index and family index.
239*/
240
241/*!
242 \variable QRhiVulkanNativeHandles::gfxQueueFamilyIdx
243
244 Graphics queue family index.
245*/
246
247/*!
248 \variable QRhiVulkanNativeHandles::gfxQueueIdx
249
250 Graphics queue index.
251*/
252
253/*!
254 \variable QRhiVulkanNativeHandles::vmemAllocator
255
256 Relevant only when importing an existing memory allocator object,
257 leave it set to \nullptr otherwise.
258*/
259
260/*!
261 \variable QRhiVulkanNativeHandles::gfxQueue
262
263 Output only, not used by QRhi::create(), only set by the
264 QRhi::nativeHandles() accessor. The graphics VkQueue used by the QRhi.
265*/
266
267/*!
268 \variable QRhiVulkanNativeHandles::inst
269
270 Output only, not used by QRhi::create(), only set by the
271 QRhi::nativeHandles() accessor. The QVulkanInstance used by the QRhi.
272*/
273
274/*!
275 \class QRhiVulkanCommandBufferNativeHandles
276 \inmodule QtGuiPrivate
277 \inheaderfile rhi/qrhi.h
278 \since 6.6
279 \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer.
280
281 \note The Vulkan command buffer object is only guaranteed to be valid, and
282 in recording state, while recording a frame. That is, between a
283 \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or
284 \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
285 \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
286
287 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
288 for details.
289 */
290
291/*!
292 \variable QRhiVulkanCommandBufferNativeHandles::commandBuffer
293
294 The VkCommandBuffer object.
295*/
296
297/*!
298 \class QRhiVulkanRenderPassNativeHandles
299 \inmodule QtGuiPrivate
300 \inheaderfile rhi/qrhi.h
301 \since 6.6
302 \brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor.
303
304 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
305 for details.
306 */
307
308/*!
309 \variable QRhiVulkanRenderPassNativeHandles::renderPass
310
311 The VkRenderPass object.
312*/
313
314/*!
315 \class QRhiVulkanQueueSubmitParams
316 \inmodule QtGui
317 \since 6.9
318 \brief References additional Vulkan API objects that get passed to \c vkQueueSubmit().
319
320 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
321 for details.
322*/
323
324/*!
325 \variable QRhiVulkanQueueSubmitParams::waitSemaphoreCount
326
327 See
328 \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSubmitInfo.html}{VkSubmitInfo}
329 for details.
330*/
331
332/*!
333 \variable QRhiVulkanQueueSubmitParams::waitSemaphores
334
335 See
336 \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSubmitInfo.html}{VkSubmitInfo}
337 for details.
338*/
339
340/*!
341 \variable QRhiVulkanQueueSubmitParams::signalSemaphoreCount
342
343 See
344 \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSubmitInfo.html}{VkSubmitInfo}
345 for details.
346*/
347
348/*!
349 \variable QRhiVulkanQueueSubmitParams::signalSemaphores
350
351 See
352 \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSubmitInfo.html}{VkSubmitInfo}
353 for details.
354*/
355
356/*!
357 \variable QRhiVulkanQueueSubmitParams::presentWaitSemaphoreCount
358
359 When non-zero, this applies to the next \c vkQueuePresentKHR() call. See
360 \l{https://registry.khronos.org/VulkanSC/specs/1.0-extensions/man/html/VkPresentInfoKHR.html}{VkPresentInfoKHR}
361 for details.
362*/
363
364/*!
365 \variable QRhiVulkanQueueSubmitParams::presentWaitSemaphores
366
367 See
368 \l{https://registry.khronos.org/VulkanSC/specs/1.0-extensions/man/html/VkPresentInfoKHR.html}{VkPresentInfoKHR}
369 for details.
370 */
371
372template <class Int>
373inline Int aligned(Int v, Int byteAlign)
374{
375 return (v + byteAlign - 1) & ~(byteAlign - 1);
376}
377
379
380static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetInstanceProcAddr(VkInstance, const char *pName)
381{
382 return globalVulkanInstance->getInstanceProcAddr(pName);
383}
384
385static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetDeviceProcAddr(VkDevice device, const char *pName)
386{
387 return globalVulkanInstance->functions()->vkGetDeviceProcAddr(device, pName);
388}
389
391{
392 return reinterpret_cast<VmaAllocation>(a);
393}
394
396{
397 return reinterpret_cast<VmaAllocator>(a);
398}
399
400/*!
401 \return the list of instance extensions that are expected to be enabled on
402 the QVulkanInstance that is used for the Vulkan-based QRhi.
403
404 The returned list can be safely passed to QVulkanInstance::setExtensions()
405 as-is, because unsupported extensions are filtered out automatically.
406 */
407QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions()
408{
409 return {
410 QByteArrayLiteral("VK_KHR_get_physical_device_properties2"),
411 // to silence validation when e.g. on Wayland a surface format's colorspace is VK_COLOR_SPACE_PASS_THROUGH_EXT
412 QByteArrayLiteral("VK_EXT_swapchain_colorspace")
413 };
414}
415
416/*!
417 \return the list of device extensions that are expected to be enabled on the
418 \c VkDevice when creating a Vulkan-based QRhi with an externally created
419 \c VkDevice object.
420 */
421QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice()
422{
423 return {
424 QByteArrayLiteral("VK_KHR_swapchain"),
425 QByteArrayLiteral("VK_EXT_vertex_attribute_divisor"),
426 QByteArrayLiteral("VK_KHR_create_renderpass2"),
427 QByteArrayLiteral("VK_KHR_depth_stencil_resolve"),
428 QByteArrayLiteral("VK_KHR_fragment_shading_rate")
429 };
430}
431
432QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importParams)
433 : ofr(this)
434{
435 inst = params->inst;
436 if (!inst) {
437 // This builds on the fact that Qt Quick also uses QVulkanDefaultInstance. While
438 // this way we can support a null inst, it has consequences, so only do it with a
439 // warning. (e.g. if Qt Quick initializes afterwards, its attempt to set flags on
440 // QVulkanDefaultInstance will be futile)
441 qWarning("QRhi for Vulkan attempted to be initialized without a QVulkanInstance; using QVulkanDefaultInstance.");
442 inst = QVulkanDefaultInstance::instance();
443 }
444
445 maybeWindow = params->window; // may be null
446 requestedDeviceExtensions = params->deviceExtensions;
447
448 if (importParams) {
449 physDev = importParams->physDev;
450 dev = importParams->dev;
451 if (dev && physDev) {
452 importedDevice = true;
453 gfxQueueFamilyIdx = importParams->gfxQueueFamilyIdx;
454 gfxQueueIdx = importParams->gfxQueueIdx;
455 // gfxQueue is output only, no point in accepting it as input
456 if (importParams->vmemAllocator) {
457 importedAllocator = true;
458 allocator = importParams->vmemAllocator;
459 }
460 }
461 }
462}
463
464static bool qvk_debug_filter(QVulkanInstance::DebugMessageSeverityFlags severity,
465 QVulkanInstance::DebugMessageTypeFlags type,
466 const void *callbackData)
467{
468 Q_UNUSED(severity);
469 Q_UNUSED(type);
470#ifdef VK_EXT_debug_utils
471 const VkDebugUtilsMessengerCallbackDataEXT *d = static_cast<const VkDebugUtilsMessengerCallbackDataEXT *>(callbackData);
472
473 // Filter out certain misleading validation layer messages, as per
474 // VulkanMemoryAllocator documentation.
475 if (strstr(d->pMessage, "Mapping an image with layout")
476 && strstr(d->pMessage, "can result in undefined behavior if this memory is used by the device"))
477 {
478 return true;
479 }
480
481 // In certain cases allocateDescriptorSet() will attempt to allocate from a
482 // pool that does not have enough descriptors of a certain type. This makes
483 // the validation layer shout. However, this is not an error since we will
484 // then move on to another pool. If there is a real error, a qWarning
485 // message is shown by allocateDescriptorSet(), so the validation warning
486 // does not have any value and is just noise.
487 if (strstr(d->pMessage, "VUID-VkDescriptorSetAllocateInfo-descriptorPool-00307"))
488 return true;
489#else
490 Q_UNUSED(callbackData);
491#endif
492 return false;
493}
494
495static inline QRhiDriverInfo::DeviceType toRhiDeviceType(VkPhysicalDeviceType type)
496{
497 switch (type) {
498 case VK_PHYSICAL_DEVICE_TYPE_OTHER:
499 return QRhiDriverInfo::UnknownDevice;
500 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
501 return QRhiDriverInfo::IntegratedDevice;
502 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
503 return QRhiDriverInfo::DiscreteDevice;
504 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
505 return QRhiDriverInfo::VirtualDevice;
506 case VK_PHYSICAL_DEVICE_TYPE_CPU:
507 return QRhiDriverInfo::CpuDevice;
508 default:
509 return QRhiDriverInfo::UnknownDevice;
510 }
511}
512
513static inline void fillDriverInfo(QRhiDriverInfo *info, const VkPhysicalDeviceProperties &physDevProperties)
514{
515 info->deviceName = QByteArray(physDevProperties.deviceName);
516 info->deviceId = physDevProperties.deviceID;
517 info->vendorId = physDevProperties.vendorID;
518 info->deviceType = toRhiDeviceType(physDevProperties.deviceType);
519}
520
521template<typename T>
522static inline void addToChain(T *head, void *entry)
523{
524 VkBaseOutStructure *s = reinterpret_cast<VkBaseOutStructure *>(head);
525 for ( ; ; ) {
526 VkBaseOutStructure *next = reinterpret_cast<VkBaseOutStructure *>(s->pNext);
527 if (next)
528 s = next;
529 else
530 break;
531 }
532 s->pNext = reinterpret_cast<VkBaseOutStructure *>(entry);
533}
534
535bool QRhiVulkan::create(QRhi::Flags flags)
536{
537 Q_ASSERT(inst);
538 if (!inst->isValid()) {
539 qWarning("Vulkan instance is not valid");
540 return false;
541 }
542
543 rhiFlags = flags;
544 qCDebug(QRHI_LOG_INFO, "Initializing QRhi Vulkan backend %p with flags %d", this, int(rhiFlags));
545
546 globalVulkanInstance = inst; // used for function resolving in vkmemalloc callbacks
547 f = inst->functions();
548 if (QRHI_LOG_INFO().isEnabled(QtDebugMsg)) {
549 qCDebug(QRHI_LOG_INFO, "Enabled instance extensions:");
550 for (const char *ext : inst->extensions())
551 qCDebug(QRHI_LOG_INFO, " %s", ext);
552 }
553
554 caps = {};
555 caps.debugUtils = inst->extensions().contains(QByteArrayLiteral("VK_EXT_debug_utils"));
556
557 QList<VkQueueFamilyProperties> queueFamilyProps;
558 auto queryQueueFamilyProps = [this, &queueFamilyProps] {
559 uint32_t queueCount = 0;
560 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
561 queueFamilyProps.resize(int(queueCount));
562 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
563 };
564
565 // Choose a physical device, unless one was provided in importParams.
566 if (!physDev) {
567 uint32_t physDevCount = 0;
568 f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr);
569 if (!physDevCount) {
570 qWarning("No physical devices");
571 return false;
572 }
573 QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount);
574 VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data());
575 if (err != VK_SUCCESS || !physDevCount) {
576 qWarning("Failed to enumerate physical devices: %d", err);
577 return false;
578 }
579
580 int physDevIndex = -1;
581 int requestedPhysDevIndex = -1;
582 if (qEnvironmentVariableIsSet("QT_VK_PHYSICAL_DEVICE_INDEX"))
583 requestedPhysDevIndex = qEnvironmentVariableIntValue("QT_VK_PHYSICAL_DEVICE_INDEX");
584
585 if (requestedPhysDevIndex < 0 && requestedRhiAdapter) {
586 VkPhysicalDevice requestedPhysDev = static_cast<QVulkanAdapter *>(requestedRhiAdapter)->physDev;
587 for (int i = 0; i < int(physDevCount); ++i) {
588 if (physDevs[i] == requestedPhysDev) {
589 requestedPhysDevIndex = i;
590 break;
591 }
592 }
593 }
594
595 if (requestedPhysDevIndex < 0 && flags.testFlag(QRhi::PreferSoftwareRenderer)) {
596 for (int i = 0; i < int(physDevCount); ++i) {
597 f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
598 if (physDevProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
599 requestedPhysDevIndex = i;
600 break;
601 }
602 }
603 }
604
605 for (int i = 0; i < int(physDevCount); ++i) {
606 f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
607 qCDebug(QRHI_LOG_INFO, "Physical device %d: '%s' %d.%d.%d (api %d.%d.%d vendor 0x%X device 0x%X type %d)",
608 i,
609 physDevProperties.deviceName,
610 VK_VERSION_MAJOR(physDevProperties.driverVersion),
611 VK_VERSION_MINOR(physDevProperties.driverVersion),
612 VK_VERSION_PATCH(physDevProperties.driverVersion),
613 VK_VERSION_MAJOR(physDevProperties.apiVersion),
614 VK_VERSION_MINOR(physDevProperties.apiVersion),
615 VK_VERSION_PATCH(physDevProperties.apiVersion),
616 physDevProperties.vendorID,
617 physDevProperties.deviceID,
618 physDevProperties.deviceType);
619 if (physDevIndex < 0 && (requestedPhysDevIndex < 0 || requestedPhysDevIndex == int(i))) {
620 physDevIndex = i;
621 qCDebug(QRHI_LOG_INFO, " using this physical device");
622 }
623 }
624
625 if (physDevIndex < 0) {
626 qWarning("No matching physical device");
627 return false;
628 }
629 physDev = physDevs[physDevIndex];
630 f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties);
631 } else {
632 f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties);
633 qCDebug(QRHI_LOG_INFO, "Using imported physical device '%s' %d.%d.%d (api %d.%d.%d vendor 0x%X device 0x%X type %d)",
634 physDevProperties.deviceName,
635 VK_VERSION_MAJOR(physDevProperties.driverVersion),
636 VK_VERSION_MINOR(physDevProperties.driverVersion),
637 VK_VERSION_PATCH(physDevProperties.driverVersion),
638 VK_VERSION_MAJOR(physDevProperties.apiVersion),
639 VK_VERSION_MINOR(physDevProperties.apiVersion),
640 VK_VERSION_PATCH(physDevProperties.apiVersion),
641 physDevProperties.vendorID,
642 physDevProperties.deviceID,
643 physDevProperties.deviceType);
644 }
645
646 caps.apiVersion = inst->apiVersion();
647
648 // Check the physical device API version against the instance API version,
649 // they do not have to match, which means whatever version was set in the
650 // QVulkanInstance may not be legally used with a given device if the
651 // physical device has a lower version.
652 const QVersionNumber physDevApiVersion(VK_VERSION_MAJOR(physDevProperties.apiVersion),
653 VK_VERSION_MINOR(physDevProperties.apiVersion)); // patch version left out intentionally
654 if (physDevApiVersion < caps.apiVersion) {
655 qCDebug(QRHI_LOG_INFO) << "Instance has api version" << caps.apiVersion
656 << "whereas the chosen physical device has" << physDevApiVersion
657 << "- restricting to the latter";
658 caps.apiVersion = physDevApiVersion;
659 }
660
661 fillDriverInfo(&driverInfoStruct, physDevProperties);
662
663 QVulkanInfoVector<QVulkanExtension> devExts;
664 uint32_t devExtCount = 0;
665 f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr);
666 if (devExtCount) {
667 QList<VkExtensionProperties> extProps(devExtCount);
668 f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data());
669 for (const VkExtensionProperties &p : std::as_const(extProps))
670 devExts.append({ p.extensionName, p.specVersion });
671 }
672 qCDebug(QRHI_LOG_INFO, "%d device extensions available", int(devExts.size()));
673
674 bool featuresQueried = false;
675#ifdef VK_VERSION_1_1
676 VkPhysicalDeviceFeatures2 physDevFeaturesChainable = {};
677 physDevFeaturesChainable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
678
679 // Extensions (that are really extensions in 1.1-1.3, not core)
680#ifdef VK_KHR_fragment_shading_rate
681 VkPhysicalDeviceFragmentShadingRateFeaturesKHR fragmentShadingRateFeatures = {};
682 fragmentShadingRateFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR;
683 if (devExts.contains("VK_KHR_fragment_shading_rate"))
684 addToChain(&physDevFeaturesChainable, &fragmentShadingRateFeatures);
685#endif
686#ifdef VK_EXT_device_fault
687 VkPhysicalDeviceFaultFeaturesEXT deviceFaultFeatures = {};
688 deviceFaultFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
689 if (devExts.contains(VK_EXT_DEVICE_FAULT_EXTENSION_NAME))
690 addToChain(&physDevFeaturesChainable, &deviceFaultFeatures);
691#endif
692#endif
693
694 // Vulkan >=1.2 headers at build time, >=1.2 implementation at run time
695#ifdef VK_VERSION_1_2
696 if (!featuresQueried) {
697 // Vulkan11Features, Vulkan12Features, etc. are only in Vulkan 1.2 and newer.
698 if (caps.apiVersion >= QVersionNumber(1, 2)) {
699 physDevFeatures11IfApi12OrNewer = {};
700 physDevFeatures11IfApi12OrNewer.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
701 physDevFeatures12 = {};
702 physDevFeatures12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
703#ifdef VK_VERSION_1_3
704 physDevFeatures13 = {};
705 physDevFeatures13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
706#ifdef VK_VERSION_1_4
707 physDevFeatures14 = {};
708 physDevFeatures14.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES;
709#endif // VK_VERSION_1_4
710#endif // VK_VERSION_1_3
711 addToChain(&physDevFeaturesChainable, &physDevFeatures11IfApi12OrNewer);
712 physDevFeatures11IfApi12OrNewer.pNext = &physDevFeatures12;
713#ifdef VK_VERSION_1_3
714 if (caps.apiVersion >= QVersionNumber(1, 3))
715 physDevFeatures12.pNext = &physDevFeatures13;
716#ifdef VK_VERSION_1_4
717 if (caps.apiVersion >= QVersionNumber(1, 4))
718 physDevFeatures13.pNext = &physDevFeatures14;
719#endif // VK_VERSION_1_4
720#endif // VK_VERSION_1_3
721 f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable);
722 memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures));
723 featuresQueried = true;
724 }
725 }
726#endif // VK_VERSION_1_2
727
728 // Vulkan >=1.1 headers at build time, 1.1 implementation at run time
729#ifdef VK_VERSION_1_1
730 if (!featuresQueried) {
731 // Vulkan versioning nightmares: if the runtime API version is 1.1,
732 // there is no Vulkan11Features (introduced in 1.2+, the headers might
733 // have the types and structs, but the Vulkan implementation version at
734 // run time is what matters). But there are individual feature structs.
735 // For multiview, it is important to get this right since at the time of
736 // writing Quest 3 Android is a Vulkan 1.1 implementation at run time on
737 // the headset.
738 if (caps.apiVersion == QVersionNumber(1, 1)) {
739 multiviewFeaturesIfApi11 = {};
740 multiviewFeaturesIfApi11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
741 addToChain(&physDevFeaturesChainable, &multiviewFeaturesIfApi11);
742 f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable);
743 memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures));
744 featuresQueried = true;
745 }
746 }
747#endif
748
749 if (!featuresQueried) {
750 // If the API version at run time is 1.0 (or we are building with
751 // ancient 1.0 headers), then do the Vulkan 1.0 query.
752 f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures);
753 featuresQueried = true;
754 }
755
756 // Choose queue and create device, unless the device was specified in importParams.
757 if (!importedDevice) {
758 // We only support combined graphics+present queues. When it comes to
759 // compute, only combined graphics+compute queue is used, compute gets
760 // disabled otherwise.
761 std::optional<uint32_t> gfxQueueFamilyIdxOpt;
762 std::optional<uint32_t> computelessGfxQueueCandidateIdxOpt;
763 queryQueueFamilyProps();
764 const uint32_t queueFamilyCount = uint32_t(queueFamilyProps.size());
765 for (uint32_t i = 0; i < queueFamilyCount; ++i) {
766 qCDebug(QRHI_LOG_INFO, "queue family %u: flags=0x%x count=%u",
767 i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount);
768 if (!gfxQueueFamilyIdxOpt.has_value()
769 && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
770 && (!maybeWindow || inst->supportsPresent(physDev, i, maybeWindow)))
771 {
772 if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
773 gfxQueueFamilyIdxOpt = i;
774 else if (!computelessGfxQueueCandidateIdxOpt.has_value())
775 computelessGfxQueueCandidateIdxOpt = i;
776 }
777 }
778 if (gfxQueueFamilyIdxOpt.has_value()) {
779 gfxQueueFamilyIdx = gfxQueueFamilyIdxOpt.value();
780 } else {
781 if (computelessGfxQueueCandidateIdxOpt.has_value()) {
782 gfxQueueFamilyIdx = computelessGfxQueueCandidateIdxOpt.value();
783 } else {
784 qWarning("No graphics (or no graphics+present) queue family found");
785 return false;
786 }
787 }
788
789 VkDeviceQueueCreateInfo queueInfo = {};
790 const float prio[] = { 0 };
791 queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
792 queueInfo.queueFamilyIndex = gfxQueueFamilyIdx;
793 queueInfo.queueCount = 1;
794 queueInfo.pQueuePriorities = prio;
795
796 QList<const char *> devLayers;
797 if (inst->layers().contains("VK_LAYER_KHRONOS_validation"))
798 devLayers.append("VK_LAYER_KHRONOS_validation");
799
800 QList<const char *> requestedDevExts;
801 requestedDevExts.append("VK_KHR_swapchain");
802
803 const bool hasPhysDevProp2 = inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2"));
804
805 if (devExts.contains(QByteArrayLiteral("VK_KHR_portability_subset"))) {
806 if (hasPhysDevProp2) {
807 requestedDevExts.append("VK_KHR_portability_subset");
808 } else {
809 qWarning("VK_KHR_portability_subset should be enabled on the device "
810 "but the instance does not have VK_KHR_get_physical_device_properties2 enabled. "
811 "Expect problems.");
812 }
813 }
814
815#ifdef VK_EXT_vertex_attribute_divisor
816 if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
817 if (hasPhysDevProp2) {
818 requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
819 caps.vertexAttribDivisor = true;
820 }
821 }
822#endif
823
824#ifdef VK_KHR_create_renderpass2
825 if (devExts.contains(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) {
826 requestedDevExts.append(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
827 caps.renderPass2KHR = true;
828 }
829#endif
830
831#ifdef VK_KHR_depth_stencil_resolve
832 if (devExts.contains(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME)) {
833 requestedDevExts.append(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
834 caps.depthStencilResolveKHR = true;
835 }
836#endif
837
838#ifdef VK_KHR_fragment_shading_rate
839 if (devExts.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME))
840 requestedDevExts.append(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
841#endif
842
843#ifdef VK_EXT_device_fault
844 if (devExts.contains(VK_EXT_DEVICE_FAULT_EXTENSION_NAME)) {
845 requestedDevExts.append(VK_EXT_DEVICE_FAULT_EXTENSION_NAME);
846 caps.deviceFault = true;
847 }
848#endif
849
850 for (const QByteArray &ext : requestedDeviceExtensions) {
851 if (!ext.isEmpty() && !requestedDevExts.contains(ext)) {
852 if (devExts.contains(ext)) {
853 requestedDevExts.append(ext.constData());
854 } else {
855 qWarning("Device extension %s requested in QRhiVulkanInitParams is not supported",
856 ext.constData());
857 }
858 }
859 }
860
861 QByteArrayList envExtList = qgetenv("QT_VULKAN_DEVICE_EXTENSIONS").split(';');
862 for (const QByteArray &ext : envExtList) {
863 if (!ext.isEmpty() && !requestedDevExts.contains(ext)) {
864 if (devExts.contains(ext)) {
865 requestedDevExts.append(ext.constData());
866 } else {
867 qWarning("Device extension %s requested in QT_VULKAN_DEVICE_EXTENSIONS is not supported",
868 ext.constData());
869 }
870 }
871 }
872
873 if (QRHI_LOG_INFO().isEnabled(QtDebugMsg)) {
874 qCDebug(QRHI_LOG_INFO, "Enabling device extensions:");
875 for (const char *ext : requestedDevExts)
876 qCDebug(QRHI_LOG_INFO, " %s", ext);
877 }
878
879 VkDeviceCreateInfo devInfo = {};
880 devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
881 devInfo.queueCreateInfoCount = 1;
882 devInfo.pQueueCreateInfos = &queueInfo;
883 devInfo.enabledLayerCount = uint32_t(devLayers.size());
884 devInfo.ppEnabledLayerNames = devLayers.constData();
885 devInfo.enabledExtensionCount = uint32_t(requestedDevExts.size());
886 devInfo.ppEnabledExtensionNames = requestedDevExts.constData();
887
888 // Enable all features that are reported as supported, except
889 // robustness because that potentially affects performance.
890 //
891 // Enabling all features mainly serves third-party renderers that may
892 // use the VkDevice created here. For the record, the backend here
893 // optionally relies on the following features, meaning just for our
894 // (QRhi/Quick/Quick 3D) purposes it would be sufficient to
895 // enable-if-supported only the following:
896 //
897 // wideLines, largePoints, fillModeNonSolid,
898 // tessellationShader, geometryShader
899 // textureCompressionETC2, textureCompressionASTC_LDR, textureCompressionBC
900
901#ifdef VK_VERSION_1_1
902 physDevFeaturesChainable.features.robustBufferAccess = VK_FALSE;
903#endif
904#ifdef VK_VERSION_1_3
905 physDevFeatures13.robustImageAccess = VK_FALSE;
906#endif
907
908#ifdef VK_VERSION_1_1
909 if (caps.apiVersion >= QVersionNumber(1, 1)) {
910 // For a >=1.2 implementation at run time, this will enable all
911 // (1.0-1.4) features reported as supported, except the ones we turn
912 // off explicitly above. (+extensions) For a 1.1 implementation at
913 // run time, this only enables the 1.0 and multiview features (+any
914 // extensions) reported as supported. We will not be bothering with
915 // the Vulkan 1.1 individual feature struct nonsense.
916 devInfo.pNext = &physDevFeaturesChainable;
917 } else
918#endif
919 {
920 physDevFeatures.robustBufferAccess = VK_FALSE;
921 devInfo.pEnabledFeatures = &physDevFeatures;
922 }
923
924 VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
925 if (err != VK_SUCCESS) {
926 qWarning("Failed to create device: %d", err);
927 return false;
928 }
929 } else {
930 qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
931
932 // Here we have no way to tell if the extensions got enabled or not.
933 // Pretend it's all there and supported. If getProcAddress fails, we'll
934 // handle that gracefully.
935 caps.deviceFault = true;
936 caps.vertexAttribDivisor = true;
937 caps.renderPass2KHR = true;
938 caps.depthStencilResolveKHR = true;
939 }
940
941 vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
942 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
943 vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
944 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
945 vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(
946 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfacePresentModesKHR"));
947
948 df = inst->deviceFunctions(dev);
949
950 VkCommandPoolCreateInfo poolInfo = {};
951 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
952 poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
953 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
954 VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool[i]);
955 if (err != VK_SUCCESS) {
956 qWarning("Failed to create command pool: %d", err);
957 return false;
958 }
959 }
960
961 qCDebug(QRHI_LOG_INFO, "Using queue family index %u and queue index %u",
962 gfxQueueFamilyIdx, gfxQueueIdx);
963
964 df->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, gfxQueueIdx, &gfxQueue);
965
966 if (queueFamilyProps.isEmpty())
967 queryQueueFamilyProps();
968
969 caps.compute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0;
970 timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits;
971
972 ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment;
973 // helps little with an optimal offset of 1 (on some drivers) when the spec
974 // elsewhere states that the minimum bufferOffset is 4...
975 texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment);
976
977 caps.depthClamp = physDevFeatures.depthClamp;
978
979 caps.wideLines = physDevFeatures.wideLines;
980
981 caps.texture3DSliceAs2D = caps.apiVersion >= QVersionNumber(1, 1);
982
983 caps.tessellation = physDevFeatures.tessellationShader;
984 caps.geometryShader = physDevFeatures.geometryShader;
985
986 caps.nonFillPolygonMode = physDevFeatures.fillModeNonSolid;
987
988 caps.drawIndirectMulti = physDevFeatures.multiDrawIndirect;
989
990#ifdef VK_VERSION_1_2
991 if (caps.apiVersion >= QVersionNumber(1, 2))
992 caps.multiView = physDevFeatures11IfApi12OrNewer.multiview;
993#endif
994
995#ifdef VK_VERSION_1_1
996 if (caps.apiVersion == QVersionNumber(1, 1))
997 caps.multiView = multiviewFeaturesIfApi11.multiview;
998#endif
999
1000#ifdef VK_KHR_fragment_shading_rate
1001 fragmentShadingRates.clear();
1002 if (caps.apiVersion >= QVersionNumber(1, 1)) {
1003 caps.perDrawShadingRate = fragmentShadingRateFeatures.pipelineFragmentShadingRate;
1004 caps.imageBasedShadingRate = fragmentShadingRateFeatures.attachmentFragmentShadingRate;
1005 if (caps.imageBasedShadingRate) {
1006 VkPhysicalDeviceFragmentShadingRatePropertiesKHR shadingRateProps = {};
1007 shadingRateProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
1008 VkPhysicalDeviceProperties2 props2 = {};
1009 props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
1010 props2.pNext = &shadingRateProps;
1011 f->vkGetPhysicalDeviceProperties2(physDev, &props2);
1012 caps.imageBasedShadingRateTileSize = int(shadingRateProps.maxFragmentShadingRateAttachmentTexelSize.width);
1013 // If it's non-square, there's nothing we can do since it is not compatible with other APIs (D3D12) then.
1014 }
1015 if (caps.perDrawShadingRate) {
1016 PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR vkGetPhysicalDeviceFragmentShadingRatesKHR =
1017 reinterpret_cast<PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR>(
1018 inst->getInstanceProcAddr("vkGetPhysicalDeviceFragmentShadingRatesKHR"));
1019 if (vkGetPhysicalDeviceFragmentShadingRatesKHR) {
1020 uint32_t count = 0;
1021 vkGetPhysicalDeviceFragmentShadingRatesKHR(physDev, &count, nullptr);
1022 fragmentShadingRates.resize(count);
1023 for (VkPhysicalDeviceFragmentShadingRateKHR &s : fragmentShadingRates) {
1024 s = {};
1025 s.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR;
1026 }
1027 vkGetPhysicalDeviceFragmentShadingRatesKHR(physDev, &count, fragmentShadingRates.data());
1028 }
1029 vkCmdSetFragmentShadingRateKHR = reinterpret_cast<PFN_vkCmdSetFragmentShadingRateKHR>(
1030 f->vkGetDeviceProcAddr(dev, "vkCmdSetFragmentShadingRateKHR"));
1031 }
1032 }
1033#endif
1034
1035 // With Vulkan 1.2 renderpass2 and depth_stencil_resolve are core, but we
1036 // have to support the case of 1.1 + extensions, in particular for the Quest
1037 // 3 (Android, Vulkan 1.1 at the time of writing). Therefore, always rely on
1038 // the KHR extension for now.
1039#ifdef VK_KHR_create_renderpass2
1040 if (caps.renderPass2KHR) {
1041 vkCreateRenderPass2KHR = reinterpret_cast<PFN_vkCreateRenderPass2KHR>(f->vkGetDeviceProcAddr(dev, "vkCreateRenderPass2KHR"));
1042 if (!vkCreateRenderPass2KHR) // handle it gracefully, the caps flag may be incorrect when using an imported VkDevice
1043 caps.renderPass2KHR = false;
1044 }
1045#endif
1046
1047 // On Windows, figure out the DXGI adapter LUID.
1048#ifdef Q_OS_WIN
1049 adapterLuidValid = false;
1050 adapterLuid = {};
1051#ifdef VK_VERSION_1_2
1052 if (caps.apiVersion >= QVersionNumber(1, 2)) {
1053 VkPhysicalDeviceVulkan11Properties v11props = {};
1054 v11props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES;
1055 VkPhysicalDeviceProperties2 props2 = {};
1056 props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
1057 props2.pNext = &v11props;
1058 f->vkGetPhysicalDeviceProperties2(physDev, &props2);
1059 if (v11props.deviceLUIDValid) {
1060 const LUID *luid = reinterpret_cast<const LUID *>(v11props.deviceLUID);
1061 memcpy(&adapterLuid, luid, VK_LUID_SIZE);
1062 adapterLuidValid = true;
1063 dxgiHdrInfo = new QDxgiHdrInfo(adapterLuid);
1064 qCDebug(QRHI_LOG_INFO, "DXGI adapter LUID for physical device is %lu, %lu",
1065 adapterLuid.LowPart, adapterLuid.HighPart);
1066 }
1067 }
1068#endif
1069#endif
1070
1071 if (!importedAllocator) {
1072 VmaVulkanFunctions funcs = {};
1073 funcs.vkGetInstanceProcAddr = wrap_vkGetInstanceProcAddr;
1074 funcs.vkGetDeviceProcAddr = wrap_vkGetDeviceProcAddr;
1075
1076 VmaAllocatorCreateInfo allocatorInfo = {};
1077 // A QRhi is supposed to be used from one single thread only. Disable
1078 // the allocator's own mutexes. This gives a performance boost.
1079 allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
1080 allocatorInfo.physicalDevice = physDev;
1081 allocatorInfo.device = dev;
1082 allocatorInfo.pVulkanFunctions = &funcs;
1083 allocatorInfo.instance = inst->vkInstance();
1084
1085 // Logic would dictate setting allocatorInfo.vulkanApiVersion to caps.apiVersion.
1086 // However, VMA has asserts to test if the header version Qt was built with is
1087 // older than the runtime version. This is nice, but a bit unnecessary (in Qt we'd
1088 // rather prefer losing the affected features automatically, and perhaps printing
1089 // a warning, instead of aborting the application). Restrict the runtime version
1090 // passed in based on the preprocessor macro to keep VMA happy.
1091#ifdef VK_VERSION_1_4
1092 if (caps.apiVersion >= QVersionNumber(1, 4))
1093 allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_4;
1094 else
1095#endif
1096#ifdef VK_VERSION_1_3
1097 if (caps.apiVersion >= QVersionNumber(1, 3))
1098 allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3;
1099 else
1100#endif
1101#ifdef VK_VERSION_1_2
1102 if (caps.apiVersion >= QVersionNumber(1, 2))
1103 allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_2;
1104 else
1105#endif
1106#ifdef VK_VERSION_1_1
1107 if (caps.apiVersion >= QVersionNumber(1, 1))
1108 allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_1;
1109 else
1110#endif
1111#ifdef VK_VERSION_1_0
1112 allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0;
1113#endif
1114
1115 VmaAllocator vmaallocator;
1116 VkResult err = vmaCreateAllocator(&allocatorInfo, &vmaallocator);
1117 if (err != VK_SUCCESS) {
1118 qWarning("Failed to create allocator: %d", err);
1119 return false;
1120 }
1121 allocator = vmaallocator;
1122 }
1123
1124 inst->installDebugOutputFilter(qvk_debug_filter);
1125
1126 VkDescriptorPool pool;
1127 VkResult err = createDescriptorPool(&pool);
1128 if (err == VK_SUCCESS)
1129 descriptorPools.append(pool);
1130 else
1131 qWarning("Failed to create initial descriptor pool: %d", err);
1132
1133 VkQueryPoolCreateInfo timestampQueryPoolInfo = {};
1134 timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
1135 timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
1136 timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2;
1137 err = df->vkCreateQueryPool(dev, &timestampQueryPoolInfo, nullptr, &timestampQueryPool);
1138 if (err != VK_SUCCESS) {
1139 qWarning("Failed to create timestamp query pool: %d", err);
1140 return false;
1141 }
1142 timestampQueryPoolMap.resize(QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair
1143 timestampQueryPoolMap.fill(false);
1144
1145#ifdef VK_EXT_debug_utils
1146 if (caps.debugUtils) {
1147 vkSetDebugUtilsObjectNameEXT = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkSetDebugUtilsObjectNameEXT"));
1148 vkCmdBeginDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdBeginDebugUtilsLabelEXT"));
1149 vkCmdEndDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdEndDebugUtilsLabelEXT"));
1150 vkCmdInsertDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdInsertDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdInsertDebugUtilsLabelEXT"));
1151 }
1152#endif
1153
1154#ifdef VK_EXT_device_fault
1155 if (caps.deviceFault) {
1156 vkGetDeviceFaultInfoEXT = reinterpret_cast<PFN_vkGetDeviceFaultInfoEXT>(f->vkGetDeviceProcAddr(dev, "vkGetDeviceFaultInfoEXT"));
1157 }
1158#endif
1159
1160 deviceLost = false;
1161
1162 nativeHandlesStruct.physDev = physDev;
1163 nativeHandlesStruct.dev = dev;
1164 nativeHandlesStruct.gfxQueueFamilyIdx = gfxQueueFamilyIdx;
1165 nativeHandlesStruct.gfxQueueIdx = gfxQueueIdx;
1166 nativeHandlesStruct.gfxQueue = gfxQueue;
1167 nativeHandlesStruct.vmemAllocator = allocator;
1168 nativeHandlesStruct.inst = inst;
1169
1170 return true;
1171}
1172
1174{
1175 if (!df)
1176 return;
1177
1178 if (!deviceLost)
1179 df->vkDeviceWaitIdle(dev);
1180
1183
1184#ifdef Q_OS_WIN
1185 delete dxgiHdrInfo;
1186 dxgiHdrInfo = nullptr;
1187#endif
1188
1189 if (ofr.cmdFence) {
1190 df->vkDestroyFence(dev, ofr.cmdFence, nullptr);
1191 ofr.cmdFence = VK_NULL_HANDLE;
1192 }
1193
1194 if (pipelineCache) {
1195 df->vkDestroyPipelineCache(dev, pipelineCache, nullptr);
1196 pipelineCache = VK_NULL_HANDLE;
1197 }
1198
1199 for (const DescriptorPoolData &pool : descriptorPools)
1200 df->vkDestroyDescriptorPool(dev, pool.pool, nullptr);
1201
1202 descriptorPools.clear();
1203
1204 if (timestampQueryPool) {
1205 df->vkDestroyQueryPool(dev, timestampQueryPool, nullptr);
1206 timestampQueryPool = VK_NULL_HANDLE;
1207 }
1208
1209 if (!importedAllocator && allocator) {
1210 vmaDestroyAllocator(toVmaAllocator(allocator));
1211 allocator = nullptr;
1212 }
1213
1214 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
1215 if (cmdPool[i]) {
1216 df->vkDestroyCommandPool(dev, cmdPool[i], nullptr);
1217 cmdPool[i] = VK_NULL_HANDLE;
1218 }
1219 freeSecondaryCbs[i].clear();
1220 ofr.cbWrapper[i]->cb = VK_NULL_HANDLE;
1221 }
1222
1223 if (!importedDevice && dev) {
1224 df->vkDestroyDevice(dev, nullptr);
1225 inst->resetDeviceFunctions(dev);
1226 dev = VK_NULL_HANDLE;
1227 }
1228
1229 f = nullptr;
1230 df = nullptr;
1231
1232 importedDevice = false;
1233 importedAllocator = false;
1234}
1235
1236QRhi::AdapterList QRhiVulkan::enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const
1237{
1238 VkPhysicalDevice requestedPhysDev = VK_NULL_HANDLE;
1239 if (nativeHandles) {
1240 QRhiVulkanNativeHandles *h = static_cast<QRhiVulkanNativeHandles *>(nativeHandles);
1241 requestedPhysDev = h->physDev;
1242 }
1243
1244 QRhi::AdapterList list;
1245 QVulkanFunctions *f = inst->functions();
1246 uint32_t physDevCount = 0;
1247 f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr);
1248 if (!physDevCount)
1249 return {};
1250
1251 QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount);
1252 VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data());
1253 if (err != VK_SUCCESS || !physDevCount)
1254 return {};
1255
1256 VkPhysicalDeviceProperties physDevProperties = {};
1257 for (uint32_t i = 0; i < physDevCount; ++i) {
1258 if (requestedPhysDev && physDevs[i] != requestedPhysDev)
1259 continue;
1260
1261 f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
1262 QVulkanAdapter *a = new QVulkanAdapter;
1263 a->physDev = physDevs[i];
1264 fillDriverInfo(&a->adapterInfo, physDevProperties);
1265 list.append(a);
1266 }
1267
1268 return list;
1269}
1270
1272{
1273 return adapterInfo;
1274}
1275
1277{
1278 VkDescriptorPoolSize descPoolSizes[] = {
1279 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, QVK_UNIFORM_BUFFERS_PER_POOL },
1280 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, QVK_UNIFORM_BUFFERS_PER_POOL },
1281 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL },
1282 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, QVK_SAMPLED_IMAGES_PER_POOL },
1283 { VK_DESCRIPTOR_TYPE_SAMPLER, QVK_SAMPLERS_PER_POOL },
1284 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, QVK_STORAGE_BUFFERS_PER_POOL },
1285 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, QVK_STORAGE_IMAGES_PER_POOL }
1286 };
1287 VkDescriptorPoolCreateInfo descPoolInfo = {};
1288 descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1289 // Do not enable vkFreeDescriptorSets - sets are never freed on their own
1290 // (good so no trouble with fragmentation), they just deref their pool
1291 // which is then reset at some point (or not).
1292 descPoolInfo.flags = 0;
1293 descPoolInfo.maxSets = QVK_DESC_SETS_PER_POOL;
1294 descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]);
1295 descPoolInfo.pPoolSizes = descPoolSizes;
1296 return df->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, pool);
1297}
1298
1299bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex)
1300{
1301 auto tryAllocate = [this, allocInfo, result](int poolIndex) {
1302 allocInfo->descriptorPool = descriptorPools[poolIndex].pool;
1303 VkResult r = df->vkAllocateDescriptorSets(dev, allocInfo, result);
1304 if (r == VK_SUCCESS)
1305 descriptorPools[poolIndex].refCount += 1;
1306 return r;
1307 };
1308
1309 int lastPoolIdx = descriptorPools.size() - 1;
1310 for (int i = lastPoolIdx; i >= 0; --i) {
1311 if (descriptorPools[i].refCount == 0) {
1312 df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0);
1313 descriptorPools[i].allocedDescSets = 0;
1314 }
1315 if (descriptorPools[i].allocedDescSets + int(allocInfo->descriptorSetCount) <= QVK_DESC_SETS_PER_POOL) {
1316 VkResult err = tryAllocate(i);
1317 if (err == VK_SUCCESS) {
1318 descriptorPools[i].allocedDescSets += allocInfo->descriptorSetCount;
1319 *resultPoolIndex = i;
1320 return true;
1321 }
1322 }
1323 }
1324
1325 VkDescriptorPool newPool;
1326 VkResult poolErr = createDescriptorPool(&newPool);
1327 if (poolErr == VK_SUCCESS) {
1328 descriptorPools.append(newPool);
1329 lastPoolIdx = descriptorPools.size() - 1;
1330 VkResult err = tryAllocate(lastPoolIdx);
1331 if (err != VK_SUCCESS) {
1332 qWarning("Failed to allocate descriptor set from new pool too, giving up: %d", err);
1333 return false;
1334 }
1335 descriptorPools[lastPoolIdx].allocedDescSets += allocInfo->descriptorSetCount;
1336 *resultPoolIndex = lastPoolIdx;
1337 return true;
1338 } else {
1339 qWarning("Failed to allocate new descriptor pool: %d", poolErr);
1340 return false;
1341 }
1342}
1343
1344static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
1345{
1346 const bool srgb = flags.testFlag(QRhiTexture::sRGB);
1347 switch (format) {
1348 case QRhiTexture::RGBA8:
1349 return srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
1350 case QRhiTexture::BGRA8:
1351 return srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
1352 case QRhiTexture::R8:
1353 return srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM;
1354 case QRhiTexture::RG8:
1355 return srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM;
1356 case QRhiTexture::R16:
1357 return VK_FORMAT_R16_UNORM;
1358 case QRhiTexture::RG16:
1359 return VK_FORMAT_R16G16_UNORM;
1360 case QRhiTexture::RED_OR_ALPHA8:
1361 return VK_FORMAT_R8_UNORM;
1362
1363 case QRhiTexture::RGBA16F:
1364 return VK_FORMAT_R16G16B16A16_SFLOAT;
1365 case QRhiTexture::RGBA32F:
1366 return VK_FORMAT_R32G32B32A32_SFLOAT;
1367 case QRhiTexture::R16F:
1368 return VK_FORMAT_R16_SFLOAT;
1369 case QRhiTexture::R32F:
1370 return VK_FORMAT_R32_SFLOAT;
1371
1372 case QRhiTexture::RGB10A2:
1373 // intentionally A2B10G10R10, not A2R10G10B10
1374 return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
1375
1376 case QRhiTexture::R8SI:
1377 return VK_FORMAT_R8_SINT;
1378 case QRhiTexture::R32SI:
1379 return VK_FORMAT_R32_SINT;
1380 case QRhiTexture::RG32SI:
1381 return VK_FORMAT_R32G32_SINT;
1382 case QRhiTexture::RGBA32SI:
1383 return VK_FORMAT_R32G32B32A32_SINT;
1384
1385 case QRhiTexture::R8UI:
1386 return VK_FORMAT_R8_UINT;
1387 case QRhiTexture::R32UI:
1388 return VK_FORMAT_R32_UINT;
1389 case QRhiTexture::RG32UI:
1390 return VK_FORMAT_R32G32_UINT;
1391 case QRhiTexture::RGBA32UI:
1392 return VK_FORMAT_R32G32B32A32_UINT;
1393
1394 case QRhiTexture::D16:
1395 return VK_FORMAT_D16_UNORM;
1396 case QRhiTexture::D24:
1397 return VK_FORMAT_X8_D24_UNORM_PACK32;
1398 case QRhiTexture::D24S8:
1399 return VK_FORMAT_D24_UNORM_S8_UINT;
1400 case QRhiTexture::D32F:
1401 return VK_FORMAT_D32_SFLOAT;
1402 case QRhiTexture::D32FS8:
1403 return VK_FORMAT_D32_SFLOAT_S8_UINT;
1404
1405 case QRhiTexture::BC1:
1406 return srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK : VK_FORMAT_BC1_RGB_UNORM_BLOCK;
1407 case QRhiTexture::BC2:
1408 return srgb ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK;
1409 case QRhiTexture::BC3:
1410 return srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK;
1411 case QRhiTexture::BC4:
1412 return VK_FORMAT_BC4_UNORM_BLOCK;
1413 case QRhiTexture::BC5:
1414 return VK_FORMAT_BC5_UNORM_BLOCK;
1415 case QRhiTexture::BC6H:
1416 return VK_FORMAT_BC6H_UFLOAT_BLOCK;
1417 case QRhiTexture::BC7:
1418 return srgb ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK;
1419
1420 case QRhiTexture::ETC2_RGB8:
1421 return srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
1422 case QRhiTexture::ETC2_RGB8A1:
1423 return srgb ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
1424 case QRhiTexture::ETC2_RGBA8:
1425 return srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
1426
1427 case QRhiTexture::ASTC_4x4:
1428 return srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
1429 case QRhiTexture::ASTC_5x4:
1430 return srgb ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
1431 case QRhiTexture::ASTC_5x5:
1432 return srgb ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
1433 case QRhiTexture::ASTC_6x5:
1434 return srgb ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
1435 case QRhiTexture::ASTC_6x6:
1436 return srgb ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
1437 case QRhiTexture::ASTC_8x5:
1438 return srgb ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
1439 case QRhiTexture::ASTC_8x6:
1440 return srgb ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
1441 case QRhiTexture::ASTC_8x8:
1442 return srgb ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
1443 case QRhiTexture::ASTC_10x5:
1444 return srgb ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
1445 case QRhiTexture::ASTC_10x6:
1446 return srgb ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
1447 case QRhiTexture::ASTC_10x8:
1448 return srgb ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
1449 case QRhiTexture::ASTC_10x10:
1450 return srgb ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
1451 case QRhiTexture::ASTC_12x10:
1452 return srgb ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
1453 case QRhiTexture::ASTC_12x12:
1454 return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
1455
1456 default:
1457 Q_UNREACHABLE_RETURN(VK_FORMAT_R8G8B8A8_UNORM);
1458 }
1459}
1460
1461static inline QRhiTexture::Format swapchainReadbackTextureFormat(VkFormat format, QRhiTexture::Flags *flags)
1462{
1463 switch (format) {
1464 case VK_FORMAT_R8G8B8A8_UNORM:
1465 return QRhiTexture::RGBA8;
1466 case VK_FORMAT_R8G8B8A8_SRGB:
1467 if (flags)
1468 (*flags) |= QRhiTexture::sRGB;
1469 return QRhiTexture::RGBA8;
1470 case VK_FORMAT_B8G8R8A8_UNORM:
1471 return QRhiTexture::BGRA8;
1472 case VK_FORMAT_B8G8R8A8_SRGB:
1473 if (flags)
1474 (*flags) |= QRhiTexture::sRGB;
1475 return QRhiTexture::BGRA8;
1476 case VK_FORMAT_R16G16B16A16_SFLOAT:
1477 return QRhiTexture::RGBA16F;
1478 case VK_FORMAT_R32G32B32A32_SFLOAT:
1479 return QRhiTexture::RGBA32F;
1480 case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
1481 return QRhiTexture::RGB10A2;
1482 default:
1483 qWarning("VkFormat %d cannot be read back", format);
1484 break;
1485 }
1486 return QRhiTexture::UnknownFormat;
1487}
1488
1489static constexpr inline bool isDepthTextureFormat(QRhiTexture::Format format)
1490{
1491 switch (format) {
1492 case QRhiTexture::Format::D16:
1493 case QRhiTexture::Format::D24:
1494 case QRhiTexture::Format::D24S8:
1495 case QRhiTexture::Format::D32F:
1496 case QRhiTexture::Format::D32FS8:
1497 return true;
1498
1499 default:
1500 return false;
1501 }
1502}
1503
1504static constexpr inline bool isStencilTextureFormat(QRhiTexture::Format format)
1505{
1506 switch (format) {
1507 case QRhiTexture::Format::D24S8:
1508 case QRhiTexture::Format::D32FS8:
1509 return true;
1510
1511 default:
1512 return false;
1513 }
1514}
1515
1516static constexpr inline VkImageAspectFlags aspectMaskForTextureFormat(QRhiTexture::Format format)
1517{
1518 if (isDepthTextureFormat(format)) {
1519 if (isStencilTextureFormat(format))
1520 return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
1521 else
1522 return VK_IMAGE_ASPECT_DEPTH_BIT;
1523 } else {
1524 return VK_IMAGE_ASPECT_COLOR_BIT;
1525 }
1526}
1527
1528// Transient images ("render buffers") backed by lazily allocated memory are
1529// managed manually without going through vk_mem_alloc since it does not offer
1530// any support for such images. This should be ok since in practice there
1531// should be very few of such images.
1532
1533uint32_t QRhiVulkan::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
1534{
1535 VkPhysicalDeviceMemoryProperties physDevMemProps;
1536 f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
1537
1538 VkMemoryRequirements memReq;
1539 df->vkGetImageMemoryRequirements(dev, img, &memReq);
1540 uint32_t memTypeIndex = uint32_t(-1);
1541
1542 if (memReq.memoryTypeBits) {
1543 // Find a device local + lazily allocated, or at least device local memtype.
1544 const VkMemoryType *memType = physDevMemProps.memoryTypes;
1545 bool foundDevLocal = false;
1546 for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
1547 if (memReq.memoryTypeBits & (1 << i)) {
1548 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
1549 if (!foundDevLocal) {
1550 foundDevLocal = true;
1551 memTypeIndex = i;
1552 }
1553 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
1554 memTypeIndex = i;
1555 break;
1556 }
1557 }
1558 }
1559 }
1560 }
1561
1562 return memTypeIndex;
1563}
1564
1565bool QRhiVulkan::createTransientImage(VkFormat format,
1566 const QSize &pixelSize,
1567 VkImageUsageFlags usage,
1568 VkImageAspectFlags aspectMask,
1569 VkSampleCountFlagBits samples,
1570 VkDeviceMemory *mem,
1571 VkImage *images,
1572 VkImageView *views,
1573 int count)
1574{
1575 VkMemoryRequirements memReq;
1576 VkResult err;
1577
1578 for (int i = 0; i < count; ++i) {
1579 VkImageCreateInfo imgInfo = {};
1580 imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1581 imgInfo.imageType = VK_IMAGE_TYPE_2D;
1582 imgInfo.format = format;
1583 imgInfo.extent.width = uint32_t(pixelSize.width());
1584 imgInfo.extent.height = uint32_t(pixelSize.height());
1585 imgInfo.extent.depth = 1;
1586 imgInfo.mipLevels = imgInfo.arrayLayers = 1;
1587 imgInfo.samples = samples;
1588 imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1589 imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
1590 imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1591
1592 err = df->vkCreateImage(dev, &imgInfo, nullptr, images + i);
1593 if (err != VK_SUCCESS) {
1594 qWarning("Failed to create image: %d", err);
1595 return false;
1596 }
1597
1598 // Assume the reqs are the same since the images are same in every way.
1599 // Still, call GetImageMemReq for every image, in order to prevent the
1600 // validation layer from complaining.
1601 df->vkGetImageMemoryRequirements(dev, images[i], &memReq);
1602 }
1603
1604 VkMemoryAllocateInfo memInfo = {};
1605 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1606 memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * VkDeviceSize(count);
1607
1608 uint32_t startIndex = 0;
1609 do {
1610 memInfo.memoryTypeIndex = chooseTransientImageMemType(images[0], startIndex);
1611 if (memInfo.memoryTypeIndex == uint32_t(-1)) {
1612 qWarning("No suitable memory type found");
1613 return false;
1614 }
1615 startIndex = memInfo.memoryTypeIndex + 1;
1616 err = df->vkAllocateMemory(dev, &memInfo, nullptr, mem);
1617 if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
1618 qWarning("Failed to allocate image memory: %d", err);
1619 return false;
1620 }
1621 } while (err != VK_SUCCESS);
1622
1623 VkDeviceSize ofs = 0;
1624 for (int i = 0; i < count; ++i) {
1625 err = df->vkBindImageMemory(dev, images[i], *mem, ofs);
1626 if (err != VK_SUCCESS) {
1627 qWarning("Failed to bind image memory: %d", err);
1628 return false;
1629 }
1630 ofs += aligned(memReq.size, memReq.alignment);
1631
1632 VkImageViewCreateInfo imgViewInfo = {};
1633 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1634 imgViewInfo.image = images[i];
1635 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1636 imgViewInfo.format = format;
1637 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1638 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1639 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1640 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1641 imgViewInfo.subresourceRange.aspectMask = aspectMask;
1642 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1643
1644 err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
1645 if (err != VK_SUCCESS) {
1646 qWarning("Failed to create image view: %d", err);
1647 return false;
1648 }
1649 }
1650
1651 return true;
1652}
1653
1655{
1656 if (optimalDsFormat != VK_FORMAT_UNDEFINED)
1657 return optimalDsFormat;
1658
1659 const VkFormat dsFormatCandidates[] = {
1660 VK_FORMAT_D24_UNORM_S8_UINT,
1661 VK_FORMAT_D32_SFLOAT_S8_UINT,
1662 VK_FORMAT_D16_UNORM_S8_UINT
1663 };
1664 const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
1665 int dsFormatIdx = 0;
1666 while (dsFormatIdx < dsFormatCandidateCount) {
1667 optimalDsFormat = dsFormatCandidates[dsFormatIdx];
1668 VkFormatProperties fmtProp;
1669 f->vkGetPhysicalDeviceFormatProperties(physDev, optimalDsFormat, &fmtProp);
1670 if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
1671 break;
1672 ++dsFormatIdx;
1673 }
1674 if (dsFormatIdx == dsFormatCandidateCount)
1675 qWarning("Failed to find an optimal depth-stencil format");
1676
1677 return optimalDsFormat;
1678}
1679
1681{
1682 bool prepare(VkRenderPassCreateInfo *rpInfo, int multiViewCount, bool multiViewCap)
1683 {
1684 if (multiViewCount < 2)
1685 return true;
1686 if (!multiViewCap) {
1687 qWarning("Cannot create multiview render pass without support for the Vulkan 1.1 multiview feature");
1688 return false;
1689 }
1690#ifdef VK_VERSION_1_1
1691 uint32_t allViewsMask = 0;
1692 for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i)
1693 allViewsMask |= (1 << i);
1694 multiViewMask = allViewsMask;
1695 multiViewCorrelationMask = allViewsMask;
1696 multiViewInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
1697 multiViewInfo.subpassCount = 1;
1698 multiViewInfo.pViewMasks = &multiViewMask;
1699 multiViewInfo.correlationMaskCount = 1;
1700 multiViewInfo.pCorrelationMasks = &multiViewCorrelationMask;
1701 rpInfo->pNext = &multiViewInfo;
1702#endif
1703 return true;
1704 }
1705
1706#ifdef VK_VERSION_1_1
1710#endif
1711};
1712
1713#ifdef VK_KHR_create_renderpass2
1714// Effectively converts a VkRenderPassCreateInfo into a VkRenderPassCreateInfo2,
1715// adding depth-stencil resolve and VRS support. Incorporates multiview into the
1716// info structs (no chaining needed). Assumes a single subpass.
1717struct RenderPass2SetupHelper
1718{
1719 RenderPass2SetupHelper(QRhiVulkan *rhiD) : rhiD(rhiD) { }
1720
1721 bool prepare(VkRenderPassCreateInfo2 *rpInfo2, const VkRenderPassCreateInfo *rpInfo, const QVkRenderPassDescriptor *rpD, int multiViewCount) {
1722 *rpInfo2 = {};
1723
1724 viewMask = 0;
1725 if (multiViewCount >= 2) {
1726 for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i)
1727 viewMask |= (1 << i);
1728 }
1729
1730 attDescs2.resize(rpInfo->attachmentCount);
1731 for (qsizetype i = 0; i < attDescs2.count(); ++i) {
1732 VkAttachmentDescription2KHR &att2(attDescs2[i]);
1733 const VkAttachmentDescription &att(rpInfo->pAttachments[i]);
1734 att2 = {};
1735 att2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
1736 att2.flags = att.flags;
1737 att2.format = att.format;
1738 att2.samples = att.samples;
1739 att2.loadOp = att.loadOp;
1740 att2.storeOp = att.storeOp;
1741 att2.stencilLoadOp = att.stencilLoadOp;
1742 att2.stencilStoreOp = att.stencilStoreOp;
1743 att2.initialLayout = att.initialLayout;
1744 att2.finalLayout = att.finalLayout;
1745 }
1746
1747 attRefs2.clear();
1748 subpass2 = {};
1749 subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
1750 const VkSubpassDescription &subpassDesc(rpInfo->pSubpasses[0]);
1751 subpass2.flags = subpassDesc.flags;
1752 subpass2.pipelineBindPoint = subpassDesc.pipelineBindPoint;
1753 if (multiViewCount >= 2)
1754 subpass2.viewMask = viewMask;
1755
1756 // color attachment refs
1757 qsizetype startIndex = attRefs2.count();
1758 for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) {
1759 attRefs2.append({});
1760 VkAttachmentReference2KHR &attref2(attRefs2.last());
1761 const VkAttachmentReference &attref(subpassDesc.pColorAttachments[j]);
1762 attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
1763 attref2.attachment = attref.attachment;
1764 attref2.layout = attref.layout;
1765 attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1766 }
1767 subpass2.colorAttachmentCount = subpassDesc.colorAttachmentCount;
1768 subpass2.pColorAttachments = attRefs2.constData() + startIndex;
1769
1770 // color resolve refs
1771 if (subpassDesc.pResolveAttachments) {
1772 startIndex = attRefs2.count();
1773 for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) {
1774 attRefs2.append({});
1775 VkAttachmentReference2KHR &attref2(attRefs2.last());
1776 const VkAttachmentReference &attref(subpassDesc.pResolveAttachments[j]);
1777 attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
1778 attref2.attachment = attref.attachment;
1779 attref2.layout = attref.layout;
1780 attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1781 }
1782 subpass2.pResolveAttachments = attRefs2.constData() + startIndex;
1783 }
1784
1785 // depth-stencil ref
1786 if (subpassDesc.pDepthStencilAttachment) {
1787 startIndex = attRefs2.count();
1788 attRefs2.append({});
1789 VkAttachmentReference2KHR &attref2(attRefs2.last());
1790 const VkAttachmentReference &attref(*subpassDesc.pDepthStencilAttachment);
1791 attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
1792 attref2.attachment = attref.attachment;
1793 attref2.layout = attref.layout;
1794 attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
1795 subpass2.pDepthStencilAttachment = attRefs2.constData() + startIndex;
1796 }
1797
1798 // depth-stencil resolve ref
1799#ifdef VK_KHR_depth_stencil_resolve
1800 dsResolveDesc = {};
1801 if (rpD->hasDepthStencilResolve) {
1802 startIndex = attRefs2.count();
1803 attRefs2.append({});
1804 VkAttachmentReference2KHR &attref2(attRefs2.last());
1805 attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
1806 attref2.attachment = rpD->dsResolveRef.attachment;
1807 attref2.layout = rpD->dsResolveRef.layout;
1808 attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
1809 dsResolveDesc.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR;
1810 dsResolveDesc.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
1811 dsResolveDesc.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
1812 dsResolveDesc.pDepthStencilResolveAttachment = attRefs2.constData() + startIndex;
1813 addToChain(&subpass2, &dsResolveDesc);
1814 }
1815#endif
1816
1817#ifdef VK_KHR_fragment_shading_rate
1818 shadingRateAttInfo = {};
1819 if (rpD->hasShadingRateMap) {
1820 startIndex = attRefs2.count();
1821 attRefs2.append({});
1822 VkAttachmentReference2KHR &attref2(attRefs2.last());
1823 attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
1824 attref2.attachment = rpD->shadingRateRef.attachment;
1825 attref2.layout = rpD->shadingRateRef.layout;
1826 shadingRateAttInfo.sType = VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR;
1827 shadingRateAttInfo.pFragmentShadingRateAttachment = attRefs2.constData() + startIndex;
1828 shadingRateAttInfo.shadingRateAttachmentTexelSize.width = rhiD->caps.imageBasedShadingRateTileSize;
1829 shadingRateAttInfo.shadingRateAttachmentTexelSize.height = rhiD->caps.imageBasedShadingRateTileSize;
1830 addToChain(&subpass2, &shadingRateAttInfo);
1831 }
1832#endif
1833
1834 // subpass dependencies, typically 0, 1, 2 of them,
1835 // depending on targeting swapchain or texture
1836 subpassDeps2.clear();
1837 for (uint32_t i = 0; i < rpInfo->dependencyCount; ++i) {
1838 const VkSubpassDependency &dep(rpInfo->pDependencies[i]);
1839 subpassDeps2.append({});
1840 VkSubpassDependency2 &dep2(subpassDeps2.last());
1841 dep2.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2_KHR;
1842 dep2.srcSubpass = dep.srcSubpass;
1843 dep2.dstSubpass = dep.dstSubpass;
1844 dep2.srcStageMask = dep.srcStageMask;
1845 dep2.dstStageMask = dep.dstStageMask;
1846 dep2.srcAccessMask = dep.srcAccessMask;
1847 dep2.dstAccessMask = dep.dstAccessMask;
1848 dep2.dependencyFlags = dep.dependencyFlags;
1849 }
1850
1851 rpInfo2->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
1852 rpInfo2->pNext = nullptr; // the 1.1 VkRenderPassMultiviewCreateInfo is part of the '2' structs
1853 rpInfo2->flags = rpInfo->flags;
1854 rpInfo2->attachmentCount = rpInfo->attachmentCount;
1855 rpInfo2->pAttachments = attDescs2.constData();
1856 rpInfo2->subpassCount = 1;
1857 rpInfo2->pSubpasses = &subpass2;
1858 rpInfo2->dependencyCount = subpassDeps2.count();
1859 rpInfo2->pDependencies = !subpassDeps2.isEmpty() ? subpassDeps2.constData() : nullptr;
1860 if (multiViewCount >= 2) {
1861 rpInfo2->correlatedViewMaskCount = 1;
1862 rpInfo2->pCorrelatedViewMasks = &viewMask;
1863 }
1864 return true;
1865 }
1866
1867 QRhiVulkan *rhiD;
1868 QVarLengthArray<VkAttachmentDescription2KHR, 8> attDescs2;
1869 QVarLengthArray<VkAttachmentReference2KHR, 8> attRefs2;
1870 VkSubpassDescription2KHR subpass2;
1871 QVarLengthArray<VkSubpassDependency2KHR, 4> subpassDeps2;
1872#ifdef VK_KHR_depth_stencil_resolve
1873 VkSubpassDescriptionDepthStencilResolveKHR dsResolveDesc;
1874#endif
1875#ifdef VK_KHR_fragment_shading_rate
1876 VkFragmentShadingRateAttachmentInfoKHR shadingRateAttInfo;
1877#endif
1878 uint32_t viewMask;
1879};
1880#endif // VK_KHR_create_renderpass2
1881
1882static void fillRenderPassCreateInfo(VkRenderPassCreateInfo *rpInfo,
1883 VkSubpassDescription *subpassDesc,
1885{
1886 memset(subpassDesc, 0, sizeof(VkSubpassDescription));
1887 subpassDesc->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1888 subpassDesc->colorAttachmentCount = uint32_t(rpD->colorRefs.size());
1889 subpassDesc->pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr;
1890 subpassDesc->pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr;
1891 subpassDesc->pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr;
1892
1893 memset(rpInfo, 0, sizeof(VkRenderPassCreateInfo));
1894 rpInfo->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1895 rpInfo->attachmentCount = uint32_t(rpD->attDescs.size());
1896 rpInfo->pAttachments = rpD->attDescs.constData();
1897 rpInfo->subpassCount = 1;
1898 rpInfo->pSubpasses = subpassDesc;
1899 rpInfo->dependencyCount = uint32_t(rpD->subpassDeps.size());
1900 rpInfo->pDependencies = !rpD->subpassDeps.isEmpty() ? rpD->subpassDeps.constData() : nullptr;
1901}
1902
1904 bool hasDepthStencil,
1905 VkSampleCountFlagBits samples,
1906 VkFormat colorFormat,
1907 QRhiShadingRateMap *shadingRateMap)
1908{
1909 // attachment list layout is color (1), ds (0-1), resolve (0-1), shading rate (0-1)
1910
1911 VkAttachmentDescription attDesc = {};
1912 attDesc.format = colorFormat;
1913 attDesc.samples = samples;
1914 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1915 attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
1916 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1917 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1918 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1919 attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1920 rpD->attDescs.append(attDesc);
1921
1922 rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
1923
1924 rpD->hasDepthStencil = hasDepthStencil;
1925 rpD->hasDepthStencilResolve = false;
1926 rpD->hasShadingRateMap = shadingRateMap != nullptr;
1927 rpD->multiViewCount = 0;
1928
1929 if (hasDepthStencil) {
1930 // clear on load + no store + lazy alloc + transient image should play
1931 // nicely with tiled GPUs (no physical backing necessary for ds buffer)
1932 attDesc = {};
1933 attDesc.format = optimalDepthStencilFormat();
1934 attDesc.samples = samples;
1935 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1936 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1937 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1938 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1939 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1940 attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1941 rpD->attDescs.append(attDesc);
1942
1943 rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
1944 } else {
1945 rpD->dsRef = {};
1946 }
1947
1948 if (samples > VK_SAMPLE_COUNT_1_BIT) {
1949 attDesc = {};
1950 attDesc.format = colorFormat;
1951 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
1952 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1953 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1954 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1955 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1956 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1957 attDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1958 rpD->attDescs.append(attDesc);
1959
1960 rpD->resolveRefs.append({ uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
1961 }
1962
1963 rpD->dsResolveRef = {};
1964
1965 rpD->shadingRateRef = {};
1966#ifdef VK_KHR_fragment_shading_rate
1967 if (shadingRateMap) {
1968 attDesc = {};
1969 attDesc.format = VK_FORMAT_R8_UINT;
1970 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
1971 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
1972 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1973 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1974 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1975 attDesc.initialLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
1976 attDesc.finalLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
1977 rpD->attDescs.append(attDesc);
1978
1979 rpD->shadingRateRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR };
1980 }
1981#endif
1982
1983 // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own.
1984 VkSubpassDependency subpassDep = {};
1985 subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL;
1986 subpassDep.dstSubpass = 0;
1987 subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1988 subpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1989 subpassDep.srcAccessMask = 0;
1990 subpassDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1991 rpD->subpassDeps.append(subpassDep);
1992 if (hasDepthStencil) {
1993 memset(&subpassDep, 0, sizeof(subpassDep));
1994 subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL;
1995 subpassDep.dstSubpass = 0;
1996 subpassDep.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
1997 | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
1998 subpassDep.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
1999 | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
2000 subpassDep.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
2001 subpassDep.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
2002 | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
2003 rpD->subpassDeps.append(subpassDep);
2004 }
2005
2006 VkRenderPassCreateInfo rpInfo;
2007 VkSubpassDescription subpassDesc;
2008 fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD);
2009
2010#ifdef VK_KHR_create_renderpass2
2011 if (caps.renderPass2KHR) {
2012 // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1.
2013 VkRenderPassCreateInfo2KHR rpInfo2;
2014 RenderPass2SetupHelper rp2Helper(this);
2015 if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, 0))
2016 return false;
2017 VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp);
2018 if (err != VK_SUCCESS) {
2019 qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err);
2020 return false;
2021 }
2022 } else
2023#endif
2024 {
2025 if (rpD->hasShadingRateMap)
2026 qWarning("Variable rate shading with image is not supported without VK_KHR_create_renderpass2");
2027 VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
2028 if (err != VK_SUCCESS) {
2029 qWarning("Failed to create renderpass: %d", err);
2030 return false;
2031 }
2032 }
2033
2034 return true;
2035}
2036
2038 const QRhiColorAttachment *colorAttachmentsBegin,
2039 const QRhiColorAttachment *colorAttachmentsEnd,
2040 bool preserveColor,
2041 bool preserveDs,
2042 bool storeDs,
2043 QRhiRenderBuffer *depthStencilBuffer,
2044 QRhiTexture *depthTexture,
2045 QRhiTexture *depthResolveTexture,
2046 QRhiShadingRateMap *shadingRateMap)
2047{
2048 // attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1)
2049
2050 int multiViewCount = 0;
2051 for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) {
2052 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
2053 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
2054 Q_ASSERT(texD || rbD);
2055 const VkFormat vkformat = texD ? texD->viewFormat : rbD->vkformat;
2056 const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples;
2057
2058 VkAttachmentDescription attDesc = {};
2059 attDesc.format = vkformat;
2060 attDesc.samples = samples;
2061 attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
2062 attDesc.storeOp = (it->resolveTexture() && !preserveColor) ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
2063 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
2064 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
2065 // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT
2066 attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
2067 attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2068 rpD->attDescs.append(attDesc);
2069
2070 const VkAttachmentReference ref = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
2071 rpD->colorRefs.append(ref);
2072
2073 if (it->multiViewCount() >= 2) {
2074 if (multiViewCount > 0 && multiViewCount != it->multiViewCount())
2075 qWarning("Inconsistent multiViewCount in color attachment set");
2076 else
2077 multiViewCount = it->multiViewCount();
2078 } else if (multiViewCount > 0) {
2079 qWarning("Mixing non-multiview color attachments within a multiview render pass");
2080 }
2081 }
2082 Q_ASSERT(multiViewCount == 0 || multiViewCount >= 2);
2083 rpD->multiViewCount = uint32_t(multiViewCount);
2084
2085 rpD->hasDepthStencil = depthStencilBuffer || depthTexture;
2086 if (rpD->hasDepthStencil) {
2087 const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->viewFormat
2088 : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat;
2089 const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples
2090 : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples;
2091 const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
2092 const VkAttachmentStoreOp storeOp = storeDs ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
2093 VkAttachmentDescription attDesc = {};
2094 attDesc.format = dsFormat;
2095 attDesc.samples = samples;
2096 attDesc.loadOp = loadOp;
2097 attDesc.storeOp = storeOp;
2098 attDesc.stencilLoadOp = loadOp;
2099 attDesc.stencilStoreOp = storeOp;
2100 attDesc.initialLayout = preserveDs ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
2101 attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
2102 rpD->attDescs.append(attDesc);
2103 if (depthTexture && depthTexture->arraySize() >= 2 && colorAttachmentsBegin == colorAttachmentsEnd) {
2104 multiViewCount = depthTexture->arraySize();
2105 rpD->multiViewCount = multiViewCount;
2106 }
2107 rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
2108 } else {
2109 rpD->dsRef = {};
2110 }
2111
2112 for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) {
2113 if (it->resolveTexture()) {
2114 QVkTexture *rtexD = QRHI_RES(QVkTexture, it->resolveTexture());
2115 const VkFormat dstFormat = rtexD->vkformat;
2116 if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
2117 qWarning("Resolving into a multisample texture is not supported");
2118
2119 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
2120 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
2121 const VkFormat srcFormat = texD ? texD->vkformat : rbD->vkformat;
2122 if (srcFormat != dstFormat) {
2123 // This is a validation error. But some implementations survive,
2124 // actually. Warn about it however, because it's an error with
2125 // some other backends (like D3D) as well.
2126 qWarning("Multisample resolve between different formats (%d and %d) is not supported.",
2127 int(srcFormat), int(dstFormat));
2128 }
2129
2130 VkAttachmentDescription attDesc = {};
2131 attDesc.format = rtexD->viewFormat;
2132 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
2133 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
2134 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
2135 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
2136 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
2137 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2138 attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2139 rpD->attDescs.append(attDesc);
2140
2141 const VkAttachmentReference ref = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
2142 rpD->resolveRefs.append(ref);
2143 } else {
2144 const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
2145 rpD->resolveRefs.append(ref);
2146 }
2147 }
2148 Q_ASSERT(rpD->colorRefs.size() == rpD->resolveRefs.size());
2149
2150 rpD->hasDepthStencilResolve = rpD->hasDepthStencil && depthResolveTexture;
2151 if (rpD->hasDepthStencilResolve) {
2152 QVkTexture *rtexD = QRHI_RES(QVkTexture, depthResolveTexture);
2153 if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
2154 qWarning("Resolving into a multisample depth texture is not supported");
2155
2156 QVkTexture *texD = QRHI_RES(QVkTexture, depthResolveTexture);
2157 if (texD->vkformat != rtexD->vkformat) {
2158 qWarning("Multisample resolve between different depth-stencil formats (%d and %d) is not supported.",
2159 int(texD->vkformat), int(rtexD->vkformat));
2160 }
2161
2162 VkAttachmentDescription attDesc = {};
2163 attDesc.format = rtexD->viewFormat;
2164 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
2165 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
2166 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
2167 attDesc.stencilLoadOp = attDesc.loadOp;
2168 attDesc.stencilStoreOp = attDesc.storeOp;
2169 attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2170 attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
2171 rpD->attDescs.append(attDesc);
2172 rpD->dsResolveRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
2173 } else {
2174 rpD->dsResolveRef = {};
2175 }
2176
2177 rpD->hasShadingRateMap = shadingRateMap != nullptr;
2178 rpD->shadingRateRef = {};
2179#ifdef VK_KHR_fragment_shading_rate
2180 if (shadingRateMap) {
2181 VkAttachmentDescription attDesc = {};
2182 attDesc.format = VK_FORMAT_R8_UINT;
2183 attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
2184 attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
2185 attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
2186 attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
2187 attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
2188 attDesc.initialLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
2189 attDesc.finalLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
2190 rpD->attDescs.append(attDesc);
2191 rpD->shadingRateRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR };
2192 }
2193#endif
2194
2195 // Add self-dependency to be able to add memory barriers for writes in graphics stages
2196 VkSubpassDependency selfDependency;
2197 VkPipelineStageFlags stageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
2198 selfDependency.srcSubpass = 0;
2199 selfDependency.dstSubpass = 0;
2200 selfDependency.srcStageMask = stageMask;
2201 selfDependency.dstStageMask = stageMask;
2202 selfDependency.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
2203 selfDependency.dstAccessMask = selfDependency.srcAccessMask;
2204#ifdef VK_VERSION_1_1
2205 selfDependency.dependencyFlags = rpD->multiViewCount >= 2 ? VK_DEPENDENCY_VIEW_LOCAL_BIT : 0;
2206#else
2207 selfDependency.dependencyFlags = 0;
2208#endif
2209 rpD->subpassDeps.append(selfDependency);
2210
2211 // rpD->subpassDeps stays empty: don't yet know the correct initial/final
2212 // access and stage stuff for the implicit deps at this point, so leave it
2213 // to the resource tracking and activateTextureRenderTarget() to generate
2214 // barriers.
2215
2216 VkRenderPassCreateInfo rpInfo;
2217 VkSubpassDescription subpassDesc;
2218 fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD);
2219
2220 MultiViewRenderPassSetupHelper multiViewHelper;
2221 if (!multiViewHelper.prepare(&rpInfo, multiViewCount, caps.multiView))
2222 return false;
2223
2224#ifdef VK_KHR_create_renderpass2
2225 if (caps.renderPass2KHR) {
2226 // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1.
2227 VkRenderPassCreateInfo2KHR rpInfo2;
2228 RenderPass2SetupHelper rp2Helper(this);
2229 if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount))
2230 return false;
2231
2232 VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp);
2233 if (err != VK_SUCCESS) {
2234 qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err);
2235 return false;
2236 }
2237 } else
2238#endif
2239 {
2240 if (rpD->hasDepthStencilResolve) {
2241 qWarning("Resolving multisample depth-stencil buffers is not supported without "
2242 "VK_KHR_depth_stencil_resolve and VK_KHR_create_renderpass2");
2243 }
2244 if (rpD->hasShadingRateMap)
2245 qWarning("Variable rate shading with image is not supported without VK_KHR_create_renderpass2");
2246 VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
2247 if (err != VK_SUCCESS) {
2248 qWarning("Failed to create renderpass: %d", err);
2249 return false;
2250 }
2251 }
2252
2253 return true;
2254}
2255
2256bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
2257{
2258 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
2259 if (swapChainD->pixelSize.isEmpty()) {
2260 qWarning("Surface size is 0, cannot create swapchain");
2261 return false;
2262 }
2263
2264 df->vkDeviceWaitIdle(dev);
2265
2266 if (!vkCreateSwapchainKHR) {
2267 vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
2268 vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
2269 vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
2270 vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
2271 vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
2272 if (!vkCreateSwapchainKHR || !vkDestroySwapchainKHR || !vkGetSwapchainImagesKHR || !vkAcquireNextImageKHR || !vkQueuePresentKHR) {
2273 qWarning("Swapchain functions not available");
2274 return false;
2275 }
2276 }
2277
2278 VkSurfaceCapabilitiesKHR surfaceCaps;
2279 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, swapChainD->surface, &surfaceCaps);
2280 quint32 reqBufferCount;
2281 if (swapChainD->m_flags.testFlag(QRhiSwapChain::MinimalBufferCount) || surfaceCaps.maxImageCount == 0) {
2282 reqBufferCount = qMax<quint32>(2, surfaceCaps.minImageCount);
2283 } else {
2284 reqBufferCount = qMax(qMin<quint32>(surfaceCaps.maxImageCount, 3), surfaceCaps.minImageCount);
2285 }
2286 VkSurfaceTransformFlagBitsKHR preTransform =
2287 (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
2288 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
2289 : surfaceCaps.currentTransform;
2290
2291 // This looks odd but matches how platforms work in practice.
2292 //
2293 // On Windows with NVIDIA for example, the only supportedCompositeAlpha
2294 // value reported is OPAQUE, nothing else. Yet transparency works
2295 // regardless, as long as the native window is set up correctly, so that's
2296 // not something we need to handle here.
2297 //
2298 // On Linux with Intel and Mesa and running on xcb reports, on one
2299 // particular system, INHERIT+PRE_MULTIPLIED. Tranparency works, regardless,
2300 // presumably due to setting INHERIT.
2301 //
2302 // On the same setup with Wayland instead of xcb we see
2303 // OPAQUE+PRE_MULTIPLIED reported. Here transparency won't work unless
2304 // PRE_MULTIPLIED is set.
2305 //
2306 // Therefore our rules are:
2307 // - Prefer INHERIT over OPAQUE.
2308 // - Then based on the request, try the requested alpha mode, but if
2309 // that's not reported as supported, try also the other (PRE/POST,
2310 // POST/PRE) as that is better than nothing. This is not different from
2311 // some other backends, e.g. D3D11 with DirectComposition there is also
2312 // no control over being straight or pre-multiplied. Whereas with
2313 // WGL/GLX/EGL we never had that sort of control.
2314
2315 VkCompositeAlphaFlagBitsKHR compositeAlpha =
2316 (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
2317 ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
2318 : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
2319
2320 if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasPreMulAlpha)) {
2321 if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
2322 compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
2323 else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
2324 compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
2325 } else if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasNonPreMulAlpha)) {
2326 if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
2327 compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
2328 else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
2329 compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
2330 }
2331
2332 VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
2333 swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
2334 if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource))
2335 usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
2336
2337 const bool stereo = bool(swapChainD->m_window) && (swapChainD->m_window->format().stereo())
2338 && surfaceCaps.maxImageArrayLayers > 1;
2339 swapChainD->stereo = stereo;
2340
2341 VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
2342 if (swapChainD->m_flags.testFlag(QRhiSwapChain::NoVSync)) {
2343 // Stereo has a weird bug, when using VK_PRESENT_MODE_MAILBOX_KHR,
2344 // black screen is shown, but there is no validation error.
2345 // Detected on Windows, with NVidia RTX A series (at least 4000 and 6000) driver 535.98
2346 if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR) && !stereo)
2347 presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
2348 else if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
2349 presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
2350 }
2351
2352 // If the surface is different than before, then passing in the old
2353 // swapchain associated with the old surface can fail the swapchain
2354 // creation. (for example, Android loses the surface when backgrounding and
2355 // restoring applications, and it also enforces failing swapchain creation
2356 // with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR if the old swapchain is provided)
2357 const bool reuseExisting = swapChainD->sc && swapChainD->lastConnectedSurface == swapChainD->surface;
2358
2359 qCDebug(QRHI_LOG_INFO, "Creating %s swapchain of %u buffers, size %dx%d, presentation mode %d",
2360 reuseExisting ? "recycled" : "new",
2361 reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode);
2362
2363 VkSwapchainCreateInfoKHR swapChainInfo = {};
2364 swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
2365 swapChainInfo.surface = swapChainD->surface;
2366 swapChainInfo.minImageCount = reqBufferCount;
2367 swapChainInfo.imageFormat = swapChainD->colorFormat;
2368 swapChainInfo.imageColorSpace = swapChainD->colorSpace;
2369 swapChainInfo.imageExtent = VkExtent2D { uint32_t(swapChainD->pixelSize.width()), uint32_t(swapChainD->pixelSize.height()) };
2370 swapChainInfo.imageArrayLayers = stereo ? 2u : 1u;
2371 swapChainInfo.imageUsage = usage;
2372 swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
2373 swapChainInfo.preTransform = preTransform;
2374 swapChainInfo.compositeAlpha = compositeAlpha;
2375 swapChainInfo.presentMode = presentMode;
2376 swapChainInfo.clipped = true;
2377 swapChainInfo.oldSwapchain = reuseExisting ? swapChainD->sc : VK_NULL_HANDLE;
2378
2379 VkSwapchainKHR newSwapChain;
2380 VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
2381 if (err != VK_SUCCESS) {
2382 qWarning("Failed to create swapchain: %d", err);
2383 return false;
2384 }
2385 setObjectName(uint64_t(newSwapChain), VK_OBJECT_TYPE_SWAPCHAIN_KHR, swapChainD->m_objectName);
2386
2387 if (swapChainD->sc)
2389
2390 swapChainD->sc = newSwapChain;
2391 swapChainD->lastConnectedSurface = swapChainD->surface;
2392
2393 quint32 actualSwapChainBufferCount = 0;
2394 err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, nullptr);
2395 if (err != VK_SUCCESS || actualSwapChainBufferCount == 0) {
2396 qWarning("Failed to get swapchain images: %d", err);
2397 return false;
2398 }
2399
2400 if (actualSwapChainBufferCount != reqBufferCount)
2401 qCDebug(QRHI_LOG_INFO, "Actual swapchain buffer count is %u", actualSwapChainBufferCount);
2402 swapChainD->bufferCount = int(actualSwapChainBufferCount);
2403
2404 QVarLengthArray<VkImage, QVkSwapChain::EXPECTED_MAX_BUFFER_COUNT> swapChainImages(actualSwapChainBufferCount);
2405 err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, swapChainImages.data());
2406 if (err != VK_SUCCESS) {
2407 qWarning("Failed to get swapchain images: %d", err);
2408 return false;
2409 }
2410
2411 QVarLengthArray<VkImage, QVkSwapChain::EXPECTED_MAX_BUFFER_COUNT> msaaImages(swapChainD->bufferCount);
2412 QVarLengthArray<VkImageView, QVkSwapChain::EXPECTED_MAX_BUFFER_COUNT> msaaViews(swapChainD->bufferCount);
2413 if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
2414 if (!createTransientImage(swapChainD->colorFormat,
2415 swapChainD->pixelSize,
2416 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
2417 VK_IMAGE_ASPECT_COLOR_BIT,
2418 swapChainD->samples,
2419 &swapChainD->msaaImageMem,
2420 msaaImages.data(),
2421 msaaViews.data(),
2422 swapChainD->bufferCount))
2423 {
2424 qWarning("Failed to create transient image for MSAA color buffer");
2425 return false;
2426 }
2427 }
2428
2429 VkFenceCreateInfo fenceInfo = {};
2430 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
2431 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
2432
2433 // Double up for stereo
2434 swapChainD->imageRes.resize(swapChainD->bufferCount * (stereo ? 2u : 1u));
2435
2436 for (int i = 0; i < swapChainD->bufferCount; ++i) {
2437 QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
2438 image.image = swapChainImages[i];
2439 if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
2440 image.msaaImage = msaaImages[i];
2441 image.msaaImageView = msaaViews[i];
2442 }
2443
2444 VkImageViewCreateInfo imgViewInfo = {};
2445 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2446 imgViewInfo.image = swapChainImages[i];
2447 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2448 imgViewInfo.format = swapChainD->colorFormat;
2449 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
2450 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
2451 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
2452 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
2453 imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2454 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
2455 err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
2456 if (err != VK_SUCCESS) {
2457 qWarning("Failed to create swapchain image view %d: %d", i, err);
2458 return false;
2459 }
2460
2462
2463 VkSemaphoreCreateInfo semInfo = {};
2464 semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
2465 df->vkCreateSemaphore(dev, &semInfo, nullptr, &image.drawSem);
2466 }
2467 if (stereo) {
2468 for (int i = 0; i < swapChainD->bufferCount; ++i) {
2469 QVkSwapChain::ImageResources &image(swapChainD->imageRes[i + swapChainD->bufferCount]);
2470 image.image = swapChainImages[i];
2471 if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
2472 image.msaaImage = msaaImages[i];
2473 image.msaaImageView = msaaViews[i];
2474 }
2475
2476 VkImageViewCreateInfo imgViewInfo = {};
2477 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2478 imgViewInfo.image = swapChainImages[i];
2479 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2480 imgViewInfo.format = swapChainD->colorFormat;
2481 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
2482 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
2483 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
2484 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
2485 imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2486 imgViewInfo.subresourceRange.baseArrayLayer = 1;
2487 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
2488 err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
2489 if (err != VK_SUCCESS) {
2490 qWarning("Failed to create swapchain image view %d: %d", i, err);
2491 return false;
2492 }
2493
2494 VkSemaphoreCreateInfo semInfo = {};
2495 semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
2496 df->vkCreateSemaphore(dev, &semInfo, nullptr, &image.drawSem);
2497
2499 }
2500 }
2501
2502 swapChainD->currentImageIndex = 0;
2503
2504 if (swapChainD->shadingRateMap() && caps.renderPass2KHR && caps.imageBasedShadingRate) {
2505 QVkTexture *texD = QRHI_RES(QVkShadingRateMap, swapChainD->shadingRateMap())->texture;
2506 Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedAsShadingRateMap));
2507 VkImageViewCreateInfo viewInfo = {};
2508 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2509 viewInfo.image = texD->image;
2510 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2511 viewInfo.format = texD->viewFormat;
2512 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
2513 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
2514 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
2515 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
2516 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2517 viewInfo.subresourceRange.baseMipLevel = 0;
2518 viewInfo.subresourceRange.levelCount = 1;
2519 viewInfo.subresourceRange.baseArrayLayer = 0;
2520 viewInfo.subresourceRange.layerCount = 1;
2521 VkResult err = df->vkCreateImageView(dev, &viewInfo, nullptr, &swapChainD->shadingRateMapView);
2522 if (err != VK_SUCCESS) {
2523 qWarning("Failed to create swapchain shading rate map view: %d", err);
2524 return false;
2525 }
2526 }
2527
2528 VkSemaphoreCreateInfo semInfo = {};
2529 semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
2530
2531 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
2532 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
2533
2534 frame.imageAcquired = false;
2535 frame.imageSemWaitable = false;
2536
2537 df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
2538
2539 err = df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
2540 if (err != VK_SUCCESS) {
2541 qWarning("Failed to create command buffer fence: %d", err);
2542 return false;
2543 }
2544 frame.cmdFenceWaitable = true; // fence was created in signaled state
2545 }
2546
2547 swapChainD->currentFrameSlot = 0;
2548
2549 return true;
2550}
2551
2552void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain)
2553{
2554 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
2555
2556 if (swapChainD->sc == VK_NULL_HANDLE)
2557 return;
2558
2559 if (!deviceLost)
2560 df->vkDeviceWaitIdle(dev);
2561
2562 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
2563 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
2564 if (frame.cmdFence) {
2565 if (!deviceLost && frame.cmdFenceWaitable)
2566 df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
2567 df->vkDestroyFence(dev, frame.cmdFence, nullptr);
2568 frame.cmdFence = VK_NULL_HANDLE;
2569 frame.cmdFenceWaitable = false;
2570 }
2571 if (frame.imageSem) {
2572 df->vkDestroySemaphore(dev, frame.imageSem, nullptr);
2573 frame.imageSem = VK_NULL_HANDLE;
2574 }
2575 }
2576
2577 for (int i = 0; i < swapChainD->bufferCount * (swapChainD->stereo ? 2 : 1); ++i) {
2578 QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
2579 if (image.fb) {
2580 df->vkDestroyFramebuffer(dev, image.fb, nullptr);
2581 image.fb = VK_NULL_HANDLE;
2582 }
2583 if (image.imageView) {
2584 df->vkDestroyImageView(dev, image.imageView, nullptr);
2585 image.imageView = VK_NULL_HANDLE;
2586 }
2587 if (image.msaaImageView) {
2588 df->vkDestroyImageView(dev, image.msaaImageView, nullptr);
2589 image.msaaImageView = VK_NULL_HANDLE;
2590 }
2591 if (image.msaaImage) {
2592 df->vkDestroyImage(dev, image.msaaImage, nullptr);
2593 image.msaaImage = VK_NULL_HANDLE;
2594 }
2595 if (image.drawSem) {
2596 df->vkDestroySemaphore(dev, image.drawSem, nullptr);
2597 image.drawSem = VK_NULL_HANDLE;
2598 }
2599 }
2600
2601 if (swapChainD->msaaImageMem) {
2602 df->vkFreeMemory(dev, swapChainD->msaaImageMem, nullptr);
2603 swapChainD->msaaImageMem = VK_NULL_HANDLE;
2604 }
2605
2606 if (swapChainD->shadingRateMapView) {
2607 df->vkDestroyImageView(dev, swapChainD->shadingRateMapView, nullptr);
2608 swapChainD->shadingRateMapView = VK_NULL_HANDLE;
2609 }
2610
2611 vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr);
2612 swapChainD->sc = VK_NULL_HANDLE;
2613
2614 // NB! surface and similar must remain intact
2615}
2616
2618{
2619 VkCommandPoolResetFlags flags = 0;
2620
2621 // While not clear what "recycles all of the resources from the command
2622 // pool back to the system" really means in practice, set it when there was
2623 // a call to releaseCachedResources() recently.
2624 if (releaseCachedResourcesCalledBeforeFrameStart)
2625 flags |= VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT;
2626
2627 // put all command buffers allocated from this slot's pool to initial state
2628 df->vkResetCommandPool(dev, cmdPool[currentFrameSlot], flags);
2629}
2630
2631double QRhiVulkan::elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok)
2632{
2633 quint64 mask = 0;
2634 for (quint64 i = 0; i < timestampValidBits; i += 8)
2635 mask |= 0xFFULL << i;
2636 const quint64 ts0 = timestamp[0] & mask;
2637 const quint64 ts1 = timestamp[1] & mask;
2638 const float nsecsPerTick = physDevProperties.limits.timestampPeriod;
2639 if (!qFuzzyIsNull(nsecsPerTick)) {
2640 const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f;
2641 const double elapsedSec = elapsedMs / 1000.0;
2642 *ok = true;
2643 return elapsedSec;
2644 }
2645 *ok = false;
2646 return 0;
2647}
2648
2650{
2651 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
2652 const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0;
2653 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]);
2654
2655 inst->handle()->beginFrame(swapChainD->window);
2656
2657 // Make sure the previous commands for the same frame slot have finished.
2658 //
2659 // Do this also for any other swapchain's commands with the same frame slot
2660 // While this reduces concurrency, it keeps resource usage safe: swapchain
2661 // A starting its frame 0, followed by swapchain B starting its own frame 0
2662 // will make B wait for A's frame 0 commands, so if a resource is written
2663 // in B's frame or when B checks for pending resource releases, that won't
2664 // mess up A's in-flight commands (as they are not in flight anymore).
2665 QRhi::FrameOpResult waitResult = waitCommandCompletion(frameResIndex);
2666 if (waitResult != QRhi::FrameOpSuccess)
2667 return waitResult;
2668
2669 if (!frame.imageAcquired) {
2670 // move on to next swapchain image
2671 uint32_t imageIndex = 0;
2672 VkResult err = vkAcquireNextImageKHR(dev, swapChainD->sc, UINT64_MAX,
2673 frame.imageSem, VK_NULL_HANDLE, &imageIndex);
2674
2675 if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
2676 swapChainD->currentImageIndex = imageIndex;
2677 frame.imageSemWaitable = true;
2678 frame.imageAcquired = true;
2679 } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
2680 return QRhi::FrameOpSwapChainOutOfDate;
2681 } else {
2682 if (err == VK_ERROR_DEVICE_LOST) {
2683 qWarning("Device loss detected in vkAcquireNextImageKHR()");
2684 printExtraErrorInfo(err);
2685 deviceLost = true;
2686 return QRhi::FrameOpDeviceLost;
2687 }
2688 qWarning("Failed to acquire next swapchain image: %d", err);
2689 return QRhi::FrameOpError;
2690 }
2691 }
2692
2693 currentFrameSlot = int(swapChainD->currentFrameSlot);
2694 currentSwapChain = swapChainD;
2695 if (swapChainD->ds)
2696 swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
2697
2698 // reset the command pool
2700
2701 // start recording to this frame's command buffer
2702 QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(&frame.cmdBuf);
2703 if (cbres != QRhi::FrameOpSuccess)
2704 return cbres;
2705
2706 swapChainD->cbWrapper.cb = frame.cmdBuf;
2707
2708 QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
2709 swapChainD->rtWrapper.d.fb = image.fb;
2710
2711 if (swapChainD->stereo) {
2713 swapChainD->imageRes[swapChainD->currentImageIndex + swapChainD->bufferCount]);
2714 swapChainD->rtWrapperRight.d.fb = image.fb;
2715 }
2716
2717 prepareNewFrame(&swapChainD->cbWrapper);
2718
2719 // Read the timestamps for the previous frame for this slot.
2720 if (frame.timestampQueryIndex >= 0) {
2721 quint64 timestamp[2] = { 0, 0 };
2722 VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2,
2723 2 * sizeof(quint64), timestamp, sizeof(quint64),
2724 VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
2725 timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2);
2726 frame.timestampQueryIndex = -1;
2727 if (err == VK_SUCCESS) {
2728 bool ok = false;
2729 const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, &ok);
2730 if (ok)
2731 swapChainD->cbWrapper.lastGpuTime = elapsedSec;
2732 } else {
2733 qWarning("Failed to query timestamp: %d", err);
2734 }
2735 }
2736
2737 // No timestamps if the client did not opt in, or when not having at least 2 frames in flight.
2738 if (rhiFlags.testFlag(QRhi::EnableTimestamps) && swapChainD->bufferCount > 1) {
2739 int timestampQueryIdx = -1;
2740 for (int i = 0; i < timestampQueryPoolMap.size(); ++i) {
2741 if (!timestampQueryPoolMap.testBit(i)) {
2742 timestampQueryPoolMap.setBit(i);
2743 timestampQueryIdx = i * 2;
2744 break;
2745 }
2746 }
2747 if (timestampQueryIdx >= 0) {
2748 df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2);
2749 // record timestamp at the start of the command buffer
2750 df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
2751 timestampQueryPool, uint32_t(timestampQueryIdx));
2752 frame.timestampQueryIndex = timestampQueryIdx;
2753 }
2754 }
2755
2756 return QRhi::FrameOpSuccess;
2757}
2758
2759QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
2760{
2761 QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
2762 Q_ASSERT(currentSwapChain == swapChainD);
2763
2764 auto cleanup = qScopeGuard([this, swapChainD] {
2765 inst->handle()->endFrame(swapChainD->window);
2766 });
2767
2768 recordPrimaryCommandBuffer(&swapChainD->cbWrapper);
2769
2770 int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0;
2771 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]);
2772 QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
2773
2775 VkImageMemoryBarrier presTrans = {};
2776 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2777 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
2778 presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2779 presTrans.image = image.image;
2780 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2781 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
2782
2784 // was not used at all (no render pass), just transition from undefined to presentable
2785 presTrans.srcAccessMask = 0;
2786 presTrans.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2787 df->vkCmdPipelineBarrier(frame.cmdBuf,
2788 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2789 0, 0, nullptr, 0, nullptr,
2790 1, &presTrans);
2792 // was used in a readback as transfer source, go back to presentable layout
2793 presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2794 presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
2795 df->vkCmdPipelineBarrier(frame.cmdBuf,
2796 VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2797 0, 0, nullptr, 0, nullptr,
2798 1, &presTrans);
2799 }
2801 }
2802
2803 // record another timestamp, when enabled
2804 if (frame.timestampQueryIndex >= 0) {
2805 df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
2806 timestampQueryPool, uint32_t(frame.timestampQueryIndex + 1));
2807 }
2808
2809 // stop recording and submit to the queue
2810 Q_ASSERT(!frame.cmdFenceWaitable);
2811 const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
2812 QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(frame.cmdBuf,
2813 frame.cmdFence,
2814 frame.imageSemWaitable ? &frame.imageSem : nullptr,
2815 needsPresent ? &image.drawSem : nullptr);
2816 if (submitres != QRhi::FrameOpSuccess)
2817 return submitres;
2818
2819 frame.imageSemWaitable = false;
2820 frame.cmdFenceWaitable = true;
2821
2822 if (needsPresent) {
2823 // add the Present to the queue
2824 VkPresentInfoKHR presInfo = {};
2825 presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
2826 presInfo.swapchainCount = 1;
2827 presInfo.pSwapchains = &swapChainD->sc;
2828 presInfo.pImageIndices = &swapChainD->currentImageIndex;
2829 waitSemaphoresForPresent.append(image.drawSem);
2830 presInfo.waitSemaphoreCount = uint32_t(waitSemaphoresForPresent.count());;
2831 presInfo.pWaitSemaphores = waitSemaphoresForPresent.constData();
2832
2833 // Do platform-specific WM notification. F.ex. essential on Wayland in
2834 // order to circumvent driver frame callbacks
2835 inst->presentAboutToBeQueued(swapChainD->window);
2836
2837 VkResult err = vkQueuePresentKHR(gfxQueue, &presInfo);
2838 waitSemaphoresForPresent.clear();
2839 if (err != VK_SUCCESS) {
2840 if (err == VK_ERROR_OUT_OF_DATE_KHR) {
2841 return QRhi::FrameOpSwapChainOutOfDate;
2842 } else if (err != VK_SUBOPTIMAL_KHR) {
2843 if (err == VK_ERROR_DEVICE_LOST) {
2844 qWarning("Device loss detected in vkQueuePresentKHR()");
2845 printExtraErrorInfo(err);
2846 deviceLost = true;
2847 return QRhi::FrameOpDeviceLost;
2848 }
2849 qWarning("Failed to present: %d", err);
2850 return QRhi::FrameOpError;
2851 }
2852 }
2853
2854 // Do platform-specific WM notification. F.ex. essential on X11 in
2855 // order to prevent glitches on resizing the window.
2856 inst->presentQueued(swapChainD->window);
2857
2858 // mark the current swapchain buffer as unused from our side
2859 frame.imageAcquired = false;
2860 // and move on to the next buffer
2861 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
2862 }
2863
2864 swapChainD->frameCount += 1;
2865 currentSwapChain = nullptr;
2866 return QRhi::FrameOpSuccess;
2867}
2868
2869void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb)
2870{
2871 // Now is the time to do things for frame N-F, where N is the current one,
2872 // F is QVK_FRAMES_IN_FLIGHT, because only here it is guaranteed that that
2873 // frame has completed on the GPU (due to the fence wait in beginFrame). To
2874 // decide if something is safe to handle now a simple "lastActiveFrameSlot
2875 // == currentFrameSlot" is sufficient (remember that e.g. with F==2
2876 // currentFrameSlot goes 0, 1, 0, 1, 0, ...)
2877 //
2878 // With multiple swapchains on the same QRhi things get more convoluted
2879 // (and currentFrameSlot strictly alternating is not true anymore) but
2880 // begin(Offscreen)Frame() blocks anyway waiting for its current frame
2881 // slot's previous commands to complete so this here is safe regardless.
2882
2884
2885 QRHI_RES(QVkCommandBuffer, cb)->resetState();
2886
2887 finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
2888
2890}
2891
2893{
2894 if (!*cb) {
2895 VkCommandBufferAllocateInfo cmdBufInfo = {};
2896 cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2897 cmdBufInfo.commandPool = cmdPool[currentFrameSlot];
2898 cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2899 cmdBufInfo.commandBufferCount = 1;
2900
2901 VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, cb);
2902 if (err != VK_SUCCESS) {
2903 if (err == VK_ERROR_DEVICE_LOST) {
2904 qWarning("Device loss detected in vkAllocateCommandBuffers()");
2905 printExtraErrorInfo(err);
2906 deviceLost = true;
2907 return QRhi::FrameOpDeviceLost;
2908 }
2909 qWarning("Failed to allocate frame command buffer: %d", err);
2910 return QRhi::FrameOpError;
2911 }
2912 }
2913
2914 VkCommandBufferBeginInfo cmdBufBeginInfo = {};
2915 cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2916
2917 VkResult err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo);
2918 if (err != VK_SUCCESS) {
2919 if (err == VK_ERROR_DEVICE_LOST) {
2920 qWarning("Device loss detected in vkBeginCommandBuffer()");
2921 printExtraErrorInfo(err);
2922 deviceLost = true;
2923 return QRhi::FrameOpDeviceLost;
2924 }
2925 qWarning("Failed to begin frame command buffer: %d", err);
2926 return QRhi::FrameOpError;
2927 }
2928
2929 return QRhi::FrameOpSuccess;
2930}
2931
2932QRhi::FrameOpResult QRhiVulkan::endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence,
2933 VkSemaphore *waitSem, VkSemaphore *signalSem)
2934{
2935 VkResult err = df->vkEndCommandBuffer(cb);
2936 if (err != VK_SUCCESS) {
2937 if (err == VK_ERROR_DEVICE_LOST) {
2938 qWarning("Device loss detected in vkEndCommandBuffer()");
2939 printExtraErrorInfo(err);
2940 deviceLost = true;
2941 return QRhi::FrameOpDeviceLost;
2942 }
2943 qWarning("Failed to end frame command buffer: %d", err);
2944 return QRhi::FrameOpError;
2945 }
2946
2947 VkSubmitInfo submitInfo = {};
2948 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2949 submitInfo.commandBufferCount = 1;
2950 submitInfo.pCommandBuffers = &cb;
2951
2952 if (waitSem)
2953 waitSemaphoresForQueueSubmit.append(*waitSem);
2954 if (signalSem)
2955 signalSemaphoresForQueueSubmit.append(*signalSem);
2956
2957 submitInfo.waitSemaphoreCount = uint32_t(waitSemaphoresForQueueSubmit.count());
2958 if (!waitSemaphoresForQueueSubmit.isEmpty()) {
2959 submitInfo.pWaitSemaphores = waitSemaphoresForQueueSubmit.constData();
2960 semaphoresWaitMasksForQueueSubmit.resize(waitSemaphoresForQueueSubmit.count(), VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
2961 submitInfo.pWaitDstStageMask = semaphoresWaitMasksForQueueSubmit.constData();
2962 }
2963 submitInfo.signalSemaphoreCount = uint32_t(signalSemaphoresForQueueSubmit.count());
2964 if (!signalSemaphoresForQueueSubmit.isEmpty()) {
2965 submitInfo.pSignalSemaphores = signalSemaphoresForQueueSubmit.constData();
2966 }
2967
2968 err = df->vkQueueSubmit(gfxQueue, 1, &submitInfo, cmdFence);
2969
2970 waitSemaphoresForQueueSubmit.clear();
2971 signalSemaphoresForQueueSubmit.clear();
2972
2973 if (err != VK_SUCCESS) {
2974 if (err == VK_ERROR_DEVICE_LOST) {
2975 qWarning("Device loss detected in vkQueueSubmit()");
2976 printExtraErrorInfo(err);
2977 deviceLost = true;
2978 return QRhi::FrameOpDeviceLost;
2979 }
2980 qWarning("Failed to submit to graphics queue: %d", err);
2981 return QRhi::FrameOpError;
2982 }
2983
2984 return QRhi::FrameOpSuccess;
2985}
2986
2988{
2989 for (QVkSwapChain *sc : std::as_const(swapchains)) {
2990 const int frameResIndex = sc->bufferCount > 1 ? frameSlot : 0;
2991 QVkSwapChain::FrameResources &frame(sc->frameRes[frameResIndex]);
2992 if (frame.cmdFenceWaitable) {
2993 VkResult err = df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
2994
2995 if (err != VK_SUCCESS) {
2996 if (err == VK_ERROR_DEVICE_LOST) {
2997 qWarning("Device loss detected in vkWaitForFences()");
2998 printExtraErrorInfo(err);
2999 deviceLost = true;
3000 return QRhi::FrameOpDeviceLost;
3001 }
3002 qWarning("Failed to wait for fence: %d", err);
3003 return QRhi::FrameOpError;
3004 }
3005
3006 df->vkResetFences(dev, 1, &frame.cmdFence);
3007 frame.cmdFenceWaitable = false;
3008 }
3009 }
3010
3011 return QRhi::FrameOpSuccess;
3012}
3013
3015{
3016 // Switch to the next slot manually. Swapchains do not know about this
3017 // which is good. So for example an onscreen, onscreen, offscreen,
3018 // onscreen, onscreen, onscreen sequence of frames leads to 0, 1, 0, 0, 1,
3019 // 0. (no strict alternation anymore) But this is not different from what
3020 // happens when multiple swapchains are involved. Offscreen frames are
3021 // synchronous anyway in the sense that they wait for execution to complete
3022 // in endOffscreenFrame, so no resources used in that frame are busy
3023 // anymore in the next frame.
3024
3025 currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
3026
3027 QRhi::FrameOpResult waitResult = waitCommandCompletion(currentFrameSlot);
3028 if (waitResult != QRhi::FrameOpSuccess)
3029 return waitResult;
3030
3032
3033 QVkCommandBuffer *cbWrapper = ofr.cbWrapper[currentFrameSlot];
3034 QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(&cbWrapper->cb);
3035 if (cbres != QRhi::FrameOpSuccess)
3036 return cbres;
3037
3038 prepareNewFrame(cbWrapper);
3039 ofr.active = true;
3040
3041 if (rhiFlags.testFlag(QRhi::EnableTimestamps)) {
3042 int timestampQueryIdx = -1;
3043 for (int i = 0; i < timestampQueryPoolMap.size(); ++i) {
3044 if (!timestampQueryPoolMap.testBit(i)) {
3045 timestampQueryPoolMap.setBit(i);
3046 timestampQueryIdx = i * 2;
3047 break;
3048 }
3049 }
3050 if (timestampQueryIdx >= 0) {
3051 df->vkCmdResetQueryPool(cbWrapper->cb, timestampQueryPool, uint32_t(timestampQueryIdx), 2);
3052 // record timestamp at the start of the command buffer
3053 df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
3054 timestampQueryPool, uint32_t(timestampQueryIdx));
3055 ofr.timestampQueryIndex = timestampQueryIdx;
3056 }
3057 }
3058
3059 *cb = cbWrapper;
3060 return QRhi::FrameOpSuccess;
3061}
3062
3064{
3065 Q_UNUSED(flags);
3066 Q_ASSERT(ofr.active);
3067 ofr.active = false;
3068
3069 QVkCommandBuffer *cbWrapper(ofr.cbWrapper[currentFrameSlot]);
3071
3072 // record another timestamp, when enabled
3073 if (ofr.timestampQueryIndex >= 0) {
3074 df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
3075 timestampQueryPool, uint32_t(ofr.timestampQueryIndex + 1));
3076 }
3077
3078 if (!ofr.cmdFence) {
3079 VkFenceCreateInfo fenceInfo = {};
3080 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
3081 VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence);
3082 if (err != VK_SUCCESS) {
3083 qWarning("Failed to create command buffer fence: %d", err);
3084 return QRhi::FrameOpError;
3085 }
3086 }
3087
3088 QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cbWrapper->cb, ofr.cmdFence, nullptr, nullptr);
3089 if (submitres != QRhi::FrameOpSuccess)
3090 return submitres;
3091
3092 // wait for completion
3093 df->vkWaitForFences(dev, 1, &ofr.cmdFence, VK_TRUE, UINT64_MAX);
3094 df->vkResetFences(dev, 1, &ofr.cmdFence);
3095
3096 // Here we know that executing the host-side reads for this (or any
3097 // previous) frame is safe since we waited for completion above.
3099
3100 // Read the timestamps, if we wrote them.
3101 if (ofr.timestampQueryIndex >= 0) {
3102 quint64 timestamp[2] = { 0, 0 };
3103 VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(ofr.timestampQueryIndex), 2,
3104 2 * sizeof(quint64), timestamp, sizeof(quint64),
3105 VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
3106 timestampQueryPoolMap.clearBit(ofr.timestampQueryIndex / 2);
3107 ofr.timestampQueryIndex = -1;
3108 if (err == VK_SUCCESS) {
3109 bool ok = false;
3110 const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, &ok);
3111 if (ok)
3112 cbWrapper->lastGpuTime = elapsedSec;
3113 } else {
3114 qWarning("Failed to query timestamp: %d", err);
3115 }
3116 }
3117
3118 return QRhi::FrameOpSuccess;
3119}
3120
3122{
3123 QVkSwapChain *swapChainD = nullptr;
3124 if (inFrame) {
3125 // There is either a swapchain or an offscreen frame on-going.
3126 // End command recording and submit what we have.
3127 VkCommandBuffer cb;
3128 if (ofr.active) {
3129 Q_ASSERT(!currentSwapChain);
3130 QVkCommandBuffer *cbWrapper(ofr.cbWrapper[currentFrameSlot]);
3131 Q_ASSERT(cbWrapper->recordingPass == QVkCommandBuffer::NoPass);
3133 cbWrapper->resetCommands();
3134 cb = cbWrapper->cb;
3135 } else {
3136 Q_ASSERT(currentSwapChain);
3137 Q_ASSERT(currentSwapChain->cbWrapper.recordingPass == QVkCommandBuffer::NoPass);
3138 swapChainD = currentSwapChain;
3139 recordPrimaryCommandBuffer(&swapChainD->cbWrapper);
3140 swapChainD->cbWrapper.resetCommands();
3141 cb = swapChainD->cbWrapper.cb;
3142 }
3143 QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb, VK_NULL_HANDLE, nullptr, nullptr);
3144 if (submitres != QRhi::FrameOpSuccess)
3145 return submitres;
3146 }
3147
3148 df->vkQueueWaitIdle(gfxQueue);
3149
3150 if (inFrame) {
3151 // The current frame slot's command pool needs to be reset.
3153 // Allocate and begin recording on a new command buffer.
3154 if (ofr.active) {
3155 startPrimaryCommandBuffer(&ofr.cbWrapper[currentFrameSlot]->cb);
3156 } else {
3157 QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrameSlot]);
3158 startPrimaryCommandBuffer(&frame.cmdBuf);
3159 swapChainD->cbWrapper.cb = frame.cmdBuf;
3160 }
3161 }
3162
3165
3166 return QRhi::FrameOpSuccess;
3167}
3168
3170{
3172 u.layout = 0; // unused with buffers
3173 u.access = int(bufUsage.access);
3174 u.stage = int(bufUsage.stage);
3175 return u;
3176}
3177
3179{
3181 u.layout = texUsage.layout;
3182 u.access = int(texUsage.access);
3183 u.stage = int(texUsage.stage);
3184 return u;
3185}
3186
3188{
3189 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(rtD->description(), rtD->d.currentResIdList))
3190 rtD->create();
3191
3192 rtD->lastActiveFrameSlot = currentFrameSlot;
3193 rtD->d.rp->lastActiveFrameSlot = currentFrameSlot;
3194 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
3195 for (auto it = rtD->m_desc.cbeginColorAttachments(), itEnd = rtD->m_desc.cendColorAttachments(); it != itEnd; ++it) {
3196 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
3197 QVkTexture *resolveTexD = QRHI_RES(QVkTexture, it->resolveTexture());
3198 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
3199 if (texD) {
3200 trackedRegisterTexture(&passResTracker, texD,
3203 texD->lastActiveFrameSlot = currentFrameSlot;
3204 } else if (rbD) {
3205 // Won't register rbD->backingTexture because it cannot be used for
3206 // anything in a renderpass, its use makes only sense in
3207 // combination with a resolveTexture.
3208 rbD->lastActiveFrameSlot = currentFrameSlot;
3209 }
3210 if (resolveTexD) {
3211 trackedRegisterTexture(&passResTracker, resolveTexD,
3214 resolveTexD->lastActiveFrameSlot = currentFrameSlot;
3215 }
3216 }
3217 if (rtD->m_desc.depthStencilBuffer()) {
3218 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, rtD->m_desc.depthStencilBuffer());
3219 Q_ASSERT(rbD->m_type == QRhiRenderBuffer::DepthStencil);
3220 // We specify no explicit VkSubpassDependency for an offscreen render
3221 // target, meaning we need an explicit barrier for the depth-stencil
3222 // buffer to avoid a write-after-write hazard (as the implicit one is
3223 // not sufficient). Textures are taken care of by the resource tracking
3224 // but that excludes the (content-wise) throwaway depth-stencil buffer.
3226 rbD->lastActiveFrameSlot = currentFrameSlot;
3227 }
3228 if (rtD->m_desc.depthTexture()) {
3229 QVkTexture *depthTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthTexture());
3230 trackedRegisterTexture(&passResTracker, depthTexD,
3233 depthTexD->lastActiveFrameSlot = currentFrameSlot;
3234 }
3235 if (rtD->m_desc.depthResolveTexture()) {
3236 QVkTexture *depthResolveTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthResolveTexture());
3237 trackedRegisterTexture(&passResTracker, depthResolveTexD,
3240 depthResolveTexD->lastActiveFrameSlot = currentFrameSlot;
3241 }
3242 if (rtD->m_desc.shadingRateMap()) {
3243 QVkTexture *texD = QRHI_RES(QVkShadingRateMap, rtD->m_desc.shadingRateMap())->texture;
3244 trackedRegisterTexture(&passResTracker, texD,
3247 texD->lastActiveFrameSlot = currentFrameSlot;
3248 }
3249}
3250
3251void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3252{
3253 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3255
3256 enqueueResourceUpdates(cbD, resourceUpdates);
3257}
3258
3260{
3261 VkCommandBuffer secondaryCb;
3262
3263 if (!freeSecondaryCbs[currentFrameSlot].isEmpty()) {
3264 secondaryCb = freeSecondaryCbs[currentFrameSlot].last();
3265 freeSecondaryCbs[currentFrameSlot].removeLast();
3266 } else {
3267 VkCommandBufferAllocateInfo cmdBufInfo = {};
3268 cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
3269 cmdBufInfo.commandPool = cmdPool[currentFrameSlot];
3270 cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
3271 cmdBufInfo.commandBufferCount = 1;
3272
3273 VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, &secondaryCb);
3274 if (err != VK_SUCCESS) {
3275 qWarning("Failed to create secondary command buffer: %d", err);
3276 return VK_NULL_HANDLE;
3277 }
3278 }
3279
3280 VkCommandBufferBeginInfo cmdBufBeginInfo = {};
3281 cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
3282 cmdBufBeginInfo.flags = rtD ? VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT : 0;
3283 VkCommandBufferInheritanceInfo cmdBufInheritInfo = {};
3284 cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
3285 cmdBufInheritInfo.subpass = 0;
3286 if (rtD) {
3287 cmdBufInheritInfo.renderPass = rtD->rp->rp;
3288 cmdBufInheritInfo.framebuffer = rtD->fb;
3289 }
3290 cmdBufBeginInfo.pInheritanceInfo = &cmdBufInheritInfo;
3291
3292 VkResult err = df->vkBeginCommandBuffer(secondaryCb, &cmdBufBeginInfo);
3293 if (err != VK_SUCCESS) {
3294 qWarning("Failed to begin secondary command buffer: %d", err);
3295 return VK_NULL_HANDLE;
3296 }
3297
3298 return secondaryCb;
3299}
3300
3302{
3303 VkResult err = df->vkEndCommandBuffer(cb);
3304 if (err != VK_SUCCESS)
3305 qWarning("Failed to end secondary command buffer: %d", err);
3306
3307 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3309 cmd.args.executeSecondary.cb = cb;
3310
3313 e.lastActiveFrameSlot = currentFrameSlot;
3314 e.secondaryCommandBuffer.cb = cb;
3315 releaseQueue.append(e);
3316}
3317
3318void QRhiVulkan::beginPass(QRhiCommandBuffer *cb,
3319 QRhiRenderTarget *rt,
3320 const QColor &colorClearValue,
3321 const QRhiDepthStencilClearValue &depthStencilClearValue,
3322 QRhiResourceUpdateBatch *resourceUpdates,
3323 QRhiCommandBuffer::BeginPassFlags flags)
3324{
3325 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3327
3328 if (resourceUpdates)
3329 enqueueResourceUpdates(cbD, resourceUpdates);
3330
3331 // Insert a TransitionPassResources into the command stream, pointing to
3332 // the tracker this pass is going to use. That's how we generate the
3333 // barriers later during recording the real VkCommandBuffer, right before
3334 // the vkCmdBeginRenderPass.
3336
3337 QVkRenderTargetData *rtD = nullptr;
3338 switch (rt->resourceType()) {
3339 case QRhiResource::SwapChainRenderTarget:
3340 rtD = &QRHI_RES(QVkSwapChainRenderTarget, rt)->d;
3341 rtD->rp->lastActiveFrameSlot = currentFrameSlot;
3342 Q_ASSERT(currentSwapChain);
3343 currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse =
3345 if (currentSwapChain->shadingRateMapView) {
3347 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
3348 trackedRegisterTexture(&passResTracker, texD,
3351 texD->lastActiveFrameSlot = currentFrameSlot;
3352 }
3353 break;
3354 case QRhiResource::TextureRenderTarget:
3355 {
3357 rtD = &rtTex->d;
3359 }
3360 break;
3361 default:
3362 Q_UNREACHABLE();
3363 break;
3364 }
3365
3367 cbD->passUsesSecondaryCb = flags.testFlag(QRhiCommandBuffer::ExternalContent);
3368 cbD->currentTarget = rt;
3369
3370 // No copy operations or image layout transitions allowed after this point
3371 // (up until endPass) as we are going to begin the renderpass.
3372
3373 VkRenderPassBeginInfo rpBeginInfo = {};
3374 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
3375 rpBeginInfo.renderPass = rtD->rp->rp;
3376 rpBeginInfo.framebuffer = rtD->fb;
3377 rpBeginInfo.renderArea.extent.width = uint32_t(rtD->pixelSize.width());
3378 rpBeginInfo.renderArea.extent.height = uint32_t(rtD->pixelSize.height());
3379
3380 const bool rpHasAnyClearOp = std::any_of(rtD->rp->attDescs.cbegin(), rtD->rp->attDescs.cend(),
3381 [](const VkAttachmentDescription &attDesc) {
3382 return (attDesc.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR
3383 || attDesc.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR);
3384 });
3385
3386 QVarLengthArray<VkClearValue, (QVkRenderTargetData::MAX_COLOR_ATTACHMENTS + 1) * 2 + 1> cvs;
3387 if (rpHasAnyClearOp) {
3388 for (int i = 0; i < rtD->colorAttCount; ++i) {
3389 VkClearValue cv;
3390 cv.color = { { colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
3391 colorClearValue.alphaF() } };
3392 cvs.append(cv);
3393 }
3394 for (int i = 0; i < rtD->dsAttCount; ++i) {
3395 VkClearValue cv;
3396 cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() };
3397 cvs.append(cv);
3398 }
3399 for (int i = 0; i < rtD->resolveAttCount; ++i) {
3400 VkClearValue cv;
3401 cv.color = { { colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
3402 colorClearValue.alphaF() } };
3403 cvs.append(cv);
3404 }
3405 for (int i = 0; i < rtD->dsResolveAttCount; ++i) {
3406 VkClearValue cv;
3407 cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() };
3408 cvs.append(cv);
3409 }
3410 for (int i = 0; i < rtD->shadingRateAttCount; ++i) {
3411 VkClearValue cv;
3412 cv.color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
3413 cvs.append(cv);
3414 }
3415 }
3416 Q_ASSERT(!rpHasAnyClearOp || cvs.size() == rtD->rp->attDescs.size());
3417 rpBeginInfo.clearValueCount = uint32_t(cvs.size());
3418
3419 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3421 cmd.args.beginRenderPass.desc = rpBeginInfo;
3422 cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.size();
3423 cmd.args.beginRenderPass.useSecondaryCb = cbD->passUsesSecondaryCb;
3424 cbD->pools.clearValue.append(cvs.constData(), cvs.size());
3425
3426 if (cbD->passUsesSecondaryCb)
3427 cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer(rtD));
3428
3429 if (cbD->hasShadingRateSet) {
3430 QVkCommandBuffer::Command &rateCmd(cbD->commands.get());
3432 rateCmd.args.setShadingRate.w = 1;
3433 rateCmd.args.setShadingRate.h = 1;
3434 }
3435
3437}
3438
3439void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3440{
3441 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3443
3444 if (cbD->passUsesSecondaryCb) {
3445 VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last();
3446 cbD->activeSecondaryCbStack.removeLast();
3447 endAndEnqueueSecondaryCommandBuffer(secondaryCb, cbD);
3448 }
3449
3450 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3452
3454 cbD->currentTarget = nullptr;
3455
3456 if (resourceUpdates)
3457 enqueueResourceUpdates(cbD, resourceUpdates);
3458}
3459
3460void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb,
3461 QRhiResourceUpdateBatch *resourceUpdates,
3462 QRhiCommandBuffer::BeginPassFlags flags)
3463{
3464 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3466
3467 if (resourceUpdates)
3468 enqueueResourceUpdates(cbD, resourceUpdates);
3469
3471
3473 cbD->passUsesSecondaryCb = flags.testFlag(QRhiCommandBuffer::ExternalContent);
3474
3475 cbD->computePassState.reset();
3476
3477 if (cbD->passUsesSecondaryCb)
3478 cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer());
3479
3481}
3482
3483void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
3484{
3485 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3487
3488 if (cbD->passUsesSecondaryCb) {
3489 VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last();
3490 cbD->activeSecondaryCbStack.removeLast();
3491 endAndEnqueueSecondaryCommandBuffer(secondaryCb, cbD);
3492 }
3493
3495
3496 if (resourceUpdates)
3497 enqueueResourceUpdates(cbD, resourceUpdates);
3498}
3499
3500void QRhiVulkan::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
3501{
3503 Q_ASSERT(psD->pipeline);
3504 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3506
3507 if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
3508 if (cbD->passUsesSecondaryCb) {
3509 df->vkCmdBindPipeline(cbD->activeSecondaryCbStack.last(), VK_PIPELINE_BIND_POINT_COMPUTE, psD->pipeline);
3510 } else {
3511 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3513 cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
3514 cmd.args.bindPipeline.pipeline = psD->pipeline;
3515 }
3516
3517 cbD->currentGraphicsPipeline = nullptr;
3518 cbD->currentComputePipeline = ps;
3519 cbD->currentPipelineGeneration = psD->generation;
3520 }
3521
3522 psD->lastActiveFrameSlot = currentFrameSlot;
3523}
3524
3525template<typename T>
3526inline void qrhivk_accumulateComputeResource(T *writtenResources, QRhiResource *resource,
3527 QRhiShaderResourceBinding::Type bindingType,
3528 int loadTypeVal, int storeTypeVal, int loadStoreTypeVal)
3529{
3530 VkAccessFlags access = 0;
3531 if (bindingType == loadTypeVal) {
3532 access = VK_ACCESS_SHADER_READ_BIT;
3533 } else {
3534 access = VK_ACCESS_SHADER_WRITE_BIT;
3535 if (bindingType == loadStoreTypeVal)
3536 access |= VK_ACCESS_SHADER_READ_BIT;
3537 }
3538 auto it = writtenResources->find(resource);
3539 if (it != writtenResources->end())
3540 it->second.accessFlags |= access;
3541 else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal)
3542 writtenResources->insert(resource, { access, true });
3543}
3544
3545void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
3546{
3547 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
3549
3550 // When there are multiple dispatches, read-after-write and
3551 // write-after-write need a barrier.
3552 QVarLengthArray<VkImageMemoryBarrier, 8> imageBarriers;
3553 QVarLengthArray<VkBufferMemoryBarrier, 8> bufferBarriers;
3554 if (cbD->currentComputeSrb) {
3555 // The key in the writtenResources map indicates that the resource was
3556 // written in a previous dispatch, whereas the value accumulates the
3557 // access mask in the current one.
3558 for (auto [res, accessAndIsNewFlag] : cbD->computePassState.writtenResources)
3559 accessAndIsNewFlag = { 0, false }; // note: accessAndIsNewFlag is a reference
3560
3561 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, cbD->currentComputeSrb);
3562 for (auto &binding : srbD->m_bindings) {
3563 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding);
3564 switch (b->type) {
3565 case QRhiShaderResourceBinding::ImageLoad:
3566 case QRhiShaderResourceBinding::ImageStore:
3567 case QRhiShaderResourceBinding::ImageLoadStore:
3568 qrhivk_accumulateComputeResource(&cbD->computePassState.writtenResources,
3569 b->u.simage.tex,
3570 b->type,
3571 QRhiShaderResourceBinding::ImageLoad,
3572 QRhiShaderResourceBinding::ImageStore,
3573 QRhiShaderResourceBinding::ImageLoadStore);
3574 break;
3575 case QRhiShaderResourceBinding::BufferLoad:
3576 case QRhiShaderResourceBinding::BufferStore:
3577 case QRhiShaderResourceBinding::BufferLoadStore:
3578 qrhivk_accumulateComputeResource(&cbD->computePassState.writtenResources,
3579 b->u.sbuf.buf,
3580 b->type,
3581 QRhiShaderResourceBinding::BufferLoad,
3582 QRhiShaderResourceBinding::BufferStore,
3583 QRhiShaderResourceBinding::BufferLoadStore);
3584 break;
3585 default:
3586 break;
3587 }
3588 }
3589
3590 for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) {
3591 const VkAccessFlags accessInThisDispatch = it->second.accessFlags;
3592 const bool isNewInThisDispatch = it->second.isNew;
3593 if (accessInThisDispatch && !isNewInThisDispatch) {
3594 if (it.key()->resourceType() == QRhiResource::Texture) {
3595 QVkTexture *texD = QRHI_RES(QVkTexture, it.key());
3596 VkImageMemoryBarrier barrier = {};
3597 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
3598 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3599 // won't care about subresources, pretend the whole resource was written
3600 barrier.subresourceRange.baseMipLevel = 0;
3601 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
3602 barrier.subresourceRange.baseArrayLayer = 0;
3603 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
3604 barrier.oldLayout = texD->usageState.layout;
3605 barrier.newLayout = texD->usageState.layout;
3606 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
3607 barrier.dstAccessMask = accessInThisDispatch;
3608 barrier.image = texD->image;
3609 imageBarriers.append(barrier);
3610 } else {
3611 QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key());
3612 VkBufferMemoryBarrier barrier = {};
3613 barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
3614 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3615 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3616 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
3617 barrier.dstAccessMask = accessInThisDispatch;
3618 barrier.buffer = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
3619 barrier.size = VK_WHOLE_SIZE;
3620 bufferBarriers.append(barrier);
3621 }
3622 }
3623 // Anything that was previously written, but is only read now, can be
3624 // removed from the written list (because that previous write got a
3625 // corresponding barrier now).
3626 if (accessInThisDispatch == VK_ACCESS_SHADER_READ_BIT)
3627 it = cbD->computePassState.writtenResources.erase(it);
3628 else
3629 ++it;
3630 }
3631 }
3632
3633 if (cbD->passUsesSecondaryCb) {
3634 VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last();
3635 if (!imageBarriers.isEmpty()) {
3636 df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
3637 0, 0, nullptr,
3638 0, nullptr,
3639 imageBarriers.size(), imageBarriers.constData());
3640 }
3641 if (!bufferBarriers.isEmpty()) {
3642 df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
3643 0, 0, nullptr,
3644 bufferBarriers.size(), bufferBarriers.constData(),
3645 0, nullptr);
3646 }
3647 df->vkCmdDispatch(secondaryCb, uint32_t(x), uint32_t(y), uint32_t(z));
3648 } else {
3649 if (!imageBarriers.isEmpty()) {
3650 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3652 cmd.args.imageBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
3653 cmd.args.imageBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
3654 cmd.args.imageBarrier.count = imageBarriers.size();
3655 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size();
3656 cbD->pools.imageBarrier.append(imageBarriers.constData(), imageBarriers.size());
3657 }
3658 if (!bufferBarriers.isEmpty()) {
3659 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3661 cmd.args.bufferBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
3662 cmd.args.bufferBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
3663 cmd.args.bufferBarrier.count = bufferBarriers.size();
3664 cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.size();
3665 cbD->pools.bufferBarrier.append(bufferBarriers.constData(), bufferBarriers.size());
3666 }
3667 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3669 cmd.args.dispatch.x = x;
3670 cmd.args.dispatch.y = y;
3671 cmd.args.dispatch.z = z;
3672 }
3673}
3674
3675VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv)
3676{
3677 VkShaderModuleCreateInfo shaderInfo = {};
3678 shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
3679 shaderInfo.codeSize = size_t(spirv.size());
3680 shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData());
3681 VkShaderModule shaderModule;
3682 VkResult err = df->vkCreateShaderModule(dev, &shaderInfo, nullptr, &shaderModule);
3683 if (err != VK_SUCCESS) {
3684 qWarning("Failed to create shader module: %d", err);
3685 return VK_NULL_HANDLE;
3686 }
3687 return shaderModule;
3688}
3689
3690bool QRhiVulkan::ensurePipelineCache(const void *initialData, size_t initialDataSize)
3691{
3692 if (pipelineCache)
3693 return true;
3694
3695 VkPipelineCacheCreateInfo pipelineCacheInfo = {};
3696 pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
3697 pipelineCacheInfo.initialDataSize = initialDataSize;
3698 pipelineCacheInfo.pInitialData = initialData;
3699 VkResult err = df->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &pipelineCache);
3700 if (err != VK_SUCCESS) {
3701 qWarning("Failed to create pipeline cache: %d", err);
3702 return false;
3703 }
3704 return true;
3705}
3706
3707void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb)
3708{
3710
3711 QVarLengthArray<VkDescriptorBufferInfo, 8> bufferInfos;
3712 using ArrayOfImageDesc = QVarLengthArray<VkDescriptorImageInfo, 8>;
3713 QVarLengthArray<ArrayOfImageDesc, 8> imageInfos;
3714 QVarLengthArray<VkWriteDescriptorSet, 12> writeInfos;
3715 QVarLengthArray<std::pair<int, int>, 12> infoIndices;
3716
3717 for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) {
3718 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i));
3719 QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[currentFrameSlot][i]);
3720
3721 VkWriteDescriptorSet writeInfo = {};
3722 writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
3723 writeInfo.dstSet = srbD->descSets[currentFrameSlot];
3724 writeInfo.dstBinding = uint32_t(b->binding);
3725 writeInfo.descriptorCount = 1;
3726
3727 int bufferInfoIndex = -1;
3728 int imageInfoIndex = -1;
3729
3730 switch (b->type) {
3731 case QRhiShaderResourceBinding::UniformBuffer:
3732 {
3733 writeInfo.descriptorType = b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
3734 : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
3735 QRhiBuffer *buf = b->u.ubuf.buf;
3736 QVkBuffer *bufD = QRHI_RES(QVkBuffer, buf);
3737 bd.ubuf.id = bufD->m_id;
3738 bd.ubuf.generation = bufD->generation;
3739 VkDescriptorBufferInfo bufInfo;
3740 bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[currentFrameSlot] : bufD->buffers[0];
3741 bufInfo.offset = b->u.ubuf.offset;
3742 bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : VK_WHOLE_SIZE;
3743 // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads
3744 Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset);
3745 bufferInfoIndex = bufferInfos.size();
3746 bufferInfos.append(bufInfo);
3747 }
3748 break;
3749 case QRhiShaderResourceBinding::SampledTexture:
3750 {
3751 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
3752 writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported
3753 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
3754 ArrayOfImageDesc imageInfo(data->count);
3755 for (int elem = 0; elem < data->count; ++elem) {
3756 QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
3757 QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler);
3758 bd.stex.d[elem].texId = texD->m_id;
3759 bd.stex.d[elem].texGeneration = texD->generation;
3760 bd.stex.d[elem].samplerId = samplerD->m_id;
3761 bd.stex.d[elem].samplerGeneration = samplerD->generation;
3762 imageInfo[elem].sampler = samplerD->sampler;
3763 imageInfo[elem].imageView = texD->imageView;
3764 imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3765 }
3766 bd.stex.count = data->count;
3767 imageInfoIndex = imageInfos.size();
3768 imageInfos.append(imageInfo);
3769 }
3770 break;
3771 case QRhiShaderResourceBinding::Texture:
3772 {
3773 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
3774 writeInfo.descriptorCount = data->count; // arrays of (separate) images are supported
3775 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
3776 ArrayOfImageDesc imageInfo(data->count);
3777 for (int elem = 0; elem < data->count; ++elem) {
3778 QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
3779 bd.stex.d[elem].texId = texD->m_id;
3780 bd.stex.d[elem].texGeneration = texD->generation;
3781 bd.stex.d[elem].samplerId = 0;
3782 bd.stex.d[elem].samplerGeneration = 0;
3783 imageInfo[elem].sampler = VK_NULL_HANDLE;
3784 imageInfo[elem].imageView = texD->imageView;
3785 imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3786 }
3787 bd.stex.count = data->count;
3788 imageInfoIndex = imageInfos.size();
3789 imageInfos.append(imageInfo);
3790 }
3791 break;
3792 case QRhiShaderResourceBinding::Sampler:
3793 {
3794 QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.texSamplers[0].sampler);
3795 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
3796 bd.stex.d[0].texId = 0;
3797 bd.stex.d[0].texGeneration = 0;
3798 bd.stex.d[0].samplerId = samplerD->m_id;
3799 bd.stex.d[0].samplerGeneration = samplerD->generation;
3800 ArrayOfImageDesc imageInfo(1);
3801 imageInfo[0].sampler = samplerD->sampler;
3802 imageInfo[0].imageView = VK_NULL_HANDLE;
3803 imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
3804 imageInfoIndex = imageInfos.size();
3805 imageInfos.append(imageInfo);
3806 }
3807 break;
3808 case QRhiShaderResourceBinding::ImageLoad:
3809 case QRhiShaderResourceBinding::ImageStore:
3810 case QRhiShaderResourceBinding::ImageLoadStore:
3811 {
3812 QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
3813 VkImageView view = texD->perLevelImageViewForLoadStore(b->u.simage.level);
3814 if (view) {
3815 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
3816 bd.simage.id = texD->m_id;
3817 bd.simage.generation = texD->generation;
3818 ArrayOfImageDesc imageInfo(1);
3819 imageInfo[0].sampler = VK_NULL_HANDLE;
3820 imageInfo[0].imageView = view;
3821 imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
3822 imageInfoIndex = imageInfos.size();
3823 imageInfos.append(imageInfo);
3824 }
3825 }
3826 break;
3827 case QRhiShaderResourceBinding::BufferLoad:
3828 case QRhiShaderResourceBinding::BufferStore:
3829 case QRhiShaderResourceBinding::BufferLoadStore:
3830 {
3831 QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf);
3832 writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
3833 bd.sbuf.id = bufD->m_id;
3834 bd.sbuf.generation = bufD->generation;
3835 VkDescriptorBufferInfo bufInfo;
3836 bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[currentFrameSlot] : bufD->buffers[0];
3837 bufInfo.offset = b->u.sbuf.offset;
3838 bufInfo.range = b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : VK_WHOLE_SIZE;
3839 bufferInfoIndex = bufferInfos.size();
3840 bufferInfos.append(bufInfo);
3841 }
3842 break;
3843 default:
3844 continue;
3845 }
3846
3847 writeInfos.append(writeInfo);
3848 infoIndices.append({ bufferInfoIndex, imageInfoIndex });
3849 }
3850
3851 for (int i = 0, writeInfoCount = writeInfos.size(); i < writeInfoCount; ++i) {
3852 const int bufferInfoIndex = infoIndices[i].first;
3853 const int imageInfoIndex = infoIndices[i].second;
3854 if (bufferInfoIndex >= 0)
3855 writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex];
3856 else if (imageInfoIndex >= 0)
3857 writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData();
3858 }
3859
3860 df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.size()), writeInfos.constData(), 0, nullptr);
3861}
3862
3863static inline bool accessIsWrite(VkAccessFlags access)
3864{
3865 return (access & VK_ACCESS_SHADER_WRITE_BIT) != 0
3866 || (access & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0
3867 || (access & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) != 0
3868 || (access & VK_ACCESS_TRANSFER_WRITE_BIT) != 0
3869 || (access & VK_ACCESS_HOST_WRITE_BIT) != 0
3870 || (access & VK_ACCESS_MEMORY_WRITE_BIT) != 0;
3871}
3872
3874 VkAccessFlags access, VkPipelineStageFlags stage)
3875{
3877 Q_ASSERT(access && stage);
3878 QVkBuffer::UsageState &s(bufD->usageState[slot]);
3879 if (!s.stage) {
3880 s.access = access;
3881 s.stage = stage;
3882 return;
3883 }
3884
3885 if (s.access == access && s.stage == stage) {
3886 // No need to flood with unnecessary read-after-read barriers.
3887 // Write-after-write is a different matter, however.
3888 if (!accessIsWrite(access))
3889 return;
3890 }
3891
3892 VkBufferMemoryBarrier bufMemBarrier = {};
3893 bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
3894 bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3895 bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3896 bufMemBarrier.srcAccessMask = s.access;
3897 bufMemBarrier.dstAccessMask = access;
3898 bufMemBarrier.buffer = bufD->buffers[slot];
3899 bufMemBarrier.size = VK_WHOLE_SIZE;
3900
3901 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3903 cmd.args.bufferBarrier.srcStageMask = s.stage;
3904 cmd.args.bufferBarrier.dstStageMask = stage;
3905 cmd.args.bufferBarrier.count = 1;
3906 cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.size();
3907 cbD->pools.bufferBarrier.append(bufMemBarrier);
3908
3909 s.access = access;
3910 s.stage = stage;
3911}
3912
3914 VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage)
3915{
3917 Q_ASSERT(layout && access && stage);
3918 QVkTexture::UsageState &s(texD->usageState);
3919 if (s.access == access && s.stage == stage && s.layout == layout) {
3920 if (!accessIsWrite(access))
3921 return;
3922 }
3923
3924 VkImageMemoryBarrier barrier = {};
3925 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
3926 barrier.subresourceRange.aspectMask = aspectMaskForTextureFormat(texD->m_format);
3927 barrier.subresourceRange.baseMipLevel = 0;
3928 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
3929 barrier.subresourceRange.baseArrayLayer = 0;
3930 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
3931 barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
3932 barrier.newLayout = layout;
3933 barrier.srcAccessMask = s.access; // may be 0 but that's fine
3934 barrier.dstAccessMask = access;
3935 barrier.image = texD->image;
3936
3937 VkPipelineStageFlags srcStage = s.stage;
3938 // stage mask cannot be 0
3939 if (!srcStage)
3940 srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
3941
3942 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3944 cmd.args.imageBarrier.srcStageMask = srcStage;
3945 cmd.args.imageBarrier.dstStageMask = stage;
3946 cmd.args.imageBarrier.count = 1;
3947 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size();
3948 cbD->pools.imageBarrier.append(barrier);
3949
3950 s.layout = layout;
3951 s.access = access;
3952 s.stage = stage;
3953}
3954
3956{
3958
3959 VkImageMemoryBarrier barrier = {};
3960 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
3961 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
3962 barrier.subresourceRange.baseMipLevel = 0;
3963 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
3964 barrier.subresourceRange.baseArrayLayer = 0;
3965 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
3966 barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
3967 barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
3968 barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
3969 barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
3970 | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
3971 barrier.image = rbD->image;
3972
3973 const VkPipelineStageFlags stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
3974 | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
3975
3976 QVkCommandBuffer::Command &cmd(cbD->commands.get());
3978 cmd.args.imageBarrier.srcStageMask = stages;
3979 cmd.args.imageBarrier.dstStageMask = stages;
3980 cmd.args.imageBarrier.count = 1;
3981 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size();
3982 cbD->pools.imageBarrier.append(barrier);
3983}
3984
3986 VkImageLayout oldLayout, VkImageLayout newLayout,
3987 VkAccessFlags srcAccess, VkAccessFlags dstAccess,
3988 VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage,
3989 int startLayer, int layerCount,
3990 int startLevel, int levelCount)
3991{
3993 VkImageMemoryBarrier barrier = {};
3994 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
3995 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3996 barrier.subresourceRange.baseMipLevel = uint32_t(startLevel);
3997 barrier.subresourceRange.levelCount = uint32_t(levelCount);
3998 barrier.subresourceRange.baseArrayLayer = uint32_t(startLayer);
3999 barrier.subresourceRange.layerCount = uint32_t(layerCount);
4000 barrier.oldLayout = oldLayout;
4001 barrier.newLayout = newLayout;
4002 barrier.srcAccessMask = srcAccess;
4003 barrier.dstAccessMask = dstAccess;
4004 barrier.image = image;
4005
4006 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4008 cmd.args.imageBarrier.srcStageMask = srcStage;
4009 cmd.args.imageBarrier.dstStageMask = dstStage;
4010 cmd.args.imageBarrier.count = 1;
4011 cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size();
4012 cbD->pools.imageBarrier.append(barrier);
4013}
4014
4015VkDeviceSize QRhiVulkan::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
4016{
4017 VkDeviceSize size = 0;
4018 const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
4019 subresDesc.data().size() : subresDesc.image().sizeInBytes();
4020 if (imageSizeBytes > 0)
4021 size += aligned(VkDeviceSize(imageSizeBytes), texbufAlign);
4022 return size;
4023}
4024
4025void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
4026 const QRhiTextureSubresourceUploadDescription &subresDesc,
4027 size_t *curOfs, void *mp,
4028 BufferImageCopyList *copyInfos)
4029{
4030 qsizetype copySizeBytes = 0;
4031 qsizetype imageSizeBytes = 0;
4032 const void *src = nullptr;
4033 const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4034 const bool is1D = texD->m_flags.testFlag(QRhiTexture::OneDimensional);
4035
4036 VkBufferImageCopy copyInfo = {};
4037 copyInfo.bufferOffset = *curOfs;
4038 copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
4039 copyInfo.imageSubresource.mipLevel = uint32_t(level);
4040 copyInfo.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(layer);
4041 copyInfo.imageSubresource.layerCount = 1;
4042 copyInfo.imageExtent.depth = 1;
4043 if (is3D)
4044 copyInfo.imageOffset.z = uint32_t(layer);
4045 if (is1D)
4046 copyInfo.imageOffset.y = uint32_t(layer);
4047
4048 const QByteArray rawData = subresDesc.data();
4049 const QPoint dp = subresDesc.destinationTopLeft();
4050 QImage image = subresDesc.image();
4051 if (!image.isNull()) {
4052 copySizeBytes = imageSizeBytes = image.sizeInBytes();
4053 QSize size = image.size();
4054 src = image.constBits();
4055 // Scanlines in QImage are 4 byte aligned so bpl must
4056 // be taken into account for bufferRowLength.
4057 int bpc = qMax(1, image.depth() / 8);
4058 // this is in pixels, not bytes, to make it more complicated...
4059 copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc);
4060 if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
4061 const int sx = subresDesc.sourceTopLeft().x();
4062 const int sy = subresDesc.sourceTopLeft().y();
4063 if (!subresDesc.sourceSize().isEmpty())
4064 size = subresDesc.sourceSize();
4065 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
4066 if (size.width() == image.width()) {
4067 // No need to make a QImage copy here, can copy from the source
4068 // QImage into staging directly.
4069 src = image.constBits() + sy * image.bytesPerLine() + sx * bpc;
4070 copySizeBytes = size.height() * image.bytesPerLine();
4071 } else {
4072 image = image.copy(sx, sy, size.width(), size.height());
4073 src = image.constBits();
4074 // The staging buffer gets the slice only. The rest of the
4075 // space reserved for this mip will be unused.
4076 copySizeBytes = image.sizeInBytes();
4077 bpc = qMax(1, image.depth() / 8);
4078 copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc);
4079 }
4080 } else {
4081 size = clampedSubResourceUploadSize(size, dp, level, texD->m_pixelSize);
4082 }
4083 copyInfo.imageOffset.x = dp.x();
4084 copyInfo.imageOffset.y = dp.y();
4085 copyInfo.imageExtent.width = uint32_t(size.width());
4086 copyInfo.imageExtent.height = uint32_t(size.height());
4087 copyInfos->append(copyInfo);
4088 } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
4089 copySizeBytes = imageSizeBytes = rawData.size();
4090 src = rawData.constData();
4091 QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
4092 const int subresw = size.width();
4093 const int subresh = size.height();
4094 if (!subresDesc.sourceSize().isEmpty())
4095 size = subresDesc.sourceSize();
4096 const int w = size.width();
4097 const int h = size.height();
4098 QSize blockDim;
4099 compressedFormatInfo(texD->m_format, QSize(w, h), nullptr, nullptr, &blockDim);
4100 // x and y must be multiples of the block width and height
4101 copyInfo.imageOffset.x = aligned(dp.x(), blockDim.width());
4102 copyInfo.imageOffset.y = aligned(dp.y(), blockDim.height());
4103 // width and height must be multiples of the block width and height
4104 // or x + width and y + height must equal the subresource width and height
4105 copyInfo.imageExtent.width = uint32_t(dp.x() + w == subresw ? w : aligned(w, blockDim.width()));
4106 copyInfo.imageExtent.height = uint32_t(dp.y() + h == subresh ? h : aligned(h, blockDim.height()));
4107 copyInfos->append(copyInfo);
4108 } else if (!rawData.isEmpty()) {
4109 copySizeBytes = imageSizeBytes = rawData.size();
4110 src = rawData.constData();
4111 QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
4112 if (subresDesc.dataStride()) {
4113 quint32 bytesPerPixel = 0;
4114 textureFormatInfo(texD->m_format, size, nullptr, nullptr, &bytesPerPixel);
4115 if (bytesPerPixel)
4116 copyInfo.bufferRowLength = subresDesc.dataStride() / bytesPerPixel;
4117 }
4118 if (!subresDesc.sourceSize().isEmpty())
4119 size = subresDesc.sourceSize();
4120 copyInfo.imageOffset.x = dp.x();
4121 copyInfo.imageOffset.y = dp.y();
4122 copyInfo.imageExtent.width = uint32_t(size.width());
4123 copyInfo.imageExtent.height = uint32_t(size.height());
4124 copyInfos->append(copyInfo);
4125 } else {
4126 qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
4127 }
4128
4129 if (src) {
4130 memcpy(reinterpret_cast<char *>(mp) + *curOfs, src, size_t(copySizeBytes));
4131 *curOfs += aligned(VkDeviceSize(imageSizeBytes), texbufAlign);
4132 }
4133}
4134
4136{
4137 if (err == VK_ERROR_DEVICE_LOST)
4139 if (err == VK_ERROR_OUT_OF_DEVICE_MEMORY)
4140 qWarning() << "Out of device memory, current allocator statistics are" << statistics();
4141}
4142
4144{
4145#ifdef VK_EXT_device_fault
4146 if (!dev || !caps.deviceFault || !vkGetDeviceFaultInfoEXT)
4147 return;
4148
4149 VkDeviceFaultCountsEXT faultCounts{};
4150 faultCounts.sType = VK_STRUCTURE_TYPE_DEVICE_FAULT_COUNTS_EXT;
4151 faultCounts.pNext = nullptr;
4152
4153 VkResult result = vkGetDeviceFaultInfoEXT(dev, &faultCounts, nullptr);
4154 if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
4155 qWarning("vkGetDeviceFaultInfoEXT failed with %d", result);
4156 return;
4157 }
4158 faultCounts.vendorBinarySize = 0;
4159
4160 QVarLengthArray<VkDeviceFaultAddressInfoEXT> addressInfos;
4161 addressInfos.resize(faultCounts.addressInfoCount);
4162
4163 QVarLengthArray<VkDeviceFaultVendorInfoEXT> vendorInfos;
4164 vendorInfos.resize(faultCounts.vendorInfoCount);
4165
4166 VkDeviceFaultInfoEXT info{};
4167 info.sType = VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_EXT;
4168 info.pNext = nullptr;
4169 info.pAddressInfos = addressInfos.isEmpty() ? nullptr : addressInfos.data();
4170 info.pVendorInfos = vendorInfos.isEmpty() ? nullptr : vendorInfos.data();
4171 info.pVendorBinaryData = nullptr;
4172
4173 result = vkGetDeviceFaultInfoEXT(dev, &faultCounts, &info);
4174 if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
4175 qWarning("vkGetDeviceFaultInfoEXT failed with %d", result);
4176 return;
4177 }
4178
4179 const char *desc = info.description[0] ? info.description : "n/a";
4180 qWarning("VK_ERROR_DEVICE_LOST (VK_EXT_device_fault): %u address infos, %u vendor infos, %llu bytes vendor binary: %s",
4181 faultCounts.addressInfoCount,
4182 faultCounts.vendorInfoCount,
4183 (unsigned long long)faultCounts.vendorBinarySize,
4184 desc);
4185
4186 for (uint32_t i = 0; i < faultCounts.addressInfoCount; ++i) {
4187 const auto &a = addressInfos[i];
4188 auto addressTypeString = [](const VkDeviceFaultAddressTypeEXT type) {
4189 switch (type) {
4190 case VK_DEVICE_FAULT_ADDRESS_TYPE_NONE_EXT: return "NONE";
4191 case VK_DEVICE_FAULT_ADDRESS_TYPE_READ_INVALID_EXT: return "READ_INVALID";
4192 case VK_DEVICE_FAULT_ADDRESS_TYPE_WRITE_INVALID_EXT: return "WRITE_INVALID";
4193 case VK_DEVICE_FAULT_ADDRESS_TYPE_EXECUTE_INVALID_EXT: return "EXECUTE_INVALID";
4194 case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_UNKNOWN_EXT: return "INSTRUCTION_POINTER_UNKNOWN";
4195 case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_INVALID_EXT: return "INSTRUCTION_POINTER_INVALID";
4196 case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_FAULT_EXT: return "INSTRUCTION_POINTER_FAULT";
4197 default: return "UNKNOWN";
4198 };
4199 };
4200 qWarning(" AddressInfo[%02u]: type=%s addr=0x%llx precision=%llu",
4201 i,
4202 addressTypeString(a.addressType),
4203 (unsigned long long)a.reportedAddress,
4204 (unsigned long long)a.addressPrecision);
4205 }
4206
4207 for (uint32_t i = 0; i < faultCounts.vendorInfoCount; ++i) {
4208 const auto &v = vendorInfos[i];
4209 qWarning(" VendorInfo[%02u]: code=%llu data=%llu desc=%s",
4210 i,
4211 (unsigned long long)v.vendorFaultCode,
4212 (unsigned long long)v.vendorFaultData,
4213 v.description);
4214 }
4215#endif // VK_EXT_device_fault
4216}
4217
4218void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
4219{
4221
4222 for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
4223 const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
4225 QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
4226 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
4227 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
4228 if (u.offset == 0 && u.data.size() == bufD->m_size)
4229 bufD->pendingDynamicUpdates[i].clear();
4230 bufD->pendingDynamicUpdates[i].append({ u.offset, u.data });
4231 }
4233 QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
4234 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
4235 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
4236
4237 if (!bufD->stagingBuffers[currentFrameSlot]) {
4238 VkBufferCreateInfo bufferInfo = {};
4239 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
4240 // must cover the entire buffer - this way multiple, partial updates per frame
4241 // are supported even when the staging buffer is reused (Static)
4242 bufferInfo.size = bufD->m_size;
4243 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
4244
4245 VmaAllocationCreateInfo allocInfo = {};
4246 allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
4247
4248 VmaAllocation allocation;
4249 VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
4250 &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
4251 if (err == VK_SUCCESS) {
4252 bufD->stagingAllocations[currentFrameSlot] = allocation;
4253 setAllocationName(allocation, bufD->name());
4254 } else {
4255 qWarning("Failed to create staging buffer of size %u: %d", bufD->m_size, err);
4256 printExtraErrorInfo(err);
4257 continue;
4258 }
4259 }
4260
4261 VkResult err = vmaCopyMemoryToAllocation(toVmaAllocator(allocator), u.data.constData(),
4262 toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]),
4263 u.offset, u.data.size());
4264 if (err != VK_SUCCESS) {
4265 qWarning("Failed to copy memory to buffer: %d", err);
4266 continue;
4267 }
4268
4269 trackedBufferBarrier(cbD, bufD, 0,
4270 VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
4271
4272 VkBufferCopy copyInfo = {};
4273 copyInfo.srcOffset = u.offset;
4274 copyInfo.dstOffset = u.offset;
4275 copyInfo.size = u.data.size();
4276
4277 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4279 cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot];
4280 cmd.args.copyBuffer.dst = bufD->buffers[0];
4281 cmd.args.copyBuffer.desc = copyInfo;
4282
4283 // Where's the barrier for read-after-write? (assuming the common case
4284 // of binding this buffer as vertex/index, or, less likely, as uniform
4285 // buffer, in a renderpass later on) That is handled by the pass
4286 // resource tracking: the appropriate pipeline barrier will be
4287 // generated and recorded right before the renderpass, that binds this
4288 // buffer in one of its commands, gets its BeginRenderPass recorded.
4289
4290 bufD->lastActiveFrameSlot = currentFrameSlot;
4291
4292 if (bufD->m_type == QRhiBuffer::Immutable) {
4295 e.lastActiveFrameSlot = currentFrameSlot;
4296 e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot];
4297 e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot];
4298 bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
4299 bufD->stagingAllocations[currentFrameSlot] = nullptr;
4300 releaseQueue.append(e);
4301 }
4303 QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
4304 if (bufD->m_type == QRhiBuffer::Dynamic) {
4305 executeBufferHostWritesForSlot(bufD, currentFrameSlot);
4306 u.result->data.resizeForOverwrite(u.readSize);
4307 VkResult err = vmaCopyAllocationToMemory(toVmaAllocator(allocator),
4308 toVmaAllocation(bufD->allocations[currentFrameSlot]),
4309 u.offset, u.result->data.data(), u.readSize);
4310 if (err != VK_SUCCESS) {
4311 qWarning("Failed to copy memory from buffer: %d", err);
4312 u.result->data.clear();
4313 }
4314 if (u.result->completed)
4315 u.result->completed();
4316 } else {
4317 // Non-Dynamic buffers may not be host visible, so have to
4318 // create a readback buffer, enqueue a copy from
4319 // bufD->buffers[0] to this buffer, and then once the command
4320 // buffer completes, copy the data out of the host visible
4321 // readback buffer. Quite similar to what we do for texture
4322 // readbacks.
4323 BufferReadback readback;
4324 readback.activeFrameSlot = currentFrameSlot;
4325 readback.result = u.result;
4326 readback.byteSize = u.readSize;
4327
4328 VkBufferCreateInfo bufferInfo = {};
4329 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
4330 bufferInfo.size = readback.byteSize;
4331 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
4332
4333 VmaAllocationCreateInfo allocInfo = {};
4334 allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
4335
4336 VmaAllocation allocation;
4337 VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr);
4338 if (err == VK_SUCCESS) {
4339 readback.stagingAlloc = allocation;
4340 setAllocationName(allocation, bufD->name());
4341 } else {
4342 qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err);
4343 printExtraErrorInfo(err);
4344 continue;
4345 }
4346
4347 trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
4348
4349 VkBufferCopy copyInfo = {};
4350 copyInfo.srcOffset = u.offset;
4351 copyInfo.size = u.readSize;
4352
4353 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4355 cmd.args.copyBuffer.src = bufD->buffers[0];
4356 cmd.args.copyBuffer.dst = readback.stagingBuf;
4357 cmd.args.copyBuffer.desc = copyInfo;
4358
4359 bufD->lastActiveFrameSlot = currentFrameSlot;
4360
4361 activeBufferReadbacks.append(readback);
4362 }
4363 }
4364 }
4365
4366 for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
4367 const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
4369 QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
4370 // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos
4371 VkDeviceSize stagingSize = 0;
4372 for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) {
4373 for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
4374 for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level]))
4375 stagingSize += subresUploadByteSize(subresDesc);
4376 }
4377 }
4378
4379 Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]);
4380 VkBufferCreateInfo bufferInfo = {};
4381 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
4382 bufferInfo.size = stagingSize;
4383 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
4384
4385 VmaAllocationCreateInfo allocInfo = {};
4386 allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
4387
4388 VmaAllocation allocation;
4389 VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
4390 &utexD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
4391 if (err != VK_SUCCESS) {
4392 qWarning("Failed to create image staging buffer of size %d: %d", int(stagingSize), err);
4393 printExtraErrorInfo(err);
4394 continue;
4395 }
4396 utexD->stagingAllocations[currentFrameSlot] = allocation;
4397 setAllocationName(allocation, utexD->name());
4398
4399 BufferImageCopyList copyInfos;
4400 size_t curOfs = 0;
4401 void *mp = nullptr;
4402 VmaAllocation a = toVmaAllocation(utexD->stagingAllocations[currentFrameSlot]);
4403 err = vmaMapMemory(toVmaAllocator(allocator), a, &mp);
4404 if (err != VK_SUCCESS) {
4405 qWarning("Failed to map image data: %d", err);
4406 continue;
4407 }
4408
4409 for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) {
4410 for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
4411 const QList<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]);
4412 if (srd.isEmpty())
4413 continue;
4414 for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(srd)) {
4415 prepareUploadSubres(utexD, layer, level,
4416 subresDesc, &curOfs, mp, &copyInfos);
4417 }
4418 }
4419 }
4420 vmaFlushAllocation(toVmaAllocator(allocator), a, 0, stagingSize);
4421 vmaUnmapMemory(toVmaAllocator(allocator), a);
4422
4423 trackedImageBarrier(cbD, utexD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
4424 VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
4425
4426 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4428 cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot];
4429 cmd.args.copyBufferToImage.dst = utexD->image;
4430 cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
4431 cmd.args.copyBufferToImage.count = copyInfos.size();
4432 cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.size();
4433 cbD->pools.bufferImageCopy.append(copyInfos.constData(), copyInfos.size());
4434
4435 // no reuse of staging, this is intentional
4438 e.lastActiveFrameSlot = currentFrameSlot;
4439 e.stagingBuffer.stagingBuffer = utexD->stagingBuffers[currentFrameSlot];
4440 e.stagingBuffer.stagingAllocation = utexD->stagingAllocations[currentFrameSlot];
4441 utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
4442 utexD->stagingAllocations[currentFrameSlot] = nullptr;
4443 releaseQueue.append(e);
4444
4445 // Similarly to buffers, transitioning away from DST is done later,
4446 // when a renderpass using the texture is encountered.
4447
4448 utexD->lastActiveFrameSlot = currentFrameSlot;
4450 Q_ASSERT(u.src && u.dst);
4451 if (u.src == u.dst) {
4452 qWarning("Texture copy with matching source and destination is not supported");
4453 continue;
4454 }
4455 QVkTexture *srcD = QRHI_RES(QVkTexture, u.src);
4456 QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst);
4457 const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4458 const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4459
4460 VkImageCopy region = {};
4461 region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
4462 region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel());
4463 region.srcSubresource.baseArrayLayer = srcIs3D ? 0 : uint32_t(u.desc.sourceLayer());
4464 region.srcSubresource.layerCount = 1;
4465
4466 region.srcOffset.x = u.desc.sourceTopLeft().x();
4467 region.srcOffset.y = u.desc.sourceTopLeft().y();
4468 if (srcIs3D)
4469 region.srcOffset.z = uint32_t(u.desc.sourceLayer());
4470
4471 region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
4472 region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel());
4473 region.dstSubresource.baseArrayLayer = dstIs3D ? 0 : uint32_t(u.desc.destinationLayer());
4474 region.dstSubresource.layerCount = 1;
4475
4476 region.dstOffset.x = u.desc.destinationTopLeft().x();
4477 region.dstOffset.y = u.desc.destinationTopLeft().y();
4478 if (dstIs3D)
4479 region.dstOffset.z = uint32_t(u.desc.destinationLayer());
4480
4481 const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
4482 const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
4483 region.extent.width = uint32_t(copySize.width());
4484 region.extent.height = uint32_t(copySize.height());
4485 region.extent.depth = 1;
4486
4487 trackedImageBarrier(cbD, srcD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
4488 VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
4489 trackedImageBarrier(cbD, dstD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
4490 VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
4491
4492 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4494 cmd.args.copyImage.src = srcD->image;
4495 cmd.args.copyImage.srcLayout = srcD->usageState.layout;
4496 cmd.args.copyImage.dst = dstD->image;
4497 cmd.args.copyImage.dstLayout = dstD->usageState.layout;
4498 cmd.args.copyImage.desc = region;
4499
4500 srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
4502 TextureReadback readback;
4503 readback.activeFrameSlot = currentFrameSlot;
4504 readback.desc = u.rb;
4505 readback.result = u.result;
4506
4507 QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture());
4508 QVkSwapChain *swapChainD = nullptr;
4509 bool is3D = false;
4510 if (texD) {
4511 if (texD->samples > VK_SAMPLE_COUNT_1_BIT) {
4512 qWarning("Multisample texture cannot be read back");
4513 continue;
4514 }
4515 is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4516 if (u.rb.rect().isValid())
4517 readback.rect = u.rb.rect();
4518 else
4519 readback.rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
4520 readback.format = texD->m_format;
4521 texD->lastActiveFrameSlot = currentFrameSlot;
4522 } else {
4523 Q_ASSERT(currentSwapChain);
4524 swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain);
4525 if (!swapChainD->supportsReadback) {
4526 qWarning("Swapchain does not support readback");
4527 continue;
4528 }
4529 if (u.rb.rect().isValid())
4530 readback.rect = u.rb.rect();
4531 else
4532 readback.rect = QRect({0, 0}, swapChainD->pixelSize);
4533 readback.format = swapchainReadbackTextureFormat(swapChainD->colorFormat, nullptr);
4534 if (readback.format == QRhiTexture::UnknownFormat)
4535 continue;
4536
4537 // Multisample swapchains need nothing special since resolving
4538 // happens when ending a renderpass.
4539 }
4540 textureFormatInfo(readback.format, readback.rect.size(), nullptr, &readback.byteSize, nullptr);
4541
4542 // Create a host visible readback buffer.
4543 VkBufferCreateInfo bufferInfo = {};
4544 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
4545 bufferInfo.size = readback.byteSize;
4546 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
4547
4548 VmaAllocationCreateInfo allocInfo = {};
4549 allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
4550
4551 VmaAllocation allocation;
4552 VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr);
4553 if (err == VK_SUCCESS) {
4554 readback.stagingAlloc = allocation;
4555 setAllocationName(allocation, texD ? texD->name() : swapChainD->name());
4556 } else {
4557 qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err);
4558 printExtraErrorInfo(err);
4559 continue;
4560 }
4561
4562 // Copy from the (optimal and not host visible) image into the buffer.
4563 VkBufferImageCopy copyDesc = {};
4564 copyDesc.bufferOffset = 0;
4565 copyDesc.imageSubresource.aspectMask = aspectMaskForTextureFormat(readback.format);
4566 copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level());
4567 copyDesc.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(u.rb.layer());
4568 copyDesc.imageSubresource.layerCount = 1;
4569 copyDesc.imageOffset.x = readback.rect.x();
4570 copyDesc.imageOffset.y = readback.rect.y();
4571 if (is3D)
4572 copyDesc.imageOffset.z = u.rb.layer();
4573 copyDesc.imageExtent.width = uint32_t(readback.rect.width());
4574 copyDesc.imageExtent.height = uint32_t(readback.rect.height());
4575 copyDesc.imageExtent.depth = 1;
4576
4577 if (texD) {
4578 trackedImageBarrier(cbD, texD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
4579 VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
4580 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4582 cmd.args.copyImageToBuffer.src = texD->image;
4583 cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout;
4584 cmd.args.copyImageToBuffer.dst = readback.stagingBuf;
4585 cmd.args.copyImageToBuffer.desc = copyDesc;
4586 } else {
4587 // use the swapchain image
4588 QVkSwapChain::ImageResources &imageRes(swapChainD->imageRes[swapChainD->currentImageIndex]);
4589 VkImage image = imageRes.image;
4592 qWarning("Attempted to read back undefined swapchain image content, "
4593 "results are undefined. (do a render pass first)");
4594 }
4595 subresourceBarrier(cbD, image,
4596 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
4597 VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT,
4598 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
4599 0, 1,
4600 0, 1);
4602 }
4603
4604 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4606 cmd.args.copyImageToBuffer.src = image;
4607 cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
4608 cmd.args.copyImageToBuffer.dst = readback.stagingBuf;
4609 cmd.args.copyImageToBuffer.desc = copyDesc;
4610 }
4611
4612 activeTextureReadbacks.append(readback);
4614 QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst);
4615 Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips));
4616 const bool isCube = utexD->m_flags.testFlag(QRhiTexture::CubeMap);
4617 const bool isArray = utexD->m_flags.testFlag(QRhiTexture::TextureArray);
4618 const bool is3D = utexD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4619
4620 VkImageLayout origLayout = utexD->usageState.layout;
4621 VkAccessFlags origAccess = utexD->usageState.access;
4622 VkPipelineStageFlags origStage = utexD->usageState.stage;
4623 if (!origStage)
4624 origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
4625
4626 for (int layer = 0; layer < (isCube ? 6 : (isArray ? qMax(0, utexD->m_arraySize) : 1)); ++layer) {
4627 int w = utexD->m_pixelSize.width();
4628 int h = utexD->m_pixelSize.height();
4629 int depth = is3D ? qMax(1, utexD->m_depth) : 1;
4630 for (int level = 1; level < int(utexD->mipLevelCount); ++level) {
4631 if (level == 1) {
4632 subresourceBarrier(cbD, utexD->image,
4633 origLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
4634 origAccess, VK_ACCESS_TRANSFER_READ_BIT,
4635 origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
4636 layer, 1,
4637 level - 1, 1);
4638 } else {
4639 subresourceBarrier(cbD, utexD->image,
4640 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
4641 VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
4642 VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
4643 layer, 1,
4644 level - 1, 1);
4645 }
4646
4647 subresourceBarrier(cbD, utexD->image,
4648 origLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
4649 origAccess, VK_ACCESS_TRANSFER_WRITE_BIT,
4650 origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
4651 layer, 1,
4652 level, 1);
4653
4654 VkImageBlit region = {};
4655 region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
4656 region.srcSubresource.mipLevel = uint32_t(level) - 1;
4657 region.srcSubresource.baseArrayLayer = uint32_t(layer);
4658 region.srcSubresource.layerCount = 1;
4659
4660 region.srcOffsets[1].x = qMax(1, w);
4661 region.srcOffsets[1].y = qMax(1, h);
4662 region.srcOffsets[1].z = qMax(1, depth);
4663
4664 region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
4665 region.dstSubresource.mipLevel = uint32_t(level);
4666 region.dstSubresource.baseArrayLayer = uint32_t(layer);
4667 region.dstSubresource.layerCount = 1;
4668
4669 region.dstOffsets[1].x = qMax(1, w >> 1);
4670 region.dstOffsets[1].y = qMax(1, h >> 1);
4671 region.dstOffsets[1].z = qMax(1, depth >> 1);
4672
4673 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4675 cmd.args.blitImage.src = utexD->image;
4676 cmd.args.blitImage.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
4677 cmd.args.blitImage.dst = utexD->image;
4678 cmd.args.blitImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
4679 cmd.args.blitImage.filter = VK_FILTER_LINEAR;
4680 cmd.args.blitImage.desc = region;
4681
4682 w >>= 1;
4683 h >>= 1;
4684 depth >>= 1;
4685 }
4686
4687 if (utexD->mipLevelCount > 1) {
4688 subresourceBarrier(cbD, utexD->image,
4689 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origLayout,
4690 VK_ACCESS_TRANSFER_READ_BIT, origAccess,
4691 VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
4692 layer, 1,
4693 0, int(utexD->mipLevelCount) - 1);
4694 subresourceBarrier(cbD, utexD->image,
4695 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origLayout,
4696 VK_ACCESS_TRANSFER_WRITE_BIT, origAccess,
4697 VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
4698 layer, 1,
4699 int(utexD->mipLevelCount) - 1, 1);
4700 }
4701 }
4702 utexD->lastActiveFrameSlot = currentFrameSlot;
4703 }
4704 }
4705
4706 ud->free();
4707}
4708
4710{
4711 if (bufD->pendingDynamicUpdates[slot].isEmpty())
4712 return;
4713
4714 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
4715 void *p = nullptr;
4716 VmaAllocation a = toVmaAllocation(bufD->allocations[slot]);
4717 // The vmaMap/Unmap are basically a no-op when persistently mapped since it
4718 // refcounts; this is great because we don't need to care if the allocation
4719 // was created as persistently mapped or not.
4720 VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
4721 if (err != VK_SUCCESS) {
4722 qWarning("Failed to map buffer: %d", err);
4723 return;
4724 }
4725 quint32 changeBegin = UINT32_MAX;
4726 quint32 changeEnd = 0;
4727 for (const QVkBuffer::DynamicUpdate &u : std::as_const(bufD->pendingDynamicUpdates[slot])) {
4728 memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
4729 if (u.offset < changeBegin)
4730 changeBegin = u.offset;
4731 if (u.offset + u.data.size() > changeEnd)
4732 changeEnd = u.offset + u.data.size();
4733 }
4734 if (changeBegin < UINT32_MAX && changeBegin < changeEnd)
4735 vmaFlushAllocation(toVmaAllocator(allocator), a, changeBegin, changeEnd - changeBegin);
4736 vmaUnmapMemory(toVmaAllocator(allocator), a);
4737
4738 bufD->pendingDynamicUpdates[slot].clear();
4739}
4740
4741static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator)
4742{
4743 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
4744 vmaDestroyBuffer(toVmaAllocator(allocator), e.buffer.buffers[i], toVmaAllocation(e.buffer.allocations[i]));
4745 vmaDestroyBuffer(toVmaAllocator(allocator), e.buffer.stagingBuffers[i], toVmaAllocation(e.buffer.stagingAllocations[i]));
4746 }
4747}
4748
4749static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
4750{
4751 df->vkDestroyImageView(dev, e.renderBuffer.imageView, nullptr);
4752 df->vkDestroyImage(dev, e.renderBuffer.image, nullptr);
4753 df->vkFreeMemory(dev, e.renderBuffer.memory, nullptr);
4754}
4755
4756static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator)
4757{
4758 df->vkDestroyImageView(dev, e.texture.imageView, nullptr);
4759 vmaDestroyImage(toVmaAllocator(allocator), e.texture.image, toVmaAllocation(e.texture.allocation));
4760 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
4761 vmaDestroyBuffer(toVmaAllocator(allocator), e.texture.stagingBuffers[i], toVmaAllocation(e.texture.stagingAllocations[i]));
4762 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) {
4763 if (e.texture.extraImageViews[i])
4764 df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr);
4765 }
4766}
4767
4768static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
4769{
4770 df->vkDestroySampler(dev, e.sampler.sampler, nullptr);
4771}
4772
4774{
4775 for (int i = releaseQueue.size() - 1; i >= 0; --i) {
4776 const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]);
4777 if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
4778 switch (e.type) {
4780 df->vkDestroyPipeline(dev, e.pipelineState.pipeline, nullptr);
4781 df->vkDestroyPipelineLayout(dev, e.pipelineState.layout, nullptr);
4782 break;
4784 df->vkDestroyDescriptorSetLayout(dev, e.shaderResourceBindings.layout, nullptr);
4785 if (e.shaderResourceBindings.poolIndex >= 0) {
4786 descriptorPools[e.shaderResourceBindings.poolIndex].refCount -= 1;
4787 Q_ASSERT(descriptorPools[e.shaderResourceBindings.poolIndex].refCount >= 0);
4788 }
4789 break;
4792 break;
4794 qrhivk_releaseRenderBuffer(e, dev, df);
4795 break;
4797 qrhivk_releaseTexture(e, dev, df, allocator);
4798 break;
4800 qrhivk_releaseSampler(e, dev, df);
4801 break;
4803 df->vkDestroyFramebuffer(dev, e.textureRenderTarget.fb, nullptr);
4804 for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
4805 df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr);
4806 df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr);
4807 }
4808 df->vkDestroyImageView(dev, e.textureRenderTarget.dsv, nullptr);
4809 df->vkDestroyImageView(dev, e.textureRenderTarget.resdsv, nullptr);
4810 df->vkDestroyImageView(dev, e.textureRenderTarget.shadingRateMapView, nullptr);
4811 break;
4813 df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr);
4814 break;
4816 vmaDestroyBuffer(toVmaAllocator(allocator), e.stagingBuffer.stagingBuffer, toVmaAllocation(e.stagingBuffer.stagingAllocation));
4817 break;
4818 case QRhiVulkan::DeferredReleaseEntry::SecondaryCommandBuffer:
4819 freeSecondaryCbs[e.lastActiveFrameSlot].append(e.secondaryCommandBuffer.cb);
4820 break;
4821 default:
4822 Q_UNREACHABLE();
4823 break;
4824 }
4825 releaseQueue.removeAt(i);
4826 }
4827 }
4828}
4829
4831{
4832 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
4833
4834 for (int i = activeTextureReadbacks.size() - 1; i >= 0; --i) {
4835 const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]);
4836 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
4837 readback.result->format = readback.format;
4838 readback.result->pixelSize = readback.rect.size();
4839 readback.result->data.resizeForOverwrite(readback.byteSize);
4840 VkResult err = vmaCopyAllocationToMemory(toVmaAllocator(allocator),
4841 toVmaAllocation(readback.stagingAlloc),
4842 0, readback.result->data.data(), readback.byteSize);
4843 if (err != VK_SUCCESS) {
4844 qWarning("Failed to copy texture readback buffer of size %u: %d", readback.byteSize, err);
4845 readback.result->data.clear();
4846 }
4847
4848 vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, toVmaAllocation(readback.stagingAlloc));
4849
4850 if (readback.result->completed)
4851 completedCallbacks.append(readback.result->completed);
4852
4853 activeTextureReadbacks.remove(i);
4854 }
4855 }
4856
4857 for (int i = activeBufferReadbacks.size() - 1; i >= 0; --i) {
4858 const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]);
4859 if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
4860 readback.result->data.resizeForOverwrite(readback.byteSize);
4861 VkResult err = vmaCopyAllocationToMemory(toVmaAllocator(allocator),
4862 toVmaAllocation(readback.stagingAlloc),
4863 0, readback.result->data.data(), readback.byteSize);
4864 if (err != VK_SUCCESS) {
4865 qWarning("Failed to copy buffer readback buffer of size %d: %d", readback.byteSize, err);
4866 readback.result->data.clear();
4867 }
4868
4869 vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, toVmaAllocation(readback.stagingAlloc));
4870
4871 if (readback.result->completed)
4872 completedCallbacks.append(readback.result->completed);
4873
4874 activeBufferReadbacks.remove(i);
4875 }
4876 }
4877
4878 for (auto f : completedCallbacks)
4879 f();
4880}
4881
4882static struct {
4885} qvk_sampleCounts[] = {
4886 // keep this sorted by 'count'
4887 { VK_SAMPLE_COUNT_1_BIT, 1 },
4888 { VK_SAMPLE_COUNT_2_BIT, 2 },
4889 { VK_SAMPLE_COUNT_4_BIT, 4 },
4890 { VK_SAMPLE_COUNT_8_BIT, 8 },
4891 { VK_SAMPLE_COUNT_16_BIT, 16 },
4892 { VK_SAMPLE_COUNT_32_BIT, 32 },
4893 { VK_SAMPLE_COUNT_64_BIT, 64 }
4895
4897{
4898 const VkPhysicalDeviceLimits *limits = &physDevProperties.limits;
4899 VkSampleCountFlags color = limits->framebufferColorSampleCounts;
4900 VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
4901 VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
4902 QList<int> result;
4903
4904 for (const auto &qvk_sampleCount : qvk_sampleCounts) {
4905 if ((color & qvk_sampleCount.mask)
4906 && (depth & qvk_sampleCount.mask)
4907 && (stencil & qvk_sampleCount.mask))
4908 {
4909 result.append(qvk_sampleCount.count);
4910 }
4911 }
4912
4913 return result;
4914}
4915
4917{
4918 const int s = effectiveSampleCount(sampleCount);
4919
4920 for (const auto &qvk_sampleCount : qvk_sampleCounts) {
4921 if (qvk_sampleCount.count == s)
4922 return qvk_sampleCount.mask;
4923 }
4924
4925 Q_UNREACHABLE_RETURN(VK_SAMPLE_COUNT_1_BIT);
4926}
4927
4929{
4930 QList<QSize> result;
4931#ifdef VK_KHR_fragment_shading_rate
4932 sampleCount = qMax(1, sampleCount);
4933 VkSampleCountFlagBits mask = VK_SAMPLE_COUNT_1_BIT;
4934 for (const auto &qvk_sampleCount : qvk_sampleCounts) {
4935 if (qvk_sampleCount.count == sampleCount) {
4936 mask = qvk_sampleCount.mask;
4937 break;
4938 }
4939 }
4940 for (const VkPhysicalDeviceFragmentShadingRateKHR &s : fragmentShadingRates) {
4941 if (s.sampleCounts & mask)
4942 result.append(QSize(int(s.fragmentSize.width), int(s.fragmentSize.height)));
4943 }
4944#else
4945 Q_UNUSED(sampleCount);
4946 result.append(QSize(1, 1));
4947#endif
4948 return result;
4949}
4950
4952{
4953 cbD->passResTrackers.emplace_back();
4954 cbD->currentPassResTrackerIndex = cbD->passResTrackers.size() - 1;
4955
4956 QVkCommandBuffer::Command &cmd(cbD->commands.get());
4958 cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.size() - 1;
4959}
4960
4962{
4964
4965 for (auto it = cbD->commands.begin(), end = cbD->commands.end(); it != end; ++it) {
4966 QVkCommandBuffer::Command &cmd(*it);
4967 switch (cmd.cmd) {
4969 df->vkCmdCopyBuffer(cbD->cb, cmd.args.copyBuffer.src, cmd.args.copyBuffer.dst,
4970 1, &cmd.args.copyBuffer.desc);
4971 break;
4973 df->vkCmdCopyBufferToImage(cbD->cb, cmd.args.copyBufferToImage.src, cmd.args.copyBufferToImage.dst,
4974 cmd.args.copyBufferToImage.dstLayout,
4975 uint32_t(cmd.args.copyBufferToImage.count),
4976 cbD->pools.bufferImageCopy.constData() + cmd.args.copyBufferToImage.bufferImageCopyIndex);
4977 break;
4979 df->vkCmdCopyImage(cbD->cb, cmd.args.copyImage.src, cmd.args.copyImage.srcLayout,
4980 cmd.args.copyImage.dst, cmd.args.copyImage.dstLayout,
4981 1, &cmd.args.copyImage.desc);
4982 break;
4984 df->vkCmdCopyImageToBuffer(cbD->cb, cmd.args.copyImageToBuffer.src, cmd.args.copyImageToBuffer.srcLayout,
4985 cmd.args.copyImageToBuffer.dst,
4986 1, &cmd.args.copyImageToBuffer.desc);
4987 break;
4989 df->vkCmdPipelineBarrier(cbD->cb, cmd.args.imageBarrier.srcStageMask, cmd.args.imageBarrier.dstStageMask,
4990 0, 0, nullptr, 0, nullptr,
4991 cmd.args.imageBarrier.count, cbD->pools.imageBarrier.constData() + cmd.args.imageBarrier.index);
4992 break;
4994 df->vkCmdPipelineBarrier(cbD->cb, cmd.args.bufferBarrier.srcStageMask, cmd.args.bufferBarrier.dstStageMask,
4995 0, 0, nullptr,
4996 cmd.args.bufferBarrier.count, cbD->pools.bufferBarrier.constData() + cmd.args.bufferBarrier.index,
4997 0, nullptr);
4998 break;
5000 df->vkCmdBlitImage(cbD->cb, cmd.args.blitImage.src, cmd.args.blitImage.srcLayout,
5001 cmd.args.blitImage.dst, cmd.args.blitImage.dstLayout,
5002 1, &cmd.args.blitImage.desc,
5003 cmd.args.blitImage.filter);
5004 break;
5006 cmd.args.beginRenderPass.desc.pClearValues = cbD->pools.clearValue.constData() + cmd.args.beginRenderPass.clearValueIndex;
5007 df->vkCmdBeginRenderPass(cbD->cb, &cmd.args.beginRenderPass.desc,
5008 cmd.args.beginRenderPass.useSecondaryCb ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
5009 : VK_SUBPASS_CONTENTS_INLINE);
5010 break;
5012 VkMemoryBarrier barrier;
5013 barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
5014 barrier.pNext = nullptr;
5015 barrier.dstAccessMask = cmd.args.memoryBarrier.dstAccessMask;
5016 barrier.srcAccessMask = cmd.args.memoryBarrier.srcAccessMask;
5017 df->vkCmdPipelineBarrier(cbD->cb, cmd.args.memoryBarrier.srcStageMask, cmd.args.memoryBarrier.dstStageMask, cmd.args.memoryBarrier.dependencyFlags,
5018 1, &barrier,
5019 0, VK_NULL_HANDLE,
5020 0, VK_NULL_HANDLE);
5021 } break;
5023 df->vkCmdEndRenderPass(cbD->cb);
5024 break;
5026 df->vkCmdBindPipeline(cbD->cb, cmd.args.bindPipeline.bindPoint, cmd.args.bindPipeline.pipeline);
5027 break;
5029 {
5030 const uint32_t *offsets = nullptr;
5031 if (cmd.args.bindDescriptorSet.dynamicOffsetCount > 0)
5032 offsets = cbD->pools.dynamicOffset.constData() + cmd.args.bindDescriptorSet.dynamicOffsetIndex;
5033 df->vkCmdBindDescriptorSets(cbD->cb, cmd.args.bindDescriptorSet.bindPoint,
5034 cmd.args.bindDescriptorSet.pipelineLayout,
5035 0, 1, &cmd.args.bindDescriptorSet.descSet,
5036 uint32_t(cmd.args.bindDescriptorSet.dynamicOffsetCount),
5037 offsets);
5038 }
5039 break;
5041 df->vkCmdBindVertexBuffers(cbD->cb, uint32_t(cmd.args.bindVertexBuffer.startBinding),
5042 uint32_t(cmd.args.bindVertexBuffer.count),
5043 cbD->pools.vertexBuffer.constData() + cmd.args.bindVertexBuffer.vertexBufferIndex,
5044 cbD->pools.vertexBufferOffset.constData() + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex);
5045 break;
5047 df->vkCmdBindIndexBuffer(cbD->cb, cmd.args.bindIndexBuffer.buf,
5048 cmd.args.bindIndexBuffer.ofs, cmd.args.bindIndexBuffer.type);
5049 break;
5051 df->vkCmdSetViewport(cbD->cb, 0, 1, &cmd.args.setViewport.viewport);
5052 break;
5054 df->vkCmdSetScissor(cbD->cb, 0, 1, &cmd.args.setScissor.scissor);
5055 break;
5057 df->vkCmdSetBlendConstants(cbD->cb, cmd.args.setBlendConstants.c);
5058 break;
5060 df->vkCmdSetStencilReference(cbD->cb, VK_STENCIL_FRONT_AND_BACK, cmd.args.setStencilRef.ref);
5061 break;
5063 df->vkCmdDraw(cbD->cb, cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
5064 cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
5065 break;
5067 df->vkCmdDrawIndexed(cbD->cb, cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
5068 cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
5069 cmd.args.drawIndexed.firstInstance);
5070 break;
5072 df->vkCmdDrawIndirect(cbD->cb, cmd.args.drawIndirect.indirectBuffer,
5073 cmd.args.drawIndirect.indirectBufferOffset,
5074 cmd.args.drawIndirect.drawCount,
5075 cmd.args.drawIndirect.stride);
5076 break;
5078 df->vkCmdDrawIndexedIndirect(cbD->cb, cmd.args.drawIndexedIndirect.indirectBuffer,
5079 cmd.args.drawIndexedIndirect.indirectBufferOffset,
5080 cmd.args.drawIndexedIndirect.drawCount,
5081 cmd.args.drawIndexedIndirect.stride);
5082 break;
5084#ifdef VK_EXT_debug_utils
5085 cmd.args.debugMarkerBegin.label.pLabelName =
5086 cbD->pools.debugMarkerData[cmd.args.debugMarkerBegin.labelNameIndex].constData();
5087 vkCmdBeginDebugUtilsLabelEXT(cbD->cb, &cmd.args.debugMarkerBegin.label);
5088#endif
5089 break;
5091#ifdef VK_EXT_debug_utils
5092 vkCmdEndDebugUtilsLabelEXT(cbD->cb);
5093#endif
5094 break;
5096#ifdef VK_EXT_debug_utils
5097 cmd.args.debugMarkerInsert.label.pLabelName =
5098 cbD->pools.debugMarkerData[cmd.args.debugMarkerInsert.labelNameIndex].constData();
5099 vkCmdInsertDebugUtilsLabelEXT(cbD->cb, &cmd.args.debugMarkerInsert.label);
5100#endif
5101 break;
5103 recordTransitionPassResources(cbD, cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]);
5104 break;
5106 df->vkCmdDispatch(cbD->cb, uint32_t(cmd.args.dispatch.x), uint32_t(cmd.args.dispatch.y), uint32_t(cmd.args.dispatch.z));
5107 break;
5109 df->vkCmdExecuteCommands(cbD->cb, 1, &cmd.args.executeSecondary.cb);
5110 break;
5112 {
5113#ifdef VK_KHR_fragment_shading_rate
5114 VkFragmentShadingRateCombinerOpKHR op[2] = {
5115 VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR,
5116 VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR
5117 };
5118 VkExtent2D size = { cmd.args.setShadingRate.w, cmd.args.setShadingRate.h };
5119 vkCmdSetFragmentShadingRateKHR(cbD->cb, &size, op);
5120#endif
5121 }
5122 break;
5123 default:
5124 break;
5125 }
5126 }
5127}
5128
5130{
5131 switch (access) {
5132 case QRhiPassResourceTracker::BufIndirectDraw:
5133 return VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
5134 case QRhiPassResourceTracker::BufVertexInput:
5135 return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
5136 case QRhiPassResourceTracker::BufIndexRead:
5137 return VK_ACCESS_INDEX_READ_BIT;
5138 case QRhiPassResourceTracker::BufUniformRead:
5139 return VK_ACCESS_UNIFORM_READ_BIT;
5140 case QRhiPassResourceTracker::BufStorageLoad:
5141 return VK_ACCESS_SHADER_READ_BIT;
5142 case QRhiPassResourceTracker::BufStorageStore:
5143 return VK_ACCESS_SHADER_WRITE_BIT;
5144 case QRhiPassResourceTracker::BufStorageLoadStore:
5145 return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
5146 default:
5147 Q_UNREACHABLE();
5148 break;
5149 }
5150 return 0;
5151}
5152
5154{
5155 switch (stage) {
5156 case QRhiPassResourceTracker::BufIndirectDrawStage:
5157 return VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
5158 case QRhiPassResourceTracker::BufVertexInputStage:
5159 return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
5160 case QRhiPassResourceTracker::BufVertexStage:
5161 return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
5162 case QRhiPassResourceTracker::BufTCStage:
5163 return VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT;
5164 case QRhiPassResourceTracker::BufTEStage:
5165 return VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
5166 case QRhiPassResourceTracker::BufFragmentStage:
5167 return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
5168 case QRhiPassResourceTracker::BufComputeStage:
5169 return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
5170 case QRhiPassResourceTracker::BufGeometryStage:
5171 return VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
5172 default:
5173 Q_UNREACHABLE();
5174 break;
5175 }
5176 return 0;
5177}
5178
5180{
5182 u.access = VkAccessFlags(usage.access);
5183 u.stage = VkPipelineStageFlags(usage.stage);
5184 return u;
5185}
5186
5188{
5189 switch (access) {
5190 case QRhiPassResourceTracker::TexSample:
5191 return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5192 case QRhiPassResourceTracker::TexColorOutput:
5193 return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
5194 case QRhiPassResourceTracker::TexDepthOutput:
5195 return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
5196 case QRhiPassResourceTracker::TexStorageLoad:
5197 case QRhiPassResourceTracker::TexStorageStore:
5198 case QRhiPassResourceTracker::TexStorageLoadStore:
5199 return VK_IMAGE_LAYOUT_GENERAL;
5200 case QRhiPassResourceTracker::TexShadingRate:
5201#ifdef VK_KHR_fragment_shading_rate
5202 return VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
5203#else
5204 return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5205#endif
5206 default:
5207 Q_UNREACHABLE();
5208 break;
5209 }
5210 return VK_IMAGE_LAYOUT_GENERAL;
5211}
5212
5214{
5215 switch (access) {
5216 case QRhiPassResourceTracker::TexSample:
5217 return VK_ACCESS_SHADER_READ_BIT;
5218 case QRhiPassResourceTracker::TexColorOutput:
5219 return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
5220 case QRhiPassResourceTracker::TexDepthOutput:
5221 return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
5222 case QRhiPassResourceTracker::TexStorageLoad:
5223 return VK_ACCESS_SHADER_READ_BIT;
5224 case QRhiPassResourceTracker::TexStorageStore:
5225 return VK_ACCESS_SHADER_WRITE_BIT;
5226 case QRhiPassResourceTracker::TexStorageLoadStore:
5227 return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
5229 return 0;
5230 default:
5231 Q_UNREACHABLE();
5232 break;
5233 }
5234 return 0;
5235}
5236
5238{
5239 switch (stage) {
5240 case QRhiPassResourceTracker::TexVertexStage:
5241 return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
5242 case QRhiPassResourceTracker::TexTCStage:
5243 return VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT;
5244 case QRhiPassResourceTracker::TexTEStage:
5245 return VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
5246 case QRhiPassResourceTracker::TexFragmentStage:
5247 return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
5248 case QRhiPassResourceTracker::TexColorOutputStage:
5249 return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
5250 case QRhiPassResourceTracker::TexDepthOutputStage:
5251 return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
5252 case QRhiPassResourceTracker::TexComputeStage:
5253 return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
5254 case QRhiPassResourceTracker::TexGeometryStage:
5255 return VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
5256 default:
5257 Q_UNREACHABLE();
5258 break;
5259 }
5260 return 0;
5261}
5262
5264{
5266 u.layout = VkImageLayout(usage.layout);
5267 u.access = VkAccessFlags(usage.access);
5268 u.stage = VkPipelineStageFlags(usage.stage);
5269 return u;
5270}
5271
5273 QVkBuffer *bufD,
5274 int slot,
5277{
5278 QVkBuffer::UsageState &u(bufD->usageState[slot]);
5279 const VkAccessFlags newAccess = toVkAccess(access);
5280 const VkPipelineStageFlags newStage = toVkPipelineStage(stage);
5281 if (u.access == newAccess && u.stage == newStage) {
5282 if (!accessIsWrite(access))
5283 return;
5284 }
5285 passResTracker->registerBuffer(bufD, slot, &access, &stage, toPassTrackerUsageState(u));
5286 u.access = newAccess;
5287 u.stage = newStage;
5288}
5289
5291 QVkTexture *texD,
5294{
5295 QVkTexture::UsageState &u(texD->usageState);
5296 const VkAccessFlags newAccess = toVkAccess(access);
5297 const VkPipelineStageFlags newStage = toVkPipelineStage(stage);
5298 const VkImageLayout newLayout = toVkLayout(access);
5299 if (u.access == newAccess && u.stage == newStage && u.layout == newLayout) {
5300 if (!accessIsWrite(access))
5301 return;
5302 }
5303 passResTracker->registerTexture(texD, &access, &stage, toPassTrackerUsageState(u));
5304 u.layout = newLayout;
5305 u.access = newAccess;
5306 u.stage = newStage;
5307}
5308
5310{
5311 if (tracker.isEmpty())
5312 return;
5313
5314 for (const auto &[rhiB, trackedB]: tracker.buffers()) {
5315 QVkBuffer *bufD = QRHI_RES(QVkBuffer, rhiB);
5316 VkAccessFlags access = toVkAccess(trackedB.access);
5317 VkPipelineStageFlags stage = toVkPipelineStage(trackedB.stage);
5318 QVkBuffer::UsageState s = toVkBufferUsageState(trackedB.stateAtPassBegin);
5319 if (!s.stage)
5320 continue;
5321 if (s.access == access && s.stage == stage) {
5322 if (!accessIsWrite(access))
5323 continue;
5324 }
5325 VkBufferMemoryBarrier bufMemBarrier = {};
5326 bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
5327 bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
5328 bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
5329 bufMemBarrier.srcAccessMask = s.access;
5330 bufMemBarrier.dstAccessMask = access;
5331 bufMemBarrier.buffer = bufD->buffers[trackedB.slot];
5332 bufMemBarrier.size = VK_WHOLE_SIZE;
5333 df->vkCmdPipelineBarrier(cbD->cb, s.stage, stage, 0,
5334 0, nullptr,
5335 1, &bufMemBarrier,
5336 0, nullptr);
5337 }
5338
5339 for (const auto &[rhiT, trackedT]: tracker.textures()) {
5340 QVkTexture *texD = QRHI_RES(QVkTexture, rhiT);
5341 VkImageLayout layout = toVkLayout(trackedT.access);
5342 VkAccessFlags access = toVkAccess(trackedT.access);
5343 VkPipelineStageFlags stage = toVkPipelineStage(trackedT.stage);
5344 QVkTexture::UsageState s = toVkTextureUsageState(trackedT.stateAtPassBegin);
5345 if (s.access == access && s.stage == stage && s.layout == layout) {
5346 if (!accessIsWrite(access))
5347 continue;
5348 }
5349 VkImageMemoryBarrier barrier = {};
5350 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
5351 barrier.subresourceRange.aspectMask = aspectMaskForTextureFormat(texD->m_format);
5352 barrier.subresourceRange.baseMipLevel = 0;
5353 barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
5354 barrier.subresourceRange.baseArrayLayer = 0;
5355 barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
5356 barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
5357 barrier.newLayout = layout;
5358 barrier.srcAccessMask = s.access; // may be 0 but that's fine
5359 barrier.dstAccessMask = access;
5360 barrier.image = texD->image;
5361 VkPipelineStageFlags srcStage = s.stage;
5362 // stage mask cannot be 0
5363 if (!srcStage)
5364 srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
5365 df->vkCmdPipelineBarrier(cbD->cb, srcStage, stage, 0,
5366 0, nullptr,
5367 0, nullptr,
5368 1, &barrier);
5369 }
5370}
5371
5373{
5374 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR
5375 || !vkGetPhysicalDeviceSurfaceFormatsKHR
5376 || !vkGetPhysicalDeviceSurfacePresentModesKHR)
5377 {
5378 qWarning("Physical device surface queries not available");
5379 return nullptr;
5380 }
5381
5382 return new QVkSwapChain(this);
5383}
5384
5385QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
5386{
5387 return new QVkBuffer(this, type, usage, size);
5388}
5389
5391{
5392 return int(ubufAlign); // typically 256 (bytes)
5393}
5394
5396{
5397 return false;
5398}
5399
5401{
5402 return false;
5403}
5404
5406{
5407 return true;
5408}
5409
5411{
5412 // See https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/
5413
5414 static QMatrix4x4 m;
5415 if (m.isIdentity()) {
5416 // NB the ctor takes row-major
5417 m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
5418 0.0f, -1.0f, 0.0f, 0.0f,
5419 0.0f, 0.0f, 0.5f, 0.5f,
5420 0.0f, 0.0f, 0.0f, 1.0f);
5421 }
5422 return m;
5423}
5424
5425bool QRhiVulkan::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
5426{
5427 // Note that with some SDKs the validation layer gives an odd warning about
5428 // BC not being supported, even when our check here succeeds. Not much we
5429 // can do about that.
5430 if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7) {
5431 if (!physDevFeatures.textureCompressionBC)
5432 return false;
5433 }
5434
5435 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8) {
5436 if (!physDevFeatures.textureCompressionETC2)
5437 return false;
5438 }
5439
5440 if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12) {
5441 if (!physDevFeatures.textureCompressionASTC_LDR)
5442 return false;
5443 }
5444
5445 VkFormat vkformat = toVkTextureFormat(format, flags);
5446 VkFormatProperties props;
5447 f->vkGetPhysicalDeviceFormatProperties(physDev, vkformat, &props);
5448 return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
5449}
5450
5451bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
5452{
5453 switch (feature) {
5454 case QRhi::MultisampleTexture:
5455 return true;
5456 case QRhi::MultisampleRenderBuffer:
5457 return true;
5458 case QRhi::DebugMarkers:
5459 return caps.debugUtils;
5460 case QRhi::Timestamps:
5461 return timestampValidBits != 0;
5462 case QRhi::Instancing:
5463 return true;
5464 case QRhi::CustomInstanceStepRate:
5465 return caps.vertexAttribDivisor;
5466 case QRhi::PrimitiveRestart:
5467 return true;
5468 case QRhi::NonDynamicUniformBuffers:
5469 return true;
5470 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
5471 return true;
5472 case QRhi::NPOTTextureRepeat:
5473 return true;
5474 case QRhi::RedOrAlpha8IsRed:
5475 return true;
5476 case QRhi::ElementIndexUint:
5477 return true;
5478 case QRhi::Compute:
5479 return caps.compute;
5480 case QRhi::WideLines:
5481 return caps.wideLines;
5482 case QRhi::VertexShaderPointSize:
5483 return true;
5484 case QRhi::BaseVertex:
5485 return true;
5486 case QRhi::BaseInstance:
5487 return true;
5488 case QRhi::TriangleFanTopology:
5489 return true;
5490 case QRhi::ReadBackNonUniformBuffer:
5491 return true;
5492 case QRhi::ReadBackNonBaseMipLevel:
5493 return true;
5494 case QRhi::TexelFetch:
5495 return true;
5496 case QRhi::RenderToNonBaseMipLevel:
5497 return true;
5498 case QRhi::IntAttributes:
5499 return true;
5500 case QRhi::ScreenSpaceDerivatives:
5501 return true;
5502 case QRhi::ReadBackAnyTextureFormat:
5503 return true;
5504 case QRhi::PipelineCacheDataLoadSave:
5505 return true;
5506 case QRhi::ImageDataStride:
5507 return true;
5508 case QRhi::RenderBufferImport:
5509 return false;
5510 case QRhi::ThreeDimensionalTextures:
5511 return true;
5512 case QRhi::RenderTo3DTextureSlice:
5513 return caps.texture3DSliceAs2D;
5514 case QRhi::TextureArrays:
5515 return true;
5516 case QRhi::Tessellation:
5517 return caps.tessellation;
5518 case QRhi::GeometryShader:
5519 return caps.geometryShader;
5520 case QRhi::TextureArrayRange:
5521 return true;
5522 case QRhi::NonFillPolygonMode:
5523 return caps.nonFillPolygonMode;
5524 case QRhi::OneDimensionalTextures:
5525 return true;
5526 case QRhi::OneDimensionalTextureMipmaps:
5527 return true;
5528 case QRhi::HalfAttributes:
5529 return true;
5530 case QRhi::RenderToOneDimensionalTexture:
5531 return true;
5532 case QRhi::ThreeDimensionalTextureMipmaps:
5533 return true;
5534 case QRhi::MultiView:
5535 return caps.multiView;
5536 case QRhi::TextureViewFormat:
5537 return true;
5538 case QRhi::ResolveDepthStencil:
5539 return caps.renderPass2KHR && caps.depthStencilResolveKHR;
5540 case QRhi::VariableRateShading:
5541 return caps.renderPass2KHR && caps.perDrawShadingRate;
5542 case QRhi::VariableRateShadingMap:
5543 case QRhi::VariableRateShadingMapWithTexture:
5544 return caps.renderPass2KHR && caps.imageBasedShadingRate;
5545 case QRhi::PerRenderTargetBlending:
5546 case QRhi::SampleVariables:
5547 return true;
5548 case QRhi::InstanceIndexIncludesBaseInstance:
5549 return true;
5550 case QRhi::DepthClamp:
5551 return true;
5552 case QRhi::DrawIndirect:
5553 return true; // available in Vulkan 1.0
5554 case QRhi::DrawIndirectMulti:
5555 return caps.drawIndirectMulti;
5556 default:
5557 Q_UNREACHABLE_RETURN(false);
5558 }
5559}
5560
5561int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const
5562{
5563 switch (limit) {
5564 case QRhi::TextureSizeMin:
5565 return 1;
5566 case QRhi::TextureSizeMax:
5567 return int(physDevProperties.limits.maxImageDimension2D);
5568 case QRhi::MaxColorAttachments:
5569 return int(physDevProperties.limits.maxColorAttachments);
5570 case QRhi::FramesInFlight:
5571 return QVK_FRAMES_IN_FLIGHT;
5572 case QRhi::MaxAsyncReadbackFrames:
5573 return QVK_FRAMES_IN_FLIGHT;
5574 case QRhi::MaxThreadGroupsPerDimension:
5575 return int(qMin(physDevProperties.limits.maxComputeWorkGroupCount[0],
5576 qMin(physDevProperties.limits.maxComputeWorkGroupCount[1],
5577 physDevProperties.limits.maxComputeWorkGroupCount[2])));
5578 case QRhi::MaxThreadsPerThreadGroup:
5579 return int(physDevProperties.limits.maxComputeWorkGroupInvocations);
5580 case QRhi::MaxThreadGroupX:
5581 return int(physDevProperties.limits.maxComputeWorkGroupSize[0]);
5582 case QRhi::MaxThreadGroupY:
5583 return int(physDevProperties.limits.maxComputeWorkGroupSize[1]);
5584 case QRhi::MaxThreadGroupZ:
5585 return int(physDevProperties.limits.maxComputeWorkGroupSize[2]);
5586 case QRhi::TextureArraySizeMax:
5587 return int(physDevProperties.limits.maxImageArrayLayers);
5588 case QRhi::MaxUniformBufferRange:
5589 return int(qMin<uint32_t>(INT_MAX, physDevProperties.limits.maxUniformBufferRange));
5590 case QRhi::MaxVertexInputs:
5591 return physDevProperties.limits.maxVertexInputAttributes;
5592 case QRhi::MaxVertexOutputs:
5593 return physDevProperties.limits.maxVertexOutputComponents / 4;
5594 case QRhi::ShadingRateImageTileSize:
5595 return caps.imageBasedShadingRateTileSize;
5596 default:
5597 Q_UNREACHABLE_RETURN(0);
5598 }
5599}
5600
5602{
5603 return &nativeHandlesStruct;
5604}
5605
5607{
5608 return driverInfoStruct;
5609}
5610
5612{
5613 QRhiStats result;
5614 result.totalPipelineCreationTime = totalPipelineCreationTime();
5615
5616 VmaBudget budgets[VK_MAX_MEMORY_HEAPS];
5617 vmaGetHeapBudgets(toVmaAllocator(allocator), budgets);
5618
5619 uint32_t count = toVmaAllocator(allocator)->GetMemoryHeapCount();
5620 for (uint32_t i = 0; i < count; ++i) {
5621 const VmaStatistics &stats(budgets[i].statistics);
5622 result.blockCount += stats.blockCount;
5623 result.allocCount += stats.allocationCount;
5624 result.usedBytes += stats.allocationBytes;
5625 result.unusedBytes += stats.blockBytes - stats.allocationBytes;
5626 }
5627
5628 return result;
5629}
5630
5632{
5633 // not applicable
5634 return false;
5635}
5636
5637void QRhiVulkan::setQueueSubmitParams(QRhiNativeHandles *params)
5638{
5639 QRhiVulkanQueueSubmitParams *sp = static_cast<QRhiVulkanQueueSubmitParams *>(params);
5640 if (!sp)
5641 return;
5642
5643 waitSemaphoresForQueueSubmit.clear();
5644 if (sp->waitSemaphoreCount)
5645 waitSemaphoresForQueueSubmit.append(sp->waitSemaphores, sp->waitSemaphoreCount);
5646
5647 signalSemaphoresForQueueSubmit.clear();
5648 if (sp->signalSemaphoreCount)
5649 signalSemaphoresForQueueSubmit.append(sp->signalSemaphores, sp->signalSemaphoreCount);
5650
5651 waitSemaphoresForPresent.clear();
5652 if (sp->presentWaitSemaphoreCount)
5653 waitSemaphoresForPresent.append(sp->presentWaitSemaphores, sp->presentWaitSemaphoreCount);
5654}
5655
5660
5662{
5663 return deviceLost;
5664}
5665
5677
5679{
5680 Q_STATIC_ASSERT(sizeof(QVkPipelineCacheDataHeader) == 32);
5681
5682 QByteArray data;
5683 if (!pipelineCache || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
5684 return data;
5685
5686 size_t dataSize = 0;
5687 VkResult err = df->vkGetPipelineCacheData(dev, pipelineCache, &dataSize, nullptr);
5688 if (err != VK_SUCCESS) {
5689 qCDebug(QRHI_LOG_INFO, "Failed to get pipeline cache data size: %d", err);
5690 return QByteArray();
5691 }
5692 const size_t headerSize = sizeof(QVkPipelineCacheDataHeader);
5693 const size_t dataOffset = headerSize + VK_UUID_SIZE;
5694 data.resize(dataOffset + dataSize);
5695 err = df->vkGetPipelineCacheData(dev, pipelineCache, &dataSize, data.data() + dataOffset);
5696 if (err != VK_SUCCESS) {
5697 qCDebug(QRHI_LOG_INFO, "Failed to get pipeline cache data of %d bytes: %d", int(dataSize), err);
5698 return QByteArray();
5699 }
5700
5702 header.rhiId = pipelineCacheRhiId();
5703 header.arch = quint32(sizeof(void*));
5704 header.driverVersion = physDevProperties.driverVersion;
5705 header.vendorId = physDevProperties.vendorID;
5706 header.deviceId = physDevProperties.deviceID;
5707 header.dataSize = quint32(dataSize);
5708 header.uuidSize = VK_UUID_SIZE;
5709 header.reserved = 0;
5710 memcpy(data.data(), &header, headerSize);
5711 memcpy(data.data() + headerSize, physDevProperties.pipelineCacheUUID, VK_UUID_SIZE);
5712
5713 return data;
5714}
5715
5716void QRhiVulkan::setPipelineCacheData(const QByteArray &data)
5717{
5718 if (data.isEmpty())
5719 return;
5720
5721 const size_t headerSize = sizeof(QVkPipelineCacheDataHeader);
5722 if (data.size() < qsizetype(headerSize)) {
5723 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size");
5724 return;
5725 }
5727 memcpy(&header, data.constData(), headerSize);
5728
5729 const quint32 rhiId = pipelineCacheRhiId();
5730 if (header.rhiId != rhiId) {
5731 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
5732 rhiId, header.rhiId);
5733 return;
5734 }
5735 const quint32 arch = quint32(sizeof(void*));
5736 if (header.arch != arch) {
5737 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)",
5738 arch, header.arch);
5739 return;
5740 }
5741 if (header.driverVersion != physDevProperties.driverVersion) {
5742 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: driverVersion does not match (%u, %u)",
5743 physDevProperties.driverVersion, header.driverVersion);
5744 return;
5745 }
5746 if (header.vendorId != physDevProperties.vendorID) {
5747 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: vendorID does not match (%u, %u)",
5748 physDevProperties.vendorID, header.vendorId);
5749 return;
5750 }
5751 if (header.deviceId != physDevProperties.deviceID) {
5752 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: deviceID does not match (%u, %u)",
5753 physDevProperties.deviceID, header.deviceId);
5754 return;
5755 }
5756 if (header.uuidSize != VK_UUID_SIZE) {
5757 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: VK_UUID_SIZE does not match (%u, %u)",
5758 quint32(VK_UUID_SIZE), header.uuidSize);
5759 return;
5760 }
5761
5762 if (data.size() < qsizetype(headerSize + VK_UUID_SIZE)) {
5763 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, no uuid");
5764 return;
5765 }
5766 if (memcmp(data.constData() + headerSize, physDevProperties.pipelineCacheUUID, VK_UUID_SIZE)) {
5767 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: pipelineCacheUUID does not match");
5768 return;
5769 }
5770
5771 const size_t dataOffset = headerSize + VK_UUID_SIZE;
5772 if (data.size() < qsizetype(dataOffset + header.dataSize)) {
5773 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, data missing");
5774 return;
5775 }
5776
5777 if (pipelineCache) {
5778 df->vkDestroyPipelineCache(dev, pipelineCache, nullptr);
5779 pipelineCache = VK_NULL_HANDLE;
5780 }
5781
5782 if (ensurePipelineCache(data.constData() + dataOffset, header.dataSize)) {
5783 qCDebug(QRHI_LOG_INFO, "Created pipeline cache with initial data of %d bytes",
5784 int(header.dataSize));
5785 } else {
5786 qCDebug(QRHI_LOG_INFO, "Failed to create pipeline cache with initial data specified");
5787 }
5788}
5789
5790QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
5791 int sampleCount, QRhiRenderBuffer::Flags flags,
5792 QRhiTexture::Format backingFormatHint)
5793{
5794 return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
5795}
5796
5797QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format,
5798 const QSize &pixelSize, int depth, int arraySize,
5799 int sampleCount, QRhiTexture::Flags flags)
5800{
5801 return new QVkTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
5802}
5803
5804QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
5805 QRhiSampler::Filter mipmapMode,
5806 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
5807{
5808 return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v, w);
5809}
5810
5811QRhiShadingRateMap *QRhiVulkan::createShadingRateMap()
5812{
5813 return new QVkShadingRateMap(this);
5814}
5815
5816QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
5817 QRhiTextureRenderTarget::Flags flags)
5818{
5819 return new QVkTextureRenderTarget(this, desc, flags);
5820}
5821
5823{
5824 return new QVkGraphicsPipeline(this);
5825}
5826
5828{
5829 return new QVkComputePipeline(this);
5830}
5831
5833{
5834 return new QVkShaderResourceBindings(this);
5835}
5836
5837void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
5838{
5840 Q_ASSERT(psD->pipeline);
5841 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
5843
5844 if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
5845 if (cbD->passUsesSecondaryCb) {
5846 df->vkCmdBindPipeline(cbD->activeSecondaryCbStack.last(), VK_PIPELINE_BIND_POINT_GRAPHICS, psD->pipeline);
5847 } else {
5848 QVkCommandBuffer::Command &cmd(cbD->commands.get());
5850 cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
5851 cmd.args.bindPipeline.pipeline = psD->pipeline;
5852 }
5853
5854 cbD->currentGraphicsPipeline = ps;
5855 cbD->currentComputePipeline = nullptr;
5856 cbD->currentPipelineGeneration = psD->generation;
5857
5858 if (cbD->hasCustomScissorSet && !psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
5860 }
5861
5862 psD->lastActiveFrameSlot = currentFrameSlot;
5863}
5864
5865void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
5866 int dynamicOffsetCount,
5867 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
5868{
5869 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
5871 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
5872 QVkGraphicsPipeline *gfxPsD = QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline);
5873 QVkComputePipeline *compPsD = QRHI_RES(QVkComputePipeline, cbD->currentComputePipeline);
5874
5875 if (!srb) {
5876 if (gfxPsD)
5877 srb = gfxPsD->m_shaderResourceBindings;
5878 else
5879 srb = compPsD->m_shaderResourceBindings;
5880 }
5881
5883 auto &descSetBd(srbD->boundResourceData[currentFrameSlot]);
5884 bool rewriteDescSet = false;
5885 bool addWriteBarrier = false;
5886 VkPipelineStageFlags writeBarrierSrcStageMask = 0;
5887 VkPipelineStageFlags writeBarrierDstStageMask = 0;
5888
5889 // Do host writes and mark referenced shader resources as in-use.
5890 // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects.
5891 for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) {
5892 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings[i]);
5894 switch (b->type) {
5895 case QRhiShaderResourceBinding::UniformBuffer:
5896 {
5897 QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.ubuf.buf);
5898 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
5899 sanityCheckResourceOwnership(bufD);
5900
5901 if (bufD->m_type == QRhiBuffer::Dynamic)
5902 executeBufferHostWritesForSlot(bufD, currentFrameSlot);
5903
5904 bufD->lastActiveFrameSlot = currentFrameSlot;
5905 trackedRegisterBuffer(&passResTracker, bufD, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
5907 QRhiPassResourceTracker::toPassTrackerBufferStage(b->stage));
5908
5909 // Check both the "local" id (the generation counter) and the
5910 // global id. The latter is relevant when a newly allocated
5911 // QRhiResource ends up with the same pointer as a previous one.
5912 // (and that previous one could have been in an srb...)
5913 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
5914 rewriteDescSet = true;
5915 bd.ubuf.id = bufD->m_id;
5916 bd.ubuf.generation = bufD->generation;
5917 }
5918 }
5919 break;
5920 case QRhiShaderResourceBinding::SampledTexture:
5921 case QRhiShaderResourceBinding::Texture:
5922 case QRhiShaderResourceBinding::Sampler:
5923 {
5924 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
5925 if (bd.stex.count != data->count) {
5926 bd.stex.count = data->count;
5927 rewriteDescSet = true;
5928 }
5929 for (int elem = 0; elem < data->count; ++elem) {
5930 QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex);
5931 QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler);
5932 // We use the same code path for both combined and separate
5933 // images and samplers, so tex or sampler (but not both) can be
5934 // null here.
5935 Q_ASSERT(texD || samplerD);
5936 sanityCheckResourceOwnership(texD);
5937 sanityCheckResourceOwnership(samplerD);
5938 if (texD) {
5939 texD->lastActiveFrameSlot = currentFrameSlot;
5940 trackedRegisterTexture(&passResTracker, texD,
5942 QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
5943 }
5944 if (samplerD)
5945 samplerD->lastActiveFrameSlot = currentFrameSlot;
5946 const quint64 texId = texD ? texD->m_id : 0;
5947 const uint texGen = texD ? texD->generation : 0;
5948 const quint64 samplerId = samplerD ? samplerD->m_id : 0;
5949 const uint samplerGen = samplerD ? samplerD->generation : 0;
5950 if (texGen != bd.stex.d[elem].texGeneration
5951 || texId != bd.stex.d[elem].texId
5952 || samplerGen != bd.stex.d[elem].samplerGeneration
5953 || samplerId != bd.stex.d[elem].samplerId)
5954 {
5955 rewriteDescSet = true;
5956 bd.stex.d[elem].texId = texId;
5957 bd.stex.d[elem].texGeneration = texGen;
5958 bd.stex.d[elem].samplerId = samplerId;
5959 bd.stex.d[elem].samplerGeneration = samplerGen;
5960 }
5961 }
5962 }
5963 break;
5964 case QRhiShaderResourceBinding::ImageLoad:
5965 case QRhiShaderResourceBinding::ImageStore:
5966 case QRhiShaderResourceBinding::ImageLoadStore:
5967 {
5968 QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
5969 sanityCheckResourceOwnership(texD);
5970 Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore));
5971 texD->lastActiveFrameSlot = currentFrameSlot;
5973 if (b->type == QRhiShaderResourceBinding::ImageLoad)
5975 else if (b->type == QRhiShaderResourceBinding::ImageStore)
5977 else
5979
5980 const auto stage = QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage);
5981 const auto prevAccess = passResTracker.textures().find(texD);
5982 if (prevAccess != passResTracker.textures().end()) {
5983 const QRhiPassResourceTracker::Texture &tex = prevAccess->second;
5986 addWriteBarrier = true;
5987 writeBarrierDstStageMask |= toVkPipelineStage(stage);
5988 writeBarrierSrcStageMask |= toVkPipelineStage(tex.stage);
5989 }
5990 }
5991
5992 trackedRegisterTexture(&passResTracker, texD,
5993 access,
5994 stage);
5995
5996 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
5997 rewriteDescSet = true;
5998 bd.simage.id = texD->m_id;
5999 bd.simage.generation = texD->generation;
6000 }
6001 }
6002 break;
6003 case QRhiShaderResourceBinding::BufferLoad:
6004 case QRhiShaderResourceBinding::BufferStore:
6005 case QRhiShaderResourceBinding::BufferLoadStore:
6006 {
6007 QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf);
6008 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
6009 sanityCheckResourceOwnership(bufD);
6010
6011 if (bufD->m_type == QRhiBuffer::Dynamic)
6012 executeBufferHostWritesForSlot(bufD, currentFrameSlot);
6013
6014 bufD->lastActiveFrameSlot = currentFrameSlot;
6016 if (b->type == QRhiShaderResourceBinding::BufferLoad)
6018 else if (b->type == QRhiShaderResourceBinding::BufferStore)
6020 else
6022
6023 const auto stage = QRhiPassResourceTracker::toPassTrackerBufferStage(b->stage);
6024 const auto prevAccess = passResTracker.buffers().find(bufD);
6025 if (prevAccess != passResTracker.buffers().end()) {
6026 const QRhiPassResourceTracker::Buffer &buf = prevAccess->second;
6029 addWriteBarrier = true;
6030 writeBarrierDstStageMask |= toVkPipelineStage(stage);
6031 writeBarrierSrcStageMask |= toVkPipelineStage(buf.stage);
6032 }
6033 }
6034 trackedRegisterBuffer(&passResTracker, bufD, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
6035 access,
6036 stage);
6037
6038 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
6039 rewriteDescSet = true;
6040 bd.sbuf.id = bufD->m_id;
6041 bd.sbuf.generation = bufD->generation;
6042 }
6043 }
6044 break;
6045 default:
6046 Q_UNREACHABLE();
6047 break;
6048 }
6049 }
6050
6051 if (addWriteBarrier) {
6052 if (cbD->passUsesSecondaryCb) {
6053 VkMemoryBarrier barrier;
6054 barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
6055 barrier.pNext = nullptr;
6056 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
6057 barrier.srcAccessMask = barrier.dstAccessMask;
6058 df->vkCmdPipelineBarrier(cbD->activeSecondaryCbStack.last(), writeBarrierSrcStageMask, writeBarrierDstStageMask, 0,
6059 1, &barrier,
6060 0, VK_NULL_HANDLE,
6061 0, VK_NULL_HANDLE);
6062 } else {
6063 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6065 cmd.args.memoryBarrier.dependencyFlags = 0;
6066 cmd.args.memoryBarrier.dstStageMask = writeBarrierDstStageMask;
6067 cmd.args.memoryBarrier.srcStageMask = writeBarrierSrcStageMask;
6068 cmd.args.memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
6069 cmd.args.memoryBarrier.srcAccessMask = cmd.args.memoryBarrier.dstAccessMask;
6070 }
6071 }
6072
6073 // write descriptor sets, if needed
6074 if (rewriteDescSet)
6075 updateShaderResourceBindings(srb);
6076
6077 // make sure the descriptors for the correct slot will get bound.
6078 // also, dynamic offsets always need a bind.
6079 const bool forceRebind = cbD->currentDescSetSlot != currentFrameSlot || srbD->hasDynamicOffset;
6080
6081 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
6082
6083 if (forceRebind || rewriteDescSet || srbChanged || cbD->currentSrbGeneration != srbD->generation) {
6084 QVarLengthArray<uint32_t, 4> dynOfs;
6085 if (srbD->hasDynamicOffset) {
6086 // Filling out dynOfs based on the sorted bindings is important
6087 // because dynOfs has to be ordered based on the binding numbers,
6088 // and neither srb nor dynamicOffsets has any such ordering
6089 // requirement.
6090 for (const QRhiShaderResourceBinding &binding : std::as_const(srbD->sortedBindings)) {
6091 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding);
6092 if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) {
6093 uint32_t offset = 0;
6094 for (int i = 0; i < dynamicOffsetCount; ++i) {
6095 const QRhiCommandBuffer::DynamicOffset &bindingOffsetPair(dynamicOffsets[i]);
6096 if (bindingOffsetPair.first == b->binding) {
6097 offset = bindingOffsetPair.second;
6098 break;
6099 }
6100 }
6101 dynOfs.append(offset); // use 0 if dynamicOffsets did not contain this binding
6102 }
6103 }
6104 }
6105
6106 if (cbD->passUsesSecondaryCb) {
6107 df->vkCmdBindDescriptorSets(cbD->activeSecondaryCbStack.last(),
6108 gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE,
6109 gfxPsD ? gfxPsD->layout : compPsD->layout,
6110 0, 1, &srbD->descSets[currentFrameSlot],
6111 uint32_t(dynOfs.size()),
6112 dynOfs.size() ? dynOfs.constData() : nullptr);
6113 } else {
6114 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6116 cmd.args.bindDescriptorSet.bindPoint = gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS
6117 : VK_PIPELINE_BIND_POINT_COMPUTE;
6118 cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->layout;
6119 cmd.args.bindDescriptorSet.descSet = srbD->descSets[currentFrameSlot];
6120 cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.size();
6121 cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.size();
6122 cbD->pools.dynamicOffset.append(dynOfs.constData(), dynOfs.size());
6123 }
6124
6125 if (gfxPsD) {
6126 cbD->currentGraphicsSrb = srb;
6127 cbD->currentComputeSrb = nullptr;
6128 } else {
6129 cbD->currentGraphicsSrb = nullptr;
6130 cbD->currentComputeSrb = srb;
6131 }
6132 cbD->currentSrbGeneration = srbD->generation;
6133 cbD->currentDescSetSlot = currentFrameSlot;
6134 }
6135
6136 srbD->lastActiveFrameSlot = currentFrameSlot;
6137}
6138
6139void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb,
6140 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
6141 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
6142{
6143 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6145 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
6146
6147 bool needsBindVBuf = false;
6148 for (int i = 0; i < bindingCount; ++i) {
6149 const int inputSlot = startBinding + i;
6150 QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
6151 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
6152 bufD->lastActiveFrameSlot = currentFrameSlot;
6153 if (bufD->m_type == QRhiBuffer::Dynamic)
6154 executeBufferHostWritesForSlot(bufD, currentFrameSlot);
6155
6156 const VkBuffer vkvertexbuf = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
6157 if (cbD->currentVertexBuffers[inputSlot] != vkvertexbuf
6158 || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
6159 {
6160 needsBindVBuf = true;
6161 cbD->currentVertexBuffers[inputSlot] = vkvertexbuf;
6162 cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
6163 }
6164 }
6165
6166 if (needsBindVBuf) {
6167 QVarLengthArray<VkBuffer, 4> bufs;
6168 QVarLengthArray<VkDeviceSize, 4> ofs;
6169 for (int i = 0; i < bindingCount; ++i) {
6170 QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
6171 const int slot = bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
6172 bufs.append(bufD->buffers[slot]);
6173 ofs.append(bindings[i].second);
6174 trackedRegisterBuffer(&passResTracker, bufD, slot,
6177 }
6178
6179 if (cbD->passUsesSecondaryCb) {
6180 df->vkCmdBindVertexBuffers(cbD->activeSecondaryCbStack.last(), uint32_t(startBinding),
6181 uint32_t(bufs.size()), bufs.constData(), ofs.constData());
6182 } else {
6183 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6185 cmd.args.bindVertexBuffer.startBinding = startBinding;
6186 cmd.args.bindVertexBuffer.count = bufs.size();
6187 cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.size();
6188 cbD->pools.vertexBuffer.append(bufs.constData(), bufs.size());
6189 cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.size();
6190 cbD->pools.vertexBufferOffset.append(ofs.constData(), ofs.size());
6191 }
6192 }
6193
6194 if (indexBuf) {
6195 QVkBuffer *ibufD = QRHI_RES(QVkBuffer, indexBuf);
6196 Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
6197 ibufD->lastActiveFrameSlot = currentFrameSlot;
6198 if (ibufD->m_type == QRhiBuffer::Dynamic)
6199 executeBufferHostWritesForSlot(ibufD, currentFrameSlot);
6200
6201 const int slot = ibufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
6202 const VkBuffer vkindexbuf = ibufD->buffers[slot];
6203 const VkIndexType type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? VK_INDEX_TYPE_UINT16
6204 : VK_INDEX_TYPE_UINT32;
6205
6206 if (cbD->currentIndexBuffer != vkindexbuf
6207 || cbD->currentIndexOffset != indexOffset
6208 || cbD->currentIndexFormat != type)
6209 {
6210 cbD->currentIndexBuffer = vkindexbuf;
6211 cbD->currentIndexOffset = indexOffset;
6212 cbD->currentIndexFormat = type;
6213
6214 if (cbD->passUsesSecondaryCb) {
6215 df->vkCmdBindIndexBuffer(cbD->activeSecondaryCbStack.last(), vkindexbuf, indexOffset, type);
6216 } else {
6217 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6219 cmd.args.bindIndexBuffer.buf = vkindexbuf;
6220 cmd.args.bindIndexBuffer.ofs = indexOffset;
6221 cmd.args.bindIndexBuffer.type = type;
6222 }
6223
6224 trackedRegisterBuffer(&passResTracker, ibufD, slot,
6227 }
6228 }
6229}
6230
6232{
6233 cbD->hasCustomScissorSet = false;
6234
6235 const QSize outputSize = cbD->currentTarget->pixelSize();
6236 std::array<float, 4> vp = cbD->currentViewport.viewport();
6237 float x = 0, y = 0, w = 0, h = 0;
6238
6239 if (qFuzzyIsNull(vp[2]) && qFuzzyIsNull(vp[3])) {
6240 x = 0;
6241 y = 0;
6242 w = outputSize.width();
6243 h = outputSize.height();
6244 } else {
6245 // x,y is top-left in VkRect2D but bottom-left in QRhiScissor
6246 qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, vp, &x, &y, &w, &h);
6247 }
6248
6249 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6250 VkRect2D *s = &cmd.args.setScissor.scissor;
6251 s->offset.x = int32_t(x);
6252 s->offset.y = int32_t(y);
6253 s->extent.width = uint32_t(w);
6254 s->extent.height = uint32_t(h);
6255
6256 if (cbD->passUsesSecondaryCb) {
6257 df->vkCmdSetScissor(cbD->activeSecondaryCbStack.last(), 0, 1, s);
6258 cbD->commands.unget();
6259 } else {
6261 }
6262}
6263
6264void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
6265{
6266 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6268 const QSize outputSize = cbD->currentTarget->pixelSize();
6269
6270 // x,y is top-left in VkViewport but bottom-left in QRhiViewport
6271 float x, y, w, h;
6272 if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
6273 return;
6274
6275 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6276 VkViewport *vp = &cmd.args.setViewport.viewport;
6277 vp->x = x;
6278 vp->y = y;
6279 vp->width = w;
6280 vp->height = h;
6281 vp->minDepth = viewport.minDepth();
6282 vp->maxDepth = viewport.maxDepth();
6283
6284 if (cbD->passUsesSecondaryCb) {
6285 df->vkCmdSetViewport(cbD->activeSecondaryCbStack.last(), 0, 1, vp);
6286 cbD->commands.unget();
6287 } else {
6289 }
6290
6291 cbD->currentViewport = viewport;
6292 if (cbD->currentGraphicsPipeline
6293 && !cbD->currentGraphicsPipeline->flags().testFlag(QRhiGraphicsPipeline::UsesScissor))
6294 {
6296 }
6297}
6298
6299void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
6300{
6301 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6303 Q_ASSERT(!cbD->currentGraphicsPipeline
6304 || QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)
6305 ->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
6306 const QSize outputSize = cbD->currentTarget->pixelSize();
6307
6308 // x,y is top-left in VkRect2D but bottom-left in QRhiScissor
6309 int x, y, w, h;
6310 if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
6311 return;
6312
6313 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6314 VkRect2D *s = &cmd.args.setScissor.scissor;
6315 s->offset.x = x;
6316 s->offset.y = y;
6317 s->extent.width = uint32_t(w);
6318 s->extent.height = uint32_t(h);
6319
6320 if (cbD->passUsesSecondaryCb) {
6321 df->vkCmdSetScissor(cbD->activeSecondaryCbStack.last(), 0, 1, s);
6322 cbD->commands.unget();
6323 } else {
6325 }
6326
6327 cbD->hasCustomScissorSet = true;
6328}
6329
6330void QRhiVulkan::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
6331{
6332 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6334
6335 if (cbD->passUsesSecondaryCb) {
6336 float constants[] = { float(c.redF()), float(c.greenF()), float(c.blueF()), float(c.alphaF()) };
6337 df->vkCmdSetBlendConstants(cbD->activeSecondaryCbStack.last(), constants);
6338 } else {
6339 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6341 cmd.args.setBlendConstants.c[0] = c.redF();
6342 cmd.args.setBlendConstants.c[1] = c.greenF();
6343 cmd.args.setBlendConstants.c[2] = c.blueF();
6344 cmd.args.setBlendConstants.c[3] = c.alphaF();
6345 }
6346}
6347
6348void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
6349{
6350 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6352
6353 if (cbD->passUsesSecondaryCb) {
6354 df->vkCmdSetStencilReference(cbD->activeSecondaryCbStack.last(), VK_STENCIL_FRONT_AND_BACK, refValue);
6355 } else {
6356 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6358 cmd.args.setStencilRef.ref = refValue;
6359 }
6360}
6361
6362void QRhiVulkan::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize)
6363{
6364#ifdef VK_KHR_fragment_shading_rate
6365 if (!vkCmdSetFragmentShadingRateKHR)
6366 return;
6367
6368 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6369 Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
6370 Q_ASSERT(!cbD->currentGraphicsPipeline || QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesShadingRate));
6371
6372 VkFragmentShadingRateCombinerOpKHR ops[2] = {
6373 VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR,
6374 VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR
6375 };
6376 VkExtent2D size = { uint32_t(coarsePixelSize.width()), uint32_t(coarsePixelSize.height()) };
6377 if (cbD->passUsesSecondaryCb) {
6378 vkCmdSetFragmentShadingRateKHR(cbD->activeSecondaryCbStack.last(), &size, ops);
6379 } else {
6380 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6381 cmd.cmd = QVkCommandBuffer::Command::SetShadingRate;
6382 cmd.args.setShadingRate.w = size.width;
6383 cmd.args.setShadingRate.h = size.height;
6384 }
6385 if (coarsePixelSize.width() != 1 || coarsePixelSize.height() != 1)
6386 cbD->hasShadingRateSet = true;
6387#else
6388 Q_UNUSED(cb);
6389 Q_UNUSED(coarsePixelSize);
6390#endif
6391}
6392
6393void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
6394 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
6395{
6396 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6398
6399 if (cbD->passUsesSecondaryCb) {
6400 df->vkCmdDraw(cbD->activeSecondaryCbStack.last(), vertexCount, instanceCount, firstVertex, firstInstance);
6401 } else {
6402 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6404 cmd.args.draw.vertexCount = vertexCount;
6405 cmd.args.draw.instanceCount = instanceCount;
6406 cmd.args.draw.firstVertex = firstVertex;
6407 cmd.args.draw.firstInstance = firstInstance;
6408 }
6409}
6410
6411void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
6412 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
6413{
6414 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6416
6417 if (cbD->passUsesSecondaryCb) {
6418 df->vkCmdDrawIndexed(cbD->activeSecondaryCbStack.last(), indexCount, instanceCount,
6419 firstIndex, vertexOffset, firstInstance);
6420 } else {
6421 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6423 cmd.args.drawIndexed.indexCount = indexCount;
6424 cmd.args.drawIndexed.instanceCount = instanceCount;
6425 cmd.args.drawIndexed.firstIndex = firstIndex;
6426 cmd.args.drawIndexed.vertexOffset = vertexOffset;
6427 cmd.args.drawIndexed.firstInstance = firstInstance;
6428 }
6429}
6430
6431void QRhiVulkan::drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
6432 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
6433{
6434 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6436 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
6437
6438 QVkBuffer *indirectBufD = QRHI_RES(QVkBuffer, indirectBuffer);
6439 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
6440 if (indirectBufD->m_type == QRhiBuffer::Dynamic)
6441 executeBufferHostWritesForSlot(indirectBufD, currentFrameSlot);
6442 const int indirectBufSlot = indirectBufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
6443 trackedRegisterBuffer(&passResTracker, indirectBufD, indirectBufSlot,
6446 VkBuffer indirectBufVk = indirectBufD->buffers[indirectBufSlot];
6447
6448 if (cbD->passUsesSecondaryCb) {
6449 if (caps.drawIndirectMulti) {
6450 df->vkCmdDrawIndirect(cbD->activeSecondaryCbStack.last(),
6451 indirectBufVk, indirectBufferOffset,
6452 drawCount, stride);
6453 } else {
6454 VkDeviceSize offset = indirectBufferOffset;
6455 for (quint32 i = 0; i < drawCount; ++i) {
6456 df->vkCmdDrawIndirect(cbD->activeSecondaryCbStack.last(),
6457 indirectBufVk, offset,
6458 1, stride);
6459 offset += stride;
6460 }
6461 }
6462 } else {
6463 if (caps.drawIndirectMulti) {
6464 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6466 cmd.args.drawIndirect.indirectBuffer = indirectBufVk;
6467 cmd.args.drawIndirect.indirectBufferOffset = indirectBufferOffset;
6468 cmd.args.drawIndirect.drawCount = drawCount;
6469 cmd.args.drawIndirect.stride = stride;
6470 } else {
6471 VkDeviceSize offset = indirectBufferOffset;
6472 for (quint32 i = 0; i < drawCount; ++i) {
6473 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6475 cmd.args.drawIndirect.indirectBuffer = indirectBufVk;
6476 cmd.args.drawIndirect.indirectBufferOffset = offset;
6477 cmd.args.drawIndirect.drawCount = 1;
6478 cmd.args.drawIndirect.stride = stride;
6479 offset += stride;
6480 }
6481 }
6482 }
6483}
6484
6485void QRhiVulkan::drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
6486 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
6487{
6488 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6490 QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
6491
6492 QVkBuffer *indirectBufD = QRHI_RES(QVkBuffer, indirectBuffer);
6493 indirectBufD->lastActiveFrameSlot = currentFrameSlot;
6494 if (indirectBufD->m_type == QRhiBuffer::Dynamic)
6495 executeBufferHostWritesForSlot(indirectBufD, currentFrameSlot);
6496 const int indirectBufSlot = indirectBufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
6497 trackedRegisterBuffer(&passResTracker, indirectBufD, indirectBufSlot,
6500 VkBuffer indirectBufVk = indirectBufD->buffers[indirectBufSlot];
6501
6502 if (cbD->passUsesSecondaryCb) {
6503 if (caps.drawIndirectMulti) {
6504 df->vkCmdDrawIndexedIndirect(cbD->activeSecondaryCbStack.last(),
6505 indirectBufVk, indirectBufferOffset,
6506 drawCount, stride);
6507 } else {
6508 VkDeviceSize offset = indirectBufferOffset;
6509 for (quint32 i = 0; i < drawCount; ++i) {
6510 df->vkCmdDrawIndexedIndirect(cbD->activeSecondaryCbStack.last(),
6511 indirectBufVk, offset,
6512 1, stride);
6513 offset += stride;
6514 }
6515 }
6516 } else {
6517 if (caps.drawIndirectMulti) {
6518 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6520 cmd.args.drawIndexedIndirect.indirectBuffer = indirectBufVk;
6521 cmd.args.drawIndexedIndirect.indirectBufferOffset = indirectBufferOffset;
6522 cmd.args.drawIndexedIndirect.drawCount = drawCount;
6523 cmd.args.drawIndexedIndirect.stride = stride;
6524 } else {
6525 VkDeviceSize offset = indirectBufferOffset;
6526 for (quint32 i = 0; i < drawCount; ++i) {
6527 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6529 cmd.args.drawIndexedIndirect.indirectBuffer = indirectBufVk;
6530 cmd.args.drawIndexedIndirect.indirectBufferOffset = offset;
6531 cmd.args.drawIndexedIndirect.drawCount = 1;
6532 cmd.args.drawIndexedIndirect.stride = stride;
6533 offset += stride;
6534 }
6535 }
6536 }
6537}
6538
6539void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
6540{
6541#ifdef VK_EXT_debug_utils
6542 if (!debugMarkers || !caps.debugUtils)
6543 return;
6544
6545 VkDebugUtilsLabelEXT label = {};
6546 label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
6547
6548 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6549 if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) {
6550 label.pLabelName = name.constData();
6551 vkCmdBeginDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last(), &label);
6552 } else {
6553 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6554 cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin;
6555 cmd.args.debugMarkerBegin.label = label;
6556 cmd.args.debugMarkerBegin.labelNameIndex = cbD->pools.debugMarkerData.size();
6557 cbD->pools.debugMarkerData.append(name);
6558 }
6559#else
6560 Q_UNUSED(cb);
6561 Q_UNUSED(name);
6562#endif
6563}
6564
6565void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb)
6566{
6567#ifdef VK_EXT_debug_utils
6568 if (!debugMarkers || !caps.debugUtils)
6569 return;
6570
6571 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6572 if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) {
6573 vkCmdEndDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last());
6574 } else {
6575 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6576 cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd;
6577 }
6578#else
6579 Q_UNUSED(cb);
6580#endif
6581}
6582
6583void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
6584{
6585#ifdef VK_EXT_debug_utils
6586 if (!debugMarkers || !caps.debugUtils)
6587 return;
6588
6589 VkDebugUtilsLabelEXT label = {};
6590 label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
6591
6592 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6593 if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) {
6594 label.pLabelName = msg.constData();
6595 vkCmdInsertDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last(), &label);
6596 } else {
6597 QVkCommandBuffer::Command &cmd(cbD->commands.get());
6598 cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert;
6599 cmd.args.debugMarkerInsert.label = label;
6600 cmd.args.debugMarkerInsert.labelNameIndex = cbD->pools.debugMarkerData.size();
6601 cbD->pools.debugMarkerData.append(msg);
6602 }
6603#else
6604 Q_UNUSED(cb);
6605 Q_UNUSED(msg);
6606#endif
6607}
6608
6609const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb)
6610{
6611 return QRHI_RES(QVkCommandBuffer, cb)->nativeHandles();
6612}
6613
6615{
6616 Q_ASSERT(cbD->currentTarget);
6617 QVkRenderTargetData *rtD = nullptr;
6619 switch (cbD->currentTarget->resourceType()) {
6620 case QRhiResource::SwapChainRenderTarget:
6621 rtD = &QRHI_RES(QVkSwapChainRenderTarget, cbD->currentTarget)->d;
6622 break;
6623 case QRhiResource::TextureRenderTarget:
6624 rtD = &QRHI_RES(QVkTextureRenderTarget, cbD->currentTarget)->d;
6625 break;
6626 default:
6627 Q_UNREACHABLE();
6628 break;
6629 }
6630 }
6631 return rtD;
6632}
6633
6634void QRhiVulkan::beginExternal(QRhiCommandBuffer *cb)
6635{
6636 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6637
6638 // When not in a pass, it is simple: record what we have (but do not
6639 // submit), the cb can then be used to record more external commands.
6643 return;
6644 }
6645
6646 // Otherwise, inside a pass, have a secondary command buffer (with
6647 // RENDER_PASS_CONTINUE). Using the main one is not acceptable since we
6648 // cannot just record at this stage, that would mess up the resource
6649 // tracking and commands like TransitionPassResources.
6650
6651 if (cbD->inExternal)
6652 return;
6653
6654 if (!cbD->passUsesSecondaryCb) {
6655 qWarning("beginExternal() within a pass is only supported with secondary command buffers. "
6656 "This can be enabled by passing QRhiCommandBuffer::ExternalContent to beginPass().");
6657 return;
6658 }
6659
6660 VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last();
6661 cbD->activeSecondaryCbStack.removeLast();
6662 endAndEnqueueSecondaryCommandBuffer(secondaryCb, cbD);
6663
6664 VkCommandBuffer extCb = startSecondaryCommandBuffer(maybeRenderTargetData(cbD));
6665 if (extCb) {
6666 cbD->activeSecondaryCbStack.append(extCb);
6667 cbD->inExternal = true;
6668 }
6669}
6670
6671void QRhiVulkan::endExternal(QRhiCommandBuffer *cb)
6672{
6673 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6674
6676 Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1);
6677 } else if (cbD->inExternal) {
6678 VkCommandBuffer extCb = cbD->activeSecondaryCbStack.last();
6679 cbD->activeSecondaryCbStack.removeLast();
6680 endAndEnqueueSecondaryCommandBuffer(extCb, cbD);
6681 cbD->activeSecondaryCbStack.append(startSecondaryCommandBuffer(maybeRenderTargetData(cbD)));
6682 }
6683
6685}
6686
6687double QRhiVulkan::lastCompletedGpuTime(QRhiCommandBuffer *cb)
6688{
6689 QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
6690 return cbD->lastGpuTime;
6691}
6692
6693void QRhiVulkan::setAllocationName(QVkAlloc allocation, const QByteArray &name, int slot)
6694{
6695 if (!debugMarkers || name.isEmpty())
6696 return;
6697
6698 QByteArray decoratedName = name;
6699 if (slot >= 0) {
6700 decoratedName += '/';
6701 decoratedName += QByteArray::number(slot);
6702 }
6703 vmaSetAllocationName(toVmaAllocator(allocator), toVmaAllocation(allocation), decoratedName.constData());
6704}
6705
6706void QRhiVulkan::setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot)
6707{
6708#ifdef VK_EXT_debug_utils
6709 if (!debugMarkers || !caps.debugUtils || name.isEmpty())
6710 return;
6711
6712 VkDebugUtilsObjectNameInfoEXT nameInfo = {};
6713 nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
6714 nameInfo.objectType = type;
6715 nameInfo.objectHandle = object;
6716 QByteArray decoratedName = name;
6717 if (slot >= 0) {
6718 decoratedName += '/';
6719 decoratedName += QByteArray::number(slot);
6720 }
6721 nameInfo.pObjectName = decoratedName.constData();
6722 vkSetDebugUtilsObjectNameEXT(dev, &nameInfo);
6723#else
6724 Q_UNUSED(object);
6725 Q_UNUSED(type);
6726 Q_UNUSED(name);
6727 Q_UNUSED(slot);
6728#endif
6729}
6730
6731static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage)
6732{
6733 int u = 0;
6734 if (usage.testFlag(QRhiBuffer::VertexBuffer))
6735 u |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
6736 if (usage.testFlag(QRhiBuffer::IndexBuffer))
6737 u |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
6738 if (usage.testFlag(QRhiBuffer::UniformBuffer))
6739 u |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
6740 if (usage.testFlag(QRhiBuffer::StorageBuffer))
6741 u |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
6742 if (usage.testFlag(QRhiBuffer::IndirectBuffer))
6743 u |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
6744 return VkBufferUsageFlagBits(u);
6745}
6746
6747static inline VkFilter toVkFilter(QRhiSampler::Filter f)
6748{
6749 switch (f) {
6750 case QRhiSampler::Nearest:
6751 return VK_FILTER_NEAREST;
6752 case QRhiSampler::Linear:
6753 return VK_FILTER_LINEAR;
6754 default:
6755 Q_UNREACHABLE_RETURN(VK_FILTER_NEAREST);
6756 }
6757}
6758
6759static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f)
6760{
6761 switch (f) {
6762 case QRhiSampler::None:
6763 return VK_SAMPLER_MIPMAP_MODE_NEAREST;
6764 case QRhiSampler::Nearest:
6765 return VK_SAMPLER_MIPMAP_MODE_NEAREST;
6766 case QRhiSampler::Linear:
6767 return VK_SAMPLER_MIPMAP_MODE_LINEAR;
6768 default:
6769 Q_UNREACHABLE_RETURN(VK_SAMPLER_MIPMAP_MODE_NEAREST);
6770 }
6771}
6772
6773static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m)
6774{
6775 switch (m) {
6776 case QRhiSampler::Repeat:
6777 return VK_SAMPLER_ADDRESS_MODE_REPEAT;
6778 case QRhiSampler::ClampToEdge:
6779 return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
6780 case QRhiSampler::Mirror:
6781 return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
6782 default:
6783 Q_UNREACHABLE_RETURN(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
6784 }
6785}
6786
6787static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type)
6788{
6789 switch (type) {
6790 case QRhiShaderStage::Vertex:
6791 return VK_SHADER_STAGE_VERTEX_BIT;
6792 case QRhiShaderStage::TessellationControl:
6793 return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
6794 case QRhiShaderStage::TessellationEvaluation:
6795 return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
6796 case QRhiShaderStage::Fragment:
6797 return VK_SHADER_STAGE_FRAGMENT_BIT;
6798 case QRhiShaderStage::Compute:
6799 return VK_SHADER_STAGE_COMPUTE_BIT;
6800 case QRhiShaderStage::Geometry:
6801 return VK_SHADER_STAGE_GEOMETRY_BIT;
6802 default:
6803 Q_UNREACHABLE_RETURN(VK_SHADER_STAGE_VERTEX_BIT);
6804 }
6805}
6806
6807static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format)
6808{
6809 switch (format) {
6810 case QRhiVertexInputAttribute::Float4:
6811 return VK_FORMAT_R32G32B32A32_SFLOAT;
6812 case QRhiVertexInputAttribute::Float3:
6813 return VK_FORMAT_R32G32B32_SFLOAT;
6814 case QRhiVertexInputAttribute::Float2:
6815 return VK_FORMAT_R32G32_SFLOAT;
6816 case QRhiVertexInputAttribute::Float:
6817 return VK_FORMAT_R32_SFLOAT;
6818 case QRhiVertexInputAttribute::UNormByte4:
6819 return VK_FORMAT_R8G8B8A8_UNORM;
6820 case QRhiVertexInputAttribute::UNormByte2:
6821 return VK_FORMAT_R8G8_UNORM;
6822 case QRhiVertexInputAttribute::UNormByte:
6823 return VK_FORMAT_R8_UNORM;
6824 case QRhiVertexInputAttribute::UInt4:
6825 return VK_FORMAT_R32G32B32A32_UINT;
6826 case QRhiVertexInputAttribute::UInt3:
6827 return VK_FORMAT_R32G32B32_UINT;
6828 case QRhiVertexInputAttribute::UInt2:
6829 return VK_FORMAT_R32G32_UINT;
6830 case QRhiVertexInputAttribute::UInt:
6831 return VK_FORMAT_R32_UINT;
6832 case QRhiVertexInputAttribute::SInt4:
6833 return VK_FORMAT_R32G32B32A32_SINT;
6834 case QRhiVertexInputAttribute::SInt3:
6835 return VK_FORMAT_R32G32B32_SINT;
6836 case QRhiVertexInputAttribute::SInt2:
6837 return VK_FORMAT_R32G32_SINT;
6838 case QRhiVertexInputAttribute::SInt:
6839 return VK_FORMAT_R32_SINT;
6840 case QRhiVertexInputAttribute::Half4:
6841 return VK_FORMAT_R16G16B16A16_SFLOAT;
6842 case QRhiVertexInputAttribute::Half3:
6843 return VK_FORMAT_R16G16B16_SFLOAT;
6844 case QRhiVertexInputAttribute::Half2:
6845 return VK_FORMAT_R16G16_SFLOAT;
6846 case QRhiVertexInputAttribute::Half:
6847 return VK_FORMAT_R16_SFLOAT;
6848 case QRhiVertexInputAttribute::UShort4:
6849 return VK_FORMAT_R16G16B16A16_UINT;
6850 case QRhiVertexInputAttribute::UShort3:
6851 return VK_FORMAT_R16G16B16_UINT;
6852 case QRhiVertexInputAttribute::UShort2:
6853 return VK_FORMAT_R16G16_UINT;
6854 case QRhiVertexInputAttribute::UShort:
6855 return VK_FORMAT_R16_UINT;
6856 case QRhiVertexInputAttribute::SShort4:
6857 return VK_FORMAT_R16G16B16A16_SINT;
6858 case QRhiVertexInputAttribute::SShort3:
6859 return VK_FORMAT_R16G16B16_SINT;
6860 case QRhiVertexInputAttribute::SShort2:
6861 return VK_FORMAT_R16G16_SINT;
6862 case QRhiVertexInputAttribute::SShort:
6863 return VK_FORMAT_R16_SINT;
6864 default:
6865 Q_UNREACHABLE_RETURN(VK_FORMAT_R32G32B32A32_SFLOAT);
6866 }
6867}
6868
6869static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t)
6870{
6871 switch (t) {
6872 case QRhiGraphicsPipeline::Triangles:
6873 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
6874 case QRhiGraphicsPipeline::TriangleStrip:
6875 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
6876 case QRhiGraphicsPipeline::TriangleFan:
6877 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
6878 case QRhiGraphicsPipeline::Lines:
6879 return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
6880 case QRhiGraphicsPipeline::LineStrip:
6881 return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
6882 case QRhiGraphicsPipeline::Points:
6883 return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
6884 case QRhiGraphicsPipeline::Patches:
6885 return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
6886 default:
6887 Q_UNREACHABLE_RETURN(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
6888 }
6889}
6890
6891static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c)
6892{
6893 switch (c) {
6894 case QRhiGraphicsPipeline::None:
6895 return VK_CULL_MODE_NONE;
6896 case QRhiGraphicsPipeline::Front:
6897 return VK_CULL_MODE_FRONT_BIT;
6898 case QRhiGraphicsPipeline::Back:
6899 return VK_CULL_MODE_BACK_BIT;
6900 default:
6901 Q_UNREACHABLE_RETURN(VK_CULL_MODE_NONE);
6902 }
6903}
6904
6905static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f)
6906{
6907 switch (f) {
6908 case QRhiGraphicsPipeline::CCW:
6909 return VK_FRONT_FACE_COUNTER_CLOCKWISE;
6910 case QRhiGraphicsPipeline::CW:
6911 return VK_FRONT_FACE_CLOCKWISE;
6912 default:
6913 Q_UNREACHABLE_RETURN(VK_FRONT_FACE_COUNTER_CLOCKWISE);
6914 }
6915}
6916
6917static inline VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c)
6918{
6919 int f = 0;
6920 if (c.testFlag(QRhiGraphicsPipeline::R))
6921 f |= VK_COLOR_COMPONENT_R_BIT;
6922 if (c.testFlag(QRhiGraphicsPipeline::G))
6923 f |= VK_COLOR_COMPONENT_G_BIT;
6924 if (c.testFlag(QRhiGraphicsPipeline::B))
6925 f |= VK_COLOR_COMPONENT_B_BIT;
6926 if (c.testFlag(QRhiGraphicsPipeline::A))
6927 f |= VK_COLOR_COMPONENT_A_BIT;
6928 return VkColorComponentFlags(f);
6929}
6930
6931static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
6932{
6933 switch (f) {
6934 case QRhiGraphicsPipeline::Zero:
6935 return VK_BLEND_FACTOR_ZERO;
6936 case QRhiGraphicsPipeline::One:
6937 return VK_BLEND_FACTOR_ONE;
6938 case QRhiGraphicsPipeline::SrcColor:
6939 return VK_BLEND_FACTOR_SRC_COLOR;
6940 case QRhiGraphicsPipeline::OneMinusSrcColor:
6941 return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
6942 case QRhiGraphicsPipeline::DstColor:
6943 return VK_BLEND_FACTOR_DST_COLOR;
6944 case QRhiGraphicsPipeline::OneMinusDstColor:
6945 return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
6946 case QRhiGraphicsPipeline::SrcAlpha:
6947 return VK_BLEND_FACTOR_SRC_ALPHA;
6948 case QRhiGraphicsPipeline::OneMinusSrcAlpha:
6949 return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
6950 case QRhiGraphicsPipeline::DstAlpha:
6951 return VK_BLEND_FACTOR_DST_ALPHA;
6952 case QRhiGraphicsPipeline::OneMinusDstAlpha:
6953 return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
6954 case QRhiGraphicsPipeline::ConstantColor:
6955 return VK_BLEND_FACTOR_CONSTANT_COLOR;
6956 case QRhiGraphicsPipeline::OneMinusConstantColor:
6957 return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
6958 case QRhiGraphicsPipeline::ConstantAlpha:
6959 return VK_BLEND_FACTOR_CONSTANT_ALPHA;
6960 case QRhiGraphicsPipeline::OneMinusConstantAlpha:
6961 return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
6962 case QRhiGraphicsPipeline::SrcAlphaSaturate:
6963 return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
6964 case QRhiGraphicsPipeline::Src1Color:
6965 return VK_BLEND_FACTOR_SRC1_COLOR;
6966 case QRhiGraphicsPipeline::OneMinusSrc1Color:
6967 return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
6968 case QRhiGraphicsPipeline::Src1Alpha:
6969 return VK_BLEND_FACTOR_SRC1_ALPHA;
6970 case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
6971 return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
6972 default:
6973 Q_UNREACHABLE_RETURN(VK_BLEND_FACTOR_ZERO);
6974 }
6975}
6976
6977static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op)
6978{
6979 switch (op) {
6980 case QRhiGraphicsPipeline::Add:
6981 return VK_BLEND_OP_ADD;
6982 case QRhiGraphicsPipeline::Subtract:
6983 return VK_BLEND_OP_SUBTRACT;
6984 case QRhiGraphicsPipeline::ReverseSubtract:
6985 return VK_BLEND_OP_REVERSE_SUBTRACT;
6986 case QRhiGraphicsPipeline::Min:
6987 return VK_BLEND_OP_MIN;
6988 case QRhiGraphicsPipeline::Max:
6989 return VK_BLEND_OP_MAX;
6990 default:
6991 Q_UNREACHABLE_RETURN(VK_BLEND_OP_ADD);
6992 }
6993}
6994
6995static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op)
6996{
6997 switch (op) {
6998 case QRhiGraphicsPipeline::Never:
6999 return VK_COMPARE_OP_NEVER;
7000 case QRhiGraphicsPipeline::Less:
7001 return VK_COMPARE_OP_LESS;
7002 case QRhiGraphicsPipeline::Equal:
7003 return VK_COMPARE_OP_EQUAL;
7004 case QRhiGraphicsPipeline::LessOrEqual:
7005 return VK_COMPARE_OP_LESS_OR_EQUAL;
7006 case QRhiGraphicsPipeline::Greater:
7007 return VK_COMPARE_OP_GREATER;
7008 case QRhiGraphicsPipeline::NotEqual:
7009 return VK_COMPARE_OP_NOT_EQUAL;
7010 case QRhiGraphicsPipeline::GreaterOrEqual:
7011 return VK_COMPARE_OP_GREATER_OR_EQUAL;
7012 case QRhiGraphicsPipeline::Always:
7013 return VK_COMPARE_OP_ALWAYS;
7014 default:
7015 Q_UNREACHABLE_RETURN(VK_COMPARE_OP_ALWAYS);
7016 }
7017}
7018
7019static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op)
7020{
7021 switch (op) {
7022 case QRhiGraphicsPipeline::StencilZero:
7023 return VK_STENCIL_OP_ZERO;
7024 case QRhiGraphicsPipeline::Keep:
7025 return VK_STENCIL_OP_KEEP;
7026 case QRhiGraphicsPipeline::Replace:
7027 return VK_STENCIL_OP_REPLACE;
7028 case QRhiGraphicsPipeline::IncrementAndClamp:
7029 return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
7030 case QRhiGraphicsPipeline::DecrementAndClamp:
7031 return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
7032 case QRhiGraphicsPipeline::Invert:
7033 return VK_STENCIL_OP_INVERT;
7034 case QRhiGraphicsPipeline::IncrementAndWrap:
7035 return VK_STENCIL_OP_INCREMENT_AND_WRAP;
7036 case QRhiGraphicsPipeline::DecrementAndWrap:
7037 return VK_STENCIL_OP_DECREMENT_AND_WRAP;
7038 default:
7039 Q_UNREACHABLE_RETURN(VK_STENCIL_OP_KEEP);
7040 }
7041}
7042
7043static inline VkPolygonMode toVkPolygonMode(QRhiGraphicsPipeline::PolygonMode mode)
7044{
7045 switch (mode) {
7046 case QRhiGraphicsPipeline::Fill:
7047 return VK_POLYGON_MODE_FILL;
7048 case QRhiGraphicsPipeline::Line:
7049 return VK_POLYGON_MODE_LINE;
7050 default:
7051 Q_UNREACHABLE_RETURN(VK_POLYGON_MODE_FILL);
7052 }
7053}
7054
7055static inline void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src)
7056{
7057 dst->failOp = toVkStencilOp(src.failOp);
7058 dst->passOp = toVkStencilOp(src.passOp);
7059 dst->depthFailOp = toVkStencilOp(src.depthFailOp);
7060 dst->compareOp = toVkCompareOp(src.compareOp);
7061}
7062
7063static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBinding::Data *b)
7064{
7065 switch (b->type) {
7066 case QRhiShaderResourceBinding::UniformBuffer:
7067 return b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
7068 : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
7069
7070 case QRhiShaderResourceBinding::SampledTexture:
7071 return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
7072
7073 case QRhiShaderResourceBinding::Texture:
7074 return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
7075
7076 case QRhiShaderResourceBinding::Sampler:
7077 return VK_DESCRIPTOR_TYPE_SAMPLER;
7078
7079 case QRhiShaderResourceBinding::ImageLoad:
7080 case QRhiShaderResourceBinding::ImageStore:
7081 case QRhiShaderResourceBinding::ImageLoadStore:
7082 return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
7083
7084 case QRhiShaderResourceBinding::BufferLoad:
7085 case QRhiShaderResourceBinding::BufferStore:
7086 case QRhiShaderResourceBinding::BufferLoadStore:
7087 return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
7088
7089 default:
7090 Q_UNREACHABLE_RETURN(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
7091 }
7092}
7093
7094static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage)
7095{
7096 int s = 0;
7097 if (stage.testFlag(QRhiShaderResourceBinding::VertexStage))
7098 s |= VK_SHADER_STAGE_VERTEX_BIT;
7099 if (stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
7100 s |= VK_SHADER_STAGE_FRAGMENT_BIT;
7101 if (stage.testFlag(QRhiShaderResourceBinding::ComputeStage))
7102 s |= VK_SHADER_STAGE_COMPUTE_BIT;
7103 if (stage.testFlag(QRhiShaderResourceBinding::TessellationControlStage))
7104 s |= VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
7105 if (stage.testFlag(QRhiShaderResourceBinding::TessellationEvaluationStage))
7106 s |= VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
7107 if (stage.testFlag(QRhiShaderResourceBinding::GeometryStage))
7108 s |= VK_SHADER_STAGE_GEOMETRY_BIT;
7109 return VkShaderStageFlags(s);
7110}
7111
7112static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op)
7113{
7114 switch (op) {
7115 case QRhiSampler::Never:
7116 return VK_COMPARE_OP_NEVER;
7117 case QRhiSampler::Less:
7118 return VK_COMPARE_OP_LESS;
7119 case QRhiSampler::Equal:
7120 return VK_COMPARE_OP_EQUAL;
7121 case QRhiSampler::LessOrEqual:
7122 return VK_COMPARE_OP_LESS_OR_EQUAL;
7123 case QRhiSampler::Greater:
7124 return VK_COMPARE_OP_GREATER;
7125 case QRhiSampler::NotEqual:
7126 return VK_COMPARE_OP_NOT_EQUAL;
7127 case QRhiSampler::GreaterOrEqual:
7128 return VK_COMPARE_OP_GREATER_OR_EQUAL;
7129 case QRhiSampler::Always:
7130 return VK_COMPARE_OP_ALWAYS;
7131 default:
7132 Q_UNREACHABLE_RETURN(VK_COMPARE_OP_NEVER);
7133 }
7134}
7135
7136QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
7138{
7139 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
7140 buffers[i] = stagingBuffers[i] = VK_NULL_HANDLE;
7141 allocations[i] = stagingAllocations[i] = nullptr;
7142 }
7143}
7144
7146{
7147 destroy();
7148}
7149
7151{
7152 if (!buffers[0])
7153 return;
7154
7158
7159 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
7160 e.buffer.buffers[i] = buffers[i];
7161 e.buffer.allocations[i] = allocations[i];
7162 e.buffer.stagingBuffers[i] = stagingBuffers[i];
7163 e.buffer.stagingAllocations[i] = stagingAllocations[i];
7164
7165 buffers[i] = VK_NULL_HANDLE;
7166 allocations[i] = nullptr;
7167 stagingBuffers[i] = VK_NULL_HANDLE;
7168 stagingAllocations[i] = nullptr;
7169 pendingDynamicUpdates[i].clear();
7170 }
7171
7172 QRHI_RES_RHI(QRhiVulkan);
7173 // destroy() implementations, unlike other functions, are expected to test
7174 // for m_rhi being null, to allow surviving in case one attempts to destroy
7175 // a (leaked) resource after the QRhi.
7176 if (rhiD) {
7177 rhiD->releaseQueue.append(e);
7178 rhiD->unregisterResource(this);
7179 }
7180}
7181
7183{
7184 if (buffers[0])
7185 destroy();
7186
7187 if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
7188 qWarning("StorageBuffer cannot be combined with Dynamic");
7189 return false;
7190 }
7191
7192 const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
7193
7194 VkBufferCreateInfo bufferInfo = {};
7195 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
7196 bufferInfo.size = nonZeroSize;
7197 bufferInfo.usage = toVkBufferUsage(m_usage);
7198
7199 VmaAllocationCreateInfo allocInfo = {};
7200
7201 if (m_type == Dynamic) {
7202#ifndef Q_OS_DARWIN // not for MoltenVK
7203 // Keep mapped all the time. Essential f.ex. with some mobile GPUs,
7204 // where mapping and unmapping an entire allocation every time updating
7205 // a suballocated buffer presents a significant perf. hit.
7206 allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
7207#endif
7208 // host visible, frequent changes
7209 allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
7210 } else {
7211 allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
7212 bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
7213 }
7214
7215 QRHI_RES_RHI(QRhiVulkan);
7216 VkResult err = VK_SUCCESS;
7217 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
7218 buffers[i] = VK_NULL_HANDLE;
7219 allocations[i] = nullptr;
7220 usageState[i].access = usageState[i].stage = 0;
7221 if (i == 0 || m_type == Dynamic) {
7222 VmaAllocation allocation;
7223 err = vmaCreateBuffer(toVmaAllocator(rhiD->allocator), &bufferInfo, &allocInfo, &buffers[i], &allocation, nullptr);
7224 if (err != VK_SUCCESS)
7225 break;
7226 allocations[i] = allocation;
7227 rhiD->setAllocationName(allocation, m_objectName, m_type == Dynamic ? i : -1);
7228 rhiD->setObjectName(uint64_t(buffers[i]), VK_OBJECT_TYPE_BUFFER, m_objectName,
7229 m_type == Dynamic ? i : -1);
7230 }
7231 }
7232
7233 if (err != VK_SUCCESS) {
7234 qWarning("Failed to create buffer of size %u: %d", nonZeroSize, err);
7235 rhiD->printExtraErrorInfo(err);
7236 return false;
7237 }
7238
7240 generation += 1;
7241 rhiD->registerResource(this);
7242 return true;
7243}
7244
7246{
7247 if (m_type == Dynamic) {
7248 QRHI_RES_RHI(QRhiVulkan);
7249 NativeBuffer b;
7250 Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QVK_FRAMES_IN_FLIGHT));
7251 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
7253 b.objects[i] = &buffers[i];
7254 }
7255 b.slotCount = QVK_FRAMES_IN_FLIGHT;
7256 return b;
7257 }
7258 return { { &buffers[0] }, 1 };
7259}
7260
7262{
7263 // Shortcut the entire buffer update mechanism and allow the client to do
7264 // the host writes directly to the buffer. This will lead to unexpected
7265 // results when combined with QRhiResourceUpdateBatch-based updates for the
7266 // buffer, but provides a fast path for dynamic buffers that have all their
7267 // content changed in every frame.
7268 Q_ASSERT(m_type == Dynamic);
7269 QRHI_RES_RHI(QRhiVulkan);
7270 Q_ASSERT(rhiD->inFrame);
7271 const int slot = rhiD->currentFrameSlot;
7272 void *p = nullptr;
7273 VmaAllocation a = toVmaAllocation(allocations[slot]);
7274 VkResult err = vmaMapMemory(toVmaAllocator(rhiD->allocator), a, &p);
7275 if (err != VK_SUCCESS) {
7276 qWarning("Failed to map buffer: %d", err);
7277 return nullptr;
7278 }
7279 return static_cast<char *>(p);
7280}
7281
7283{
7284 QRHI_RES_RHI(QRhiVulkan);
7285 const int slot = rhiD->currentFrameSlot;
7286 VmaAllocation a = toVmaAllocation(allocations[slot]);
7287 vmaFlushAllocation(toVmaAllocator(rhiD->allocator), a, 0, m_size);
7288 vmaUnmapMemory(toVmaAllocator(rhiD->allocator), a);
7289}
7290
7291QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
7292 int sampleCount, Flags flags,
7293 QRhiTexture::Format backingFormatHint)
7295{
7296}
7297
7299{
7300 destroy();
7301 delete backingTexture;
7302}
7303
7305{
7306 if (!memory && !backingTexture)
7307 return;
7308
7312
7313 e.renderBuffer.memory = memory;
7314 e.renderBuffer.image = image;
7315 e.renderBuffer.imageView = imageView;
7316
7317 memory = VK_NULL_HANDLE;
7318 image = VK_NULL_HANDLE;
7319 imageView = VK_NULL_HANDLE;
7320
7321 if (backingTexture) {
7325 }
7326
7327 QRHI_RES_RHI(QRhiVulkan);
7328 if (rhiD) {
7329 rhiD->releaseQueue.append(e);
7330 rhiD->unregisterResource(this);
7331 }
7332}
7333
7335{
7336 if (memory || backingTexture)
7337 destroy();
7338
7339 if (m_pixelSize.isEmpty())
7340 return false;
7341
7342 QRHI_RES_RHI(QRhiVulkan);
7343 samples = rhiD->effectiveSampleCountBits(m_sampleCount);
7344
7345 switch (m_type) {
7346 case QRhiRenderBuffer::Color:
7347 {
7348 if (!backingTexture) {
7349 backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(backingFormat(),
7350 m_pixelSize,
7351 1,
7352 0,
7353 m_sampleCount,
7354 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
7355 } else {
7356 backingTexture->setPixelSize(m_pixelSize);
7357 backingTexture->setSampleCount(m_sampleCount);
7358 }
7359 backingTexture->setName(m_objectName);
7361 return false;
7362 vkformat = backingTexture->vkformat;
7363 }
7364 break;
7365 case QRhiRenderBuffer::DepthStencil:
7366 vkformat = rhiD->optimalDepthStencilFormat();
7367 if (!rhiD->createTransientImage(vkformat,
7368 m_pixelSize,
7369 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
7370 VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
7371 samples,
7372 &memory,
7373 &image,
7374 &imageView,
7375 1))
7376 {
7377 return false;
7378 }
7379 rhiD->setObjectName(uint64_t(image), VK_OBJECT_TYPE_IMAGE, m_objectName);
7380 break;
7381 default:
7382 Q_UNREACHABLE();
7383 break;
7384 }
7385
7387 generation += 1;
7388 rhiD->registerResource(this);
7389 return true;
7390}
7391
7393{
7394 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
7395 return m_backingFormatHint;
7396 else
7397 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
7398}
7399
7400QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
7401 int arraySize, int sampleCount, Flags flags)
7403{
7404 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
7405 stagingBuffers[i] = VK_NULL_HANDLE;
7406 stagingAllocations[i] = nullptr;
7407 }
7408 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
7409 perLevelImageViews[i] = VK_NULL_HANDLE;
7410}
7411
7413{
7414 destroy();
7415}
7416
7418{
7419 if (!image)
7420 return;
7421
7425
7426 e.texture.image = owns ? image : VK_NULL_HANDLE;
7427 e.texture.imageView = imageView;
7428 e.texture.allocation = owns ? imageAlloc : nullptr;
7429
7430 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
7431 e.texture.stagingBuffers[i] = stagingBuffers[i];
7432 e.texture.stagingAllocations[i] = stagingAllocations[i];
7433
7434 stagingBuffers[i] = VK_NULL_HANDLE;
7435 stagingAllocations[i] = nullptr;
7436 }
7437
7438 for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) {
7439 e.texture.extraImageViews[i] = perLevelImageViews[i];
7440 perLevelImageViews[i] = VK_NULL_HANDLE;
7441 }
7442
7443 image = VK_NULL_HANDLE;
7444 imageView = VK_NULL_HANDLE;
7445 imageAlloc = nullptr;
7446
7447 QRHI_RES_RHI(QRhiVulkan);
7448 if (rhiD) {
7449 rhiD->releaseQueue.append(e);
7450 rhiD->unregisterResource(this);
7451 }
7452}
7453
7454bool QVkTexture::prepareCreate(QSize *adjustedSize)
7455{
7456 if (image)
7457 destroy();
7458
7459 QRHI_RES_RHI(QRhiVulkan);
7460 vkformat = toVkTextureFormat(m_format, m_flags);
7461 if (m_writeViewFormat.format != UnknownFormat)
7462 viewFormat = toVkTextureFormat(m_writeViewFormat.format, m_writeViewFormat.srgb ? sRGB : Flags());
7463 else
7464 viewFormat = vkformat;
7465 if (m_readViewFormat.format != UnknownFormat)
7466 viewFormatForSampling = toVkTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags());
7467 else
7468 viewFormatForSampling = vkformat;
7469
7470 VkFormatProperties props;
7471 rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props);
7472 const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
7473 if (!canSampleOptimal) {
7474 qWarning("Texture sampling with optimal tiling for format %d not supported", vkformat);
7475 return false;
7476 }
7477
7478 const bool isCube = m_flags.testFlag(CubeMap);
7479 const bool isArray = m_flags.testFlag(TextureArray);
7480 const bool is3D = m_flags.testFlag(ThreeDimensional);
7481 const bool is1D = m_flags.testFlag(OneDimensional);
7482 const bool hasMipMaps = m_flags.testFlag(MipMapped);
7483
7484 const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
7485 : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
7486
7487 mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
7488 const int maxLevels = QRhi::MAX_MIP_LEVELS;
7489 if (mipLevelCount > maxLevels) {
7490 qWarning("Too many mip levels (%d, max is %d), truncating mip chain", mipLevelCount, maxLevels);
7491 mipLevelCount = maxLevels;
7492 }
7493 samples = rhiD->effectiveSampleCountBits(m_sampleCount);
7494 if (samples > VK_SAMPLE_COUNT_1_BIT) {
7495 if (isCube) {
7496 qWarning("Cubemap texture cannot be multisample");
7497 return false;
7498 }
7499 if (is3D) {
7500 qWarning("3D texture cannot be multisample");
7501 return false;
7502 }
7503 if (hasMipMaps) {
7504 qWarning("Multisample texture cannot have mipmaps");
7505 return false;
7506 }
7507 }
7508 if (isCube && is3D) {
7509 qWarning("Texture cannot be both cube and 3D");
7510 return false;
7511 }
7512 if (isArray && is3D) {
7513 qWarning("Texture cannot be both array and 3D");
7514 return false;
7515 }
7516 if (isCube && is1D) {
7517 qWarning("Texture cannot be both cube and 1D");
7518 return false;
7519 }
7520 if (is1D && is3D) {
7521 qWarning("Texture cannot be both 1D and 3D");
7522 return false;
7523 }
7524 if (m_depth > 1 && !is3D) {
7525 qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
7526 return false;
7527 }
7528 if (m_arraySize > 0 && !isArray) {
7529 qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
7530 return false;
7531 }
7532 if (m_arraySize < 1 && isArray) {
7533 qWarning("Texture is an array but array size is %d", m_arraySize);
7534 return false;
7535 }
7536
7537 usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
7538 usageState.access = 0;
7539 usageState.stage = 0;
7540
7541 if (adjustedSize)
7542 *adjustedSize = size;
7543
7544 return true;
7545}
7546
7548{
7549 QRHI_RES_RHI(QRhiVulkan);
7550
7551 const auto aspectMask = aspectMaskForTextureFormat(m_format);
7552 const bool isCube = m_flags.testFlag(CubeMap);
7553 const bool isArray = m_flags.testFlag(TextureArray);
7554 const bool is3D = m_flags.testFlag(ThreeDimensional);
7555 const bool is1D = m_flags.testFlag(OneDimensional);
7556
7557 VkImageViewCreateInfo viewInfo = {};
7558 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
7559 viewInfo.image = image;
7560 viewInfo.viewType = isCube
7561 ? VK_IMAGE_VIEW_TYPE_CUBE
7562 : (is3D ? VK_IMAGE_VIEW_TYPE_3D
7563 : (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D)
7564 : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D)));
7565 viewInfo.format = viewFormatForSampling;
7566 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
7567 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
7568 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
7569 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
7570 viewInfo.subresourceRange.aspectMask = aspectMask;
7571 // Force-remove the VK_IMAGE_ASPECT_STENCIL_BIT
7572 // Another view with this bit is probably needed for stencil
7573 viewInfo.subresourceRange.aspectMask &= ~VK_IMAGE_ASPECT_STENCIL_BIT;
7574 viewInfo.subresourceRange.levelCount = mipLevelCount;
7575 if (isArray && m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
7576 viewInfo.subresourceRange.baseArrayLayer = uint32_t(m_arrayRangeStart);
7577 viewInfo.subresourceRange.layerCount = uint32_t(m_arrayRangeLength);
7578 } else {
7579 viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1);
7580 }
7581
7582 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView);
7583 if (err != VK_SUCCESS) {
7584 qWarning("Failed to create image view: %d", err);
7585 return false;
7586 }
7587
7589 generation += 1;
7590
7591 return true;
7592}
7593
7595{
7596 QSize size;
7597 if (!prepareCreate(&size))
7598 return false;
7599
7600 QRHI_RES_RHI(QRhiVulkan);
7601 const bool isRenderTarget = m_flags.testFlag(QRhiTexture::RenderTarget);
7602 const bool isDepth = isDepthTextureFormat(m_format);
7603 const bool isCube = m_flags.testFlag(CubeMap);
7604 const bool isArray = m_flags.testFlag(TextureArray);
7605 const bool is3D = m_flags.testFlag(ThreeDimensional);
7606 const bool is1D = m_flags.testFlag(OneDimensional);
7607
7608 VkImageCreateInfo imageInfo = {};
7609 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
7610 imageInfo.flags = 0;
7611 if (isCube)
7612 imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
7613
7614 if (is3D && isRenderTarget) {
7615 // This relies on a Vulkan 1.1 constant. For guaranteed proper behavior
7616 // this also requires that at run time the VkInstance has at least API 1.1
7617 // enabled. (though it works as expected with some Vulkan (1.2)
7618 // implementations regardless of the requested API version, but f.ex. the
7619 // validation layer complains when using this without enabling >=1.1)
7620 if (!rhiD->caps.texture3DSliceAs2D)
7621 qWarning("QRhiVulkan: Rendering to 3D texture slice may not be functional without API 1.1 on the VkInstance");
7622#ifdef VK_VERSION_1_1
7623 imageInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
7624#else
7625 imageInfo.flags |= 0x00000020;
7626#endif
7627 }
7628
7629 imageInfo.imageType = is1D ? VK_IMAGE_TYPE_1D : is3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D;
7630 imageInfo.format = vkformat;
7631 imageInfo.extent.width = uint32_t(size.width());
7632 imageInfo.extent.height = uint32_t(size.height());
7633 imageInfo.extent.depth = is3D ? qMax(1, m_depth) : 1;
7634 imageInfo.mipLevels = mipLevelCount;
7635 imageInfo.arrayLayers = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1);
7636 imageInfo.samples = samples;
7637 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
7638 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
7639
7640 imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
7641 if (isRenderTarget) {
7642 if (isDepth)
7643 imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
7644 else
7645 imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
7646 }
7647 if (m_flags.testFlag(QRhiTexture::UsedAsTransferSource))
7648 imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
7649 if (m_flags.testFlag(QRhiTexture::UsedWithGenerateMips))
7650 imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
7651 if (m_flags.testFlag(QRhiTexture::UsedWithLoadStore))
7652 imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
7653#ifdef VK_KHR_fragment_shading_rate
7654 if (m_flags.testFlag(QRhiTexture::UsedAsShadingRateMap) && rhiD->caps.imageBasedShadingRate)
7655 imageInfo.usage |= VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
7656#endif
7657
7658 VmaAllocationCreateInfo allocInfo = {};
7659 allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
7660
7661 VmaAllocation allocation;
7662 VkResult err = vmaCreateImage(toVmaAllocator(rhiD->allocator), &imageInfo, &allocInfo, &image, &allocation, nullptr);
7663 if (err != VK_SUCCESS) {
7664 qWarning("Failed to create image (with VkImageCreateInfo %ux%u depth %u vkformat 0x%X mips %u layers %u vksamples 0x%X): %d",
7665 imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth,
7666 int(imageInfo.format),
7667 imageInfo.mipLevels,
7668 imageInfo.arrayLayers,
7669 int(imageInfo.samples),
7670 err);
7671 rhiD->printExtraErrorInfo(err);
7672 return false;
7673 }
7674 imageAlloc = allocation;
7675 rhiD->setAllocationName(allocation, m_objectName);
7676
7677 if (!finishCreate())
7678 return false;
7679
7680 rhiD->setObjectName(uint64_t(image), VK_OBJECT_TYPE_IMAGE, m_objectName);
7681
7682 owns = true;
7683 rhiD->registerResource(this);
7684 return true;
7685}
7686
7687bool QVkTexture::createFrom(QRhiTexture::NativeTexture src)
7688{
7689 VkImage img = VkImage(src.object);
7690 if (img == 0)
7691 return false;
7692
7693 if (!prepareCreate())
7694 return false;
7695
7696 image = img;
7697
7698 if (!finishCreate())
7699 return false;
7700
7701 usageState.layout = VkImageLayout(src.layout);
7702
7703 owns = false;
7704 QRHI_RES_RHI(QRhiVulkan);
7705 rhiD->registerResource(this);
7706 return true;
7707}
7708
7710{
7711 return {quint64(image), usageState.layout};
7712}
7713
7715{
7716 usageState.layout = VkImageLayout(layout);
7717}
7718
7720{
7721 Q_ASSERT(level >= 0 && level < int(mipLevelCount));
7722 if (perLevelImageViews[level] != VK_NULL_HANDLE)
7723 return perLevelImageViews[level];
7724
7725 const VkImageAspectFlags aspectMask = aspectMaskForTextureFormat(m_format);
7726 const bool isCube = m_flags.testFlag(CubeMap);
7727 const bool isArray = m_flags.testFlag(TextureArray);
7728 const bool is3D = m_flags.testFlag(ThreeDimensional);
7729 const bool is1D = m_flags.testFlag(OneDimensional);
7730
7731 VkImageViewCreateInfo viewInfo = {};
7732 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
7733 viewInfo.image = image;
7734 viewInfo.viewType = isCube
7735 ? VK_IMAGE_VIEW_TYPE_CUBE
7736 : (is3D ? VK_IMAGE_VIEW_TYPE_3D
7737 : (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D)
7738 : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D)));
7739 viewInfo.format = viewFormat; // this is writeViewFormat, regardless of Load, Store, or LoadStore; intentional
7740 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
7741 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
7742 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
7743 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
7744 viewInfo.subresourceRange.aspectMask = aspectMask;
7745 viewInfo.subresourceRange.baseMipLevel = uint32_t(level);
7746 viewInfo.subresourceRange.levelCount = 1;
7747 viewInfo.subresourceRange.baseArrayLayer = 0;
7748 viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(0, m_arraySize) : 1);
7749
7750 VkImageView v = VK_NULL_HANDLE;
7751 QRHI_RES_RHI(QRhiVulkan);
7752 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &v);
7753 if (err != VK_SUCCESS) {
7754 qWarning("Failed to create image view: %d", err);
7755 return VK_NULL_HANDLE;
7756 }
7757
7758 perLevelImageViews[level] = v;
7759 return v;
7760}
7761
7762QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
7763 AddressMode u, AddressMode v, AddressMode w)
7765{
7766}
7767
7769{
7770 destroy();
7771}
7772
7774{
7775 if (!sampler)
7776 return;
7777
7781
7782 e.sampler.sampler = sampler;
7783 sampler = VK_NULL_HANDLE;
7784
7785 QRHI_RES_RHI(QRhiVulkan);
7786 if (rhiD) {
7787 rhiD->releaseQueue.append(e);
7788 rhiD->unregisterResource(this);
7789 }
7790}
7791
7793{
7794 if (sampler)
7795 destroy();
7796
7797 VkSamplerCreateInfo samplerInfo = {};
7798 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
7799 samplerInfo.magFilter = toVkFilter(m_magFilter);
7800 samplerInfo.minFilter = toVkFilter(m_minFilter);
7801 samplerInfo.mipmapMode = toVkMipmapMode(m_mipmapMode);
7802 samplerInfo.addressModeU = toVkAddressMode(m_addressU);
7803 samplerInfo.addressModeV = toVkAddressMode(m_addressV);
7804 samplerInfo.addressModeW = toVkAddressMode(m_addressW);
7805 samplerInfo.maxAnisotropy = 1.0f;
7806 samplerInfo.compareEnable = m_compareOp != Never;
7807 samplerInfo.compareOp = toVkTextureCompareOp(m_compareOp);
7808 samplerInfo.maxLod = m_mipmapMode == None ? 0.25f : 1000.0f;
7809
7810 QRHI_RES_RHI(QRhiVulkan);
7811 VkResult err = rhiD->df->vkCreateSampler(rhiD->dev, &samplerInfo, nullptr, &sampler);
7812 if (err != VK_SUCCESS) {
7813 qWarning("Failed to create sampler: %d", err);
7814 return false;
7815 }
7816
7818 generation += 1;
7819 rhiD->registerResource(this);
7820 return true;
7821}
7822
7825{
7826 serializedFormatData.reserve(64);
7827}
7828
7833
7835{
7836 if (!rp)
7837 return;
7838
7839 if (!ownsRp) {
7840 rp = VK_NULL_HANDLE;
7841 return;
7842 }
7843
7847
7848 e.renderPass.rp = rp;
7849
7850 rp = VK_NULL_HANDLE;
7851
7852 QRHI_RES_RHI(QRhiVulkan);
7853 if (rhiD) {
7854 rhiD->releaseQueue.append(e);
7855 rhiD->unregisterResource(this);
7856 }
7857}
7858
7859static inline bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b)
7860{
7861 return a.format == b.format
7862 && a.samples == b.samples
7863 && a.loadOp == b.loadOp
7864 && a.storeOp == b.storeOp
7865 && a.stencilLoadOp == b.stencilLoadOp
7866 && a.stencilStoreOp == b.stencilStoreOp
7867 && a.initialLayout == b.initialLayout
7868 && a.finalLayout == b.finalLayout;
7869}
7870
7871bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
7872{
7873 if (other == this)
7874 return true;
7875
7876 if (!other)
7877 return false;
7878
7880
7881 if (attDescs.size() != o->attDescs.size())
7882 return false;
7883 if (colorRefs.size() != o->colorRefs.size())
7884 return false;
7885 if (resolveRefs.size() != o->resolveRefs.size())
7886 return false;
7888 return false;
7890 return false;
7891 if (multiViewCount != o->multiViewCount)
7892 return false;
7894 return false;
7895
7896 for (int i = 0, ie = colorRefs.size(); i != ie; ++i) {
7897 const uint32_t attIdx = colorRefs[i].attachment;
7898 if (attIdx != o->colorRefs[i].attachment)
7899 return false;
7900 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
7901 return false;
7902 }
7903
7904 if (hasDepthStencil) {
7905 const uint32_t attIdx = dsRef.attachment;
7906 if (attIdx != o->dsRef.attachment)
7907 return false;
7908 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
7909 return false;
7910 }
7911
7912 for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) {
7913 const uint32_t attIdx = resolveRefs[i].attachment;
7914 if (attIdx != o->resolveRefs[i].attachment)
7915 return false;
7916 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
7917 return false;
7918 }
7919
7921 const uint32_t attIdx = dsResolveRef.attachment;
7922 if (attIdx != o->dsResolveRef.attachment)
7923 return false;
7924 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
7925 return false;
7926 }
7927
7928 if (hasShadingRateMap) {
7929 const uint32_t attIdx = shadingRateRef.attachment;
7930 if (attIdx != o->shadingRateRef.attachment)
7931 return false;
7932 if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
7933 return false;
7934 }
7935
7936 // subpassDeps is not included
7937
7938 return true;
7939}
7940
7942{
7943 serializedFormatData.clear();
7944 auto p = std::back_inserter(serializedFormatData);
7945
7946 *p++ = attDescs.size();
7947 *p++ = colorRefs.size();
7948 *p++ = resolveRefs.size();
7949 *p++ = hasDepthStencil;
7951 *p++ = hasShadingRateMap;
7952 *p++ = multiViewCount;
7953
7954 auto serializeAttachmentData = [this, &p](uint32_t attIdx) {
7955 const bool used = attIdx != VK_ATTACHMENT_UNUSED;
7956 const VkAttachmentDescription *a = used ? &attDescs[attIdx] : nullptr;
7957 *p++ = used ? a->format : 0;
7958 *p++ = used ? a->samples : 0;
7959 *p++ = used ? a->loadOp : 0;
7960 *p++ = used ? a->storeOp : 0;
7961 *p++ = used ? a->stencilLoadOp : 0;
7962 *p++ = used ? a->stencilStoreOp : 0;
7963 *p++ = used ? a->initialLayout : 0;
7964 *p++ = used ? a->finalLayout : 0;
7965 };
7966
7967 for (int i = 0, ie = colorRefs.size(); i != ie; ++i) {
7968 const uint32_t attIdx = colorRefs[i].attachment;
7969 *p++ = attIdx;
7970 serializeAttachmentData(attIdx);
7971 }
7972
7973 if (hasDepthStencil) {
7974 const uint32_t attIdx = dsRef.attachment;
7975 *p++ = attIdx;
7976 serializeAttachmentData(attIdx);
7977 }
7978
7979 for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) {
7980 const uint32_t attIdx = resolveRefs[i].attachment;
7981 *p++ = attIdx;
7982 serializeAttachmentData(attIdx);
7983 }
7984
7986 const uint32_t attIdx = dsResolveRef.attachment;
7987 *p++ = attIdx;
7988 serializeAttachmentData(attIdx);
7989 }
7990
7991 if (hasShadingRateMap) {
7992 const uint32_t attIdx = shadingRateRef.attachment;
7993 *p++ = attIdx;
7994 serializeAttachmentData(attIdx);
7995 }
7996}
7997
7999{
8000 QVkRenderPassDescriptor *rpD = new QVkRenderPassDescriptor(m_rhi);
8001
8002 rpD->ownsRp = true;
8003 rpD->attDescs = attDescs;
8004 rpD->colorRefs = colorRefs;
8005 rpD->resolveRefs = resolveRefs;
8006 rpD->subpassDeps = subpassDeps;
8010 rpD->multiViewCount = multiViewCount;
8011 rpD->dsRef = dsRef;
8012 rpD->dsResolveRef = dsResolveRef;
8013 rpD->shadingRateRef = shadingRateRef;
8014
8015 VkRenderPassCreateInfo rpInfo;
8016 VkSubpassDescription subpassDesc;
8017 fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD);
8018
8019 QRHI_RES_RHI(QRhiVulkan);
8020 MultiViewRenderPassSetupHelper multiViewHelper;
8021 if (!multiViewHelper.prepare(&rpInfo, multiViewCount, rhiD->caps.multiView)) {
8022 delete rpD;
8023 return nullptr;
8024 }
8025
8026#ifdef VK_KHR_create_renderpass2
8027 if (rhiD->caps.renderPass2KHR) {
8028 // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1.
8029 VkRenderPassCreateInfo2KHR rpInfo2;
8030 RenderPass2SetupHelper rp2Helper(rhiD);
8031 if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount)) {
8032 delete rpD;
8033 return nullptr;
8034 }
8035 VkResult err = rhiD->vkCreateRenderPass2KHR(rhiD->dev, &rpInfo2, nullptr, &rpD->rp);
8036 if (err != VK_SUCCESS) {
8037 qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err);
8038 delete rpD;
8039 return nullptr;
8040 }
8041 } else
8042#endif
8043 {
8044 VkResult err = rhiD->df->vkCreateRenderPass(rhiD->dev, &rpInfo, nullptr, &rpD->rp);
8045 if (err != VK_SUCCESS) {
8046 qWarning("Failed to create renderpass: %d", err);
8047 delete rpD;
8048 return nullptr;
8049 }
8050 }
8051
8053 rhiD->registerResource(rpD);
8054 return rpD;
8055}
8056
8058{
8059 return serializedFormatData;
8060}
8061
8063{
8064 nativeHandlesStruct.renderPass = rp;
8065 return &nativeHandlesStruct;
8066}
8067
8068QVkShadingRateMap::QVkShadingRateMap(QRhiImplementation *rhi)
8070{
8071}
8072
8077
8079{
8080 if (!texture)
8081 return;
8082
8083 texture = nullptr;
8084}
8085
8086bool QVkShadingRateMap::createFrom(QRhiTexture *src)
8087{
8088 if (texture)
8089 destroy();
8090
8091 texture = QRHI_RES(QVkTexture, src);
8092
8093 return true;
8094}
8095
8096QVkSwapChainRenderTarget::QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
8098{
8099}
8100
8105
8107{
8108 // nothing to do here
8109}
8110
8112{
8113 return d.pixelSize;
8114}
8115
8117{
8118 return d.dpr;
8119}
8120
8122{
8123 return d.sampleCount;
8124}
8125
8127 const QRhiTextureRenderTargetDescription &desc,
8128 Flags flags)
8130{
8131 for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
8132 rtv[att] = VK_NULL_HANDLE;
8133 resrtv[att] = VK_NULL_HANDLE;
8134 }
8135}
8136
8141
8143{
8144 if (!d.fb)
8145 return;
8146
8150
8151 e.textureRenderTarget.fb = d.fb;
8152 d.fb = VK_NULL_HANDLE;
8153
8154 for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
8155 e.textureRenderTarget.rtv[att] = rtv[att];
8156 e.textureRenderTarget.resrtv[att] = resrtv[att];
8157 rtv[att] = VK_NULL_HANDLE;
8158 resrtv[att] = VK_NULL_HANDLE;
8159 }
8160
8161 e.textureRenderTarget.dsv = dsv;
8162 dsv = VK_NULL_HANDLE;
8163 e.textureRenderTarget.resdsv = resdsv;
8164 resdsv = VK_NULL_HANDLE;
8165
8166 e.textureRenderTarget.shadingRateMapView = shadingRateMapView;
8167 shadingRateMapView = VK_NULL_HANDLE;
8168
8169 QRHI_RES_RHI(QRhiVulkan);
8170 if (rhiD) {
8171 rhiD->releaseQueue.append(e);
8172 rhiD->unregisterResource(this);
8173 }
8174}
8175
8177{
8178 // not yet built so cannot rely on data computed in create()
8179
8180 QRHI_RES_RHI(QRhiVulkan);
8181 QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
8182 if (!rhiD->createOffscreenRenderPass(rp,
8183 m_desc.cbeginColorAttachments(),
8184 m_desc.cendColorAttachments(),
8185 m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents),
8186 m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents),
8187 m_desc.depthTexture() && !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(),
8188 m_desc.depthStencilBuffer(),
8189 m_desc.depthTexture(),
8190 m_desc.depthResolveTexture(),
8191 m_desc.shadingRateMap()))
8192 {
8193 delete rp;
8194 return nullptr;
8195 }
8196
8197 rp->ownsRp = true;
8199 rhiD->registerResource(rp);
8200 return rp;
8201}
8202
8204{
8205 if (d.fb)
8206 destroy();
8207
8208 Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
8209 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
8210 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
8211
8212 QRHI_RES_RHI(QRhiVulkan);
8213 QVarLengthArray<VkImageView, 8> views;
8214 d.multiViewCount = 0;
8215
8216 d.colorAttCount = 0;
8217 int attIndex = 0;
8218 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
8219 d.colorAttCount += 1;
8220 QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
8221 QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
8222 Q_ASSERT(texD || rbD);
8223 if (texD) {
8224 Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget));
8225 const bool is1D = texD->flags().testFlag(QRhiTexture::OneDimensional);
8226 const bool isMultiView = it->multiViewCount() >= 2;
8227 if (isMultiView && d.multiViewCount == 0)
8228 d.multiViewCount = it->multiViewCount();
8229 VkImageViewCreateInfo viewInfo = {};
8230 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
8231 viewInfo.image = texD->image;
8232 viewInfo.viewType = is1D ? VK_IMAGE_VIEW_TYPE_1D
8233 : (isMultiView ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
8234 : VK_IMAGE_VIEW_TYPE_2D);
8235 viewInfo.format = texD->viewFormat;
8236 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
8237 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
8238 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
8239 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
8240 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
8241 viewInfo.subresourceRange.baseMipLevel = uint32_t(it->level());
8242 viewInfo.subresourceRange.levelCount = 1;
8243 viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->layer());
8244 viewInfo.subresourceRange.layerCount = uint32_t(isMultiView ? it->multiViewCount() : 1);
8245 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[attIndex]);
8246 if (err != VK_SUCCESS) {
8247 qWarning("Failed to create render target image view: %d", err);
8248 return false;
8249 }
8250 views.append(rtv[attIndex]);
8251 if (attIndex == 0) {
8252 d.pixelSize = rhiD->q->sizeForMipLevel(it->level(), texD->pixelSize());
8253 d.sampleCount = texD->samples;
8254 }
8255 } else if (rbD) {
8256 Q_ASSERT(rbD->backingTexture);
8257 views.append(rbD->backingTexture->imageView);
8258 if (attIndex == 0) {
8259 d.pixelSize = rbD->pixelSize();
8260 d.sampleCount = rbD->samples;
8261 }
8262 }
8263 }
8264 d.dpr = 1;
8265
8266 if (hasDepthStencil) {
8267 if (m_desc.depthTexture()) {
8268 QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture());
8269 // need a dedicated view just because viewFormat may differ from vkformat
8270 VkImageViewCreateInfo viewInfo = {};
8271 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
8272 viewInfo.image = depthTexD->image;
8273 viewInfo.viewType = d.multiViewCount > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
8274 viewInfo.format = depthTexD->viewFormat;
8275 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
8276 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
8277 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
8278 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
8279 viewInfo.subresourceRange.aspectMask = aspectMaskForTextureFormat(depthTexD->format());
8280 viewInfo.subresourceRange.levelCount = 1;
8281 viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
8282 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &dsv);
8283 if (err != VK_SUCCESS) {
8284 qWarning("Failed to create depth-stencil image view for rt: %d", err);
8285 return false;
8286 }
8287 views.append(dsv);
8288 if (d.colorAttCount == 0) {
8289 d.pixelSize = depthTexD->pixelSize();
8290 d.sampleCount = depthTexD->samples;
8291 }
8292 } else {
8293 QVkRenderBuffer *depthRbD = QRHI_RES(QVkRenderBuffer, m_desc.depthStencilBuffer());
8294 views.append(depthRbD->imageView);
8295 if (d.colorAttCount == 0) {
8296 d.pixelSize = depthRbD->pixelSize();
8297 d.sampleCount = depthRbD->samples;
8298 }
8299 }
8300 d.dsAttCount = 1;
8301 } else {
8302 d.dsAttCount = 0;
8303 }
8304
8305 d.resolveAttCount = 0;
8306 attIndex = 0;
8307 Q_ASSERT(d.multiViewCount == 0 || d.multiViewCount >= 2);
8308 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
8309 if (it->resolveTexture()) {
8310 QVkTexture *resTexD = QRHI_RES(QVkTexture, it->resolveTexture());
8311 Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
8312 d.resolveAttCount += 1;
8313
8314 VkImageViewCreateInfo viewInfo = {};
8315 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
8316 viewInfo.image = resTexD->image;
8317 viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
8318 : VK_IMAGE_VIEW_TYPE_2D;
8319 viewInfo.format = resTexD->viewFormat;
8320 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
8321 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
8322 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
8323 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
8324 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
8325 viewInfo.subresourceRange.baseMipLevel = uint32_t(it->resolveLevel());
8326 viewInfo.subresourceRange.levelCount = 1;
8327 viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->resolveLayer());
8328 viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
8329 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[attIndex]);
8330 if (err != VK_SUCCESS) {
8331 qWarning("Failed to create render target resolve image view: %d", err);
8332 return false;
8333 }
8334 views.append(resrtv[attIndex]);
8335 }
8336 }
8337
8338 if (m_desc.depthResolveTexture()) {
8339 QVkTexture *resTexD = QRHI_RES(QVkTexture, m_desc.depthResolveTexture());
8340 Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
8341
8342 VkImageViewCreateInfo viewInfo = {};
8343 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
8344 viewInfo.image = resTexD->image;
8345 viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
8346 : VK_IMAGE_VIEW_TYPE_2D;
8347 viewInfo.format = resTexD->viewFormat;
8348 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
8349 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
8350 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
8351 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
8352 viewInfo.subresourceRange.aspectMask = aspectMaskForTextureFormat(resTexD->format());
8353 viewInfo.subresourceRange.baseMipLevel = 0;
8354 viewInfo.subresourceRange.levelCount = 1;
8355 viewInfo.subresourceRange.baseArrayLayer = 0;
8356 viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
8357 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resdsv);
8358 if (err != VK_SUCCESS) {
8359 qWarning("Failed to create render target depth resolve image view: %d", err);
8360 return false;
8361 }
8362 views.append(resdsv);
8363 d.dsResolveAttCount = 1;
8364 } else {
8365 d.dsResolveAttCount = 0;
8366 }
8367
8368 if (m_desc.shadingRateMap() && rhiD->caps.renderPass2KHR && rhiD->caps.imageBasedShadingRate) {
8369 QVkTexture *texD = QRHI_RES(QVkShadingRateMap, m_desc.shadingRateMap())->texture;
8370 Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedAsShadingRateMap));
8371
8372 VkImageViewCreateInfo viewInfo = {};
8373 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
8374 viewInfo.image = texD->image;
8375 viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
8376 : VK_IMAGE_VIEW_TYPE_2D;
8377 viewInfo.format = texD->viewFormat;
8378 viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
8379 viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
8380 viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
8381 viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
8382 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
8383 viewInfo.subresourceRange.baseMipLevel = 0;
8384 viewInfo.subresourceRange.levelCount = 1;
8385 viewInfo.subresourceRange.baseArrayLayer = 0;
8386 viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
8387 VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &shadingRateMapView);
8388 if (err != VK_SUCCESS) {
8389 qWarning("Failed to create render target shading rate map view: %d", err);
8390 return false;
8391 }
8392 views.append(shadingRateMapView);
8393 d.shadingRateAttCount = 1;
8394 } else {
8395 d.shadingRateAttCount = 0;
8396 }
8397
8398 if (!m_renderPassDesc)
8399 qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
8400
8401 d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
8402 Q_ASSERT(d.rp && d.rp->rp);
8403
8404 VkFramebufferCreateInfo fbInfo = {};
8405 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
8406 fbInfo.renderPass = d.rp->rp;
8407 fbInfo.attachmentCount = uint32_t(views.count());
8408 fbInfo.pAttachments = views.constData();
8409 fbInfo.width = uint32_t(d.pixelSize.width());
8410 fbInfo.height = uint32_t(d.pixelSize.height());
8411 fbInfo.layers = 1;
8412
8413 VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &d.fb);
8414 if (err != VK_SUCCESS) {
8415 qWarning("Failed to create framebuffer: %d", err);
8416 return false;
8417 }
8418
8419 QRhiRenderTargetAttachmentTracker::updateResIdList<QVkTexture, QVkRenderBuffer>(m_desc, &d.currentResIdList);
8420
8422 rhiD->registerResource(this);
8423 return true;
8424}
8425
8427{
8428 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(m_desc, d.currentResIdList))
8429 const_cast<QVkTextureRenderTarget *>(this)->create();
8430
8431 return d.pixelSize;
8432}
8433
8435{
8436 return d.dpr;
8437}
8438
8440{
8441 return d.sampleCount;
8442}
8443
8448
8453
8455{
8456 if (!layout)
8457 return;
8458
8459 sortedBindings.clear();
8460
8464
8465 e.shaderResourceBindings.poolIndex = poolIndex;
8466 e.shaderResourceBindings.layout = layout;
8467
8468 poolIndex = -1;
8469 layout = VK_NULL_HANDLE;
8470 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
8471 descSets[i] = VK_NULL_HANDLE;
8472
8473 QRHI_RES_RHI(QRhiVulkan);
8474 if (rhiD) {
8475 rhiD->releaseQueue.append(e);
8476 rhiD->unregisterResource(this);
8477 }
8478}
8479
8481{
8482 if (layout)
8483 destroy();
8484
8485 QRHI_RES_RHI(QRhiVulkan);
8486 if (!rhiD->sanityCheckShaderResourceBindings(this))
8487 return false;
8488
8489 rhiD->updateLayoutDesc(this);
8490
8491 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
8492 descSets[i] = VK_NULL_HANDLE;
8493
8494 sortedBindings.clear();
8495 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
8496 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
8497
8498 hasDynamicOffset = false;
8499 for (const QRhiShaderResourceBinding &binding : std::as_const(sortedBindings)) {
8500 const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding);
8501 if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.buf) {
8502 if (b->u.ubuf.hasDynamicOffset)
8503 hasDynamicOffset = true;
8504 }
8505 }
8506
8507 QVarLengthArray<VkDescriptorSetLayoutBinding, BINDING_PREALLOC> vkbindings;
8508 vkbindings.reserve(sortedBindings.size());
8509 for (const QRhiShaderResourceBinding &binding : std::as_const(sortedBindings)) {
8510 const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding);
8511 VkDescriptorSetLayoutBinding &vkbinding = vkbindings.emplace_back();
8512 vkbinding.binding = uint32_t(b->binding);
8513 vkbinding.descriptorType = toVkDescriptorType(b);
8514 if (b->type == QRhiShaderResourceBinding::SampledTexture || b->type == QRhiShaderResourceBinding::Texture)
8515 vkbinding.descriptorCount = b->u.stex.count;
8516 else
8517 vkbinding.descriptorCount = 1;
8518 vkbinding.stageFlags = toVkShaderStageFlags(b->stage);
8519 }
8520
8521 VkDescriptorSetLayoutCreateInfo layoutInfo = {};
8522 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
8523 layoutInfo.bindingCount = uint32_t(vkbindings.size());
8524 layoutInfo.pBindings = vkbindings.constData();
8525
8526 VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout);
8527 if (err != VK_SUCCESS) {
8528 qWarning("Failed to create descriptor set layout: %d", err);
8529 return false;
8530 }
8531 rhiD->setObjectName(uint64_t(layout), VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, m_objectName);
8532
8533 VkDescriptorSetAllocateInfo allocInfo = {};
8534 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
8535 allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT;
8536 VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT];
8537 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
8538 layouts[i] = layout;
8539 allocInfo.pSetLayouts = layouts;
8540 if (!rhiD->allocateDescriptorSet(&allocInfo, descSets, &poolIndex))
8541 return false;
8542
8543 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
8544 boundResourceData[i].resize(sortedBindings.size());
8545 for (BoundResourceData &bd : boundResourceData[i])
8546 memset(&bd, 0, sizeof(BoundResourceData));
8547 }
8548
8549 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
8550 rhiD->setObjectName(uint64_t(descSets[i]), VK_OBJECT_TYPE_DESCRIPTOR_SET, m_objectName, i);
8551
8553 generation += 1;
8554 rhiD->registerResource(this);
8555 return true;
8556}
8557
8559{
8560 sortedBindings.clear();
8561 std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
8562 if (!flags.testFlag(BindingsAreSorted))
8563 std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
8564
8565 // Reset the state tracking table too - it can deal with assigning a
8566 // different QRhiBuffer/Texture/Sampler for a binding point, but it cannot
8567 // detect changes in the associated data, such as the buffer offset. And
8568 // just like after a create(), a call to updateResources() may lead to now
8569 // specifying a different offset for the same QRhiBuffer for a given binding
8570 // point. The same applies to other type of associated data that is not part
8571 // of the layout, such as the mip level for a StorageImage. Instead of
8572 // complicating the checks in setShaderResources(), reset the table here
8573 // just like we do in create().
8574 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
8575 Q_ASSERT(boundResourceData[i].size() == sortedBindings.size());
8576 for (BoundResourceData &bd : boundResourceData[i])
8577 memset(&bd, 0, sizeof(BoundResourceData));
8578 }
8579
8580 generation += 1;
8581}
8582
8585{
8586}
8587
8592
8594{
8595 if (!pipeline && !layout)
8596 return;
8597
8601
8602 e.pipelineState.pipeline = pipeline;
8603 e.pipelineState.layout = layout;
8604
8605 pipeline = VK_NULL_HANDLE;
8606 layout = VK_NULL_HANDLE;
8607
8608 QRHI_RES_RHI(QRhiVulkan);
8609 if (rhiD) {
8610 rhiD->releaseQueue.append(e);
8611 rhiD->unregisterResource(this);
8612 }
8613}
8614
8616{
8617 if (pipeline)
8618 destroy();
8619
8620 QRHI_RES_RHI(QRhiVulkan);
8621 rhiD->pipelineCreationStart();
8622 if (!rhiD->sanityCheckGraphicsPipeline(this))
8623 return false;
8624
8625 if (!rhiD->ensurePipelineCache())
8626 return false;
8627
8628 VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
8629 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
8630 pipelineLayoutInfo.setLayoutCount = 1;
8631 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
8632 Q_ASSERT(m_shaderResourceBindings && srbD->layout);
8633 pipelineLayoutInfo.pSetLayouts = &srbD->layout;
8634 VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
8635 if (err != VK_SUCCESS) {
8636 qWarning("Failed to create pipeline layout: %d", err);
8637 return false;
8638 }
8639
8640 VkGraphicsPipelineCreateInfo pipelineInfo = {};
8641 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
8642
8643 QVarLengthArray<VkShaderModule, 4> shaders;
8644 QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos;
8645 for (const QRhiShaderStage &shaderStage : m_shaderStages) {
8646 const QShader bakedShader = shaderStage.shader();
8647 const QShaderCode spirv = bakedShader.shader({ QShader::SpirvShader, 100, shaderStage.shaderVariant() });
8648 if (spirv.shader().isEmpty()) {
8649 qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
8650 return false;
8651 }
8652 VkShaderModule shader = rhiD->createShader(spirv.shader());
8653 if (shader) {
8654 shaders.append(shader);
8655 VkPipelineShaderStageCreateInfo shaderInfo = {};
8656 shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
8657 shaderInfo.stage = toVkShaderStage(shaderStage.type());
8658 shaderInfo.module = shader;
8659 shaderInfo.pName = spirv.entryPoint().constData();
8660 shaderStageCreateInfos.append(shaderInfo);
8661 }
8662 }
8663 pipelineInfo.stageCount = uint32_t(shaderStageCreateInfos.size());
8664 pipelineInfo.pStages = shaderStageCreateInfos.constData();
8665
8666 QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings;
8667#ifdef VK_EXT_vertex_attribute_divisor
8668 QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates;
8669#endif
8670 int bindingIndex = 0;
8671 for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings();
8672 it != itEnd; ++it, ++bindingIndex)
8673 {
8674 VkVertexInputBindingDescription bindingInfo = {
8675 uint32_t(bindingIndex),
8676 it->stride(),
8677 it->classification() == QRhiVertexInputBinding::PerVertex
8678 ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE
8679 };
8680 if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) {
8681#ifdef VK_EXT_vertex_attribute_divisor
8682 if (rhiD->caps.vertexAttribDivisor) {
8683 nonOneStepRates.append({ uint32_t(bindingIndex), it->instanceStepRate() });
8684 } else
8685#endif
8686 {
8687 qWarning("QRhiVulkan: Instance step rates other than 1 not supported without "
8688 "VK_EXT_vertex_attribute_divisor on the device and "
8689 "VK_KHR_get_physical_device_properties2 on the instance");
8690 }
8691 }
8692 vertexBindings.append(bindingInfo);
8693 }
8694 QVarLengthArray<VkVertexInputAttributeDescription, 4> vertexAttributes;
8695 for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
8696 it != itEnd; ++it)
8697 {
8698 VkVertexInputAttributeDescription attributeInfo = {
8699 uint32_t(it->location()),
8700 uint32_t(it->binding()),
8701 toVkAttributeFormat(it->format()),
8702 it->offset()
8703 };
8704 vertexAttributes.append(attributeInfo);
8705 }
8706 VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
8707 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
8708 vertexInputInfo.vertexBindingDescriptionCount = uint32_t(vertexBindings.size());
8709 vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData();
8710 vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.size());
8711 vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData();
8712#ifdef VK_EXT_vertex_attribute_divisor
8713 VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo = {};
8714 if (!nonOneStepRates.isEmpty()) {
8715 divisorInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT;
8716 divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.size());
8717 divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData();
8718 vertexInputInfo.pNext = &divisorInfo;
8719 }
8720#endif
8721 pipelineInfo.pVertexInputState = &vertexInputInfo;
8722
8723 QVarLengthArray<VkDynamicState, 8> dynEnable;
8724 dynEnable << VK_DYNAMIC_STATE_VIEWPORT;
8725 dynEnable << VK_DYNAMIC_STATE_SCISSOR; // ignore UsesScissor - Vulkan requires a scissor for the viewport always
8726 if (m_flags.testFlag(QRhiGraphicsPipeline::UsesBlendConstants))
8727 dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS;
8728 if (m_flags.testFlag(QRhiGraphicsPipeline::UsesStencilRef))
8729 dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE;
8730#ifdef VK_KHR_fragment_shading_rate
8731 if (m_flags.testFlag(QRhiGraphicsPipeline::UsesShadingRate) && rhiD->caps.perDrawShadingRate)
8732 dynEnable << VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR;
8733#endif
8734
8735 VkPipelineDynamicStateCreateInfo dynamicInfo = {};
8736 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
8737 dynamicInfo.dynamicStateCount = uint32_t(dynEnable.size());
8738 dynamicInfo.pDynamicStates = dynEnable.constData();
8739 pipelineInfo.pDynamicState = &dynamicInfo;
8740
8741 VkPipelineViewportStateCreateInfo viewportInfo = {};
8742 viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
8743 viewportInfo.viewportCount = viewportInfo.scissorCount = 1;
8744 pipelineInfo.pViewportState = &viewportInfo;
8745
8746 VkPipelineInputAssemblyStateCreateInfo inputAsmInfo = {};
8747 inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
8748 inputAsmInfo.topology = toVkTopology(m_topology);
8749 inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip);
8750 pipelineInfo.pInputAssemblyState = &inputAsmInfo;
8751
8752 VkPipelineTessellationStateCreateInfo tessInfo = {};
8753#ifdef VK_VERSION_1_1
8754 VkPipelineTessellationDomainOriginStateCreateInfo originInfo = {};
8755#endif
8756 if (m_topology == Patches) {
8757 tessInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
8758 tessInfo.patchControlPoints = uint32_t(qMax(1, m_patchControlPointCount));
8759
8760 // To be able to use the same tess.evaluation shader with both OpenGL
8761 // and Vulkan, flip the tessellation domain origin to be lower left.
8762 // This allows declaring the winding order in the shader to be CCW and
8763 // still have it working with both APIs. This requires Vulkan 1.1 (or
8764 // VK_KHR_maintenance2 but don't bother with that).
8765#ifdef VK_VERSION_1_1
8766 if (rhiD->caps.apiVersion >= QVersionNumber(1, 1)) {
8767 originInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO;
8768 originInfo.domainOrigin = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT;
8769 tessInfo.pNext = &originInfo;
8770 } else {
8771 qWarning("Proper tessellation support requires Vulkan 1.1 or newer, leaving domain origin unset");
8772 }
8773#else
8774 qWarning("QRhi was built without Vulkan 1.1 headers, this is not sufficient for proper tessellation support");
8775#endif
8776
8777 pipelineInfo.pTessellationState = &tessInfo;
8778 }
8779
8780 VkPipelineRasterizationStateCreateInfo rastInfo = {};
8781 rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
8782 if (m_depthClamp && rhiD->caps.depthClamp)
8783 rastInfo.depthClampEnable = m_depthClamp;
8784 rastInfo.cullMode = toVkCullMode(m_cullMode);
8785 rastInfo.frontFace = toVkFrontFace(m_frontFace);
8786 if (m_depthBias != 0 || !qFuzzyIsNull(m_slopeScaledDepthBias)) {
8787 rastInfo.depthBiasEnable = true;
8788 rastInfo.depthBiasConstantFactor = float(m_depthBias);
8789 rastInfo.depthBiasSlopeFactor = m_slopeScaledDepthBias;
8790 }
8791 rastInfo.lineWidth = rhiD->caps.wideLines ? m_lineWidth : 1.0f;
8792 rastInfo.polygonMode = toVkPolygonMode(m_polygonMode);
8793 pipelineInfo.pRasterizationState = &rastInfo;
8794
8795 VkPipelineMultisampleStateCreateInfo msInfo = {};
8796 msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
8797 msInfo.rasterizationSamples = rhiD->effectiveSampleCountBits(m_sampleCount);
8798 pipelineInfo.pMultisampleState = &msInfo;
8799
8800 VkPipelineDepthStencilStateCreateInfo dsInfo = {};
8801 dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
8802 dsInfo.depthTestEnable = m_depthTest;
8803 dsInfo.depthWriteEnable = m_depthWrite;
8804 dsInfo.depthCompareOp = toVkCompareOp(m_depthOp);
8805 dsInfo.stencilTestEnable = m_stencilTest;
8806 if (m_stencilTest) {
8807 fillVkStencilOpState(&dsInfo.front, m_stencilFront);
8808 dsInfo.front.compareMask = m_stencilReadMask;
8809 dsInfo.front.writeMask = m_stencilWriteMask;
8810 fillVkStencilOpState(&dsInfo.back, m_stencilBack);
8811 dsInfo.back.compareMask = m_stencilReadMask;
8812 dsInfo.back.writeMask = m_stencilWriteMask;
8813 }
8814 pipelineInfo.pDepthStencilState = &dsInfo;
8815
8816 VkPipelineColorBlendStateCreateInfo blendInfo = {};
8817 blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
8818 QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends;
8819 for (const QRhiGraphicsPipeline::TargetBlend &b : std::as_const(m_targetBlends)) {
8820 VkPipelineColorBlendAttachmentState blend = {};
8821 blend.blendEnable = b.enable;
8822 blend.srcColorBlendFactor = toVkBlendFactor(b.srcColor);
8823 blend.dstColorBlendFactor = toVkBlendFactor(b.dstColor);
8824 blend.colorBlendOp = toVkBlendOp(b.opColor);
8825 blend.srcAlphaBlendFactor = toVkBlendFactor(b.srcAlpha);
8826 blend.dstAlphaBlendFactor = toVkBlendFactor(b.dstAlpha);
8827 blend.alphaBlendOp = toVkBlendOp(b.opAlpha);
8828 blend.colorWriteMask = toVkColorComponents(b.colorWrite);
8829 vktargetBlends.append(blend);
8830 }
8831 if (vktargetBlends.isEmpty()) {
8832 VkPipelineColorBlendAttachmentState blend = {};
8833 blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
8834 | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
8835 vktargetBlends.append(blend);
8836 }
8837 blendInfo.attachmentCount = uint32_t(vktargetBlends.size());
8838 blendInfo.pAttachments = vktargetBlends.constData();
8839 pipelineInfo.pColorBlendState = &blendInfo;
8840
8841 pipelineInfo.layout = layout;
8842
8843 Q_ASSERT(m_renderPassDesc && QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp);
8844 pipelineInfo.renderPass = QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp;
8845
8846 err = rhiD->df->vkCreateGraphicsPipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
8847
8848 for (VkShaderModule shader : shaders)
8849 rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
8850
8851 if (err != VK_SUCCESS) {
8852 qWarning("Failed to create graphics pipeline: %d", err);
8853 return false;
8854 }
8855
8856 rhiD->setObjectName(uint64_t(pipeline), VK_OBJECT_TYPE_PIPELINE, m_objectName);
8857
8858 rhiD->pipelineCreationEnd();
8860 generation += 1;
8861 rhiD->registerResource(this);
8862 return true;
8863}
8864
8867{
8868}
8869
8874
8876{
8877 if (!pipeline && !layout)
8878 return;
8879
8883
8884 e.pipelineState.pipeline = pipeline;
8885 e.pipelineState.layout = layout;
8886
8887 pipeline = VK_NULL_HANDLE;
8888 layout = VK_NULL_HANDLE;
8889
8890 QRHI_RES_RHI(QRhiVulkan);
8891 if (rhiD) {
8892 rhiD->releaseQueue.append(e);
8893 rhiD->unregisterResource(this);
8894 }
8895}
8896
8898{
8899 if (pipeline)
8900 destroy();
8901
8902 QRHI_RES_RHI(QRhiVulkan);
8903 rhiD->pipelineCreationStart();
8904 if (!rhiD->ensurePipelineCache())
8905 return false;
8906
8907 VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
8908 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
8909 pipelineLayoutInfo.setLayoutCount = 1;
8910 QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
8911 Q_ASSERT(m_shaderResourceBindings && srbD->layout);
8912 pipelineLayoutInfo.pSetLayouts = &srbD->layout;
8913 VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
8914 if (err != VK_SUCCESS) {
8915 qWarning("Failed to create pipeline layout: %d", err);
8916 return false;
8917 }
8918
8919 VkComputePipelineCreateInfo pipelineInfo = {};
8920 pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
8921 pipelineInfo.layout = layout;
8922
8923 if (m_shaderStage.type() != QRhiShaderStage::Compute) {
8924 qWarning("Compute pipeline requires a compute shader stage");
8925 return false;
8926 }
8927 const QShader bakedShader = m_shaderStage.shader();
8928 const QShaderCode spirv = bakedShader.shader({ QShader::SpirvShader, 100, m_shaderStage.shaderVariant() });
8929 if (spirv.shader().isEmpty()) {
8930 qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
8931 return false;
8932 }
8933 if (bakedShader.stage() != QShader::ComputeStage) {
8934 qWarning() << bakedShader << "is not a compute shader";
8935 return false;
8936 }
8937 VkShaderModule shader = rhiD->createShader(spirv.shader());
8938 VkPipelineShaderStageCreateInfo shaderInfo = {};
8939 shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
8940 shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
8941 shaderInfo.module = shader;
8942 shaderInfo.pName = spirv.entryPoint().constData();
8943 pipelineInfo.stage = shaderInfo;
8944
8945 err = rhiD->df->vkCreateComputePipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
8946 rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
8947 if (err != VK_SUCCESS) {
8948 qWarning("Failed to create graphics pipeline: %d", err);
8949 return false;
8950 }
8951
8952 rhiD->setObjectName(uint64_t(pipeline), VK_OBJECT_TYPE_PIPELINE, m_objectName);
8953
8954 rhiD->pipelineCreationEnd();
8956 generation += 1;
8957 rhiD->registerResource(this);
8958 return true;
8959}
8960
8961QVkCommandBuffer::QVkCommandBuffer(QRhiImplementation *rhi)
8963{
8965}
8966
8971
8973{
8974 // nothing to do here, cb is not owned by us
8975}
8976
8978{
8979 // Ok this is messy but no other way has been devised yet. Outside
8980 // begin(Compute)Pass - end(Compute)Pass it is simple - just return the
8981 // primary VkCommandBuffer. Inside, however, we need to provide the current
8982 // secondary command buffer (typically the one started by beginExternal(),
8983 // in case we are between beginExternal - endExternal inside a pass).
8984
8986 nativeHandlesStruct.commandBuffer = cb;
8987 } else {
8988 if (passUsesSecondaryCb && !activeSecondaryCbStack.isEmpty())
8989 nativeHandlesStruct.commandBuffer = activeSecondaryCbStack.last();
8990 else
8991 nativeHandlesStruct.commandBuffer = cb;
8992 }
8993
8994 return &nativeHandlesStruct;
8995}
8996
8997QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi)
8998 : QRhiSwapChain(rhi),
8999 rtWrapper(rhi, this),
9000 rtWrapperRight(rhi, this),
9001 cbWrapper(rhi)
9002{
9003}
9004
9009
9011{
9012 if (sc == VK_NULL_HANDLE)
9013 return;
9014
9015 QRHI_RES_RHI(QRhiVulkan);
9016 if (rhiD) {
9017 rhiD->swapchains.remove(this);
9019 }
9020
9021 for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
9022 QVkSwapChain::FrameResources &frame(frameRes[i]);
9023 frame.cmdBuf = VK_NULL_HANDLE;
9024 frame.timestampQueryIndex = -1;
9025 }
9026
9027 surface = lastConnectedSurface = VK_NULL_HANDLE;
9028
9029 if (rhiD)
9030 rhiD->unregisterResource(this);
9031}
9032
9034{
9035 return &cbWrapper;
9036}
9037
9039{
9040 return &rtWrapper;
9041}
9042
9044{
9045 return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
9046}
9047
9049{
9050 if (!ensureSurface())
9051 return QSize();
9052
9053 // The size from the QWindow may not exactly match the surface... so if a
9054 // size is reported from the surface, use that.
9055 VkSurfaceCapabilitiesKHR surfaceCaps = {};
9056 QRHI_RES_RHI(QRhiVulkan);
9057 rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps);
9058 VkExtent2D bufferSize = surfaceCaps.currentExtent;
9059 if (bufferSize.width == uint32_t(-1)) {
9060 Q_ASSERT(bufferSize.height == uint32_t(-1));
9061 return m_window->size() * m_window->devicePixelRatio();
9062 }
9063 return QSize(int(bufferSize.width), int(bufferSize.height));
9064}
9065
9066static inline bool hdrFormatMatchesVkSurfaceFormat(QRhiSwapChain::Format f, const VkSurfaceFormatKHR &s)
9067{
9068 switch (f) {
9069 case QRhiSwapChain::HDRExtendedSrgbLinear:
9070 return s.format == VK_FORMAT_R16G16B16A16_SFLOAT
9071 && s.colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT;
9072 case QRhiSwapChain::HDR10:
9073 return s.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32
9074 && s.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT;
9075 case QRhiSwapChain::HDRExtendedDisplayP3Linear:
9076 return s.format == VK_FORMAT_R16G16B16A16_SFLOAT
9077 && s.colorSpace == VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT;
9078 default:
9079 break;
9080 }
9081 return false;
9082}
9083
9085{
9086 if (f == SDR)
9087 return true;
9088
9089 if (!m_window) {
9090 qWarning("Attempted to call isFormatSupported() without a window set");
9091 return false;
9092 }
9093
9094 // we may be called before create so query the surface
9095 VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(m_window);
9096
9097 QRHI_RES_RHI(QRhiVulkan);
9098 uint32_t formatCount = 0;
9099 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surf, &formatCount, nullptr);
9100 QVarLengthArray<VkSurfaceFormatKHR, 8> formats(formatCount);
9101 if (formatCount) {
9102 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surf, &formatCount, formats.data());
9103 for (uint32_t i = 0; i < formatCount; ++i) {
9104 if (hdrFormatMatchesVkSurfaceFormat(f, formats[i]))
9105 return true;
9106 }
9107 }
9108
9109 return false;
9110}
9111
9113{
9114 QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
9115#ifdef Q_OS_WIN
9116 QRHI_RES_RHI(QRhiVulkan);
9117 // Must use m_window, not window, given this may be called before createOrResize().
9118 if (m_window && rhiD->adapterLuidValid)
9119 info = rhiD->dxgiHdrInfo->queryHdrInfo(m_window);
9120#endif
9121 return info;
9122}
9123
9125{
9126 // not yet built so cannot rely on data computed in createOrResize()
9127
9128 if (!ensureSurface()) // make sure sampleCount and colorFormat reflect what was requested
9129 return nullptr;
9130
9131 QRHI_RES_RHI(QRhiVulkan);
9132 QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
9133 if (!rhiD->createDefaultRenderPass(rp,
9134 m_depthStencil != nullptr,
9135 samples,
9136 colorFormat,
9137 m_shadingRateMap))
9138 {
9139 delete rp;
9140 return nullptr;
9141 }
9142
9143 rp->ownsRp = true;
9145 rhiD->registerResource(rp);
9146 return rp;
9147}
9148
9149static inline bool isSrgbFormat(VkFormat format)
9150{
9151 switch (format) {
9152 case VK_FORMAT_R8_SRGB:
9153 case VK_FORMAT_R8G8_SRGB:
9154 case VK_FORMAT_R8G8B8_SRGB:
9155 case VK_FORMAT_B8G8R8_SRGB:
9156 case VK_FORMAT_R8G8B8A8_SRGB:
9157 case VK_FORMAT_B8G8R8A8_SRGB:
9158 case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
9159 return true;
9160 default:
9161 return false;
9162 }
9163}
9164
9166{
9167 // Do nothing when already done, however window may change so check the
9168 // surface is still the same. Some of the queries below are very expensive
9169 // with some implementations so it is important to do the rest only once
9170 // per surface.
9171
9172 Q_ASSERT(m_window);
9173 VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(m_window);
9174 if (!surf) {
9175 qWarning("Failed to get surface for window");
9176 return false;
9177 }
9178 if (surface == surf)
9179 return true;
9180
9181 surface = surf;
9182
9183 QRHI_RES_RHI(QRhiVulkan);
9184 if (!rhiD->inst->supportsPresent(rhiD->physDev, rhiD->gfxQueueFamilyIdx, m_window)) {
9185 qWarning("Presenting not supported on this window");
9186 return false;
9187 }
9188
9189 quint32 formatCount = 0;
9190 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, nullptr);
9191 QList<VkSurfaceFormatKHR> formats(formatCount);
9192 rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount,
9193 formats.data());
9194
9195 // Initially select the first available format, will only be used as a worst-case fallback
9196 colorFormat = formats.constFirst().format;
9197 colorSpace = formats.constFirst().colorSpace;
9198
9199 // See if we can find the preferred SDR format
9200 const bool srgbRequested = m_flags.testFlag(sRGB);
9201 bool foundBestFormat = false;
9202 if (m_format == SDR) {
9203 for (int i = 0; i < int(formatCount); ++i) {
9204 bool ok;
9205 if (srgbRequested) {
9206 ok = defaultSrgbColorFormat == formats[i].format;
9207 } else {
9208 ok = defaultColorFormat == formats[i].format;
9209 }
9210 if (ok) {
9211 foundBestFormat = true;
9212 colorFormat = formats[i].format;
9213 colorSpace = formats[i].colorSpace;
9214 break;
9215 }
9216 }
9217 }
9218
9219 // Otherwise pick one that fits our requirements:
9220 if (!foundBestFormat && (m_format != SDR || srgbRequested)) {
9221 for (int i = 0; i < int(formatCount); ++i) {
9222 bool ok = false;
9223 if (m_format != SDR) {
9224 ok = hdrFormatMatchesVkSurfaceFormat(m_format, formats[i]);
9225 } else if (srgbRequested) {
9226 ok = isSrgbFormat(formats[i].format);
9227 }
9228 if (ok) {
9229 colorFormat = formats[i].format;
9230 colorSpace = formats[i].colorSpace;
9231 break;
9232 }
9233 }
9234 }
9235
9236 // Although this should be rare, warn when we fail to select a suitable format
9237 if (m_format != SDR) {
9238 VkSurfaceFormatKHR format{};
9239 format.format = colorFormat;
9240 format.colorSpace = colorSpace;
9241 if (!hdrFormatMatchesVkSurfaceFormat(m_format, format)) {
9242 qWarning("Failed to select a suitable VkFormat for HDR, using format %d as fallback",
9243 colorFormat);
9244 }
9245 } else {
9246 if (srgbRequested && !isSrgbFormat(colorFormat)) {
9247 qWarning("Failed to select a suitable VkFormat for sRGB, using format %d as fallback",
9248 colorFormat);
9249 }
9250 }
9251
9252#if QT_CONFIG(wayland)
9253 // On Wayland, only one color management surface can be created at a time without
9254 // triggering a protocol error, and we create one ourselves in some situations.
9255 // To avoid this problem, use VK_COLOR_SPACE_PASS_THROUGH_EXT when supported,
9256 // so that the driver doesn't create a color management surface as well.
9257 const bool hasPassThrough =
9258 std::any_of(formats.begin(), formats.end(), [this](const VkSurfaceFormatKHR &fmt) {
9259 return fmt.format == colorFormat
9260 && fmt.colorSpace == VK_COLOR_SPACE_PASS_THROUGH_EXT;
9261 });
9262 if (hasPassThrough) {
9263 colorSpace = VK_COLOR_SPACE_PASS_THROUGH_EXT;
9264 }
9265#endif
9266
9267 samples = rhiD->effectiveSampleCountBits(m_sampleCount);
9268
9269 quint32 presModeCount = 0;
9270 rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr);
9271 supportedPresentationModes.resize(presModeCount);
9272 rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount,
9273 supportedPresentationModes.data());
9274
9275 return true;
9276}
9277
9279{
9280 QRHI_RES_RHI(QRhiVulkan);
9281 const bool needsRegistration = !window || window != m_window;
9282
9283 // Can be called multiple times due to window resizes - that is not the
9284 // same as a simple destroy+create (as with other resources). Thus no
9285 // destroy() here. See recreateSwapChain().
9286
9287 // except if the window actually changes
9288 if (window && window != m_window)
9289 destroy();
9290
9291 window = m_window;
9292 m_currentPixelSize = surfacePixelSize();
9293 pixelSize = m_currentPixelSize;
9294
9295 if (!rhiD->recreateSwapChain(this)) {
9296 qWarning("Failed to create new swapchain");
9297 return false;
9298 }
9299
9300 if (needsRegistration || !rhiD->swapchains.contains(this))
9301 rhiD->swapchains.insert(this);
9302
9303 if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
9304 qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
9305 m_depthStencil->sampleCount(), m_sampleCount);
9306 }
9307 if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
9308 if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
9309 m_depthStencil->setPixelSize(pixelSize);
9310 if (!m_depthStencil->create())
9311 qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
9312 pixelSize.width(), pixelSize.height());
9313 } else {
9314 qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
9315 m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
9316 pixelSize.width(), pixelSize.height());
9317 }
9318 }
9319
9320 if (!m_renderPassDesc)
9321 qWarning("QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
9322
9323 rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
9324 rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
9325 Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp);
9326
9327 rtWrapper.d.pixelSize = pixelSize;
9328 rtWrapper.d.dpr = float(window->devicePixelRatio());
9329 rtWrapper.d.sampleCount = samples;
9330 rtWrapper.d.colorAttCount = 1;
9331 if (m_depthStencil) {
9332 rtWrapper.d.dsAttCount = 1;
9333 ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
9334 } else {
9335 rtWrapper.d.dsAttCount = 0;
9336 ds = nullptr;
9337 }
9338 rtWrapper.d.dsResolveAttCount = 0;
9339 if (samples > VK_SAMPLE_COUNT_1_BIT)
9340 rtWrapper.d.resolveAttCount = 1;
9341 else
9342 rtWrapper.d.resolveAttCount = 0;
9343
9344 if (shadingRateMapView)
9345 rtWrapper.d.shadingRateAttCount = 1;
9346 else
9347 rtWrapper.d.shadingRateAttCount = 0;
9348
9349 for (int i = 0; i < bufferCount; ++i) {
9350 QVkSwapChain::ImageResources &image(imageRes[i]);
9351 // color, ds, resolve, shading rate
9352 QVarLengthArray<VkImageView, 4> views;
9353 views.append(samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView);
9354 if (ds)
9355 views.append(ds->imageView);
9356 if (samples > VK_SAMPLE_COUNT_1_BIT)
9357 views.append(image.imageView);
9358 if (shadingRateMapView)
9359 views.append(shadingRateMapView);
9360
9361 VkFramebufferCreateInfo fbInfo = {};
9362 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
9363 fbInfo.renderPass = rtWrapper.d.rp->rp;
9364 fbInfo.attachmentCount = uint32_t(views.count());
9365 fbInfo.pAttachments = views.constData();
9366 fbInfo.width = uint32_t(pixelSize.width());
9367 fbInfo.height = uint32_t(pixelSize.height());
9368 fbInfo.layers = 1;
9369
9370 VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb);
9371 if (err != VK_SUCCESS) {
9372 qWarning("Failed to create framebuffer: %d", err);
9373 return false;
9374 }
9375 }
9376
9377 if (stereo) {
9378 rtWrapperRight.setRenderPassDescriptor(
9379 m_renderPassDesc); // for the public getter in QRhiRenderTarget
9380 rtWrapperRight.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
9381 Q_ASSERT(rtWrapperRight.d.rp && rtWrapperRight.d.rp->rp);
9382
9383 rtWrapperRight.d.pixelSize = pixelSize;
9384 rtWrapperRight.d.dpr = float(window->devicePixelRatio());
9385 rtWrapperRight.d.sampleCount = samples;
9386 rtWrapperRight.d.colorAttCount = 1;
9387 if (m_depthStencil) {
9388 rtWrapperRight.d.dsAttCount = 1;
9389 ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
9390 } else {
9391 rtWrapperRight.d.dsAttCount = 0;
9392 ds = nullptr;
9393 }
9394 rtWrapperRight.d.dsResolveAttCount = 0;
9395 if (samples > VK_SAMPLE_COUNT_1_BIT)
9396 rtWrapperRight.d.resolveAttCount = 1;
9397 else
9398 rtWrapperRight.d.resolveAttCount = 0;
9399
9400 for (int i = 0; i < bufferCount; ++i) {
9401 QVkSwapChain::ImageResources &image(imageRes[i + bufferCount]);
9402 // color, ds, resolve, shading rate
9403 QVarLengthArray<VkImageView, 4> views;
9404 views.append(samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView);
9405 if (ds)
9406 views.append(ds->imageView);
9407 if (samples > VK_SAMPLE_COUNT_1_BIT)
9408 views.append(image.imageView);
9409 if (shadingRateMapView)
9410 views.append(shadingRateMapView);
9411
9412 VkFramebufferCreateInfo fbInfo = {};
9413 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
9414 fbInfo.renderPass = rtWrapperRight.d.rp->rp;
9415 fbInfo.attachmentCount = uint32_t(views.count());
9416 fbInfo.pAttachments = views.constData();
9417 fbInfo.width = uint32_t(pixelSize.width());
9418 fbInfo.height = uint32_t(pixelSize.height());
9419 fbInfo.layers = 1;
9420
9421 VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb);
9422 if (err != VK_SUCCESS) {
9423 qWarning("Failed to create framebuffer: %d", err);
9424 return false;
9425 }
9426 }
9427 }
9428
9429 frameCount = 0;
9430
9431 if (needsRegistration)
9432 rhiD->registerResource(this);
9433
9434 return true;
9435}
9436
9437QT_END_NAMESPACE
const char * constData() const
Definition qrhi_p.h:372
bool isEmpty() const
Definition qrhi.cpp:11979
void registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage, const UsageState &state)
Definition qrhi.cpp:11996
void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, const UsageState &state)
Definition qrhi.cpp:12036
static QRhiResourceUpdateBatchPrivate * get(QRhiResourceUpdateBatch *b)
Definition qrhi_p.h:597
void recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker)
QVulkanFunctions * f
VkCommandBuffer startSecondaryCommandBuffer(QVkRenderTargetData *rtD=nullptr)
QRhiSwapChain * createSwapChain() override
void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override
int resourceLimit(QRhi::ResourceLimit limit) const override
bool isDeviceLost() const override
void prepareUploadSubres(QVkTexture *texD, int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc, size_t *curOfs, void *mp, BufferImageCopyList *copyInfos)
void executeDeferredReleases(bool forced=false)
uint32_t chooseTransientImageMemType(VkImage img, uint32_t startIndex)
QRhi::FrameOpResult finish() override
QRhiTextureRenderTarget * createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override
bool createTransientImage(VkFormat format, const QSize &pixelSize, VkImageUsageFlags usage, VkImageAspectFlags aspectMask, VkSampleCountFlagBits samples, VkDeviceMemory *mem, VkImage *images, VkImageView *views, int count)
void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override
void releaseCachedResources() override
QVkSwapChain * currentSwapChain
bool importedDevice
QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importParams=nullptr)
void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override
void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override
void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override
QList< int > supportedSampleCounts() const override
void destroy() override
QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override
void updateShaderResourceBindings(QRhiShaderResourceBindings *srb)
double elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok)
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override
bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, const QRhiColorAttachment *colorAttachmentsBegin, const QRhiColorAttachment *colorAttachmentsEnd, bool preserveColor, bool preserveDs, bool storeDs, QRhiRenderBuffer *depthStencilBuffer, QRhiTexture *depthTexture, QRhiTexture *depthResolveTexture, QRhiShadingRateMap *shadingRateMap)
void trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot, VkAccessFlags access, VkPipelineStageFlags stage)
QRhi::FrameOpResult waitCommandCompletion(int frameSlot)
void endExternal(QRhiCommandBuffer *cb) override
QWindow * maybeWindow
void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override
bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex)
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override
void drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, quint32 drawCount, quint32 stride) override
VkResult createDescriptorPool(VkDescriptorPool *pool)
void prepareNewFrame(QRhiCommandBuffer *cb)
void subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, VkAccessFlags srcAccess, VkAccessFlags dstAccess, VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, int startLayer, int layerCount, int startLevel, int levelCount)
QList< QSize > supportedShadingRates(int sampleCount) const override
void printExtraErrorInfo(VkResult err)
void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override
QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override
QRhi::FrameOpResult startPrimaryCommandBuffer(VkCommandBuffer *cb)
double lastCompletedGpuTime(QRhiCommandBuffer *cb) override
void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, QVkTexture *texD, QRhiPassResourceTracker::TextureAccess access, QRhiPassResourceTracker::TextureStage stage)
bool releaseCachedResourcesCalledBeforeFrameStart
QRhiGraphicsPipeline * createGraphicsPipeline() override
QRhiComputePipeline * createComputePipeline() override
void setAllocationName(QVkAlloc allocation, const QByteArray &name, int slot=-1)
QRhiTexture * createTexture(QRhiTexture::Format format, const QSize &pixelSize, int depth, int arraySize, int sampleCount, QRhiTexture::Flags flags) override
void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override
const QRhiNativeHandles * nativeHandles(QRhiCommandBuffer *cb) override
bool recreateSwapChain(QRhiSwapChain *swapChain)
void printDeviceLossErrorInfo() const
bool ensurePipelineCache(const void *initialData=nullptr, size_t initialDataSize=0)
void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override
void depthStencilExplicitBarrier(QVkCommandBuffer *cbD, QVkRenderBuffer *rbD)
void trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage)
VkShaderModule createShader(const QByteArray &spirv)
void enqueueTransitionPassResources(QVkCommandBuffer *cbD)
void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override
bool create(QRhi::Flags flags) override
QVulkanDeviceFunctions * df
void setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot=-1)
bool isFeatureSupported(QRhi::Feature feature) const override
void recordPrimaryCommandBuffer(QVkCommandBuffer *cbD)
bool isYUpInFramebuffer() const override
void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) override
void debugMarkEnd(QRhiCommandBuffer *cb) override
QRhiSampler * createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) override
void releaseSwapChainResources(QRhiSwapChain *swapChain)
void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) override
VkDeviceSize subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override
void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, QVkBuffer *bufD, int slot, QRhiPassResourceTracker::BufferAccess access, QRhiPassResourceTracker::BufferStage stage)
VkFormat optimalDepthStencilFormat()
QRhiStats statistics() override
void setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) override
void drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset, quint32 drawCount, quint32 stride) override
void activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD)
void executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot)
void setDefaultScissor(QVkCommandBuffer *cbD)
bool isYUpInNDC() const override
const QRhiNativeHandles * nativeHandles() override
QRhiShadingRateMap * createShadingRateMap() override
void setPipelineCacheData(const QByteArray &data) override
void setVertexInput(QRhiCommandBuffer *cb, int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) override
QRhiShaderResourceBindings * createShaderResourceBindings() override
void finishActiveReadbacks(bool forced=false)
void ensureCommandPoolForNewFrame()
QByteArray pipelineCacheData() override
void endAndEnqueueSecondaryCommandBuffer(VkCommandBuffer cb, QVkCommandBuffer *cbD)
void beginPass(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override
void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override
bool isClipDepthZeroToOne() const override
void enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
void setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override
QVkAllocator allocator
bool importedAllocator
QMatrix4x4 clipSpaceCorrMatrix() const override
int ubufAlignment() const override
QRhiDriverInfo driverInfo() const override
void beginExternal(QRhiCommandBuffer *cb) override
void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override
bool createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat, QRhiShadingRateMap *shadingRateMap)
QRhi::FrameOpResult endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence, VkSemaphore *waitSem, VkSemaphore *signalSem)
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override
VkSampleCountFlagBits effectiveSampleCountBits(int sampleCount)
bool makeThreadLocalNativeContextCurrent() override
QRhiDriverInfo info() const override
Combined button and popup list for selecting options.
@ UnBounded
Definition qrhi_p.h:285
@ Bounded
Definition qrhi_p.h:286
#define QRHI_RES_RHI(t)
Definition qrhi_p.h:31
#define QRHI_RES(t, x)
Definition qrhi_p.h:30
static VkPolygonMode toVkPolygonMode(QRhiGraphicsPipeline::PolygonMode mode)
static VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c)
static bool accessIsWrite(VkAccessFlags access)
static VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op)
static QVulkanInstance * globalVulkanInstance
static VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage)
static QRhiTexture::Format swapchainReadbackTextureFormat(VkFormat format, QRhiTexture::Flags *flags)
static VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op)
static bool qvk_debug_filter(QVulkanInstance::DebugMessageSeverityFlags severity, QVulkanInstance::DebugMessageTypeFlags type, const void *callbackData)
static QVkBuffer::UsageState toVkBufferUsageState(QRhiPassResourceTracker::UsageState usage)
static bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b)
static bool isSrgbFormat(VkFormat format)
static VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::TextureStage stage)
static VkAccessFlags toVkAccess(QRhiPassResourceTracker::BufferAccess access)
static VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess access)
static VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format)
static QVkTexture::UsageState toVkTextureUsageState(QRhiPassResourceTracker::UsageState usage)
static VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::BufferStage stage)
static QRhiDriverInfo::DeviceType toRhiDeviceType(VkPhysicalDeviceType type)
static QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkBuffer::UsageState &bufUsage)
static VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c)
static VkFilter toVkFilter(QRhiSampler::Filter f)
static VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t)
static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator)
static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator)
static VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess access)
static VmaAllocator toVmaAllocator(QVkAllocator a)
static VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m)
static constexpr bool isDepthTextureFormat(QRhiTexture::Format format)
static void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src)
VkSampleCountFlagBits mask
static VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage)
static constexpr VkImageAspectFlags aspectMaskForTextureFormat(QRhiTexture::Format format)
static VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBinding::Data *b)
static VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
static void fillDriverInfo(QRhiDriverInfo *info, const VkPhysicalDeviceProperties &physDevProperties)
static VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op)
static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
static VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f)
void qrhivk_accumulateComputeResource(T *writtenResources, QRhiResource *resource, QRhiShaderResourceBinding::Type bindingType, int loadTypeVal, int storeTypeVal, int loadStoreTypeVal)
static VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
static VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op)
static void fillRenderPassCreateInfo(VkRenderPassCreateInfo *rpInfo, VkSubpassDescription *subpassDesc, QVkRenderPassDescriptor *rpD)
static VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f)
static bool hdrFormatMatchesVkSurfaceFormat(QRhiSwapChain::Format f, const VkSurfaceFormatKHR &s)
static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
int count
static constexpr bool isStencilTextureFormat(QRhiTexture::Format format)
static QVkRenderTargetData * maybeRenderTargetData(QVkCommandBuffer *cbD)
static QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkTexture::UsageState &texUsage)
static VmaAllocation toVmaAllocation(QVkAlloc a)
static void addToChain(T *head, void *entry)
static VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type)
void * QVkAllocator
static const int QVK_MAX_ACTIVE_TIMESTAMP_PAIRS
static const int QVK_DESC_SETS_PER_POOL
void * QVkAlloc
static const int QVK_FRAMES_IN_FLIGHT
bool prepare(VkRenderPassCreateInfo *rpInfo, int multiViewCount, bool multiViewCap)
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1867
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1557
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]
QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]
void endFullDynamicBufferUpdateForCurrentFrame() override
To be called when the entire contents of the buffer data has been updated in the memory block returne...
QRhiBuffer::NativeBuffer nativeBuffer() override
bool create() override
Creates the corresponding native graphics resources.
char * beginFullDynamicBufferUpdateForCurrentFrame() override
int lastActiveFrameSlot
QVkCommandBuffer(QRhiImplementation *rhi)
const QRhiNativeHandles * nativeHandles()
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
PassType recordingPass
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QVkComputePipeline(QRhiImplementation *rhi)
bool create() override
QVkGraphicsPipeline(QRhiImplementation *rhi)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
bool create() override
Creates the corresponding native graphics resources.
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, Flags flags, QRhiTexture::Format backingFormatHint)
QRhiTexture::Format backingFormat() const override
bool create() override
Creates the corresponding native graphics resources.
QVkTexture * backingTexture
const QRhiNativeHandles * nativeHandles() override
QRhiRenderPassDescriptor * newCompatibleRenderPassDescriptor() const override
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QVector< quint32 > serializedFormat() const override
QVkRenderPassDescriptor(QRhiImplementation *rhi)
bool isCompatible(const QRhiRenderPassDescriptor *other) const override
QVkRenderPassDescriptor * rp
static const int MAX_COLOR_ATTACHMENTS
int lastActiveFrameSlot
bool create() override
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, AddressMode u, AddressMode v, AddressMode w)
void updateResources(UpdateFlags flags) override
QVkShaderResourceBindings(QRhiImplementation *rhi)
bool create() override
Creates the corresponding resource binding set.
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
bool createFrom(QRhiTexture *src) override
Sets up the shading rate map to use the texture src as the image containing the per-tile shading rate...
QVkShadingRateMap(QRhiImplementation *rhi)
QVkTexture * texture
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QSize pixelSize() const override
int sampleCount() const override
float devicePixelRatio() const override
QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
bool createOrResize() override
Creates the swapchain if not already done and resizes the swapchain buffers to match the current size...
QRhiRenderTarget * currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override
bool isFormatSupported(Format f) override
QRhiRenderPassDescriptor * newCompatibleRenderPassDescriptor() override
bool supportsReadback
QVkSwapChain(QRhiImplementation *rhi)
QVkRenderBuffer * ds
QSize surfacePixelSize() override
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QRhiRenderTarget * currentFrameRenderTarget() override
bool ensureSurface()
QRhiSwapChainHdrInfo hdrInfo() override
\variable QRhiSwapChainHdrInfo::limitsType
QRhiCommandBuffer * currentFrameCommandBuffer() override
QVkTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags)
float devicePixelRatio() const override
bool create() override
Creates the corresponding native graphics resources.
int sampleCount() const override
QSize pixelSize() const override
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
QRhiRenderPassDescriptor * newCompatibleRenderPassDescriptor() override
bool create() override
Creates the corresponding native graphics resources.
void destroy() override
Releases (or requests deferred releasing of) the underlying native graphics resources.
bool finishCreate()
VkImageView perLevelImageViewForLoadStore(int level)
int lastActiveFrameSlot
QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]
bool createFrom(NativeTexture src) override
Similar to create(), except that no new native textures are created.
QVkAlloc imageAlloc
void setNativeLayout(int layout) override
With some graphics APIs, such as Vulkan, integrating custom rendering code that uses the graphics API...
QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, int arraySize, int sampleCount, Flags flags)
NativeTexture nativeTexture() override
bool prepareCreate(QSize *adjustedSize=nullptr)