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

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