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

source code of qtbase/src/gui/vulkan/qvulkanwindow.cpp