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