Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qvulkanwindow.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 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
5#include "qvulkanfunctions.h"
7#include <QLoggingCategory>
8#include <QTimer>
9#include <QThread>
10#include <QCoreApplication>
11#include <qevent.h>
12
14
15/*!
16 \class QVulkanWindow
17 \inmodule QtGui
18 \since 5.10
19 \brief The QVulkanWindow class is a convenience subclass of QWindow to perform Vulkan rendering.
20
21 QVulkanWindow is a Vulkan-capable QWindow that manages a Vulkan device, a
22 graphics queue, a command pool and buffer, a depth-stencil image and a
23 double-buffered FIFO swapchain, while taking care of correct behavior when it
24 comes to events like resize, special situations like not having a device
25 queue supporting both graphics and presentation, device lost scenarios, and
26 additional functionality like reading the rendered content back. Conceptually
27 it is the counterpart of QOpenGLWindow in the Vulkan world.
28
29 \note QVulkanWindow does not always eliminate the need to implement a fully
30 custom QWindow subclass as it will not necessarily be sufficient in advanced
31 use cases.
32
33 QVulkanWindow can be embedded into QWidget-based user interfaces via
34 QWidget::createWindowContainer(). This approach has a number of limitations,
35 however. Make sure to study the
36 \l{QWidget::createWindowContainer()}{documentation} first.
37
38 A typical application using QVulkanWindow may look like the following:
39
40 \snippet code/src_gui_vulkan_qvulkanwindow.cpp 0
41
42 As it can be seen in the example, the main patterns in QVulkanWindow usage are:
43
44 \list
45
46 \li The QVulkanInstance is associated via QWindow::setVulkanInstance(). It is
47 then retrievable via QWindow::vulkanInstance() from everywhere, on any
48 thread.
49
50 \li Similarly to QVulkanInstance, device extensions can be queried via
51 supportedDeviceExtensions() before the actual initialization. Requesting an
52 extension to be enabled is done via setDeviceExtensions(). Such calls must be
53 made before the window becomes visible, that is, before calling show() or
54 similar functions. Unsupported extension requests are gracefully ignored.
55
56 \li The renderer is implemented in a QVulkanWindowRenderer subclass, an
57 instance of which is created in the createRenderer() factory function.
58
59 \li The core Vulkan commands are exposed via the QVulkanFunctions object,
60 retrievable by calling QVulkanInstance::functions(). Device level functions
61 are available after creating a VkDevice by calling
62 QVulkanInstance::deviceFunctions().
63
64 \li The building of the draw calls for the next frame happens in
65 QVulkanWindowRenderer::startNextFrame(). The implementation is expected to
66 add commands to the command buffer returned from currentCommandBuffer().
67 Returning from the function does not indicate that the commands are ready for
68 submission. Rather, an explicit call to frameReady() is required. This allows
69 asynchronous generation of commands, possibly on multiple threads. Simple
70 implementations will simply call frameReady() at the end of their
71 QVulkanWindowRenderer::startNextFrame().
72
73 \li The basic Vulkan resources (physical device, graphics queue, a command
74 pool, the window's main command buffer, image formats, etc.) are exposed on
75 the QVulkanWindow via lightweight getter functions. Some of these are for
76 convenience only, and applications are always free to query, create and
77 manage additional resources directly via the Vulkan API.
78
79 \li The renderer lives in the gui/main thread, like the window itself. This
80 thread is then throttled to the presentation rate, similarly to how OpenGL
81 with a swap interval of 1 would behave. However, the renderer implementation
82 is free to utilize multiple threads in any way it sees fit. The accessors
83 like vulkanInstance(), currentCommandBuffer(), etc. can be called from any
84 thread. The submission of the main command buffer, the queueing of present,
85 and the building of the next frame do not start until frameReady() is
86 invoked on the gui/main thread.
87
88 \li When the window is made visible, the content is updated automatically.
89 Further updates can be requested by calling QWindow::requestUpdate(). To
90 render continuously, call requestUpdate() after frameReady().
91
92 \endlist
93
94 For troubleshooting, enable the logging category \c{qt.vulkan}. Critical
95 errors are printed via qWarning() automatically.
96
97 \section1 Coordinate system differences between OpenGL and Vulkan
98
99 There are two notable differences to be aware of: First, with Vulkan Y points
100 down the screen in clip space, while OpenGL uses an upwards pointing Y axis.
101 Second, the standard OpenGL projection matrix assume a near and far plane
102 values of -1 and 1, while Vulkan prefers 0 and 1.
103
104 In order to help applications migrate from OpenGL-based code without having
105 to flip Y coordinates in the vertex data, and to allow using QMatrix4x4
106 functions like QMatrix4x4::perspective() while keeping the Vulkan viewport's
107 minDepth and maxDepth set to 0 and 1, QVulkanWindow provides a correction
108 matrix retrievable by calling clipCorrectionMatrix().
109
110 \section1 Multisampling
111
112 While disabled by default, multisample antialiasing is fully supported by
113 QVulkanWindow. Additional color buffers and resolving into the swapchain's
114 non-multisample buffers are all managed automatically.
115
116 To query the supported sample counts, call supportedSampleCounts(). When the
117 returned set contains 4, 8, ..., passing one of those values to setSampleCount()
118 requests multisample rendering.
119
120 \note unlike QSurfaceFormat::setSamples(), the list of supported sample
121 counts are exposed to the applications in advance and there is no automatic
122 falling back to lower sample counts in setSampleCount(). If the requested value
123 is not supported, a warning is shown and a no multisampling will be used.
124
125 \section1 Reading images back
126
127 When supportsGrab() returns true, QVulkanWindow can perform readbacks from
128 the color buffer into a QImage. grab() is a slow and inefficient operation,
129 so frequent usage should be avoided. It is nonetheless valuable since it
130 allows applications to take screenshots, or tools and tests to process and
131 verify the output of the GPU rendering.
132
133 \section1 sRGB support
134
135 While many applications will be fine with the default behavior of
136 QVulkanWindow when it comes to swapchain image formats,
137 setPreferredColorFormats() allows requesting a pre-defined format. This is
138 useful most notably when working in the sRGB color space. Passing a format
139 like \c{VK_FORMAT_B8G8R8A8_SRGB} results in choosing an sRGB format, when
140 available.
141
142 \section1 Validation layers
143
144 During application development it can be extremely valuable to have the
145 Vulkan validation layers enabled. As shown in the example code above, calling
146 QVulkanInstance::setLayers() on the QVulkanInstance before
147 QVulkanInstance::create() enables validation, assuming the Vulkan driver
148 stack in the system contains the necessary layers.
149
150 \note Be aware of platform-specific differences. On desktop platforms
151 installing the \l{https://www.lunarg.com/vulkan-sdk/}{Vulkan SDK} is
152 typically sufficient. However, Android for example requires deploying
153 additional shared libraries together with the application, and also mandates
154 a different list of validation layer names. See
155 \l{https://developer.android.com/ndk/guides/graphics/validation-layer.html}{the
156 Android Vulkan development pages} for more information.
157
158 \note QVulkanWindow does not expose device layers since this functionality
159 has been deprecated since version 1.0.13 of the Vulkan API.
160
161 \section1 Layers, device features, and extensions
162
163 To enable instance layers, call QVulkanInstance::setLayers() before creating
164 the QVulkanInstance. To query what instance layer are available, call
165 QVulkanInstance::supportedLayers().
166
167 To enable device extensions, call setDeviceExtensions() early on when setting
168 up the QVulkanWindow. To query what device extensions are available, call
169 supportedDeviceExtensions().
170
171 Specifying an unsupported layer or extension is handled gracefully: this will
172 not fail instance or device creation, but the layer or extension request is
173 rather ignored.
174
175 When it comes to device features, QVulkanWindow enables all Vulkan 1.0
176 features that are reported as supported from vkGetPhysicalDeviceFeatures().
177 As an exception to this rule, \c robustBufferAccess is never enabled. Use the
178 callback mechanism described below, if enabling that feature is desired.
179
180 This is not always desirable, and may be insufficient with Vulkan 1.1 and
181 higher. Therefore, full control over the VkPhysicalDeviceFeatures used for
182 device creation is possible too by registering a callback function with
183 setEnabledFeaturesModifier(). When set, the callback function is invoked,
184 letting it alter the VkPhysicalDeviceFeatures or VkPhysicalDeviceFeatures2.
185
186 \sa QVulkanInstance, QWindow
187 */
188
189/*!
190 \class QVulkanWindowRenderer
191 \inmodule QtGui
192 \since 5.10
193
194 \brief The QVulkanWindowRenderer class is used to implement the
195 application-specific rendering logic for a QVulkanWindow.
196
197 Applications typically subclass both QVulkanWindow and QVulkanWindowRenderer.
198 The former allows handling events, for example, input, while the latter allows
199 implementing the Vulkan resource management and command buffer building that
200 make up the application's rendering.
201
202 In addition to event handling, the QVulkanWindow subclass is responsible for
203 providing an implementation for QVulkanWindow::createRenderer() as well. This
204 is where the window and renderer get connected. A typical implementation will
205 simply create a new instance of a subclass of QVulkanWindowRenderer.
206 */
207
208/*!
209 Constructs a new QVulkanWindow with the given \a parent.
210
211 The surface type is set to QSurface::VulkanSurface.
212 */
213QVulkanWindow::QVulkanWindow(QWindow *parent)
214 : QWindow(*(new QVulkanWindowPrivate), parent)
215{
216 setSurfaceType(QSurface::VulkanSurface);
217}
218
219/*!
220 Destructor.
221*/
222QVulkanWindow::~QVulkanWindow()
223{
224}
225
226QVulkanWindowPrivate::~QVulkanWindowPrivate()
227{
228 // graphics resource cleanup is already done at this point due to
229 // QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed
230
231 delete renderer;
232}
233
234/*!
235 \enum QVulkanWindow::Flag
236
237 This enum describes the flags that can be passed to setFlags().
238
239 \value PersistentResources Ensures no graphics resources are released when
240 the window becomes unexposed. The default behavior is to release
241 everything, and reinitialize later when becoming visible again.
242 */
243
244/*!
245 Configures the behavior based on the provided \a flags.
246
247 \note This function must be called before the window is made visible or at
248 latest in QVulkanWindowRenderer::preInitResources(), and has no effect if
249 called afterwards.
250 */
251void QVulkanWindow::setFlags(Flags flags)
252{
253 Q_D(QVulkanWindow);
254 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
255 qWarning("QVulkanWindow: Attempted to set flags when already initialized");
256 return;
257 }
258 d->flags = flags;
259}
260
261/*!
262 Return the requested flags.
263 */
264QVulkanWindow::Flags QVulkanWindow::flags() const
265{
266 Q_D(const QVulkanWindow);
267 return d->flags;
268}
269
270/*!
271 Returns the list of properties for the supported physical devices in the system.
272
273 \note This function can be called before making the window visible.
274 */
275QList<VkPhysicalDeviceProperties> QVulkanWindow::availablePhysicalDevices()
276{
277 Q_D(QVulkanWindow);
278 if (!d->physDevs.isEmpty() && !d->physDevProps.isEmpty())
279 return d->physDevProps;
280
281 QVulkanInstance *inst = vulkanInstance();
282 if (!inst) {
283 qWarning("QVulkanWindow: Attempted to call availablePhysicalDevices() without a QVulkanInstance");
284 return d->physDevProps;
285 }
286
287 QVulkanFunctions *f = inst->functions();
288 uint32_t count = 1;
289 VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &count, nullptr);
290 if (err != VK_SUCCESS) {
291 qWarning("QVulkanWindow: Failed to get physical device count: %d", err);
292 return d->physDevProps;
293 }
294
295 qCDebug(lcGuiVk, "%d physical devices", count);
296 if (!count)
297 return d->physDevProps;
298
299 QList<VkPhysicalDevice> devs(count);
300 err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &count, devs.data());
301 if (err != VK_SUCCESS) {
302 qWarning("QVulkanWindow: Failed to enumerate physical devices: %d", err);
303 return d->physDevProps;
304 }
305
306 d->physDevs = devs;
307 d->physDevProps.resize(count);
308 for (uint32_t i = 0; i < count; ++i) {
309 VkPhysicalDeviceProperties *p = &d->physDevProps[i];
310 f->vkGetPhysicalDeviceProperties(d->physDevs.at(i), p);
311 qCDebug(lcGuiVk, "Physical device [%d]: name '%s' version %d.%d.%d", i, p->deviceName,
312 VK_VERSION_MAJOR(p->driverVersion), VK_VERSION_MINOR(p->driverVersion),
313 VK_VERSION_PATCH(p->driverVersion));
314 }
315
316 return d->physDevProps;
317}
318
319/*!
320 Requests the usage of the physical device with index \a idx. The index
321 corresponds to the list returned from availablePhysicalDevices().
322
323 By default the first physical device is used.
324
325 \note This function must be called before the window is made visible or at
326 latest in QVulkanWindowRenderer::preInitResources(), and has no effect if
327 called afterwards.
328 */
329void QVulkanWindow::setPhysicalDeviceIndex(int idx)
330{
331 Q_D(QVulkanWindow);
332 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
333 qWarning("QVulkanWindow: Attempted to set physical device when already initialized");
334 return;
335 }
336 const int count = availablePhysicalDevices().size();
337 if (idx < 0 || idx >= count) {
338 qWarning("QVulkanWindow: Invalid physical device index %d (total physical devices: %d)", idx, count);
339 return;
340 }
341 d->physDevIndex = idx;
342}
343
344/*!
345 Returns the list of the extensions that are supported by logical devices
346 created from the physical device selected by setPhysicalDeviceIndex().
347
348 \note This function can be called before making the window visible.
349 */
350QVulkanInfoVector<QVulkanExtension> QVulkanWindow::supportedDeviceExtensions()
351{
352 Q_D(QVulkanWindow);
353
354 availablePhysicalDevices();
355
356 if (d->physDevs.isEmpty()) {
357 qWarning("QVulkanWindow: No physical devices found");
358 return QVulkanInfoVector<QVulkanExtension>();
359 }
360
361 VkPhysicalDevice physDev = d->physDevs.at(d->physDevIndex);
362 if (d->supportedDevExtensions.contains(physDev))
363 return d->supportedDevExtensions.value(physDev);
364
365 QVulkanFunctions *f = vulkanInstance()->functions();
366 uint32_t count = 0;
367 VkResult err = f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &count, nullptr);
368 if (err == VK_SUCCESS) {
369 QList<VkExtensionProperties> extProps(count);
370 err = f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &count, extProps.data());
371 if (err == VK_SUCCESS) {
372 QVulkanInfoVector<QVulkanExtension> exts;
373 for (const VkExtensionProperties &prop : extProps) {
374 QVulkanExtension ext;
375 ext.name = prop.extensionName;
376 ext.version = prop.specVersion;
377 exts.append(ext);
378 }
379 d->supportedDevExtensions.insert(physDev, exts);
380 qCDebug(lcGuiVk) << "Supported device extensions:" << exts;
381 return exts;
382 }
383 }
384
385 qWarning("QVulkanWindow: Failed to query device extension count: %d", err);
386 return QVulkanInfoVector<QVulkanExtension>();
387}
388
389/*!
390 Sets the list of device \a extensions to be enabled.
391
392 Unsupported extensions are ignored.
393
394 The swapchain extension will always be added automatically, no need to
395 include it in this list.
396
397 \note This function must be called before the window is made visible or at
398 latest in QVulkanWindowRenderer::preInitResources(), and has no effect if
399 called afterwards.
400 */
401void QVulkanWindow::setDeviceExtensions(const QByteArrayList &extensions)
402{
403 Q_D(QVulkanWindow);
404 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
405 qWarning("QVulkanWindow: Attempted to set device extensions when already initialized");
406 return;
407 }
408 d->requestedDevExtensions = extensions;
409}
410
411/*!
412 Sets the preferred \a formats of the swapchain.
413
414 By default no application-preferred format is set. In this case the
415 surface's preferred format will be used or, in absence of that,
416 \c{VK_FORMAT_B8G8R8A8_UNORM}.
417
418 The list in \a formats is ordered. If the first format is not supported,
419 the second will be considered, and so on. When no formats in the list are
420 supported, the behavior is the same as in the default case.
421
422 To query the actual format after initialization, call colorFormat().
423
424 \note This function must be called before the window is made visible or at
425 latest in QVulkanWindowRenderer::preInitResources(), and has no effect if
426 called afterwards.
427
428 \note Reimplementing QVulkanWindowRenderer::preInitResources() allows
429 dynamically examining the list of supported formats, should that be
430 desired. There the surface is retrievable via
431 QVulkanInstace::surfaceForWindow(), while this function can still safely be
432 called to affect the later stages of initialization.
433
434 \sa colorFormat()
435 */
436void QVulkanWindow::setPreferredColorFormats(const QList<VkFormat> &formats)
437{
438 Q_D(QVulkanWindow);
439 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
440 qWarning("QVulkanWindow: Attempted to set preferred color format when already initialized");
441 return;
442 }
443 d->requestedColorFormats = formats;
444}
445
446static struct {
448 int count;
449} q_vk_sampleCounts[] = {
450 // keep this sorted by 'count'
451 { VK_SAMPLE_COUNT_1_BIT, 1 },
452 { VK_SAMPLE_COUNT_2_BIT, 2 },
453 { VK_SAMPLE_COUNT_4_BIT, 4 },
454 { VK_SAMPLE_COUNT_8_BIT, 8 },
455 { VK_SAMPLE_COUNT_16_BIT, 16 },
456 { VK_SAMPLE_COUNT_32_BIT, 32 },
457 { VK_SAMPLE_COUNT_64_BIT, 64 }
459
460/*!
461 Returns the set of supported sample counts when using the physical device
462 selected by setPhysicalDeviceIndex(), as a sorted list.
463
464 By default QVulkanWindow uses a sample count of 1. By calling setSampleCount()
465 with a different value (2, 4, 8, ...) from the set returned by this
466 function, multisample anti-aliasing can be requested.
467
468 \note This function can be called before making the window visible.
469
470 \sa setSampleCount()
471 */
472QList<int> QVulkanWindow::supportedSampleCounts()
473{
474 Q_D(const QVulkanWindow);
475 QList<int> result;
476
477 availablePhysicalDevices();
478
479 if (d->physDevs.isEmpty()) {
480 qWarning("QVulkanWindow: No physical devices found");
481 return result;
482 }
483
484 const VkPhysicalDeviceLimits *limits = &d->physDevProps[d->physDevIndex].limits;
485 VkSampleCountFlags color = limits->framebufferColorSampleCounts;
486 VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
487 VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
488
489 for (const auto &qvk_sampleCount : q_vk_sampleCounts) {
490 if ((color & qvk_sampleCount.mask)
491 && (depth & qvk_sampleCount.mask)
492 && (stencil & qvk_sampleCount.mask))
493 {
494 result.append(qvk_sampleCount.count);
495 }
496 }
497
498 return result;
499}
500
501/*!
502 Requests multisample antialiasing with the given \a sampleCount. The valid
503 values are 1, 2, 4, 8, ... up until the maximum value supported by the
504 physical device.
505
506 When the sample count is greater than 1, QVulkanWindow will create a
507 multisample color buffer instead of simply targeting the swapchain's
508 images. The rendering in the multisample buffer will get resolved into the
509 non-multisample buffers at the end of each frame.
510
511 To examine the list of supported sample counts, call supportedSampleCounts().
512
513 When setting up the rendering pipeline, call sampleCountFlagBits() to query the
514 active sample count as a \c VkSampleCountFlagBits value.
515
516 \note This function must be called before the window is made visible or at
517 latest in QVulkanWindowRenderer::preInitResources(), and has no effect if
518 called afterwards.
519
520 \sa supportedSampleCounts(), sampleCountFlagBits()
521 */
522void QVulkanWindow::setSampleCount(int sampleCount)
523{
524 Q_D(QVulkanWindow);
525 if (d->status != QVulkanWindowPrivate::StatusUninitialized) {
526 qWarning("QVulkanWindow: Attempted to set sample count when already initialized");
527 return;
528 }
529
530 // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
531 sampleCount = qBound(1, sampleCount, 64);
532
533 if (!supportedSampleCounts().contains(sampleCount)) {
534 qWarning("QVulkanWindow: Attempted to set unsupported sample count %d", sampleCount);
535 return;
536 }
537
538 for (const auto &qvk_sampleCount : q_vk_sampleCounts) {
539 if (qvk_sampleCount.count == sampleCount) {
540 d->sampleCount = qvk_sampleCount.mask;
541 return;
542 }
543 }
544
545 Q_UNREACHABLE();
546}
547
548void QVulkanWindowPrivate::init()
549{
550 Q_Q(QVulkanWindow);
551 Q_ASSERT(status == StatusUninitialized);
552
553 qCDebug(lcGuiVk, "QVulkanWindow init");
554
555 inst = q->vulkanInstance();
556 if (!inst) {
557 qWarning("QVulkanWindow: Attempted to initialize without a QVulkanInstance");
558 // This is a simple user error, recheck on the next expose instead of
559 // going into the permanent failure state.
560 status = StatusFailRetry;
561 return;
562 }
563
564 if (!renderer)
565 renderer = q->createRenderer();
566
567 surface = QVulkanInstance::surfaceForWindow(q);
568 if (surface == VK_NULL_HANDLE) {
569 qWarning("QVulkanWindow: Failed to retrieve Vulkan surface for window");
570 status = StatusFailRetry;
571 return;
572 }
573
574 q->availablePhysicalDevices();
575
576 if (physDevs.isEmpty()) {
577 qWarning("QVulkanWindow: No physical devices found");
578 status = StatusFail;
579 return;
580 }
581
582 if (physDevIndex < 0 || physDevIndex >= physDevs.size()) {
583 qWarning("QVulkanWindow: Invalid physical device index; defaulting to 0");
584 physDevIndex = 0;
585 }
586 qCDebug(lcGuiVk, "Using physical device [%d]", physDevIndex);
587
588 // Give a last chance to do decisions based on the physical device and the surface.
589 if (renderer)
590 renderer->preInitResources();
591
592 VkPhysicalDevice physDev = physDevs.at(physDevIndex);
593 QVulkanFunctions *f = inst->functions();
594
595 uint32_t queueCount = 0;
596 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
597 QList<VkQueueFamilyProperties> queueFamilyProps(queueCount);
598 f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
599 gfxQueueFamilyIdx = uint32_t(-1);
600 presQueueFamilyIdx = uint32_t(-1);
601 for (int i = 0; i < queueFamilyProps.size(); ++i) {
602 const bool supportsPresent = inst->supportsPresent(physDev, i, q);
603 qCDebug(lcGuiVk, "queue family %d: flags=0x%x count=%d supportsPresent=%d", i,
604 queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount, supportsPresent);
605 if (gfxQueueFamilyIdx == uint32_t(-1)
606 && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
607 && supportsPresent)
608 gfxQueueFamilyIdx = i;
609 }
610 if (gfxQueueFamilyIdx != uint32_t(-1)) {
611 presQueueFamilyIdx = gfxQueueFamilyIdx;
612 } else {
613 qCDebug(lcGuiVk, "No queue with graphics+present; trying separate queues");
614 for (int i = 0; i < queueFamilyProps.size(); ++i) {
615 if (gfxQueueFamilyIdx == uint32_t(-1) && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
616 gfxQueueFamilyIdx = i;
617 if (presQueueFamilyIdx == uint32_t(-1) && inst->supportsPresent(physDev, i, q))
618 presQueueFamilyIdx = i;
619 }
620 }
621 if (gfxQueueFamilyIdx == uint32_t(-1)) {
622 qWarning("QVulkanWindow: No graphics queue family found");
623 status = StatusFail;
624 return;
625 }
626 if (presQueueFamilyIdx == uint32_t(-1)) {
627 qWarning("QVulkanWindow: No present queue family found");
628 status = StatusFail;
629 return;
630 }
631#ifdef QT_DEBUG
632 // allow testing the separate present queue case in debug builds on AMD cards
633 if (qEnvironmentVariableIsSet("QT_VK_PRESENT_QUEUE_INDEX"))
634 presQueueFamilyIdx = qEnvironmentVariableIntValue("QT_VK_PRESENT_QUEUE_INDEX");
635#endif
636 qCDebug(lcGuiVk, "Using queue families: graphics = %u present = %u", gfxQueueFamilyIdx, presQueueFamilyIdx);
637
638 QList<VkDeviceQueueCreateInfo> queueInfo;
639 queueInfo.reserve(2);
640 const float prio[] = { 0 };
641 VkDeviceQueueCreateInfo addQueueInfo;
642 memset(&addQueueInfo, 0, sizeof(addQueueInfo));
643 addQueueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
644 addQueueInfo.queueFamilyIndex = gfxQueueFamilyIdx;
645 addQueueInfo.queueCount = 1;
646 addQueueInfo.pQueuePriorities = prio;
647 queueInfo.append(addQueueInfo);
648 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
649 addQueueInfo.queueFamilyIndex = presQueueFamilyIdx;
650 addQueueInfo.queueCount = 1;
651 addQueueInfo.pQueuePriorities = prio;
652 queueInfo.append(addQueueInfo);
653 }
654 if (queueCreateInfoModifier) {
655 queueCreateInfoModifier(queueFamilyProps.constData(), queueCount, queueInfo);
656 bool foundGfxQueue = false;
657 bool foundPresQueue = false;
658 for (const VkDeviceQueueCreateInfo& createInfo : std::as_const(queueInfo)) {
659 foundGfxQueue |= createInfo.queueFamilyIndex == gfxQueueFamilyIdx;
660 foundPresQueue |= createInfo.queueFamilyIndex == presQueueFamilyIdx;
661 }
662 if (!foundGfxQueue) {
663 qWarning("QVulkanWindow: Graphics queue missing after call to queueCreateInfoModifier");
664 status = StatusFail;
665 return;
666 }
667 if (!foundPresQueue) {
668 qWarning("QVulkanWindow: Present queue missing after call to queueCreateInfoModifier");
669 status = StatusFail;
670 return;
671 }
672 }
673
674 // Filter out unsupported extensions in order to keep symmetry
675 // with how QVulkanInstance behaves. Add the swapchain extension.
676 QList<const char *> devExts;
677 QVulkanInfoVector<QVulkanExtension> supportedExtensions = q->supportedDeviceExtensions();
678 QByteArrayList reqExts = requestedDevExtensions;
679 reqExts.append("VK_KHR_swapchain");
680
681 QByteArray envExts = qgetenv("QT_VULKAN_DEVICE_EXTENSIONS");
682 if (!envExts.isEmpty()) {
683 QByteArrayList envExtList = envExts.split(';');
684 for (auto ext : reqExts)
685 envExtList.removeAll(ext);
686 reqExts.append(envExtList);
687 }
688
689 for (const QByteArray &ext : reqExts) {
690 if (supportedExtensions.contains(ext))
691 devExts.append(ext.constData());
692 }
693 qCDebug(lcGuiVk) << "Enabling device extensions:" << devExts;
694
695 VkDeviceCreateInfo devInfo;
696 memset(&devInfo, 0, sizeof(devInfo));
697 devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
698 devInfo.queueCreateInfoCount = queueInfo.size();
699 devInfo.pQueueCreateInfos = queueInfo.constData();
700 devInfo.enabledExtensionCount = devExts.size();
701 devInfo.ppEnabledExtensionNames = devExts.constData();
702
703 VkPhysicalDeviceFeatures features = {};
704 VkPhysicalDeviceFeatures2 features2 = {};
705 if (enabledFeatures2Modifier) {
706 features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
707 enabledFeatures2Modifier(features2);
708 devInfo.pNext = &features2;
709 } else if (enabledFeaturesModifier) {
710 enabledFeaturesModifier(features);
711 devInfo.pEnabledFeatures = &features;
712 } else {
713 // Enable all supported 1.0 core features, except ones that likely
714 // involve a performance penalty.
715 f->vkGetPhysicalDeviceFeatures(physDev, &features);
716 features.robustBufferAccess = VK_FALSE;
717 devInfo.pEnabledFeatures = &features;
718 }
719
720 // Device layers are not supported by QVulkanWindow since that's an already deprecated
721 // API. However, have a workaround for systems with older API and layers (f.ex. L4T
722 // 24.2 for the Jetson TX1 provides API 1.0.13 and crashes when the validation layer
723 // is enabled for the instance but not the device).
724 uint32_t apiVersion = physDevProps[physDevIndex].apiVersion;
725 if (VK_VERSION_MAJOR(apiVersion) == 1
726 && VK_VERSION_MINOR(apiVersion) == 0
727 && VK_VERSION_PATCH(apiVersion) <= 13)
728 {
729 // Make standard validation work at least.
730 const QByteArray stdValName = QByteArrayLiteral("VK_LAYER_KHRONOS_validation");
731 const char *stdValNamePtr = stdValName.constData();
732 if (inst->layers().contains(stdValName)) {
733 uint32_t count = 0;
734 VkResult err = f->vkEnumerateDeviceLayerProperties(physDev, &count, nullptr);
735 if (err == VK_SUCCESS) {
736 QList<VkLayerProperties> layerProps(count);
737 err = f->vkEnumerateDeviceLayerProperties(physDev, &count, layerProps.data());
738 if (err == VK_SUCCESS) {
739 for (const VkLayerProperties &prop : layerProps) {
740 if (!strncmp(prop.layerName, stdValNamePtr, stdValName.size())) {
741 devInfo.enabledLayerCount = 1;
742 devInfo.ppEnabledLayerNames = &stdValNamePtr;
743 break;
744 }
745 }
746 }
747 }
748 }
749 }
750
751 VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
752 if (err == VK_ERROR_DEVICE_LOST) {
753 qWarning("QVulkanWindow: Physical device lost");
754 if (renderer)
755 renderer->physicalDeviceLost();
756 // clear the caches so the list of physical devices is re-queried
757 physDevs.clear();
758 physDevProps.clear();
759 status = StatusUninitialized;
760 qCDebug(lcGuiVk, "Attempting to restart in 2 seconds");
761 QTimer::singleShot(2000, q, [this]() { ensureStarted(); });
762 return;
763 }
764 if (err != VK_SUCCESS) {
765 qWarning("QVulkanWindow: Failed to create device: %d", err);
766 status = StatusFail;
767 return;
768 }
769
770 devFuncs = inst->deviceFunctions(dev);
771 Q_ASSERT(devFuncs);
772
773 devFuncs->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, 0, &gfxQueue);
774 if (gfxQueueFamilyIdx == presQueueFamilyIdx)
775 presQueue = gfxQueue;
776 else
777 devFuncs->vkGetDeviceQueue(dev, presQueueFamilyIdx, 0, &presQueue);
778
779 VkCommandPoolCreateInfo poolInfo;
780 memset(&poolInfo, 0, sizeof(poolInfo));
781 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
782 poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
783 err = devFuncs->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
784 if (err != VK_SUCCESS) {
785 qWarning("QVulkanWindow: Failed to create command pool: %d", err);
786 status = StatusFail;
787 return;
788 }
789 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
790 poolInfo.queueFamilyIndex = presQueueFamilyIdx;
791 err = devFuncs->vkCreateCommandPool(dev, &poolInfo, nullptr, &presCmdPool);
792 if (err != VK_SUCCESS) {
793 qWarning("QVulkanWindow: Failed to create command pool for present queue: %d", err);
794 status = StatusFail;
795 return;
796 }
797 }
798
799 hostVisibleMemIndex = 0;
800 VkPhysicalDeviceMemoryProperties physDevMemProps;
801 bool hostVisibleMemIndexSet = false;
802 f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
803 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
804 const VkMemoryType *memType = physDevMemProps.memoryTypes;
805 qCDebug(lcGuiVk, "memtype %d: flags=0x%x", i, memType[i].propertyFlags);
806 // Find a host visible, host coherent memtype. If there is one that is
807 // cached as well (in addition to being coherent), prefer that.
808 const int hostVisibleAndCoherent = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
809 if ((memType[i].propertyFlags & hostVisibleAndCoherent) == hostVisibleAndCoherent) {
810 if (!hostVisibleMemIndexSet
811 || (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)) {
812 hostVisibleMemIndexSet = true;
813 hostVisibleMemIndex = i;
814 }
815 }
816 }
817 qCDebug(lcGuiVk, "Picked memtype %d for host visible memory", hostVisibleMemIndex);
818 deviceLocalMemIndex = 0;
819 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
820 const VkMemoryType *memType = physDevMemProps.memoryTypes;
821 // Just pick the first device local memtype.
822 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
823 deviceLocalMemIndex = i;
824 break;
825 }
826 }
827 qCDebug(lcGuiVk, "Picked memtype %d for device local memory", deviceLocalMemIndex);
828
829 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR) {
830 vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
831 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
832 vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
833 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
834 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR) {
835 qWarning("QVulkanWindow: Physical device surface queries not available");
836 status = StatusFail;
837 return;
838 }
839 }
840
841 // Figure out the color format here. Must not wait until recreateSwapChain()
842 // because the renderpass should be available already from initResources (so
843 // that apps do not have to defer pipeline creation to
844 // initSwapChainResources), but the renderpass needs the final color format.
845
846 uint32_t formatCount = 0;
847 vkGetPhysicalDeviceSurfaceFormatsKHR(physDev, surface, &formatCount, nullptr);
848 QList<VkSurfaceFormatKHR> formats(formatCount);
849 if (formatCount)
850 vkGetPhysicalDeviceSurfaceFormatsKHR(physDev, surface, &formatCount, formats.data());
851
852 colorFormat = VK_FORMAT_B8G8R8A8_UNORM; // our documented default if all else fails
853 colorSpace = VkColorSpaceKHR(0); // this is in fact VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
854
855 // Pick the preferred format, if there is one.
856 if (!formats.isEmpty() && formats[0].format != VK_FORMAT_UNDEFINED) {
857 colorFormat = formats[0].format;
858 colorSpace = formats[0].colorSpace;
859 }
860
861 // Try to honor the user request.
862 if (!formats.isEmpty() && !requestedColorFormats.isEmpty()) {
863 for (VkFormat reqFmt : std::as_const(requestedColorFormats)) {
864 auto r = std::find_if(formats.cbegin(), formats.cend(),
865 [reqFmt](const VkSurfaceFormatKHR &sfmt) { return sfmt.format == reqFmt; });
866 if (r != formats.cend()) {
867 colorFormat = r->format;
868 colorSpace = r->colorSpace;
869 break;
870 }
871 }
872 }
873
874 const VkFormat dsFormatCandidates[] = {
875 VK_FORMAT_D24_UNORM_S8_UINT,
876 VK_FORMAT_D32_SFLOAT_S8_UINT,
877 VK_FORMAT_D16_UNORM_S8_UINT
878 };
879 const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
880 int dsFormatIdx = 0;
881 while (dsFormatIdx < dsFormatCandidateCount) {
882 dsFormat = dsFormatCandidates[dsFormatIdx];
883 VkFormatProperties fmtProp;
884 f->vkGetPhysicalDeviceFormatProperties(physDev, dsFormat, &fmtProp);
885 if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
886 break;
887 ++dsFormatIdx;
888 }
889 if (dsFormatIdx == dsFormatCandidateCount)
890 qWarning("QVulkanWindow: Failed to find an optimal depth-stencil format");
891
892 qCDebug(lcGuiVk, "Color format: %d Depth-stencil format: %d", colorFormat, dsFormat);
893
894 if (!createDefaultRenderPass())
895 return;
896
897 if (renderer)
898 renderer->initResources();
899
900 status = StatusDeviceReady;
901}
902
903void QVulkanWindowPrivate::reset()
904{
905 if (!dev) // do not rely on 'status', a half done init must be cleaned properly too
906 return;
907
908 qCDebug(lcGuiVk, "QVulkanWindow reset");
909
910 devFuncs->vkDeviceWaitIdle(dev);
911
912 if (renderer) {
913 renderer->releaseResources();
914 devFuncs->vkDeviceWaitIdle(dev);
915 }
916
917 if (defaultRenderPass) {
918 devFuncs->vkDestroyRenderPass(dev, defaultRenderPass, nullptr);
919 defaultRenderPass = VK_NULL_HANDLE;
920 }
921
922 if (cmdPool) {
923 devFuncs->vkDestroyCommandPool(dev, cmdPool, nullptr);
924 cmdPool = VK_NULL_HANDLE;
925 }
926
927 if (presCmdPool) {
928 devFuncs->vkDestroyCommandPool(dev, presCmdPool, nullptr);
929 presCmdPool = VK_NULL_HANDLE;
930 }
931
932 if (frameGrabImage) {
933 devFuncs->vkDestroyImage(dev, frameGrabImage, nullptr);
934 frameGrabImage = VK_NULL_HANDLE;
935 }
936
937 if (frameGrabImageMem) {
938 devFuncs->vkFreeMemory(dev, frameGrabImageMem, nullptr);
939 frameGrabImageMem = VK_NULL_HANDLE;
940 }
941
942 if (dev) {
943 devFuncs->vkDestroyDevice(dev, nullptr);
944 inst->resetDeviceFunctions(dev);
945 dev = VK_NULL_HANDLE;
946 vkCreateSwapchainKHR = nullptr; // re-resolve swapchain funcs later on since some come via the device
947 }
948
949 surface = VK_NULL_HANDLE;
950
951 status = StatusUninitialized;
952}
953
954bool QVulkanWindowPrivate::createDefaultRenderPass()
955{
956 VkAttachmentDescription attDesc[3];
957 memset(attDesc, 0, sizeof(attDesc));
958
959 const bool msaa = sampleCount > VK_SAMPLE_COUNT_1_BIT;
960
961 // This is either the non-msaa render target or the resolve target.
962 attDesc[0].format = colorFormat;
963 attDesc[0].samples = VK_SAMPLE_COUNT_1_BIT;
964 attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // ignored when msaa
965 attDesc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
966 attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
967 attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
968 attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
969 attDesc[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
970
971 attDesc[1].format = dsFormat;
972 attDesc[1].samples = sampleCount;
973 attDesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
974 attDesc[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
975 attDesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
976 attDesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
977 attDesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
978 attDesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
979
980 if (msaa) {
981 // msaa render target
982 attDesc[2].format = colorFormat;
983 attDesc[2].samples = sampleCount;
984 attDesc[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
985 attDesc[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
986 attDesc[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
987 attDesc[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
988 attDesc[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
989 attDesc[2].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
990 }
991
992 VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
993 VkAttachmentReference resolveRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
994 VkAttachmentReference dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
995
996 VkSubpassDescription subPassDesc;
997 memset(&subPassDesc, 0, sizeof(subPassDesc));
998 subPassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
999 subPassDesc.colorAttachmentCount = 1;
1000 subPassDesc.pColorAttachments = &colorRef;
1001 subPassDesc.pDepthStencilAttachment = &dsRef;
1002
1003 VkRenderPassCreateInfo rpInfo;
1004 memset(&rpInfo, 0, sizeof(rpInfo));
1005 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1006 rpInfo.attachmentCount = 2;
1007 rpInfo.pAttachments = attDesc;
1008 rpInfo.subpassCount = 1;
1009 rpInfo.pSubpasses = &subPassDesc;
1010
1011 if (msaa) {
1012 colorRef.attachment = 2;
1013 subPassDesc.pResolveAttachments = &resolveRef;
1014 rpInfo.attachmentCount = 3;
1015 }
1016
1017 VkResult err = devFuncs->vkCreateRenderPass(dev, &rpInfo, nullptr, &defaultRenderPass);
1018 if (err != VK_SUCCESS) {
1019 qWarning("QVulkanWindow: Failed to create renderpass: %d", err);
1020 return false;
1021 }
1022
1023 return true;
1024}
1025
1026void QVulkanWindowPrivate::recreateSwapChain()
1027{
1028 Q_Q(QVulkanWindow);
1029 Q_ASSERT(status >= StatusDeviceReady);
1030
1031 swapChainImageSize = q->size() * q->devicePixelRatio(); // note: may change below due to surfaceCaps
1032
1033 if (swapChainImageSize.isEmpty()) // handle null window size gracefully
1034 return;
1035
1036 QVulkanInstance *inst = q->vulkanInstance();
1037 QVulkanFunctions *f = inst->functions();
1038 devFuncs->vkDeviceWaitIdle(dev);
1039
1040 if (!vkCreateSwapchainKHR) {
1041 vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
1042 vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
1043 vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
1044 vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
1045 vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
1046 }
1047
1048 VkPhysicalDevice physDev = physDevs.at(physDevIndex);
1049 VkSurfaceCapabilitiesKHR surfaceCaps;
1050 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, surface, &surfaceCaps);
1051 uint32_t reqBufferCount;
1052 if (surfaceCaps.maxImageCount == 0)
1053 reqBufferCount = qMax<uint32_t>(2, surfaceCaps.minImageCount);
1054 else
1055 reqBufferCount = qMax(qMin<uint32_t>(surfaceCaps.maxImageCount, 3), surfaceCaps.minImageCount);
1056
1057 VkExtent2D bufferSize = surfaceCaps.currentExtent;
1058 if (bufferSize.width == uint32_t(-1)) {
1059 Q_ASSERT(bufferSize.height == uint32_t(-1));
1060 bufferSize.width = swapChainImageSize.width();
1061 bufferSize.height = swapChainImageSize.height();
1062 } else {
1063 swapChainImageSize = QSize(bufferSize.width, bufferSize.height);
1064 }
1065
1066 VkSurfaceTransformFlagBitsKHR preTransform =
1067 (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
1068 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
1069 : surfaceCaps.currentTransform;
1070
1071 VkCompositeAlphaFlagBitsKHR compositeAlpha =
1072 (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
1073 ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
1074 : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1075
1076 if (q->requestedFormat().hasAlpha()) {
1077 if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
1078 compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
1079 else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
1080 compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
1081 }
1082
1083 VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1084 swapChainSupportsReadBack = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
1085 if (swapChainSupportsReadBack)
1086 usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1087
1088 VkSwapchainKHR oldSwapChain = swapChain;
1089 VkSwapchainCreateInfoKHR swapChainInfo;
1090 memset(&swapChainInfo, 0, sizeof(swapChainInfo));
1091 swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1092 swapChainInfo.surface = surface;
1093 swapChainInfo.minImageCount = reqBufferCount;
1094 swapChainInfo.imageFormat = colorFormat;
1095 swapChainInfo.imageColorSpace = colorSpace;
1096 swapChainInfo.imageExtent = bufferSize;
1097 swapChainInfo.imageArrayLayers = 1;
1098 swapChainInfo.imageUsage = usage;
1099 swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1100 swapChainInfo.preTransform = preTransform;
1101 swapChainInfo.compositeAlpha = compositeAlpha;
1102 swapChainInfo.presentMode = presentMode;
1103 swapChainInfo.clipped = true;
1104 swapChainInfo.oldSwapchain = oldSwapChain;
1105
1106 qCDebug(lcGuiVk, "Creating new swap chain of %d buffers, size %dx%d", reqBufferCount, bufferSize.width, bufferSize.height);
1107
1108 VkSwapchainKHR newSwapChain;
1109 VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
1110 if (err != VK_SUCCESS) {
1111 qWarning("QVulkanWindow: Failed to create swap chain: %d", err);
1112 return;
1113 }
1114
1115 if (oldSwapChain)
1116 releaseSwapChain();
1117
1118 swapChain = newSwapChain;
1119
1120 uint32_t actualSwapChainBufferCount = 0;
1121 err = vkGetSwapchainImagesKHR(dev, swapChain, &actualSwapChainBufferCount, nullptr);
1122 if (err != VK_SUCCESS || actualSwapChainBufferCount < 2) {
1123 qWarning("QVulkanWindow: Failed to get swapchain images: %d (count=%d)", err, actualSwapChainBufferCount);
1124 return;
1125 }
1126
1127 qCDebug(lcGuiVk, "Actual swap chain buffer count: %d (supportsReadback=%d)",
1128 actualSwapChainBufferCount, swapChainSupportsReadBack);
1129 if (actualSwapChainBufferCount > MAX_SWAPCHAIN_BUFFER_COUNT) {
1130 qWarning("QVulkanWindow: Too many swapchain buffers (%d)", actualSwapChainBufferCount);
1131 return;
1132 }
1133 swapChainBufferCount = actualSwapChainBufferCount;
1134
1135 VkImage swapChainImages[MAX_SWAPCHAIN_BUFFER_COUNT];
1136 err = vkGetSwapchainImagesKHR(dev, swapChain, &actualSwapChainBufferCount, swapChainImages);
1137 if (err != VK_SUCCESS) {
1138 qWarning("QVulkanWindow: Failed to get swapchain images: %d", err);
1139 return;
1140 }
1141
1142 if (!createTransientImage(dsFormat,
1143 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
1144 VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
1145 &dsImage,
1146 &dsMem,
1147 &dsView,
1148 1))
1149 {
1150 return;
1151 }
1152
1153 const bool msaa = sampleCount > VK_SAMPLE_COUNT_1_BIT;
1154 VkImage msaaImages[MAX_SWAPCHAIN_BUFFER_COUNT];
1155 VkImageView msaaViews[MAX_SWAPCHAIN_BUFFER_COUNT];
1156
1157 if (msaa) {
1158 if (!createTransientImage(colorFormat,
1159 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
1160 VK_IMAGE_ASPECT_COLOR_BIT,
1161 msaaImages,
1162 &msaaImageMem,
1163 msaaViews,
1164 swapChainBufferCount))
1165 {
1166 return;
1167 }
1168 }
1169
1170 VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT };
1171
1172 for (int i = 0; i < swapChainBufferCount; ++i) {
1173 ImageResources &image(imageRes[i]);
1174 image.image = swapChainImages[i];
1175
1176 if (msaa) {
1177 image.msaaImage = msaaImages[i];
1178 image.msaaImageView = msaaViews[i];
1179 }
1180
1181 VkImageViewCreateInfo imgViewInfo;
1182 memset(&imgViewInfo, 0, sizeof(imgViewInfo));
1183 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1184 imgViewInfo.image = swapChainImages[i];
1185 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1186 imgViewInfo.format = colorFormat;
1187 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1188 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1189 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1190 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1191 imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1192 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1193 err = devFuncs->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
1194 if (err != VK_SUCCESS) {
1195 qWarning("QVulkanWindow: Failed to create swapchain image view %d: %d", i, err);
1196 return;
1197 }
1198
1199 VkImageView views[3] = { image.imageView,
1200 dsView,
1201 msaa ? image.msaaImageView : VK_NULL_HANDLE };
1202 VkFramebufferCreateInfo fbInfo;
1203 memset(&fbInfo, 0, sizeof(fbInfo));
1204 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1205 fbInfo.renderPass = defaultRenderPass;
1206 fbInfo.attachmentCount = msaa ? 3 : 2;
1207 fbInfo.pAttachments = views;
1208 fbInfo.width = swapChainImageSize.width();
1209 fbInfo.height = swapChainImageSize.height();
1210 fbInfo.layers = 1;
1211 VkResult err = devFuncs->vkCreateFramebuffer(dev, &fbInfo, nullptr, &image.fb);
1212 if (err != VK_SUCCESS) {
1213 qWarning("QVulkanWindow: Failed to create framebuffer: %d", err);
1214 return;
1215 }
1216
1217 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
1218 // pre-build the static image-acquire-on-present-queue command buffer
1219 VkCommandBufferAllocateInfo cmdBufInfo = {
1220 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, presCmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
1221 err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &image.presTransCmdBuf);
1222 if (err != VK_SUCCESS) {
1223 qWarning("QVulkanWindow: Failed to allocate acquire-on-present-queue command buffer: %d", err);
1224 return;
1225 }
1226 VkCommandBufferBeginInfo cmdBufBeginInfo = {
1227 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
1228 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, nullptr };
1229 err = devFuncs->vkBeginCommandBuffer(image.presTransCmdBuf, &cmdBufBeginInfo);
1230 if (err != VK_SUCCESS) {
1231 qWarning("QVulkanWindow: Failed to begin acquire-on-present-queue command buffer: %d", err);
1232 return;
1233 }
1234 VkImageMemoryBarrier presTrans;
1235 memset(&presTrans, 0, sizeof(presTrans));
1236 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1237 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1238 presTrans.oldLayout = presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1239 presTrans.srcQueueFamilyIndex = gfxQueueFamilyIdx;
1240 presTrans.dstQueueFamilyIndex = presQueueFamilyIdx;
1241 presTrans.image = image.image;
1242 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1243 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
1244 devFuncs->vkCmdPipelineBarrier(image.presTransCmdBuf,
1245 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1246 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1247 0, 0, nullptr, 0, nullptr,
1248 1, &presTrans);
1249 err = devFuncs->vkEndCommandBuffer(image.presTransCmdBuf);
1250 if (err != VK_SUCCESS) {
1251 qWarning("QVulkanWindow: Failed to end acquire-on-present-queue command buffer: %d", err);
1252 return;
1253 }
1254 }
1255 }
1256
1257 currentImage = 0;
1258
1259 VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
1260 for (int i = 0; i < frameLag; ++i) {
1261 FrameResources &frame(frameRes[i]);
1262
1263 frame.imageAcquired = false;
1264 frame.imageSemWaitable = false;
1265
1266 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
1267 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
1268 if (gfxQueueFamilyIdx != presQueueFamilyIdx)
1269 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.presTransSem);
1270
1271 err = devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
1272 if (err != VK_SUCCESS) {
1273 qWarning("QVulkanWindow: Failed to create command buffer fence: %d", err);
1274 return;
1275 }
1276 frame.cmdFenceWaitable = true; // fence was created in signaled state
1277 }
1278
1279 currentFrame = 0;
1280
1281 if (renderer)
1282 renderer->initSwapChainResources();
1283
1284 status = StatusReady;
1285}
1286
1287uint32_t QVulkanWindowPrivate::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
1288{
1289 VkPhysicalDeviceMemoryProperties physDevMemProps;
1290 inst->functions()->vkGetPhysicalDeviceMemoryProperties(physDevs[physDevIndex], &physDevMemProps);
1291
1292 VkMemoryRequirements memReq;
1293 devFuncs->vkGetImageMemoryRequirements(dev, img, &memReq);
1294 uint32_t memTypeIndex = uint32_t(-1);
1295
1296 if (memReq.memoryTypeBits) {
1297 // Find a device local + lazily allocated, or at least device local memtype.
1298 const VkMemoryType *memType = physDevMemProps.memoryTypes;
1299 bool foundDevLocal = false;
1300 for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
1301 if (memReq.memoryTypeBits & (1 << i)) {
1302 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
1303 if (!foundDevLocal) {
1304 foundDevLocal = true;
1305 memTypeIndex = i;
1306 }
1307 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
1308 memTypeIndex = i;
1309 break;
1310 }
1311 }
1312 }
1313 }
1314 }
1315
1316 return memTypeIndex;
1317}
1318
1319static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
1320{
1321 return (v + byteAlign - 1) & ~(byteAlign - 1);
1322}
1323
1324bool QVulkanWindowPrivate::createTransientImage(VkFormat format,
1325 VkImageUsageFlags usage,
1326 VkImageAspectFlags aspectMask,
1327 VkImage *images,
1328 VkDeviceMemory *mem,
1329 VkImageView *views,
1330 int count)
1331{
1332 VkMemoryRequirements memReq;
1333 VkResult err;
1334
1335 Q_ASSERT(count > 0);
1336 for (int i = 0; i < count; ++i) {
1337 VkImageCreateInfo imgInfo;
1338 memset(&imgInfo, 0, sizeof(imgInfo));
1339 imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1340 imgInfo.imageType = VK_IMAGE_TYPE_2D;
1341 imgInfo.format = format;
1342 imgInfo.extent.width = swapChainImageSize.width();
1343 imgInfo.extent.height = swapChainImageSize.height();
1344 imgInfo.extent.depth = 1;
1345 imgInfo.mipLevels = imgInfo.arrayLayers = 1;
1346 imgInfo.samples = sampleCount;
1347 imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1348 imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
1349
1350 err = devFuncs->vkCreateImage(dev, &imgInfo, nullptr, images + i);
1351 if (err != VK_SUCCESS) {
1352 qWarning("QVulkanWindow: Failed to create image: %d", err);
1353 return false;
1354 }
1355
1356 // Assume the reqs are the same since the images are same in every way.
1357 // Still, call GetImageMemReq for every image, in order to prevent the
1358 // validation layer from complaining.
1359 devFuncs->vkGetImageMemoryRequirements(dev, images[i], &memReq);
1360 }
1361
1362 VkMemoryAllocateInfo memInfo;
1363 memset(&memInfo, 0, sizeof(memInfo));
1364 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1365 memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * count;
1366
1367 uint32_t startIndex = 0;
1368 do {
1369 memInfo.memoryTypeIndex = chooseTransientImageMemType(images[0], startIndex);
1370 if (memInfo.memoryTypeIndex == uint32_t(-1)) {
1371 qWarning("QVulkanWindow: No suitable memory type found");
1372 return false;
1373 }
1374 startIndex = memInfo.memoryTypeIndex + 1;
1375 qCDebug(lcGuiVk, "Allocating %u bytes for transient image (memtype %u)",
1376 uint32_t(memInfo.allocationSize), memInfo.memoryTypeIndex);
1377 err = devFuncs->vkAllocateMemory(dev, &memInfo, nullptr, mem);
1378 if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
1379 qWarning("QVulkanWindow: Failed to allocate image memory: %d", err);
1380 return false;
1381 }
1382 } while (err != VK_SUCCESS);
1383
1384 VkDeviceSize ofs = 0;
1385 for (int i = 0; i < count; ++i) {
1386 err = devFuncs->vkBindImageMemory(dev, images[i], *mem, ofs);
1387 if (err != VK_SUCCESS) {
1388 qWarning("QVulkanWindow: Failed to bind image memory: %d", err);
1389 return false;
1390 }
1391 ofs += aligned(memReq.size, memReq.alignment);
1392
1393 VkImageViewCreateInfo imgViewInfo;
1394 memset(&imgViewInfo, 0, sizeof(imgViewInfo));
1395 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1396 imgViewInfo.image = images[i];
1397 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1398 imgViewInfo.format = format;
1399 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1400 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1401 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1402 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1403 imgViewInfo.subresourceRange.aspectMask = aspectMask;
1404 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1405
1406 err = devFuncs->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
1407 if (err != VK_SUCCESS) {
1408 qWarning("QVulkanWindow: Failed to create image view: %d", err);
1409 return false;
1410 }
1411 }
1412
1413 return true;
1414}
1415
1416void QVulkanWindowPrivate::releaseSwapChain()
1417{
1418 if (!dev || !swapChain) // do not rely on 'status', a half done init must be cleaned properly too
1419 return;
1420
1421 qCDebug(lcGuiVk, "Releasing swapchain");
1422
1423 devFuncs->vkDeviceWaitIdle(dev);
1424
1425 if (renderer) {
1426 renderer->releaseSwapChainResources();
1427 devFuncs->vkDeviceWaitIdle(dev);
1428 }
1429
1430 for (int i = 0; i < frameLag; ++i) {
1431 FrameResources &frame(frameRes[i]);
1432 if (frame.cmdBuf) {
1433 devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
1434 frame.cmdBuf = VK_NULL_HANDLE;
1435 }
1436 if (frame.imageSem) {
1437 devFuncs->vkDestroySemaphore(dev, frame.imageSem, nullptr);
1438 frame.imageSem = VK_NULL_HANDLE;
1439 }
1440 if (frame.drawSem) {
1441 devFuncs->vkDestroySemaphore(dev, frame.drawSem, nullptr);
1442 frame.drawSem = VK_NULL_HANDLE;
1443 }
1444 if (frame.presTransSem) {
1445 devFuncs->vkDestroySemaphore(dev, frame.presTransSem, nullptr);
1446 frame.presTransSem = VK_NULL_HANDLE;
1447 }
1448 if (frame.cmdFence) {
1449 if (frame.cmdFenceWaitable)
1450 devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
1451 devFuncs->vkDestroyFence(dev, frame.cmdFence, nullptr);
1452 frame.cmdFence = VK_NULL_HANDLE;
1453 frame.cmdFenceWaitable = false;
1454 }
1455 }
1456
1457 for (int i = 0; i < swapChainBufferCount; ++i) {
1458 ImageResources &image(imageRes[i]);
1459 if (image.fb) {
1460 devFuncs->vkDestroyFramebuffer(dev, image.fb, nullptr);
1461 image.fb = VK_NULL_HANDLE;
1462 }
1463 if (image.imageView) {
1464 devFuncs->vkDestroyImageView(dev, image.imageView, nullptr);
1465 image.imageView = VK_NULL_HANDLE;
1466 }
1467 if (image.presTransCmdBuf) {
1468 devFuncs->vkFreeCommandBuffers(dev, presCmdPool, 1, &image.presTransCmdBuf);
1469 image.presTransCmdBuf = VK_NULL_HANDLE;
1470 }
1471 if (image.msaaImageView) {
1472 devFuncs->vkDestroyImageView(dev, image.msaaImageView, nullptr);
1473 image.msaaImageView = VK_NULL_HANDLE;
1474 }
1475 if (image.msaaImage) {
1476 devFuncs->vkDestroyImage(dev, image.msaaImage, nullptr);
1477 image.msaaImage = VK_NULL_HANDLE;
1478 }
1479 }
1480
1481 if (msaaImageMem) {
1482 devFuncs->vkFreeMemory(dev, msaaImageMem, nullptr);
1483 msaaImageMem = VK_NULL_HANDLE;
1484 }
1485
1486 if (dsView) {
1487 devFuncs->vkDestroyImageView(dev, dsView, nullptr);
1488 dsView = VK_NULL_HANDLE;
1489 }
1490 if (dsImage) {
1491 devFuncs->vkDestroyImage(dev, dsImage, nullptr);
1492 dsImage = VK_NULL_HANDLE;
1493 }
1494 if (dsMem) {
1495 devFuncs->vkFreeMemory(dev, dsMem, nullptr);
1496 dsMem = VK_NULL_HANDLE;
1497 }
1498
1499 if (swapChain) {
1500 vkDestroySwapchainKHR(dev, swapChain, nullptr);
1501 swapChain = VK_NULL_HANDLE;
1502 }
1503
1504 if (status == StatusReady)
1505 status = StatusDeviceReady;
1506}
1507
1508/*!
1509 \internal
1510 */
1511void QVulkanWindow::exposeEvent(QExposeEvent *)
1512{
1513 Q_D(QVulkanWindow);
1514
1515 if (isExposed()) {
1516 d->ensureStarted();
1517 } else {
1518 if (!d->flags.testFlag(PersistentResources)) {
1519 d->releaseSwapChain();
1520 d->reset();
1521 }
1522 }
1523}
1524
1525void QVulkanWindowPrivate::ensureStarted()
1526{
1527 Q_Q(QVulkanWindow);
1528 if (status == QVulkanWindowPrivate::StatusFailRetry)
1529 status = QVulkanWindowPrivate::StatusUninitialized;
1530 if (status == QVulkanWindowPrivate::StatusUninitialized) {
1531 init();
1532 if (status == QVulkanWindowPrivate::StatusDeviceReady)
1533 recreateSwapChain();
1534 }
1535 if (status == QVulkanWindowPrivate::StatusReady)
1536 q->requestUpdate();
1537}
1538
1539/*!
1540 \internal
1541 */
1542void QVulkanWindow::resizeEvent(QResizeEvent *)
1543{
1544 // Nothing to do here - recreating the swapchain is handled when building the next frame.
1545}
1546
1547/*!
1548 \internal
1549 */
1550bool QVulkanWindow::event(QEvent *e)
1551{
1552 Q_D(QVulkanWindow);
1553
1554 switch (e->type()) {
1555 case QEvent::Paint:
1556 case QEvent::UpdateRequest:
1557 d->beginFrame();
1558 break;
1559
1560 // The swapchain must be destroyed before the surface as per spec. This is
1561 // not ideal for us because the surface is managed by the QPlatformWindow
1562 // which may be gone already when the unexpose comes, making the validation
1563 // layer scream. The solution is to listen to the PlatformSurface events.
1564 case QEvent::PlatformSurface:
1565 if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1566 d->releaseSwapChain();
1567 d->reset();
1568 }
1569 break;
1570
1571 default:
1572 break;
1573 }
1574
1575 return QWindow::event(e);
1576}
1577
1578/*!
1579 \typedef QVulkanWindow::QueueCreateInfoModifier
1580
1581 A function that is called during graphics initialization to add
1582 additional queues that should be created.
1583
1584 Set if the renderer needs additional queues besides the default graphics
1585 queue (e.g. a transfer queue).
1586 The provided queue family properties can be used to select the indices for
1587 the additional queues.
1588 The renderer can subsequently request the actual queue in initResources().
1589
1590 \note When requesting additional graphics queues, Qt itself always requests
1591 a graphics queue. You'll need to search queueCreateInfo for the appropriate
1592 entry and manipulate it to obtain the additional queue.
1593
1594 \sa setQueueCreateInfoModifier()
1595 */
1596
1597/*!
1598 Sets the queue create info modification function \a modifier.
1599
1600 \sa QueueCreateInfoModifier
1601
1602 \since 5.15
1603 */
1604void QVulkanWindow::setQueueCreateInfoModifier(const QueueCreateInfoModifier &modifier)
1605{
1606 Q_D(QVulkanWindow);
1607 d->queueCreateInfoModifier = modifier;
1608}
1609
1610/*!
1611 \typedef QVulkanWindow::EnabledFeaturesModifier
1612
1613 A function that is called during graphics initialization to alter the
1614 VkPhysicalDeviceFeatures that is passed in when creating a Vulkan device
1615 object.
1616
1617 By default QVulkanWindow enables all Vulkan 1.0 core features that the
1618 physical device reports as supported, with certain exceptions. In
1619 praticular, \c robustBufferAccess is always disabled in order to avoid
1620 unexpected performance hits.
1621
1622 The VkPhysicalDeviceFeatures reference passed in is all zeroed out at the
1623 point when the function is invoked. It is up to the function to change
1624 members as it sees fit.
1625
1626 \note To control Vulkan 1.1, 1.2, or 1.3 features, use
1627 EnabledFeatures2Modifier instead.
1628
1629 \sa setEnabledFeaturesModifier()
1630 */
1631
1632/*!
1633 Sets the enabled device features modification function \a modifier.
1634
1635 \note To control Vulkan 1.1, 1.2, or 1.3 features, use
1636 the overload taking a EnabledFeatures2Modifier instead.
1637
1638 \note \a modifier is passed to the callback function with all members set
1639 to false. It is up to the function to change members as it sees fit.
1640
1641 \since 6.7
1642 \sa EnabledFeaturesModifier
1643 */
1644void QVulkanWindow::setEnabledFeaturesModifier(const EnabledFeaturesModifier &modifier)
1645{
1646 Q_D(QVulkanWindow);
1647 d->enabledFeaturesModifier = modifier;
1648}
1649
1650/*!
1651 \typedef QVulkanWindow::EnabledFeatures2Modifier
1652
1653 A function that is called during graphics initialization to alter the
1654 VkPhysicalDeviceFeatures2 that is changed to the VkDeviceCreateInfo.
1655
1656 By default QVulkanWindow enables all Vulkan 1.0 core features that the
1657 physical device reports as supported, with certain exceptions. In
1658 praticular, \c robustBufferAccess is always disabled in order to avoid
1659 unexpected performance hits.
1660
1661 This however is not always sufficient when working with Vulkan 1.1, 1.2, or
1662 1.3 features and extensions. Hence this callback mechanism. If only Vulkan
1663 1.0 is relevant at run time, use setEnabledFeaturesModifier() instead.
1664
1665 The VkPhysicalDeviceFeatures2 reference passed to the callback function
1666 with \c sType set, but the rest zeroed out. It is up to the function to
1667 change members to true, or set up \c pNext chains as it sees fit.
1668
1669 \note When setting up \c pNext chains, make sure the referenced objects
1670 have a long enough lifetime, for example by storing them as member
1671 variables in the QVulkanWindow subclass.
1672
1673 \since 6.7
1674 \sa setEnabledFeaturesModifier()
1675 */
1676
1677/*!
1678 Sets the enabled device features modification function \a modifier.
1679 \overload
1680 \since 6.7
1681 \sa EnabledFeatures2Modifier
1682*/
1683void QVulkanWindow::setEnabledFeaturesModifier(EnabledFeatures2Modifier modifier)
1684{
1685 Q_D(QVulkanWindow);
1686 d->enabledFeatures2Modifier = std::move(modifier);
1687}
1688
1689/*!
1690 Returns true if this window has successfully initialized all Vulkan
1691 resources, including the swapchain.
1692
1693 \note Initialization happens on the first expose event after the window is
1694 made visible.
1695 */
1696bool QVulkanWindow::isValid() const
1697{
1698 Q_D(const QVulkanWindow);
1699 return d->status == QVulkanWindowPrivate::StatusReady;
1700}
1701
1702/*!
1703 Returns a new instance of QVulkanWindowRenderer.
1704
1705 This virtual function is called once during the lifetime of the window, at
1706 some point after making it visible for the first time.
1707
1708 The default implementation returns null and so no rendering will be
1709 performed apart from clearing the buffers.
1710
1711 The window takes ownership of the returned renderer object.
1712 */
1713QVulkanWindowRenderer *QVulkanWindow::createRenderer()
1714{
1715 return nullptr;
1716}
1717
1718/*!
1719 Virtual destructor.
1720 */
1721QVulkanWindowRenderer::~QVulkanWindowRenderer()
1722{
1723}
1724
1725/*!
1726 This virtual function is called right before graphics initialization, that
1727 ends up in calling initResources(), is about to begin.
1728
1729 Normally there is no need to reimplement this function. However, there are
1730 cases that involve decisions based on both the physical device and the
1731 surface. These cannot normally be performed before making the QVulkanWindow
1732 visible since the Vulkan surface is not retrievable at that stage.
1733
1734 Instead, applications can reimplement this function. Here both
1735 QVulkanWindow::physicalDevice() and QVulkanInstance::surfaceForWindow() are
1736 functional, but no further logical device initialization has taken place
1737 yet.
1738
1739 The default implementation is empty.
1740 */
1741void QVulkanWindowRenderer::preInitResources()
1742{
1743}
1744
1745/*!
1746 This virtual function is called when it is time to create the renderer's
1747 graphics resources.
1748
1749 Depending on the QVulkanWindow::PersistentResources flag, device lost
1750 situations, etc. this function may be called more than once during the
1751 lifetime of a QVulkanWindow. However, subsequent invocations are always
1752 preceded by a call to releaseResources().
1753
1754 Accessors like device(), graphicsQueue() and graphicsCommandPool() are only
1755 guaranteed to return valid values inside this function and afterwards, up
1756 until releaseResources() is called.
1757
1758 The default implementation is empty.
1759 */
1760void QVulkanWindowRenderer::initResources()
1761{
1762}
1763
1764/*!
1765 This virtual function is called when swapchain, framebuffer or renderpass
1766 related initialization can be performed. Swapchain and related resources
1767 are reset and then recreated in response to window resize events, and
1768 therefore a pair of calls to initResources() and releaseResources() can
1769 have multiple calls to initSwapChainResources() and
1770 releaseSwapChainResources() calls in-between.
1771
1772 Accessors like QVulkanWindow::swapChainImageSize() are only guaranteed to
1773 return valid values inside this function and afterwards, up until
1774 releaseSwapChainResources() is called.
1775
1776 This is also the place where size-dependent calculations (for example, the
1777 projection matrix) should be made since this function is called effectively
1778 on every resize.
1779
1780 The default implementation is empty.
1781 */
1782void QVulkanWindowRenderer::initSwapChainResources()
1783{
1784}
1785
1786/*!
1787 This virtual function is called when swapchain, framebuffer or renderpass
1788 related resources must be released.
1789
1790 The implementation must be prepared that a call to this function may be
1791 followed by a new call to initSwapChainResources() at a later point.
1792
1793 QVulkanWindow takes care of waiting for the device to become idle before
1794 and after invoking this function.
1795
1796 The default implementation is empty.
1797
1798 \note This is the last place to act with all graphics resources intact
1799 before QVulkanWindow starts releasing them. It is therefore essential that
1800 implementations with an asynchronous, potentially multi-threaded
1801 startNextFrame() perform a blocking wait and call
1802 QVulkanWindow::frameReady() before returning from this function in case
1803 there is a pending frame submission.
1804 */
1805void QVulkanWindowRenderer::releaseSwapChainResources()
1806{
1807}
1808
1809/*!
1810 This virtual function is called when the renderer's graphics resources must be
1811 released.
1812
1813 The implementation must be prepared that a call to this function may be
1814 followed by an initResources() at a later point.
1815
1816 QVulkanWindow takes care of waiting for the device to become idle before
1817 and after invoking this function.
1818
1819 The default implementation is empty.
1820 */
1821void QVulkanWindowRenderer::releaseResources()
1822{
1823}
1824
1825/*!
1826 \fn void QVulkanWindowRenderer::startNextFrame()
1827
1828 This virtual function is called when the draw calls for the next frame are
1829 to be added to the command buffer.
1830
1831 Each call to this function must be followed by a call to
1832 QVulkanWindow::frameReady(). Failing to do so will stall the rendering
1833 loop. The call can also be made at a later time, after returning from this
1834 function. This means that it is possible to kick off asynchronous work, and
1835 only update the command buffer and notify QVulkanWindow when that work has
1836 finished.
1837
1838 All Vulkan resources are initialized and ready when this function is
1839 invoked. The current framebuffer and main command buffer can be retrieved
1840 via QVulkanWindow::currentFramebuffer() and
1841 QVulkanWindow::currentCommandBuffer(). The logical device and the active
1842 graphics queue are available via QVulkanWindow::device() and
1843 QVulkanWindow::graphicsQueue(). Implementations can create additional
1844 command buffers from the pool returned by
1845 QVulkanWindow::graphicsCommandPool(). For convenience, the index of a host
1846 visible and device local memory type index are exposed via
1847 QVulkanWindow::hostVisibleMemoryIndex() and
1848 QVulkanWindow::deviceLocalMemoryIndex(). All these accessors are safe to be
1849 called from any thread.
1850
1851 \sa QVulkanWindow::frameReady(), QVulkanWindow
1852 */
1853
1854/*!
1855 This virtual function is called when the physical device is lost, meaning
1856 the creation of the logical device fails with \c{VK_ERROR_DEVICE_LOST}.
1857
1858 The default implementation is empty.
1859
1860 There is typically no need to perform anything special in this function
1861 because QVulkanWindow will automatically retry to initialize itself after a
1862 certain amount of time.
1863
1864 \sa logicalDeviceLost()
1865 */
1866void QVulkanWindowRenderer::physicalDeviceLost()
1867{
1868}
1869
1870/*!
1871 This virtual function is called when the logical device (VkDevice) is lost,
1872 meaning some operation failed with \c{VK_ERROR_DEVICE_LOST}.
1873
1874 The default implementation is empty.
1875
1876 There is typically no need to perform anything special in this function.
1877 QVulkanWindow will automatically release all resources (invoking
1878 releaseSwapChainResources() and releaseResources() as necessary) and will
1879 attempt to reinitialize, acquiring a new device. When the physical device
1880 was also lost, this reinitialization attempt may then result in
1881 physicalDeviceLost().
1882
1883 \sa physicalDeviceLost()
1884 */
1885void QVulkanWindowRenderer::logicalDeviceLost()
1886{
1887}
1888
1889QSize QVulkanWindowPrivate::surfacePixelSize() const
1890{
1891 Q_Q(const QVulkanWindow);
1892 VkSurfaceCapabilitiesKHR surfaceCaps = {};
1893 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevs.at(physDevIndex), surface, &surfaceCaps);
1894 VkExtent2D bufferSize = surfaceCaps.currentExtent;
1895 if (bufferSize.width == uint32_t(-1)) {
1896 Q_ASSERT(bufferSize.height == uint32_t(-1));
1897 return q->size() * q->devicePixelRatio();
1898 }
1899 return QSize(int(bufferSize.width), int(bufferSize.height));
1900}
1901
1902void QVulkanWindowPrivate::beginFrame()
1903{
1904 if (!swapChain || framePending)
1905 return;
1906
1907 Q_Q(QVulkanWindow);
1908 if (swapChainImageSize != surfacePixelSize()) {
1909 recreateSwapChain();
1910 if (!swapChain)
1911 return;
1912 }
1913
1914 // wait if we are too far ahead
1915 FrameResources &frame(frameRes[currentFrame]);
1916 if (frame.cmdFenceWaitable) {
1917 devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
1918 devFuncs->vkResetFences(dev, 1, &frame.cmdFence);
1919 frame.cmdFenceWaitable = false;
1920 }
1921
1922 // move on to next swapchain image
1923 if (!frame.imageAcquired) {
1924 VkResult err = vkAcquireNextImageKHR(dev, swapChain, UINT64_MAX,
1925 frame.imageSem, VK_NULL_HANDLE, &currentImage);
1926 if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
1927 frame.imageSemWaitable = true;
1928 frame.imageAcquired = true;
1929 } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
1930 recreateSwapChain();
1931 q->requestUpdate();
1932 return;
1933 } else {
1934 if (!checkDeviceLost(err))
1935 qWarning("QVulkanWindow: Failed to acquire next swapchain image: %d", err);
1936 q->requestUpdate();
1937 return;
1938 }
1939 }
1940
1941 // build new draw command buffer
1942 if (frame.cmdBuf) {
1943 devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
1944 frame.cmdBuf = nullptr;
1945 }
1946
1947 VkCommandBufferAllocateInfo cmdBufInfo = {
1948 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
1949 VkResult err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &frame.cmdBuf);
1950 if (err != VK_SUCCESS) {
1951 if (!checkDeviceLost(err))
1952 qWarning("QVulkanWindow: Failed to allocate frame command buffer: %d", err);
1953 return;
1954 }
1955
1956 VkCommandBufferBeginInfo cmdBufBeginInfo = {
1957 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, 0, nullptr };
1958 err = devFuncs->vkBeginCommandBuffer(frame.cmdBuf, &cmdBufBeginInfo);
1959 if (err != VK_SUCCESS) {
1960 if (!checkDeviceLost(err))
1961 qWarning("QVulkanWindow: Failed to begin frame command buffer: %d", err);
1962 return;
1963 }
1964
1965 if (frameGrabbing)
1966 frameGrabTargetImage = QImage(swapChainImageSize, QImage::Format_RGBA8888); // the format is as documented
1967
1968 ImageResources &image(imageRes[currentImage]);
1969 if (renderer) {
1970 framePending = true;
1971 renderer->startNextFrame();
1972 // done for now - endFrame() will get invoked when frameReady() is called back
1973 } else {
1974 VkClearColorValue clearColor = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1975 VkClearDepthStencilValue clearDS = { 1.0f, 0 };
1976 VkClearValue clearValues[3];
1977 memset(clearValues, 0, sizeof(clearValues));
1978 clearValues[0].color = clearValues[2].color = clearColor;
1979 clearValues[1].depthStencil = clearDS;
1980
1981 VkRenderPassBeginInfo rpBeginInfo;
1982 memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
1983 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1984 rpBeginInfo.renderPass = defaultRenderPass;
1985 rpBeginInfo.framebuffer = image.fb;
1986 rpBeginInfo.renderArea.extent.width = swapChainImageSize.width();
1987 rpBeginInfo.renderArea.extent.height = swapChainImageSize.height();
1988 rpBeginInfo.clearValueCount = sampleCount > VK_SAMPLE_COUNT_1_BIT ? 3 : 2;
1989 rpBeginInfo.pClearValues = clearValues;
1990 devFuncs->vkCmdBeginRenderPass(frame.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
1991 devFuncs->vkCmdEndRenderPass(frame.cmdBuf);
1992
1993 endFrame();
1994 }
1995}
1996
1997void QVulkanWindowPrivate::endFrame()
1998{
1999 Q_Q(QVulkanWindow);
2000
2001 FrameResources &frame(frameRes[currentFrame]);
2002 ImageResources &image(imageRes[currentImage]);
2003
2004 if (gfxQueueFamilyIdx != presQueueFamilyIdx && !frameGrabbing) {
2005 // Add the swapchain image release to the command buffer that will be
2006 // submitted to the graphics queue.
2007 VkImageMemoryBarrier presTrans;
2008 memset(&presTrans, 0, sizeof(presTrans));
2009 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2010 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
2011 presTrans.oldLayout = presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2012 presTrans.srcQueueFamilyIndex = gfxQueueFamilyIdx;
2013 presTrans.dstQueueFamilyIndex = presQueueFamilyIdx;
2014 presTrans.image = image.image;
2015 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2016 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
2017 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2018 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2019 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
2020 0, 0, nullptr, 0, nullptr,
2021 1, &presTrans);
2022 }
2023
2024 // When grabbing a frame, add a readback at the end and skip presenting.
2025 if (frameGrabbing)
2026 addReadback();
2027
2028 VkResult err = devFuncs->vkEndCommandBuffer(frame.cmdBuf);
2029 if (err != VK_SUCCESS) {
2030 if (!checkDeviceLost(err))
2031 qWarning("QVulkanWindow: Failed to end frame command buffer: %d", err);
2032 return;
2033 }
2034
2035 // submit draw calls
2036 VkSubmitInfo submitInfo;
2037 memset(&submitInfo, 0, sizeof(submitInfo));
2038 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2039 submitInfo.commandBufferCount = 1;
2040 submitInfo.pCommandBuffers = &frame.cmdBuf;
2041 if (frame.imageSemWaitable) {
2042 submitInfo.waitSemaphoreCount = 1;
2043 submitInfo.pWaitSemaphores = &frame.imageSem;
2044 }
2045 if (!frameGrabbing) {
2046 submitInfo.signalSemaphoreCount = 1;
2047 submitInfo.pSignalSemaphores = &frame.drawSem;
2048 }
2049 VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
2050 submitInfo.pWaitDstStageMask = &psf;
2051
2052 Q_ASSERT(!frame.cmdFenceWaitable);
2053
2054 err = devFuncs->vkQueueSubmit(gfxQueue, 1, &submitInfo, frame.cmdFence);
2055 if (err == VK_SUCCESS) {
2056 frame.imageSemWaitable = false;
2057 frame.cmdFenceWaitable = true;
2058 } else {
2059 if (!checkDeviceLost(err))
2060 qWarning("QVulkanWindow: Failed to submit to graphics queue: %d", err);
2061 return;
2062 }
2063
2064 // block and then bail out when grabbing
2065 if (frameGrabbing) {
2066 finishBlockingReadback();
2067 frameGrabbing = false;
2068 // Leave frame.imageAcquired set to true.
2069 // Do not change currentFrame.
2070 emit q->frameGrabbed(frameGrabTargetImage);
2071 return;
2072 }
2073
2074 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
2075 // Submit the swapchain image acquire to the present queue.
2076 submitInfo.pWaitSemaphores = &frame.drawSem;
2077 submitInfo.pSignalSemaphores = &frame.presTransSem;
2078 submitInfo.pCommandBuffers = &image.presTransCmdBuf; // must be USAGE_SIMULTANEOUS
2079 err = devFuncs->vkQueueSubmit(presQueue, 1, &submitInfo, VK_NULL_HANDLE);
2080 if (err != VK_SUCCESS) {
2081 if (!checkDeviceLost(err))
2082 qWarning("QVulkanWindow: Failed to submit to present queue: %d", err);
2083 return;
2084 }
2085 }
2086
2087 // queue present
2088 VkPresentInfoKHR presInfo;
2089 memset(&presInfo, 0, sizeof(presInfo));
2090 presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
2091 presInfo.swapchainCount = 1;
2092 presInfo.pSwapchains = &swapChain;
2093 presInfo.pImageIndices = &currentImage;
2094 presInfo.waitSemaphoreCount = 1;
2095 presInfo.pWaitSemaphores = gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem;
2096
2097 // Do platform-specific WM notification. F.ex. essential on Wayland in
2098 // order to circumvent driver frame callbacks
2099 inst->presentAboutToBeQueued(q);
2100
2101 err = vkQueuePresentKHR(presQueue, &presInfo);
2102 if (err != VK_SUCCESS) {
2103 if (err == VK_ERROR_OUT_OF_DATE_KHR) {
2104 recreateSwapChain();
2105 q->requestUpdate();
2106 return;
2107 } else if (err != VK_SUBOPTIMAL_KHR) {
2108 if (!checkDeviceLost(err))
2109 qWarning("QVulkanWindow: Failed to present: %d", err);
2110 return;
2111 }
2112 }
2113
2114 frame.imageAcquired = false;
2115
2116 inst->presentQueued(q);
2117
2118 currentFrame = (currentFrame + 1) % frameLag;
2119}
2120
2121/*!
2122 This function must be called exactly once in response to each invocation of
2123 the QVulkanWindowRenderer::startNextFrame() implementation. At the time of
2124 this call, the main command buffer, exposed via currentCommandBuffer(),
2125 must have all necessary rendering commands added to it since this function
2126 will trigger submitting the commands and queuing the present command.
2127
2128 \note This function must only be called from the gui/main thread, which is
2129 where QVulkanWindowRenderer's functions are invoked and where the
2130 QVulkanWindow instance lives.
2131
2132 \sa QVulkanWindowRenderer::startNextFrame()
2133 */
2134void QVulkanWindow::frameReady()
2135{
2136 Q_ASSERT_X(QThread::isMainThread(),
2137 "QVulkanWindow", "frameReady() can only be called from the GUI (main) thread");
2138
2139 Q_D(QVulkanWindow);
2140
2141 if (!d->framePending) {
2142 qWarning("QVulkanWindow: frameReady() called without a corresponding startNextFrame()");
2143 return;
2144 }
2145
2146 d->framePending = false;
2147
2148 d->endFrame();
2149}
2150
2151bool QVulkanWindowPrivate::checkDeviceLost(VkResult err)
2152{
2153 if (err == VK_ERROR_DEVICE_LOST) {
2154 qWarning("QVulkanWindow: Device lost");
2155 if (renderer)
2156 renderer->logicalDeviceLost();
2157 qCDebug(lcGuiVk, "Releasing all resources due to device lost");
2158 releaseSwapChain();
2159 reset();
2160 qCDebug(lcGuiVk, "Restarting");
2161 ensureStarted();
2162 return true;
2163 }
2164 return false;
2165}
2166
2167void QVulkanWindowPrivate::addReadback()
2168{
2169 VkImageCreateInfo imageInfo;
2170 memset(&imageInfo, 0, sizeof(imageInfo));
2171 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2172 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2173 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2174 imageInfo.extent.width = frameGrabTargetImage.width();
2175 imageInfo.extent.height = frameGrabTargetImage.height();
2176 imageInfo.extent.depth = 1;
2177 imageInfo.mipLevels = 1;
2178 imageInfo.arrayLayers = 1;
2179 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2180 imageInfo.tiling = VK_IMAGE_TILING_LINEAR;
2181 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
2182 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2183
2184 VkResult err = devFuncs->vkCreateImage(dev, &imageInfo, nullptr, &frameGrabImage);
2185 if (err != VK_SUCCESS) {
2186 qWarning("QVulkanWindow: Failed to create image for readback: %d", err);
2187 return;
2188 }
2189
2190 VkMemoryRequirements memReq;
2191 devFuncs->vkGetImageMemoryRequirements(dev, frameGrabImage, &memReq);
2192
2193 VkMemoryAllocateInfo allocInfo = {
2194 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
2195 nullptr,
2196 memReq.size,
2197 hostVisibleMemIndex
2198 };
2199
2200 err = devFuncs->vkAllocateMemory(dev, &allocInfo, nullptr, &frameGrabImageMem);
2201 if (err != VK_SUCCESS) {
2202 qWarning("QVulkanWindow: Failed to allocate memory for readback image: %d", err);
2203 return;
2204 }
2205
2206 err = devFuncs->vkBindImageMemory(dev, frameGrabImage, frameGrabImageMem, 0);
2207 if (err != VK_SUCCESS) {
2208 qWarning("QVulkanWindow: Failed to bind readback image memory: %d", err);
2209 return;
2210 }
2211
2212 FrameResources &frame(frameRes[currentFrame]);
2213 ImageResources &image(imageRes[currentImage]);
2214
2215 VkImageMemoryBarrier barrier;
2216 memset(&barrier, 0, sizeof(barrier));
2217 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2218 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2219 barrier.subresourceRange.levelCount = barrier.subresourceRange.layerCount = 1;
2220
2221 barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2222 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
2223 barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2224 barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2225 barrier.image = image.image;
2226
2227 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2228 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2229 VK_PIPELINE_STAGE_TRANSFER_BIT,
2230 0, 0, nullptr, 0, nullptr,
2231 1, &barrier);
2232
2233 barrier.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2234 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2235 barrier.srcAccessMask = 0;
2236 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2237 barrier.image = frameGrabImage;
2238
2239 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2240 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
2241 VK_PIPELINE_STAGE_TRANSFER_BIT,
2242 0, 0, nullptr, 0, nullptr,
2243 1, &barrier);
2244
2245 VkImageCopy copyInfo;
2246 memset(&copyInfo, 0, sizeof(copyInfo));
2247 copyInfo.srcSubresource.aspectMask = copyInfo.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2248 copyInfo.srcSubresource.layerCount = copyInfo.dstSubresource.layerCount = 1;
2249 copyInfo.extent.width = frameGrabTargetImage.width();
2250 copyInfo.extent.height = frameGrabTargetImage.height();
2251 copyInfo.extent.depth = 1;
2252
2253 devFuncs->vkCmdCopyImage(frame.cmdBuf, image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
2254 frameGrabImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyInfo);
2255
2256 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2257 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
2258 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2259 barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
2260 barrier.image = frameGrabImage;
2261
2262 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2263 VK_PIPELINE_STAGE_TRANSFER_BIT,
2264 VK_PIPELINE_STAGE_HOST_BIT,
2265 0, 0, nullptr, 0, nullptr,
2266 1, &barrier);
2267}
2268
2269void QVulkanWindowPrivate::finishBlockingReadback()
2270{
2271 // Block until the current frame is done. Normally this wait would only be
2272 // done in current + concurrentFrameCount().
2273 FrameResources &frame(frameRes[currentFrame]);
2274 if (frame.cmdFenceWaitable) {
2275 devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
2276 devFuncs->vkResetFences(dev, 1, &frame.cmdFence);
2277 frame.cmdFenceWaitable = false;
2278 }
2279
2280 VkImageSubresource subres = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 };
2281 VkSubresourceLayout layout;
2282 devFuncs->vkGetImageSubresourceLayout(dev, frameGrabImage, &subres, &layout);
2283
2284 uchar *p;
2285 VkResult err = devFuncs->vkMapMemory(dev, frameGrabImageMem, layout.offset, layout.size, 0, reinterpret_cast<void **>(&p));
2286 if (err != VK_SUCCESS) {
2287 qWarning("QVulkanWindow: Failed to map readback image memory after transfer: %d", err);
2288 return;
2289 }
2290
2291 for (int y = 0; y < frameGrabTargetImage.height(); ++y) {
2292 memcpy(frameGrabTargetImage.scanLine(y), p, frameGrabTargetImage.width() * 4);
2293 p += layout.rowPitch;
2294 }
2295
2296 devFuncs->vkUnmapMemory(dev, frameGrabImageMem);
2297
2298 devFuncs->vkDestroyImage(dev, frameGrabImage, nullptr);
2299 frameGrabImage = VK_NULL_HANDLE;
2300 devFuncs->vkFreeMemory(dev, frameGrabImageMem, nullptr);
2301 frameGrabImageMem = VK_NULL_HANDLE;
2302}
2303
2304/*!
2305 Returns the active physical device.
2306
2307 \note Calling this function is only valid from the invocation of
2308 QVulkanWindowRenderer::preInitResources() up until
2309 QVulkanWindowRenderer::releaseResources().
2310 */
2311VkPhysicalDevice QVulkanWindow::physicalDevice() const
2312{
2313 Q_D(const QVulkanWindow);
2314 if (d->physDevIndex < d->physDevs.size())
2315 return d->physDevs[d->physDevIndex];
2316 qWarning("QVulkanWindow: Physical device not available");
2317 return VK_NULL_HANDLE;
2318}
2319
2320/*!
2321 Returns a pointer to the properties for the active physical device.
2322
2323 \note Calling this function is only valid from the invocation of
2324 QVulkanWindowRenderer::preInitResources() up until
2325 QVulkanWindowRenderer::releaseResources().
2326 */
2327const VkPhysicalDeviceProperties *QVulkanWindow::physicalDeviceProperties() const
2328{
2329 Q_D(const QVulkanWindow);
2330 if (d->physDevIndex < d->physDevProps.size())
2331 return &d->physDevProps[d->physDevIndex];
2332 qWarning("QVulkanWindow: Physical device properties not available");
2333 return nullptr;
2334}
2335
2336/*!
2337 Returns the active logical device.
2338
2339 \note Calling this function is only valid from the invocation of
2340 QVulkanWindowRenderer::initResources() up until
2341 QVulkanWindowRenderer::releaseResources().
2342 */
2343VkDevice QVulkanWindow::device() const
2344{
2345 Q_D(const QVulkanWindow);
2346 return d->dev;
2347}
2348
2349/*!
2350 Returns the active graphics queue.
2351
2352 \note Calling this function is only valid from the invocation of
2353 QVulkanWindowRenderer::initResources() up until
2354 QVulkanWindowRenderer::releaseResources().
2355 */
2356VkQueue QVulkanWindow::graphicsQueue() const
2357{
2358 Q_D(const QVulkanWindow);
2359 return d->gfxQueue;
2360}
2361
2362/*!
2363 Returns the family index of the active graphics queue.
2364
2365 \note Calling this function is only valid from the invocation of
2366 QVulkanWindowRenderer::initResources() up until
2367 QVulkanWindowRenderer::releaseResources(). Implementations of
2368 QVulkanWindowRenderer::updateQueueCreateInfo() can also call this
2369 function.
2370
2371 \since 5.15
2372 */
2373uint32_t QVulkanWindow::graphicsQueueFamilyIndex() const
2374{
2375 Q_D(const QVulkanWindow);
2376 return d->gfxQueueFamilyIdx;
2377}
2378
2379/*!
2380 Returns the active graphics command pool.
2381
2382 \note Calling this function is only valid from the invocation of
2383 QVulkanWindowRenderer::initResources() up until
2384 QVulkanWindowRenderer::releaseResources().
2385 */
2386VkCommandPool QVulkanWindow::graphicsCommandPool() const
2387{
2388 Q_D(const QVulkanWindow);
2389 return d->cmdPool;
2390}
2391
2392/*!
2393 Returns a host visible memory type index suitable for general use.
2394
2395 The returned memory type will be both host visible and coherent. In
2396 addition, it will also be cached, if possible.
2397
2398 \note Calling this function is only valid from the invocation of
2399 QVulkanWindowRenderer::initResources() up until
2400 QVulkanWindowRenderer::releaseResources().
2401 */
2402uint32_t QVulkanWindow::hostVisibleMemoryIndex() const
2403{
2404 Q_D(const QVulkanWindow);
2405 return d->hostVisibleMemIndex;
2406}
2407
2408/*!
2409 Returns a device local memory type index suitable for general use.
2410
2411 \note Calling this function is only valid from the invocation of
2412 QVulkanWindowRenderer::initResources() up until
2413 QVulkanWindowRenderer::releaseResources().
2414
2415 \note It is not guaranteed that this memory type is always suitable. The
2416 correct, cross-implementation solution - especially for device local images
2417 - is to manually pick a memory type after checking the mask returned from
2418 \c{vkGetImageMemoryRequirements}.
2419 */
2420uint32_t QVulkanWindow::deviceLocalMemoryIndex() const
2421{
2422 Q_D(const QVulkanWindow);
2423 return d->deviceLocalMemIndex;
2424}
2425
2426/*!
2427 Returns a typical render pass with one sub-pass.
2428
2429 \note Applications are not required to use this render pass. However, they
2430 are then responsible for ensuring the current swap chain and depth-stencil
2431 images get transitioned from \c{VK_IMAGE_LAYOUT_UNDEFINED} to
2432 \c{VK_IMAGE_LAYOUT_PRESENT_SRC_KHR} and
2433 \c{VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL} either via the
2434 application's custom render pass or by other means.
2435
2436 \note Stencil read/write is not enabled in this render pass.
2437
2438 \note Calling this function is only valid from the invocation of
2439 QVulkanWindowRenderer::initResources() up until
2440 QVulkanWindowRenderer::releaseResources().
2441
2442 \sa currentFramebuffer()
2443 */
2444VkRenderPass QVulkanWindow::defaultRenderPass() const
2445{
2446 Q_D(const QVulkanWindow);
2447 return d->defaultRenderPass;
2448}
2449
2450/*!
2451 Returns the color buffer format used by the swapchain.
2452
2453 \note Calling this function is only valid from the invocation of
2454 QVulkanWindowRenderer::initResources() up until
2455 QVulkanWindowRenderer::releaseResources().
2456
2457 \sa setPreferredColorFormats()
2458 */
2459VkFormat QVulkanWindow::colorFormat() const
2460{
2461 Q_D(const QVulkanWindow);
2462 return d->colorFormat;
2463}
2464
2465/*!
2466 Returns the format used by the depth-stencil buffer(s).
2467
2468 \note Calling this function is only valid from the invocation of
2469 QVulkanWindowRenderer::initResources() up until
2470 QVulkanWindowRenderer::releaseResources().
2471 */
2472VkFormat QVulkanWindow::depthStencilFormat() const
2473{
2474 Q_D(const QVulkanWindow);
2475 return d->dsFormat;
2476}
2477
2478/*!
2479 Returns the image size of the swapchain.
2480
2481 This usually matches the size of the window, but may also differ in case
2482 \c vkGetPhysicalDeviceSurfaceCapabilitiesKHR reports a fixed size.
2483
2484 In addition, it has been observed on some platforms that the
2485 Vulkan-reported surface size is different with high DPI scaling active,
2486 meaning the QWindow-reported
2487 \l{QWindow::}{size()} multiplied with the \l{QWindow::}{devicePixelRatio()}
2488 was 1 pixel less or more when compared to the value returned from here,
2489 presumably due to differences in rounding. Rendering code should be aware
2490 of this, and any related rendering logic must be based in the value returned
2491 from here, never on the QWindow-reported size. Regardless of which pixel size
2492 is correct in theory, Vulkan rendering must only ever rely on the Vulkan
2493 API-reported surface size. Otherwise validation errors may occur, e.g. when
2494 setting the viewport, because the application-provided values may become
2495 out-of-bounds from Vulkan's perspective.
2496
2497 \note Calling this function is only valid from the invocation of
2498 QVulkanWindowRenderer::initSwapChainResources() up until
2499 QVulkanWindowRenderer::releaseSwapChainResources().
2500 */
2501QSize QVulkanWindow::swapChainImageSize() const
2502{
2503 Q_D(const QVulkanWindow);
2504 return d->swapChainImageSize;
2505}
2506
2507/*!
2508 Returns The active command buffer for the current swap chain frame.
2509 Implementations of QVulkanWindowRenderer::startNextFrame() are expected to
2510 add commands to this command buffer.
2511
2512 \note This function must only be called from within startNextFrame() and, in
2513 case of asynchronous command generation, up until the call to frameReady().
2514 */
2515VkCommandBuffer QVulkanWindow::currentCommandBuffer() const
2516{
2517 Q_D(const QVulkanWindow);
2518 if (!d->framePending) {
2519 qWarning("QVulkanWindow: Attempted to call currentCommandBuffer() without an active frame");
2520 return VK_NULL_HANDLE;
2521 }
2522 return d->frameRes[d->currentFrame].cmdBuf;
2523}
2524
2525/*!
2526 Returns a VkFramebuffer for the current swapchain image using the default
2527 render pass.
2528
2529 The framebuffer has two attachments (color, depth-stencil) when
2530 multisampling is not in use, and three (color resolve, depth-stencil,
2531 multisample color) when sampleCountFlagBits() is greater than
2532 \c{VK_SAMPLE_COUNT_1_BIT}. Renderers must take this into account, for
2533 example when providing clear values.
2534
2535 \note Applications are not required to use this framebuffer in case they
2536 provide their own render pass instead of using the one returned from
2537 defaultRenderPass().
2538
2539 \note This function must only be called from within startNextFrame() and, in
2540 case of asynchronous command generation, up until the call to frameReady().
2541
2542 \sa defaultRenderPass()
2543 */
2544VkFramebuffer QVulkanWindow::currentFramebuffer() const
2545{
2546 Q_D(const QVulkanWindow);
2547 if (!d->framePending) {
2548 qWarning("QVulkanWindow: Attempted to call currentFramebuffer() without an active frame");
2549 return VK_NULL_HANDLE;
2550 }
2551 return d->imageRes[d->currentImage].fb;
2552}
2553
2554/*!
2555 Returns the current frame index in the range [0, concurrentFrameCount() - 1].
2556
2557 Renderer implementations will have to ensure that uniform data and other
2558 dynamic resources exist in multiple copies, in order to prevent frame N
2559 altering the data used by the still-active frames N - 1, N - 2, ... N -
2560 concurrentFrameCount() + 1.
2561
2562 To avoid relying on dynamic array sizes, applications can use
2563 MAX_CONCURRENT_FRAME_COUNT when declaring arrays. This is guaranteed to be
2564 always equal to or greater than the value returned from
2565 concurrentFrameCount(). Such arrays can then be indexed by the value
2566 returned from this function.
2567
2568 \snippet code/src_gui_vulkan_qvulkanwindow.cpp 1
2569
2570 \note This function must only be called from within startNextFrame() and, in
2571 case of asynchronous command generation, up until the call to frameReady().
2572
2573 \sa concurrentFrameCount()
2574 */
2575int QVulkanWindow::currentFrame() const
2576{
2577 Q_D(const QVulkanWindow);
2578 if (!d->framePending)
2579 qWarning("QVulkanWindow: Attempted to call currentFrame() without an active frame");
2580 return d->currentFrame;
2581}
2582
2583/*!
2584 \variable QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT
2585
2586 \brief A constant value that is always equal to or greater than the maximum value
2587 of concurrentFrameCount().
2588 */
2589
2590/*!
2591 Returns the number of frames that can be potentially active at the same time.
2592
2593 \note The value is constant for the entire lifetime of the QVulkanWindow.
2594
2595 \snippet code/src_gui_vulkan_qvulkanwindow.cpp 2
2596
2597 \sa currentFrame()
2598 */
2599int QVulkanWindow::concurrentFrameCount() const
2600{
2601 Q_D(const QVulkanWindow);
2602 return d->frameLag;
2603}
2604
2605/*!
2606 Returns the number of images in the swap chain.
2607
2608 \note Accessing this is necessary when providing a custom render pass and
2609 framebuffer. The framebuffer is specific to the current swapchain image and
2610 hence the application must provide multiple framebuffers.
2611
2612 \note Calling this function is only valid from the invocation of
2613 QVulkanWindowRenderer::initSwapChainResources() up until
2614 QVulkanWindowRenderer::releaseSwapChainResources().
2615 */
2616int QVulkanWindow::swapChainImageCount() const
2617{
2618 Q_D(const QVulkanWindow);
2619 return d->swapChainBufferCount;
2620}
2621
2622/*!
2623 Returns the current swap chain image index in the range [0, swapChainImageCount() - 1].
2624
2625 \note This function must only be called from within startNextFrame() and, in
2626 case of asynchronous command generation, up until the call to frameReady().
2627 */
2628int QVulkanWindow::currentSwapChainImageIndex() const
2629{
2630 Q_D(const QVulkanWindow);
2631 if (!d->framePending)
2632 qWarning("QVulkanWindow: Attempted to call currentSwapChainImageIndex() without an active frame");
2633 return d->currentImage;
2634}
2635
2636/*!
2637 Returns the specified swap chain image.
2638
2639 \a idx must be in the range [0, swapChainImageCount() - 1].
2640
2641 \note Calling this function is only valid from the invocation of
2642 QVulkanWindowRenderer::initSwapChainResources() up until
2643 QVulkanWindowRenderer::releaseSwapChainResources().
2644 */
2645VkImage QVulkanWindow::swapChainImage(int idx) const
2646{
2647 Q_D(const QVulkanWindow);
2648 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].image : VK_NULL_HANDLE;
2649}
2650
2651/*!
2652 Returns the specified swap chain image view.
2653
2654 \a idx must be in the range [0, swapChainImageCount() - 1].
2655
2656 \note Calling this function is only valid from the invocation of
2657 QVulkanWindowRenderer::initSwapChainResources() up until
2658 QVulkanWindowRenderer::releaseSwapChainResources().
2659 */
2660VkImageView QVulkanWindow::swapChainImageView(int idx) const
2661{
2662 Q_D(const QVulkanWindow);
2663 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].imageView : VK_NULL_HANDLE;
2664}
2665
2666/*!
2667 Returns the depth-stencil image.
2668
2669 \note Calling this function is only valid from the invocation of
2670 QVulkanWindowRenderer::initSwapChainResources() up until
2671 QVulkanWindowRenderer::releaseSwapChainResources().
2672 */
2673VkImage QVulkanWindow::depthStencilImage() const
2674{
2675 Q_D(const QVulkanWindow);
2676 return d->dsImage;
2677}
2678
2679/*!
2680 Returns the depth-stencil image view.
2681
2682 \note Calling this function is only valid from the invocation of
2683 QVulkanWindowRenderer::initSwapChainResources() up until
2684 QVulkanWindowRenderer::releaseSwapChainResources().
2685 */
2686VkImageView QVulkanWindow::depthStencilImageView() const
2687{
2688 Q_D(const QVulkanWindow);
2689 return d->dsView;
2690}
2691
2692/*!
2693 Returns the current sample count as a \c VkSampleCountFlagBits value.
2694
2695 When targeting the default render target, the \c rasterizationSamples field
2696 of \c VkPipelineMultisampleStateCreateInfo must be set to this value.
2697
2698 \sa setSampleCount(), supportedSampleCounts()
2699 */
2700VkSampleCountFlagBits QVulkanWindow::sampleCountFlagBits() const
2701{
2702 Q_D(const QVulkanWindow);
2703 return d->sampleCount;
2704}
2705
2706/*!
2707 Returns the specified multisample color image, or \c{VK_NULL_HANDLE} if
2708 multisampling is not in use.
2709
2710 \a idx must be in the range [0, swapChainImageCount() - 1].
2711
2712 \note Calling this function is only valid from the invocation of
2713 QVulkanWindowRenderer::initSwapChainResources() up until
2714 QVulkanWindowRenderer::releaseSwapChainResources().
2715 */
2716VkImage QVulkanWindow::msaaColorImage(int idx) const
2717{
2718 Q_D(const QVulkanWindow);
2719 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].msaaImage : VK_NULL_HANDLE;
2720}
2721
2722/*!
2723 Returns the specified multisample color image view, or \c{VK_NULL_HANDLE} if
2724 multisampling is not in use.
2725
2726 \a idx must be in the range [0, swapChainImageCount() - 1].
2727
2728 \note Calling this function is only valid from the invocation of
2729 QVulkanWindowRenderer::initSwapChainResources() up until
2730 QVulkanWindowRenderer::releaseSwapChainResources().
2731 */
2732VkImageView QVulkanWindow::msaaColorImageView(int idx) const
2733{
2734 Q_D(const QVulkanWindow);
2735 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].msaaImageView : VK_NULL_HANDLE;
2736}
2737
2738/*!
2739 Returns true if the swapchain supports usage as transfer source, meaning
2740 grab() is functional.
2741
2742 \note Calling this function is only valid from the invocation of
2743 QVulkanWindowRenderer::initSwapChainResources() up until
2744 QVulkanWindowRenderer::releaseSwapChainResources().
2745 */
2746bool QVulkanWindow::supportsGrab() const
2747{
2748 Q_D(const QVulkanWindow);
2749 return d->swapChainSupportsReadBack;
2750}
2751
2752/*!
2753 \fn void QVulkanWindow::frameGrabbed(const QImage &image)
2754
2755 This signal is emitted when the \a image is ready.
2756*/
2757
2758/*!
2759 Builds and renders the next frame without presenting it, then performs a
2760 blocking readback of the image content.
2761
2762 Returns the image if the renderer's
2763 \l{QVulkanWindowRenderer::startNextFrame()}{startNextFrame()}
2764 implementation calls back frameReady() directly. Otherwise, returns an
2765 incomplete image, that has the correct size but not the content yet. The
2766 content will be delivered via the frameGrabbed() signal in the latter case.
2767
2768 The returned QImage always has a format of QImage::Format_RGBA8888. If the
2769 colorFormat() is \c VK_FORMAT_B8G8R8A8_UNORM, the red and blue channels are
2770 swapped automatically since this format is commonly used as the default
2771 choice for swapchain color buffers. With any other color buffer format,
2772 there is no conversion performed by this function.
2773
2774 \note This function should not be called when a frame is in progress
2775 (that is, frameReady() has not yet been called back by the application).
2776
2777 \note This function is potentially expensive due to the additional,
2778 blocking readback.
2779
2780 \note This function currently requires that the swapchain supports usage as
2781 a transfer source (\c{VK_IMAGE_USAGE_TRANSFER_SRC_BIT}), and will fail otherwise.
2782 */
2783QImage QVulkanWindow::grab()
2784{
2785 Q_D(QVulkanWindow);
2786 if (!d->swapChain) {
2787 qWarning("QVulkanWindow: Attempted to call grab() without a swapchain");
2788 return QImage();
2789 }
2790 if (d->framePending) {
2791 qWarning("QVulkanWindow: Attempted to call grab() while a frame is still pending");
2792 return QImage();
2793 }
2794 if (!d->swapChainSupportsReadBack) {
2795 qWarning("QVulkanWindow: Attempted to call grab() with a swapchain that does not support usage as transfer source");
2796 return QImage();
2797 }
2798
2799 d->frameGrabbing = true;
2800 d->beginFrame();
2801
2802 if (d->colorFormat == VK_FORMAT_B8G8R8A8_UNORM)
2803 d->frameGrabTargetImage = std::move(d->frameGrabTargetImage).rgbSwapped();
2804
2805 return d->frameGrabTargetImage;
2806}
2807
2808/*!
2809 Returns a QMatrix4x4 that can be used to correct for coordinate
2810 system differences between OpenGL and Vulkan.
2811
2812 By pre-multiplying the projection matrix with this matrix, applications can
2813 continue to assume that Y is pointing upwards, and can set minDepth and
2814 maxDepth in the viewport to 0 and 1, respectively, without having to do any
2815 further corrections to the vertex Z positions. Geometry from OpenGL
2816 applications can then be used as-is, assuming a rasterization state matching
2817 the OpenGL culling and front face settings.
2818 */
2819QMatrix4x4 QVulkanWindow::clipCorrectionMatrix()
2820{
2821 Q_D(QVulkanWindow);
2822 if (d->m_clipCorrect.isIdentity()) {
2823 // NB the ctor takes row-major
2824 d->m_clipCorrect = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
2825 0.0f, -1.0f, 0.0f, 0.0f,
2826 0.0f, 0.0f, 0.5f, 0.5f,
2827 0.0f, 0.0f, 0.0f, 1.0f);
2828 }
2829 return d->m_clipCorrect;
2830}
2831
2832QT_END_NAMESPACE
2833
2834#include "moc_qvulkanwindow.cpp"
VkSampleCountFlagBits mask
int count
static VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)