1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qvulkanwindow_p.h"
41#include "qvulkanfunctions.h"
42#include <QLoggingCategory>
43#include <QTimer>
44#include <QThread>
45#include <QCoreApplication>
46#include <qevent.h>
47
48QT_BEGIN_NAMESPACE
49
50Q_LOGGING_CATEGORY(lcGuiVk, "qt.vulkan")
51
52/*!
53 \class QVulkanWindow
54 \inmodule QtGui
55 \since 5.10
56 \brief The QVulkanWindow class is a convenience subclass of QWindow to perform Vulkan rendering.
57
58 QVulkanWindow is a Vulkan-capable QWindow that manages a Vulkan device, a
59 graphics queue, a command pool and buffer, a depth-stencil image and a
60 double-buffered FIFO swapchain, while taking care of correct behavior when it
61 comes to events like resize, special situations like not having a device
62 queue supporting both graphics and presentation, device lost scenarios, and
63 additional functionality like reading the rendered content back. Conceptually
64 it is the counterpart of QOpenGLWindow in the Vulkan world.
65
66 \note QVulkanWindow does not always eliminate the need to implement a fully
67 custom QWindow subclass as it will not necessarily be sufficient in advanced
68 use cases.
69
70 QVulkanWindow can be embedded into QWidget-based user interfaces via
71 QWidget::createWindowContainer(). This approach has a number of limitations,
72 however. Make sure to study the
73 \l{QWidget::createWindowContainer()}{documentation} first.
74
75 A typical application using QVulkanWindow may look like the following:
76
77 \snippet code/src_gui_vulkan_qvulkanwindow.cpp 0
78
79 As it can be seen in the example, the main patterns in QVulkanWindow usage are:
80
81 \list
82
83 \li The QVulkanInstance is associated via QWindow::setVulkanInstance(). It is
84 then retrievable via QWindow::vulkanInstance() from everywhere, on any
85 thread.
86
87 \li Similarly to QVulkanInstance, device extensions can be queried via
88 supportedDeviceExtensions() before the actual initialization. Requesting an
89 extension to be enabled is done via setDeviceExtensions(). Such calls must be
90 made before the window becomes visible, that is, before calling show() or
91 similar functions. Unsupported extension requests are gracefully ignored.
92
93 \li The renderer is implemented in a QVulkanWindowRenderer subclass, an
94 instance of which is created in the createRenderer() factory function.
95
96 \li The core Vulkan commands are exposed via the QVulkanFunctions object,
97 retrievable by calling QVulkanInstance::functions(). Device level functions
98 are available after creating a VkDevice by calling
99 QVulkanInstance::deviceFunctions().
100
101 \li The building of the draw calls for the next frame happens in
102 QVulkanWindowRenderer::startNextFrame(). The implementation is expected to
103 add commands to the command buffer returned from currentCommandBuffer().
104 Returning from the function does not indicate that the commands are ready for
105 submission. Rather, an explicit call to frameReady() is required. This allows
106 asynchronous generation of commands, possibly on multiple threads. Simple
107 implementations will simply call frameReady() at the end of their
108 QVulkanWindowRenderer::startNextFrame().
109
110 \li The basic Vulkan resources (physical device, graphics queue, a command
111 pool, the window's main command buffer, image formats, etc.) are exposed on
112 the QVulkanWindow via lightweight getter functions. Some of these are for
113 convenience only, and applications are always free to query, create and
114 manage additional resources directly via the Vulkan API.
115
116 \li The renderer lives in the gui/main thread, like the window itself. This
117 thread is then throttled to the presentation rate, similarly to how OpenGL
118 with a swap interval of 1 would behave. However, the renderer implementation
119 is free to utilize multiple threads in any way it sees fit. The accessors
120 like vulkanInstance(), currentCommandBuffer(), etc. can be called from any
121 thread. The submission of the main command buffer, the queueing of present,
122 and the building of the next frame do not start until frameReady() is
123 invoked on the gui/main thread.
124
125 \li When the window is made visible, the content is updated automatically.
126 Further updates can be requested by calling QWindow::requestUpdate(). To
127 render continuously, call requestUpdate() after frameReady().
128
129 \endlist
130
131 For troubleshooting, enable the logging category \c{qt.vulkan}. Critical
132 errors are printed via qWarning() automatically.
133
134 \section1 Coordinate system differences between OpenGL and Vulkan
135
136 There are two notable differences to be aware of: First, with Vulkan Y points
137 down the screen in clip space, while OpenGL uses an upwards pointing Y axis.
138 Second, the standard OpenGL projection matrix assume a near and far plane
139 values of -1 and 1, while Vulkan prefers 0 and 1.
140
141 In order to help applications migrate from OpenGL-based code without having
142 to flip Y coordinates in the vertex data, and to allow using QMatrix4x4
143 functions like QMatrix4x4::perspective() while keeping the Vulkan viewport's
144 minDepth and maxDepth set to 0 and 1, QVulkanWindow provides a correction
145 matrix retrievable by calling clipCorrectionMatrix().
146
147 \section1 Multisampling
148
149 While disabled by default, multisample antialiasing is fully supported by
150 QVulkanWindow. Additional color buffers and resolving into the swapchain's
151 non-multisample buffers are all managed automatically.
152
153 To query the supported sample counts, call supportedSampleCounts(). When the
154 returned set contains 4, 8, ..., passing one of those values to setSampleCount()
155 requests multisample rendering.
156
157 \note unlike QSurfaceFormat::setSamples(), the list of supported sample
158 counts are exposed to the applications in advance and there is no automatic
159 falling back to lower sample counts in setSampleCount(). If the requested value
160 is not supported, a warning is shown and a no multisampling will be used.
161
162 \section1 Reading images back
163
164 When supportsGrab() returns true, QVulkanWindow can perform readbacks from
165 the color buffer into a QImage. grab() is a slow and inefficient operation,
166 so frequent usage should be avoided. It is nonetheless valuable since it
167 allows applications to take screenshots, or tools and tests to process and
168 verify the output of the GPU rendering.
169
170 \section1 sRGB support
171
172 While many applications will be fine with the default behavior of
173 QVulkanWindow when it comes to swapchain image formats,
174 setPreferredColorFormats() allows requesting a pre-defined format. This is
175 useful most notably when working in the sRGB color space. Passing a format
176 like \c{VK_FORMAT_B8G8R8A8_SRGB} results in choosing an sRGB format, when
177 available.
178
179 \section1 Validation layers
180
181 During application development it can be extremely valuable to have the
182 Vulkan validation layers enabled. As shown in the example code above, calling
183 QVulkanInstance::setLayers() on the QVulkanInstance before
184 QVulkanInstance::create() enables validation, assuming the Vulkan driver
185 stack in the system contains the necessary layers.
186
187 \note Be aware of platform-specific differences. On desktop platforms
188 installing the \l{https://www.lunarg.com/vulkan-sdk/}{Vulkan SDK} is
189 typically sufficient. However, Android for example requires deploying
190 additional shared libraries together with the application, and also mandates
191 a different list of validation layer names. See
192 \l{https://developer.android.com/ndk/guides/graphics/validation-layer.html}{the
193 Android Vulkan development pages} for more information.
194
195 \note QVulkanWindow does not expose device layers since this functionality
196 has been deprecated since version 1.0.13 of the Vulkan API.
197
198 \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 */
225QVulkanWindow::QVulkanWindow(QWindow *parent)
226 : QWindow(*(new QVulkanWindowPrivate), parent)
227{
228 setSurfaceType(QSurface::VulkanSurface);
229}
230
231/*!
232 Destructor.
233*/
234QVulkanWindow::~QVulkanWindow()
235{
236}
237
238QVulkanWindowPrivate::~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 */
263void 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 */
276QVulkanWindow::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 */
287QList<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 QList<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 */
341void 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 */
362QVulkanInfoVector<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 QList<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 */
413void 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 */
448void QVulkanWindow::setPreferredColorFormats(const QList<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
458static 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 list.
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 */
484QList<int> QVulkanWindow::supportedSampleCounts()
485{
486 Q_D(const QVulkanWindow);
487 QList<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 */
534void 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
560void 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 QList<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 QList<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 QList<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 QList<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 QList<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
898void 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
949bool 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
1021void 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
1283uint32_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
1315static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
1316{
1317 return (v + byteAlign - 1) & ~(byteAlign - 1);
1318}
1319
1320bool 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
1411void 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 */
1513void 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
1527void 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 */
1544void 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 */
1552bool 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 that is called during graphics initialization to add
1583 additional 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 Sets the queue create info modification function \a modifier.
1600
1601 \sa QueueCreateInfoModifier
1602
1603 \since 5.15
1604 */
1605void 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 */
1619bool 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 */
1636QVulkanWindowRenderer *QVulkanWindow::createRenderer()
1637{
1638 return nullptr;
1639}
1640
1641/*!
1642 Virtual destructor.
1643 */
1644QVulkanWindowRenderer::~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 */
1664void 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 */
1683void 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 */
1705void 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 */
1728void 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 */
1744void QVulkanWindowRenderer::releaseResources()
1745{
1746}
1747
1748/*!
1749 \fn void 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 */
1789void 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 */
1808void QVulkanWindowRenderer::logicalDeviceLost()
1809{
1810}
1811
1812void 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
1917void 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 */
2054void 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
2071bool 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
2087void 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
2188void 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 */
2230VkPhysicalDevice 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 */
2246const 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 */
2262VkDevice 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 */
2275VkQueue 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 */
2292uint32_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 */
2305VkCommandPool 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 */
2321uint32_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 */
2339uint32_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 */
2363VkRenderPass 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 */
2378VkFormat 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 */
2391VkFormat 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 */
2407QSize 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 */
2421VkCommandBuffer 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 */
2450VkFramebuffer 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 */
2481int 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 */
2505int 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 */
2522int 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 */
2534int 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 */
2551VkImage 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 */
2566VkImageView 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 */
2579VkImage 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 */
2592VkImageView 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 */
2606VkSampleCountFlagBits 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 */
2622VkImage 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 */
2638VkImageView 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 */
2652bool 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 */
2683QImage 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 */
2716QMatrix4x4 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
2729QT_END_NAMESPACE
2730