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

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