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