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