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

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