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 (auto ext : reqExts)
686 envExtList.removeAll(ext);
687 reqExts.append(envExtList);
688 }
689
690 for (const QByteArray &ext : 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 // Device layers are not supported by QVulkanWindow since that's an already deprecated
722 // API. However, have a workaround for systems with older API and layers (f.ex. L4T
723 // 24.2 for the Jetson TX1 provides API 1.0.13 and crashes when the validation layer
724 // is enabled for the instance but not the device).
725 uint32_t apiVersion = physDevProps[physDevIndex].apiVersion;
726 if (VK_VERSION_MAJOR(apiVersion) == 1
727 && VK_VERSION_MINOR(apiVersion) == 0
728 && VK_VERSION_PATCH(apiVersion) <= 13)
729 {
730 // Make standard validation work at least.
731 const QByteArray stdValName = QByteArrayLiteral("VK_LAYER_KHRONOS_validation");
732 const char *stdValNamePtr = stdValName.constData();
733 if (inst->layers().contains(stdValName)) {
734 uint32_t count = 0;
735 VkResult err = f->vkEnumerateDeviceLayerProperties(physDev, &count, nullptr);
736 if (err == VK_SUCCESS) {
737 QList<VkLayerProperties> layerProps(count);
738 err = f->vkEnumerateDeviceLayerProperties(physDev, &count, layerProps.data());
739 if (err == VK_SUCCESS) {
740 for (const VkLayerProperties &prop : layerProps) {
741 if (!strncmp(prop.layerName, stdValNamePtr, stdValName.size())) {
742 devInfo.enabledLayerCount = 1;
743 devInfo.ppEnabledLayerNames = &stdValNamePtr;
744 break;
745 }
746 }
747 }
748 }
749 }
750 }
751
752 VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
753 if (err == VK_ERROR_DEVICE_LOST) {
754 qWarning("QVulkanWindow: Physical device lost");
755 if (renderer)
756 renderer->physicalDeviceLost();
757 // clear the caches so the list of physical devices is re-queried
758 physDevs.clear();
759 physDevProps.clear();
760 status = StatusUninitialized;
761 qCDebug(lcGuiVk, "Attempting to restart in 2 seconds");
762 QTimer::singleShot(2000, q, [this]() { ensureStarted(); });
763 return;
764 }
765 if (err != VK_SUCCESS) {
766 qWarning("QVulkanWindow: Failed to create device: %d", err);
767 status = StatusFail;
768 return;
769 }
770
771 devFuncs = inst->deviceFunctions(dev);
772 Q_ASSERT(devFuncs);
773
774 devFuncs->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, 0, &gfxQueue);
775 if (gfxQueueFamilyIdx == presQueueFamilyIdx)
776 presQueue = gfxQueue;
777 else
778 devFuncs->vkGetDeviceQueue(dev, presQueueFamilyIdx, 0, &presQueue);
779
780 VkCommandPoolCreateInfo poolInfo;
781 memset(&poolInfo, 0, sizeof(poolInfo));
782 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
783 poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
784 err = devFuncs->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
785 if (err != VK_SUCCESS) {
786 qWarning("QVulkanWindow: Failed to create command pool: %d", err);
787 status = StatusFail;
788 return;
789 }
790 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
791 poolInfo.queueFamilyIndex = presQueueFamilyIdx;
792 err = devFuncs->vkCreateCommandPool(dev, &poolInfo, nullptr, &presCmdPool);
793 if (err != VK_SUCCESS) {
794 qWarning("QVulkanWindow: Failed to create command pool for present queue: %d", err);
795 status = StatusFail;
796 return;
797 }
798 }
799
800 hostVisibleMemIndex = 0;
801 VkPhysicalDeviceMemoryProperties physDevMemProps;
802 bool hostVisibleMemIndexSet = false;
803 f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
804 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
805 const VkMemoryType *memType = physDevMemProps.memoryTypes;
806 qCDebug(lcGuiVk, "memtype %d: flags=0x%x", i, memType[i].propertyFlags);
807 // Find a host visible, host coherent memtype. If there is one that is
808 // cached as well (in addition to being coherent), prefer that.
809 const int hostVisibleAndCoherent = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
810 if ((memType[i].propertyFlags & hostVisibleAndCoherent) == hostVisibleAndCoherent) {
811 if (!hostVisibleMemIndexSet
812 || (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)) {
813 hostVisibleMemIndexSet = true;
814 hostVisibleMemIndex = i;
815 }
816 }
817 }
818 qCDebug(lcGuiVk, "Picked memtype %d for host visible memory", hostVisibleMemIndex);
819 deviceLocalMemIndex = 0;
820 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
821 const VkMemoryType *memType = physDevMemProps.memoryTypes;
822 // Just pick the first device local memtype.
823 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
824 deviceLocalMemIndex = i;
825 break;
826 }
827 }
828 qCDebug(lcGuiVk, "Picked memtype %d for device local memory", deviceLocalMemIndex);
829
830 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR) {
831 vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
832 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
833 vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
834 inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
835 if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR || !vkGetPhysicalDeviceSurfaceFormatsKHR) {
836 qWarning("QVulkanWindow: Physical device surface queries not available");
837 status = StatusFail;
838 return;
839 }
840 }
841
842 // Figure out the color format here. Must not wait until recreateSwapChain()
843 // because the renderpass should be available already from initResources (so
844 // that apps do not have to defer pipeline creation to
845 // initSwapChainResources), but the renderpass needs the final color format.
846
847 uint32_t formatCount = 0;
848 vkGetPhysicalDeviceSurfaceFormatsKHR(physDev, surface, &formatCount, nullptr);
849 QList<VkSurfaceFormatKHR> formats(formatCount);
850 if (formatCount)
851 vkGetPhysicalDeviceSurfaceFormatsKHR(physDev, surface, &formatCount, formats.data());
852
853 colorFormat = VK_FORMAT_B8G8R8A8_UNORM; // our documented default if all else fails
854 colorSpace = VkColorSpaceKHR(0); // this is in fact VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
855
856 // Pick the preferred format, if there is one.
857 if (!formats.isEmpty() && formats[0].format != VK_FORMAT_UNDEFINED) {
858 colorFormat = formats[0].format;
859 colorSpace = formats[0].colorSpace;
860 }
861
862 // Try to honor the user request.
863 if (!formats.isEmpty() && !requestedColorFormats.isEmpty()) {
864 for (VkFormat reqFmt : std::as_const(requestedColorFormats)) {
865 auto r = std::find_if(formats.cbegin(), formats.cend(),
866 [reqFmt](const VkSurfaceFormatKHR &sfmt) { return sfmt.format == reqFmt; });
867 if (r != formats.cend()) {
868 colorFormat = r->format;
869 colorSpace = r->colorSpace;
870 break;
871 }
872 }
873 }
874
875#if QT_CONFIG(wayland)
876 // On Wayland, only one color management surface can be created at a time without
877 // triggering a protocol error, and we create one ourselves in some situations.
878 // To avoid this problem, use VK_COLOR_SPACE_PASS_THROUGH_EXT when supported,
879 // so that the driver doesn't create a color management surface as well.
880 const bool hasPassthrough = std::any_of(formats.cbegin(), formats.cend(), [this](const VkSurfaceFormatKHR &format) {
881 return format.format == colorFormat && format.colorSpace == VK_COLOR_SPACE_PASS_THROUGH_EXT;
882 });
883 if (hasPassthrough) {
884 colorSpace = VK_COLOR_SPACE_PASS_THROUGH_EXT;
885 }
886#endif
887
888 const VkFormat dsFormatCandidates[] = {
889 VK_FORMAT_D24_UNORM_S8_UINT,
890 VK_FORMAT_D32_SFLOAT_S8_UINT,
891 VK_FORMAT_D16_UNORM_S8_UINT
892 };
893 const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
894 int dsFormatIdx = 0;
895 while (dsFormatIdx < dsFormatCandidateCount) {
896 dsFormat = dsFormatCandidates[dsFormatIdx];
897 VkFormatProperties fmtProp;
898 f->vkGetPhysicalDeviceFormatProperties(physDev, dsFormat, &fmtProp);
899 if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
900 break;
901 ++dsFormatIdx;
902 }
903 if (dsFormatIdx == dsFormatCandidateCount)
904 qWarning("QVulkanWindow: Failed to find an optimal depth-stencil format");
905
906 qCDebug(lcGuiVk, "Color format: %d Depth-stencil format: %d", colorFormat, dsFormat);
907
908 if (!createDefaultRenderPass())
909 return;
910
911 if (renderer)
912 renderer->initResources();
913
914 status = StatusDeviceReady;
915}
916
917void QVulkanWindowPrivate::reset()
918{
919 if (!dev) // do not rely on 'status', a half done init must be cleaned properly too
920 return;
921
922 qCDebug(lcGuiVk, "QVulkanWindow reset");
923
924 devFuncs->vkDeviceWaitIdle(dev);
925
926 if (renderer) {
927 renderer->releaseResources();
928 devFuncs->vkDeviceWaitIdle(dev);
929 }
930
931 if (defaultRenderPass) {
932 devFuncs->vkDestroyRenderPass(dev, defaultRenderPass, nullptr);
933 defaultRenderPass = VK_NULL_HANDLE;
934 }
935
936 if (cmdPool) {
937 devFuncs->vkDestroyCommandPool(dev, cmdPool, nullptr);
938 cmdPool = VK_NULL_HANDLE;
939 }
940
941 if (presCmdPool) {
942 devFuncs->vkDestroyCommandPool(dev, presCmdPool, nullptr);
943 presCmdPool = VK_NULL_HANDLE;
944 }
945
946 if (frameGrabImage) {
947 devFuncs->vkDestroyImage(dev, frameGrabImage, nullptr);
948 frameGrabImage = VK_NULL_HANDLE;
949 }
950
951 if (frameGrabImageMem) {
952 devFuncs->vkFreeMemory(dev, frameGrabImageMem, nullptr);
953 frameGrabImageMem = VK_NULL_HANDLE;
954 }
955
956 if (dev) {
957 devFuncs->vkDestroyDevice(dev, nullptr);
958 inst->resetDeviceFunctions(dev);
959 dev = VK_NULL_HANDLE;
960 vkCreateSwapchainKHR = nullptr; // re-resolve swapchain funcs later on since some come via the device
961 }
962
963 surface = VK_NULL_HANDLE;
964
965 status = StatusUninitialized;
966}
967
968bool QVulkanWindowPrivate::createDefaultRenderPass()
969{
970 VkAttachmentDescription attDesc[3];
971 memset(attDesc, 0, sizeof(attDesc));
972
973 const bool msaa = sampleCount > VK_SAMPLE_COUNT_1_BIT;
974
975 // This is either the non-msaa render target or the resolve target.
976 attDesc[0].format = colorFormat;
977 attDesc[0].samples = VK_SAMPLE_COUNT_1_BIT;
978 attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // ignored when msaa
979 attDesc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
980 attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
981 attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
982 attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
983 attDesc[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
984
985 attDesc[1].format = dsFormat;
986 attDesc[1].samples = sampleCount;
987 attDesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
988 attDesc[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
989 attDesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
990 attDesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
991 attDesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
992 attDesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
993
994 if (msaa) {
995 // msaa render target
996 attDesc[2].format = colorFormat;
997 attDesc[2].samples = sampleCount;
998 attDesc[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
999 attDesc[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1000 attDesc[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1001 attDesc[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1002 attDesc[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1003 attDesc[2].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1004 }
1005
1006 VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
1007 VkAttachmentReference resolveRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
1008 VkAttachmentReference dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
1009
1010 VkSubpassDescription subPassDesc;
1011 memset(&subPassDesc, 0, sizeof(subPassDesc));
1012 subPassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1013 subPassDesc.colorAttachmentCount = 1;
1014 subPassDesc.pColorAttachments = &colorRef;
1015 subPassDesc.pDepthStencilAttachment = &dsRef;
1016
1017 VkRenderPassCreateInfo rpInfo;
1018 memset(&rpInfo, 0, sizeof(rpInfo));
1019 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1020 rpInfo.attachmentCount = 2;
1021 rpInfo.pAttachments = attDesc;
1022 rpInfo.subpassCount = 1;
1023 rpInfo.pSubpasses = &subPassDesc;
1024
1025 if (msaa) {
1026 colorRef.attachment = 2;
1027 subPassDesc.pResolveAttachments = &resolveRef;
1028 rpInfo.attachmentCount = 3;
1029 }
1030
1031 VkResult err = devFuncs->vkCreateRenderPass(dev, &rpInfo, nullptr, &defaultRenderPass);
1032 if (err != VK_SUCCESS) {
1033 qWarning("QVulkanWindow: Failed to create renderpass: %d", err);
1034 return false;
1035 }
1036
1037 return true;
1038}
1039
1040void QVulkanWindowPrivate::recreateSwapChain()
1041{
1042 Q_Q(QVulkanWindow);
1043 Q_ASSERT(status >= StatusDeviceReady);
1044
1045 swapChainImageSize = q->size() * q->devicePixelRatio(); // note: may change below due to surfaceCaps
1046
1047 if (swapChainImageSize.isEmpty()) // handle null window size gracefully
1048 return;
1049
1050 QVulkanInstance *inst = q->vulkanInstance();
1051 QVulkanFunctions *f = inst->functions();
1052 devFuncs->vkDeviceWaitIdle(dev);
1053
1054 if (!vkCreateSwapchainKHR) {
1055 vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
1056 vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
1057 vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
1058 vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
1059 vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
1060 }
1061
1062 VkPhysicalDevice physDev = physDevs.at(physDevIndex);
1063 VkSurfaceCapabilitiesKHR surfaceCaps;
1064 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, surface, &surfaceCaps);
1065 uint32_t reqBufferCount;
1066 if (surfaceCaps.maxImageCount == 0)
1067 reqBufferCount = qMax<uint32_t>(2, surfaceCaps.minImageCount);
1068 else
1069 reqBufferCount = qMax(qMin<uint32_t>(surfaceCaps.maxImageCount, 3), surfaceCaps.minImageCount);
1070
1071 VkExtent2D bufferSize = surfaceCaps.currentExtent;
1072 if (bufferSize.width == uint32_t(-1)) {
1073 Q_ASSERT(bufferSize.height == uint32_t(-1));
1074 bufferSize.width = swapChainImageSize.width();
1075 bufferSize.height = swapChainImageSize.height();
1076 } else {
1077 swapChainImageSize = QSize(bufferSize.width, bufferSize.height);
1078 }
1079
1080 VkSurfaceTransformFlagBitsKHR preTransform =
1081 (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
1082 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
1083 : surfaceCaps.currentTransform;
1084
1085 VkCompositeAlphaFlagBitsKHR compositeAlpha =
1086 (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
1087 ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
1088 : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1089
1090 if (q->requestedFormat().hasAlpha()) {
1091 if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
1092 compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
1093 else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
1094 compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
1095 }
1096
1097 VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1098 swapChainSupportsReadBack = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
1099 if (swapChainSupportsReadBack)
1100 usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1101
1102 VkSwapchainKHR oldSwapChain = swapChain;
1103 VkSwapchainCreateInfoKHR swapChainInfo;
1104 memset(&swapChainInfo, 0, sizeof(swapChainInfo));
1105 swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1106 swapChainInfo.surface = surface;
1107 swapChainInfo.minImageCount = reqBufferCount;
1108 swapChainInfo.imageFormat = colorFormat;
1109 swapChainInfo.imageColorSpace = colorSpace;
1110 swapChainInfo.imageExtent = bufferSize;
1111 swapChainInfo.imageArrayLayers = 1;
1112 swapChainInfo.imageUsage = usage;
1113 swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1114 swapChainInfo.preTransform = preTransform;
1115 swapChainInfo.compositeAlpha = compositeAlpha;
1116 swapChainInfo.presentMode = presentMode;
1117 swapChainInfo.clipped = true;
1118 swapChainInfo.oldSwapchain = oldSwapChain;
1119
1120 qCDebug(lcGuiVk, "Creating new swap chain of %d buffers, size %dx%d", reqBufferCount, bufferSize.width, bufferSize.height);
1121
1122 VkSwapchainKHR newSwapChain;
1123 VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
1124 if (err != VK_SUCCESS) {
1125 qWarning("QVulkanWindow: Failed to create swap chain: %d", err);
1126 return;
1127 }
1128
1129 if (oldSwapChain)
1130 releaseSwapChain();
1131
1132 swapChain = newSwapChain;
1133
1134 uint32_t actualSwapChainBufferCount = 0;
1135 err = vkGetSwapchainImagesKHR(dev, swapChain, &actualSwapChainBufferCount, nullptr);
1136 if (err != VK_SUCCESS || actualSwapChainBufferCount < 2) {
1137 qWarning("QVulkanWindow: Failed to get swapchain images: %d (count=%d)", err, actualSwapChainBufferCount);
1138 return;
1139 }
1140
1141 qCDebug(lcGuiVk, "Actual swap chain buffer count: %d (supportsReadback=%d)",
1142 actualSwapChainBufferCount, swapChainSupportsReadBack);
1143 if (actualSwapChainBufferCount > MAX_SWAPCHAIN_BUFFER_COUNT) {
1144 qWarning("QVulkanWindow: Too many swapchain buffers (%d)", actualSwapChainBufferCount);
1145 return;
1146 }
1147 swapChainBufferCount = actualSwapChainBufferCount;
1148
1149 VkImage swapChainImages[MAX_SWAPCHAIN_BUFFER_COUNT];
1150 err = vkGetSwapchainImagesKHR(dev, swapChain, &actualSwapChainBufferCount, swapChainImages);
1151 if (err != VK_SUCCESS) {
1152 qWarning("QVulkanWindow: Failed to get swapchain images: %d", err);
1153 return;
1154 }
1155
1156 if (!createTransientImage(dsFormat,
1157 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
1158 VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
1159 &dsImage,
1160 &dsMem,
1161 &dsView,
1162 1))
1163 {
1164 return;
1165 }
1166
1167 const bool msaa = sampleCount > VK_SAMPLE_COUNT_1_BIT;
1168 VkImage msaaImages[MAX_SWAPCHAIN_BUFFER_COUNT];
1169 VkImageView msaaViews[MAX_SWAPCHAIN_BUFFER_COUNT];
1170
1171 if (msaa) {
1172 if (!createTransientImage(colorFormat,
1173 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
1174 VK_IMAGE_ASPECT_COLOR_BIT,
1175 msaaImages,
1176 &msaaImageMem,
1177 msaaViews,
1178 swapChainBufferCount))
1179 {
1180 return;
1181 }
1182 }
1183
1184 VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT };
1185
1186 for (int i = 0; i < swapChainBufferCount; ++i) {
1187 ImageResources &image(imageRes[i]);
1188 image.image = swapChainImages[i];
1189
1190 if (msaa) {
1191 image.msaaImage = msaaImages[i];
1192 image.msaaImageView = msaaViews[i];
1193 }
1194
1195 VkImageViewCreateInfo imgViewInfo;
1196 memset(&imgViewInfo, 0, sizeof(imgViewInfo));
1197 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1198 imgViewInfo.image = swapChainImages[i];
1199 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1200 imgViewInfo.format = colorFormat;
1201 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1202 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1203 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1204 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1205 imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1206 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1207 err = devFuncs->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
1208 if (err != VK_SUCCESS) {
1209 qWarning("QVulkanWindow: Failed to create swapchain image view %d: %d", i, err);
1210 return;
1211 }
1212
1213 VkImageView views[3] = { image.imageView,
1214 dsView,
1215 msaa ? image.msaaImageView : VK_NULL_HANDLE };
1216 VkFramebufferCreateInfo fbInfo;
1217 memset(&fbInfo, 0, sizeof(fbInfo));
1218 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1219 fbInfo.renderPass = defaultRenderPass;
1220 fbInfo.attachmentCount = msaa ? 3 : 2;
1221 fbInfo.pAttachments = views;
1222 fbInfo.width = swapChainImageSize.width();
1223 fbInfo.height = swapChainImageSize.height();
1224 fbInfo.layers = 1;
1225 VkResult err = devFuncs->vkCreateFramebuffer(dev, &fbInfo, nullptr, &image.fb);
1226 if (err != VK_SUCCESS) {
1227 qWarning("QVulkanWindow: Failed to create framebuffer: %d", err);
1228 return;
1229 }
1230
1231 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
1232 // pre-build the static image-acquire-on-present-queue command buffer
1233 VkCommandBufferAllocateInfo cmdBufInfo = {
1234 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, presCmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
1235 err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &image.presTransCmdBuf);
1236 if (err != VK_SUCCESS) {
1237 qWarning("QVulkanWindow: Failed to allocate acquire-on-present-queue command buffer: %d", err);
1238 return;
1239 }
1240 VkCommandBufferBeginInfo cmdBufBeginInfo = {
1241 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
1242 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, nullptr };
1243 err = devFuncs->vkBeginCommandBuffer(image.presTransCmdBuf, &cmdBufBeginInfo);
1244 if (err != VK_SUCCESS) {
1245 qWarning("QVulkanWindow: Failed to begin acquire-on-present-queue command buffer: %d", err);
1246 return;
1247 }
1248 VkImageMemoryBarrier presTrans;
1249 memset(&presTrans, 0, sizeof(presTrans));
1250 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1251 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1252 presTrans.oldLayout = presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1253 presTrans.srcQueueFamilyIndex = gfxQueueFamilyIdx;
1254 presTrans.dstQueueFamilyIndex = presQueueFamilyIdx;
1255 presTrans.image = image.image;
1256 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1257 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
1258 devFuncs->vkCmdPipelineBarrier(image.presTransCmdBuf,
1259 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1260 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1261 0, 0, nullptr, 0, nullptr,
1262 1, &presTrans);
1263 err = devFuncs->vkEndCommandBuffer(image.presTransCmdBuf);
1264 if (err != VK_SUCCESS) {
1265 qWarning("QVulkanWindow: Failed to end acquire-on-present-queue command buffer: %d", err);
1266 return;
1267 }
1268 }
1269 }
1270
1271 currentImage = 0;
1272
1273 VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
1274 for (int i = 0; i < frameLag; ++i) {
1275 FrameResources &frame(frameRes[i]);
1276
1277 frame.imageAcquired = false;
1278 frame.imageSemWaitable = false;
1279
1280 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
1281 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
1282 if (gfxQueueFamilyIdx != presQueueFamilyIdx)
1283 devFuncs->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.presTransSem);
1284
1285 err = devFuncs->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
1286 if (err != VK_SUCCESS) {
1287 qWarning("QVulkanWindow: Failed to create command buffer fence: %d", err);
1288 return;
1289 }
1290 frame.cmdFenceWaitable = true; // fence was created in signaled state
1291 }
1292
1293 currentFrame = 0;
1294
1295 if (renderer)
1296 renderer->initSwapChainResources();
1297
1298 status = StatusReady;
1299}
1300
1301uint32_t QVulkanWindowPrivate::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
1302{
1303 VkPhysicalDeviceMemoryProperties physDevMemProps;
1304 inst->functions()->vkGetPhysicalDeviceMemoryProperties(physDevs[physDevIndex], &physDevMemProps);
1305
1306 VkMemoryRequirements memReq;
1307 devFuncs->vkGetImageMemoryRequirements(dev, img, &memReq);
1308 uint32_t memTypeIndex = uint32_t(-1);
1309
1310 if (memReq.memoryTypeBits) {
1311 // Find a device local + lazily allocated, or at least device local memtype.
1312 const VkMemoryType *memType = physDevMemProps.memoryTypes;
1313 bool foundDevLocal = false;
1314 for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
1315 if (memReq.memoryTypeBits & (1 << i)) {
1316 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
1317 if (!foundDevLocal) {
1318 foundDevLocal = true;
1319 memTypeIndex = i;
1320 }
1321 if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
1322 memTypeIndex = i;
1323 break;
1324 }
1325 }
1326 }
1327 }
1328 }
1329
1330 return memTypeIndex;
1331}
1332
1333static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
1334{
1335 return (v + byteAlign - 1) & ~(byteAlign - 1);
1336}
1337
1338bool QVulkanWindowPrivate::createTransientImage(VkFormat format,
1339 VkImageUsageFlags usage,
1340 VkImageAspectFlags aspectMask,
1341 VkImage *images,
1342 VkDeviceMemory *mem,
1343 VkImageView *views,
1344 int count)
1345{
1346 VkMemoryRequirements memReq;
1347 VkResult err;
1348
1349 Q_ASSERT(count > 0);
1350 for (int i = 0; i < count; ++i) {
1351 VkImageCreateInfo imgInfo;
1352 memset(&imgInfo, 0, sizeof(imgInfo));
1353 imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1354 imgInfo.imageType = VK_IMAGE_TYPE_2D;
1355 imgInfo.format = format;
1356 imgInfo.extent.width = swapChainImageSize.width();
1357 imgInfo.extent.height = swapChainImageSize.height();
1358 imgInfo.extent.depth = 1;
1359 imgInfo.mipLevels = imgInfo.arrayLayers = 1;
1360 imgInfo.samples = sampleCount;
1361 imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1362 imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
1363
1364 err = devFuncs->vkCreateImage(dev, &imgInfo, nullptr, images + i);
1365 if (err != VK_SUCCESS) {
1366 qWarning("QVulkanWindow: Failed to create image: %d", err);
1367 return false;
1368 }
1369
1370 // Assume the reqs are the same since the images are same in every way.
1371 // Still, call GetImageMemReq for every image, in order to prevent the
1372 // validation layer from complaining.
1373 devFuncs->vkGetImageMemoryRequirements(dev, images[i], &memReq);
1374 }
1375
1376 VkMemoryAllocateInfo memInfo;
1377 memset(&memInfo, 0, sizeof(memInfo));
1378 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1379 memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * count;
1380
1381 uint32_t startIndex = 0;
1382 do {
1383 memInfo.memoryTypeIndex = chooseTransientImageMemType(images[0], startIndex);
1384 if (memInfo.memoryTypeIndex == uint32_t(-1)) {
1385 qWarning("QVulkanWindow: No suitable memory type found");
1386 return false;
1387 }
1388 startIndex = memInfo.memoryTypeIndex + 1;
1389 qCDebug(lcGuiVk, "Allocating %u bytes for transient image (memtype %u)",
1390 uint32_t(memInfo.allocationSize), memInfo.memoryTypeIndex);
1391 err = devFuncs->vkAllocateMemory(dev, &memInfo, nullptr, mem);
1392 if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
1393 qWarning("QVulkanWindow: Failed to allocate image memory: %d", err);
1394 return false;
1395 }
1396 } while (err != VK_SUCCESS);
1397
1398 VkDeviceSize ofs = 0;
1399 for (int i = 0; i < count; ++i) {
1400 err = devFuncs->vkBindImageMemory(dev, images[i], *mem, ofs);
1401 if (err != VK_SUCCESS) {
1402 qWarning("QVulkanWindow: Failed to bind image memory: %d", err);
1403 return false;
1404 }
1405 ofs += aligned(memReq.size, memReq.alignment);
1406
1407 VkImageViewCreateInfo imgViewInfo;
1408 memset(&imgViewInfo, 0, sizeof(imgViewInfo));
1409 imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1410 imgViewInfo.image = images[i];
1411 imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1412 imgViewInfo.format = format;
1413 imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
1414 imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
1415 imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
1416 imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
1417 imgViewInfo.subresourceRange.aspectMask = aspectMask;
1418 imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
1419
1420 err = devFuncs->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
1421 if (err != VK_SUCCESS) {
1422 qWarning("QVulkanWindow: Failed to create image view: %d", err);
1423 return false;
1424 }
1425 }
1426
1427 return true;
1428}
1429
1430void QVulkanWindowPrivate::releaseSwapChain()
1431{
1432 if (!dev || !swapChain) // do not rely on 'status', a half done init must be cleaned properly too
1433 return;
1434
1435 qCDebug(lcGuiVk, "Releasing swapchain");
1436
1437 devFuncs->vkDeviceWaitIdle(dev);
1438
1439 if (renderer) {
1440 renderer->releaseSwapChainResources();
1441 devFuncs->vkDeviceWaitIdle(dev);
1442 }
1443
1444 for (int i = 0; i < frameLag; ++i) {
1445 FrameResources &frame(frameRes[i]);
1446 if (frame.cmdBuf) {
1447 devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
1448 frame.cmdBuf = VK_NULL_HANDLE;
1449 }
1450 if (frame.imageSem) {
1451 devFuncs->vkDestroySemaphore(dev, frame.imageSem, nullptr);
1452 frame.imageSem = VK_NULL_HANDLE;
1453 }
1454 if (frame.drawSem) {
1455 devFuncs->vkDestroySemaphore(dev, frame.drawSem, nullptr);
1456 frame.drawSem = VK_NULL_HANDLE;
1457 }
1458 if (frame.presTransSem) {
1459 devFuncs->vkDestroySemaphore(dev, frame.presTransSem, nullptr);
1460 frame.presTransSem = VK_NULL_HANDLE;
1461 }
1462 if (frame.cmdFence) {
1463 if (frame.cmdFenceWaitable)
1464 devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
1465 devFuncs->vkDestroyFence(dev, frame.cmdFence, nullptr);
1466 frame.cmdFence = VK_NULL_HANDLE;
1467 frame.cmdFenceWaitable = false;
1468 }
1469 }
1470
1471 for (int i = 0; i < swapChainBufferCount; ++i) {
1472 ImageResources &image(imageRes[i]);
1473 if (image.fb) {
1474 devFuncs->vkDestroyFramebuffer(dev, image.fb, nullptr);
1475 image.fb = VK_NULL_HANDLE;
1476 }
1477 if (image.imageView) {
1478 devFuncs->vkDestroyImageView(dev, image.imageView, nullptr);
1479 image.imageView = VK_NULL_HANDLE;
1480 }
1481 if (image.presTransCmdBuf) {
1482 devFuncs->vkFreeCommandBuffers(dev, presCmdPool, 1, &image.presTransCmdBuf);
1483 image.presTransCmdBuf = VK_NULL_HANDLE;
1484 }
1485 if (image.msaaImageView) {
1486 devFuncs->vkDestroyImageView(dev, image.msaaImageView, nullptr);
1487 image.msaaImageView = VK_NULL_HANDLE;
1488 }
1489 if (image.msaaImage) {
1490 devFuncs->vkDestroyImage(dev, image.msaaImage, nullptr);
1491 image.msaaImage = VK_NULL_HANDLE;
1492 }
1493 }
1494
1495 if (msaaImageMem) {
1496 devFuncs->vkFreeMemory(dev, msaaImageMem, nullptr);
1497 msaaImageMem = VK_NULL_HANDLE;
1498 }
1499
1500 if (dsView) {
1501 devFuncs->vkDestroyImageView(dev, dsView, nullptr);
1502 dsView = VK_NULL_HANDLE;
1503 }
1504 if (dsImage) {
1505 devFuncs->vkDestroyImage(dev, dsImage, nullptr);
1506 dsImage = VK_NULL_HANDLE;
1507 }
1508 if (dsMem) {
1509 devFuncs->vkFreeMemory(dev, dsMem, nullptr);
1510 dsMem = VK_NULL_HANDLE;
1511 }
1512
1513 if (swapChain) {
1514 vkDestroySwapchainKHR(dev, swapChain, nullptr);
1515 swapChain = VK_NULL_HANDLE;
1516 }
1517
1518 if (status == StatusReady)
1519 status = StatusDeviceReady;
1520}
1521
1522/*!
1523 \internal
1524 */
1525void QVulkanWindow::exposeEvent(QExposeEvent *)
1526{
1527 Q_D(QVulkanWindow);
1528
1529 if (isExposed()) {
1530 d->ensureStarted();
1531 } else {
1532 if (!d->flags.testFlag(PersistentResources)) {
1533 d->releaseSwapChain();
1534 d->reset();
1535 }
1536 }
1537}
1538
1539void QVulkanWindowPrivate::ensureStarted()
1540{
1541 Q_Q(QVulkanWindow);
1542 if (status == QVulkanWindowPrivate::StatusFailRetry)
1543 status = QVulkanWindowPrivate::StatusUninitialized;
1544 if (status == QVulkanWindowPrivate::StatusUninitialized) {
1545 init();
1546 if (status == QVulkanWindowPrivate::StatusDeviceReady)
1547 recreateSwapChain();
1548 }
1549 if (status == QVulkanWindowPrivate::StatusReady)
1550 q->requestUpdate();
1551}
1552
1553/*!
1554 \internal
1555 */
1556void QVulkanWindow::resizeEvent(QResizeEvent *)
1557{
1558 // Nothing to do here - recreating the swapchain is handled when building the next frame.
1559}
1560
1561/*!
1562 \internal
1563 */
1564bool QVulkanWindow::event(QEvent *e)
1565{
1566 Q_D(QVulkanWindow);
1567
1568 switch (e->type()) {
1569 case QEvent::Paint:
1570 case QEvent::UpdateRequest:
1571 d->beginFrame();
1572 break;
1573
1574 // The swapchain must be destroyed before the surface as per spec. This is
1575 // not ideal for us because the surface is managed by the QPlatformWindow
1576 // which may be gone already when the unexpose comes, making the validation
1577 // layer scream. The solution is to listen to the PlatformSurface events.
1578 case QEvent::PlatformSurface:
1579 if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1580 d->releaseSwapChain();
1581 d->reset();
1582 }
1583 break;
1584
1585 default:
1586 break;
1587 }
1588
1589 return QWindow::event(e);
1590}
1591
1592/*!
1593 \typedef QVulkanWindow::QueueCreateInfoModifier
1594
1595 A function that is called during graphics initialization to add
1596 additional queues that should be created.
1597
1598 Set if the renderer needs additional queues besides the default graphics
1599 queue (e.g. a transfer queue).
1600 The provided queue family properties can be used to select the indices for
1601 the additional queues.
1602 The renderer can subsequently request the actual queue in initResources().
1603
1604 \note When requesting additional graphics queues, Qt itself always requests
1605 a graphics queue. You'll need to search queueCreateInfo for the appropriate
1606 entry and manipulate it to obtain the additional queue.
1607
1608 \sa setQueueCreateInfoModifier()
1609 */
1610
1611/*!
1612 Sets the queue create info modification function \a modifier.
1613
1614 \sa QueueCreateInfoModifier
1615
1616 \since 5.15
1617 */
1618void QVulkanWindow::setQueueCreateInfoModifier(const QueueCreateInfoModifier &modifier)
1619{
1620 Q_D(QVulkanWindow);
1621 d->queueCreateInfoModifier = modifier;
1622}
1623
1624/*!
1625 \typedef QVulkanWindow::EnabledFeaturesModifier
1626
1627 A function that is called during graphics initialization to alter the
1628 VkPhysicalDeviceFeatures that is passed in when creating a Vulkan device
1629 object.
1630
1631 By default QVulkanWindow enables all Vulkan 1.0 core features that the
1632 physical device reports as supported, with certain exceptions. In
1633 praticular, \c robustBufferAccess is always disabled in order to avoid
1634 unexpected performance hits.
1635
1636 The VkPhysicalDeviceFeatures reference passed in is all zeroed out at the
1637 point when the function is invoked. It is up to the function to change
1638 members as it sees fit.
1639
1640 \note To control Vulkan 1.1, 1.2, or 1.3 features, use
1641 EnabledFeatures2Modifier instead.
1642
1643 \sa setEnabledFeaturesModifier()
1644 */
1645
1646/*!
1647 Sets the enabled device features modification function \a modifier.
1648
1649 \note To control Vulkan 1.1, 1.2, or 1.3 features, use
1650 the overload taking a EnabledFeatures2Modifier instead.
1651
1652 \note \a modifier is passed to the callback function with all members set
1653 to false. It is up to the function to change members as it sees fit.
1654
1655 \since 6.7
1656 \sa EnabledFeaturesModifier
1657 */
1658void QVulkanWindow::setEnabledFeaturesModifier(const EnabledFeaturesModifier &modifier)
1659{
1660 Q_D(QVulkanWindow);
1661 d->enabledFeaturesModifier = modifier;
1662}
1663
1664/*!
1665 \typedef QVulkanWindow::EnabledFeatures2Modifier
1666
1667 A function that is called during graphics initialization to alter the
1668 VkPhysicalDeviceFeatures2 that is changed to the VkDeviceCreateInfo.
1669
1670 By default QVulkanWindow enables all Vulkan 1.0 core features that the
1671 physical device reports as supported, with certain exceptions. In
1672 praticular, \c robustBufferAccess is always disabled in order to avoid
1673 unexpected performance hits.
1674
1675 This however is not always sufficient when working with Vulkan 1.1, 1.2, or
1676 1.3 features and extensions. Hence this callback mechanism. If only Vulkan
1677 1.0 is relevant at run time, use setEnabledFeaturesModifier() instead.
1678
1679 The VkPhysicalDeviceFeatures2 reference passed to the callback function
1680 with \c sType set, but the rest zeroed out. It is up to the function to
1681 change members to true, or set up \c pNext chains as it sees fit.
1682
1683 \note When setting up \c pNext chains, make sure the referenced objects
1684 have a long enough lifetime, for example by storing them as member
1685 variables in the QVulkanWindow subclass.
1686
1687 \since 6.7
1688 \sa setEnabledFeaturesModifier()
1689 */
1690
1691/*!
1692 Sets the enabled device features modification function \a modifier.
1693 \overload
1694 \since 6.7
1695 \sa EnabledFeatures2Modifier
1696*/
1697void QVulkanWindow::setEnabledFeaturesModifier(EnabledFeatures2Modifier modifier)
1698{
1699 Q_D(QVulkanWindow);
1700 d->enabledFeatures2Modifier = std::move(modifier);
1701}
1702
1703/*!
1704 Returns true if this window has successfully initialized all Vulkan
1705 resources, including the swapchain.
1706
1707 \note Initialization happens on the first expose event after the window is
1708 made visible.
1709 */
1710bool QVulkanWindow::isValid() const
1711{
1712 Q_D(const QVulkanWindow);
1713 return d->status == QVulkanWindowPrivate::StatusReady;
1714}
1715
1716/*!
1717 Returns a new instance of QVulkanWindowRenderer.
1718
1719 This virtual function is called once during the lifetime of the window, at
1720 some point after making it visible for the first time.
1721
1722 The default implementation returns null and so no rendering will be
1723 performed apart from clearing the buffers.
1724
1725 The window takes ownership of the returned renderer object.
1726 */
1727QVulkanWindowRenderer *QVulkanWindow::createRenderer()
1728{
1729 return nullptr;
1730}
1731
1732/*!
1733 Virtual destructor.
1734 */
1735QVulkanWindowRenderer::~QVulkanWindowRenderer()
1736{
1737}
1738
1739/*!
1740 This virtual function is called right before graphics initialization, that
1741 ends up in calling initResources(), is about to begin.
1742
1743 Normally there is no need to reimplement this function. However, there are
1744 cases that involve decisions based on both the physical device and the
1745 surface. These cannot normally be performed before making the QVulkanWindow
1746 visible since the Vulkan surface is not retrievable at that stage.
1747
1748 Instead, applications can reimplement this function. Here both
1749 QVulkanWindow::physicalDevice() and QVulkanInstance::surfaceForWindow() are
1750 functional, but no further logical device initialization has taken place
1751 yet.
1752
1753 The default implementation is empty.
1754 */
1755void QVulkanWindowRenderer::preInitResources()
1756{
1757}
1758
1759/*!
1760 This virtual function is called when it is time to create the renderer's
1761 graphics resources.
1762
1763 Depending on the QVulkanWindow::PersistentResources flag, device lost
1764 situations, etc. this function may be called more than once during the
1765 lifetime of a QVulkanWindow. However, subsequent invocations are always
1766 preceded by a call to releaseResources().
1767
1768 Accessors like device(), graphicsQueue() and graphicsCommandPool() are only
1769 guaranteed to return valid values inside this function and afterwards, up
1770 until releaseResources() is called.
1771
1772 The default implementation is empty.
1773 */
1774void QVulkanWindowRenderer::initResources()
1775{
1776}
1777
1778/*!
1779 This virtual function is called when swapchain, framebuffer or renderpass
1780 related initialization can be performed. Swapchain and related resources
1781 are reset and then recreated in response to window resize events, and
1782 therefore a pair of calls to initResources() and releaseResources() can
1783 have multiple calls to initSwapChainResources() and
1784 releaseSwapChainResources() calls in-between.
1785
1786 Accessors like QVulkanWindow::swapChainImageSize() are only guaranteed to
1787 return valid values inside this function and afterwards, up until
1788 releaseSwapChainResources() is called.
1789
1790 This is also the place where size-dependent calculations (for example, the
1791 projection matrix) should be made since this function is called effectively
1792 on every resize.
1793
1794 The default implementation is empty.
1795 */
1796void QVulkanWindowRenderer::initSwapChainResources()
1797{
1798}
1799
1800/*!
1801 This virtual function is called when swapchain, framebuffer or renderpass
1802 related resources must be released.
1803
1804 The implementation must be prepared that a call to this function may be
1805 followed by a new call to initSwapChainResources() at a later point.
1806
1807 QVulkanWindow takes care of waiting for the device to become idle before
1808 and after invoking this function.
1809
1810 The default implementation is empty.
1811
1812 \note This is the last place to act with all graphics resources intact
1813 before QVulkanWindow starts releasing them. It is therefore essential that
1814 implementations with an asynchronous, potentially multi-threaded
1815 startNextFrame() perform a blocking wait and call
1816 QVulkanWindow::frameReady() before returning from this function in case
1817 there is a pending frame submission.
1818 */
1819void QVulkanWindowRenderer::releaseSwapChainResources()
1820{
1821}
1822
1823/*!
1824 This virtual function is called when the renderer's graphics resources must be
1825 released.
1826
1827 The implementation must be prepared that a call to this function may be
1828 followed by an initResources() at a later point.
1829
1830 QVulkanWindow takes care of waiting for the device to become idle before
1831 and after invoking this function.
1832
1833 The default implementation is empty.
1834 */
1835void QVulkanWindowRenderer::releaseResources()
1836{
1837}
1838
1839/*!
1840 \fn void QVulkanWindowRenderer::startNextFrame()
1841
1842 This virtual function is called when the draw calls for the next frame are
1843 to be added to the command buffer.
1844
1845 Each call to this function must be followed by a call to
1846 QVulkanWindow::frameReady(). Failing to do so will stall the rendering
1847 loop. The call can also be made at a later time, after returning from this
1848 function. This means that it is possible to kick off asynchronous work, and
1849 only update the command buffer and notify QVulkanWindow when that work has
1850 finished.
1851
1852 All Vulkan resources are initialized and ready when this function is
1853 invoked. The current framebuffer and main command buffer can be retrieved
1854 via QVulkanWindow::currentFramebuffer() and
1855 QVulkanWindow::currentCommandBuffer(). The logical device and the active
1856 graphics queue are available via QVulkanWindow::device() and
1857 QVulkanWindow::graphicsQueue(). Implementations can create additional
1858 command buffers from the pool returned by
1859 QVulkanWindow::graphicsCommandPool(). For convenience, the index of a host
1860 visible and device local memory type index are exposed via
1861 QVulkanWindow::hostVisibleMemoryIndex() and
1862 QVulkanWindow::deviceLocalMemoryIndex(). All these accessors are safe to be
1863 called from any thread.
1864
1865 \sa QVulkanWindow::frameReady(), QVulkanWindow
1866 */
1867
1868/*!
1869 This virtual function is called when the physical device is lost, meaning
1870 the creation of the logical device fails with \c{VK_ERROR_DEVICE_LOST}.
1871
1872 The default implementation is empty.
1873
1874 There is typically no need to perform anything special in this function
1875 because QVulkanWindow will automatically retry to initialize itself after a
1876 certain amount of time.
1877
1878 \sa logicalDeviceLost()
1879 */
1880void QVulkanWindowRenderer::physicalDeviceLost()
1881{
1882}
1883
1884/*!
1885 This virtual function is called when the logical device (VkDevice) is lost,
1886 meaning some operation failed with \c{VK_ERROR_DEVICE_LOST}.
1887
1888 The default implementation is empty.
1889
1890 There is typically no need to perform anything special in this function.
1891 QVulkanWindow will automatically release all resources (invoking
1892 releaseSwapChainResources() and releaseResources() as necessary) and will
1893 attempt to reinitialize, acquiring a new device. When the physical device
1894 was also lost, this reinitialization attempt may then result in
1895 physicalDeviceLost().
1896
1897 \sa physicalDeviceLost()
1898 */
1899void QVulkanWindowRenderer::logicalDeviceLost()
1900{
1901}
1902
1903QSize QVulkanWindowPrivate::surfacePixelSize() const
1904{
1905 Q_Q(const QVulkanWindow);
1906 VkSurfaceCapabilitiesKHR surfaceCaps = {};
1907 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevs.at(physDevIndex), surface, &surfaceCaps);
1908 VkExtent2D bufferSize = surfaceCaps.currentExtent;
1909 if (bufferSize.width == uint32_t(-1)) {
1910 Q_ASSERT(bufferSize.height == uint32_t(-1));
1911 return q->size() * q->devicePixelRatio();
1912 }
1913 return QSize(int(bufferSize.width), int(bufferSize.height));
1914}
1915
1916void QVulkanWindowPrivate::beginFrame()
1917{
1918 if (!swapChain || framePending)
1919 return;
1920
1921 Q_Q(QVulkanWindow);
1922 if (swapChainImageSize != surfacePixelSize()) {
1923 recreateSwapChain();
1924 if (!swapChain)
1925 return;
1926 }
1927
1928 // wait if we are too far ahead
1929 FrameResources &frame(frameRes[currentFrame]);
1930 if (frame.cmdFenceWaitable) {
1931 devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
1932 devFuncs->vkResetFences(dev, 1, &frame.cmdFence);
1933 frame.cmdFenceWaitable = false;
1934 }
1935
1936 // move on to next swapchain image
1937 if (!frame.imageAcquired) {
1938 VkResult err = vkAcquireNextImageKHR(dev, swapChain, UINT64_MAX,
1939 frame.imageSem, VK_NULL_HANDLE, &currentImage);
1940 if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
1941 frame.imageSemWaitable = true;
1942 frame.imageAcquired = true;
1943 } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
1944 recreateSwapChain();
1945 q->requestUpdate();
1946 return;
1947 } else {
1948 if (!checkDeviceLost(err))
1949 qWarning("QVulkanWindow: Failed to acquire next swapchain image: %d", err);
1950 q->requestUpdate();
1951 return;
1952 }
1953 }
1954
1955 // build new draw command buffer
1956 if (frame.cmdBuf) {
1957 devFuncs->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
1958 frame.cmdBuf = nullptr;
1959 }
1960
1961 VkCommandBufferAllocateInfo cmdBufInfo = {
1962 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
1963 VkResult err = devFuncs->vkAllocateCommandBuffers(dev, &cmdBufInfo, &frame.cmdBuf);
1964 if (err != VK_SUCCESS) {
1965 if (!checkDeviceLost(err))
1966 qWarning("QVulkanWindow: Failed to allocate frame command buffer: %d", err);
1967 return;
1968 }
1969
1970 VkCommandBufferBeginInfo cmdBufBeginInfo = {
1971 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, 0, nullptr };
1972 err = devFuncs->vkBeginCommandBuffer(frame.cmdBuf, &cmdBufBeginInfo);
1973 if (err != VK_SUCCESS) {
1974 if (!checkDeviceLost(err))
1975 qWarning("QVulkanWindow: Failed to begin frame command buffer: %d", err);
1976 return;
1977 }
1978
1979 if (frameGrabbing)
1980 frameGrabTargetImage = QImage(swapChainImageSize, QImage::Format_RGBA8888); // the format is as documented
1981
1982 ImageResources &image(imageRes[currentImage]);
1983 if (renderer) {
1984 framePending = true;
1985 renderer->startNextFrame();
1986 // done for now - endFrame() will get invoked when frameReady() is called back
1987 } else {
1988 VkClearColorValue clearColor = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1989 VkClearDepthStencilValue clearDS = { 1.0f, 0 };
1990 VkClearValue clearValues[3];
1991 memset(clearValues, 0, sizeof(clearValues));
1992 clearValues[0].color = clearValues[2].color = clearColor;
1993 clearValues[1].depthStencil = clearDS;
1994
1995 VkRenderPassBeginInfo rpBeginInfo;
1996 memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
1997 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1998 rpBeginInfo.renderPass = defaultRenderPass;
1999 rpBeginInfo.framebuffer = image.fb;
2000 rpBeginInfo.renderArea.extent.width = swapChainImageSize.width();
2001 rpBeginInfo.renderArea.extent.height = swapChainImageSize.height();
2002 rpBeginInfo.clearValueCount = sampleCount > VK_SAMPLE_COUNT_1_BIT ? 3 : 2;
2003 rpBeginInfo.pClearValues = clearValues;
2004 devFuncs->vkCmdBeginRenderPass(frame.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
2005 devFuncs->vkCmdEndRenderPass(frame.cmdBuf);
2006
2007 endFrame();
2008 }
2009}
2010
2011void QVulkanWindowPrivate::endFrame()
2012{
2013 Q_Q(QVulkanWindow);
2014
2015 FrameResources &frame(frameRes[currentFrame]);
2016 ImageResources &image(imageRes[currentImage]);
2017
2018 if (gfxQueueFamilyIdx != presQueueFamilyIdx && !frameGrabbing) {
2019 // Add the swapchain image release to the command buffer that will be
2020 // submitted to the graphics queue.
2021 VkImageMemoryBarrier presTrans;
2022 memset(&presTrans, 0, sizeof(presTrans));
2023 presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2024 presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
2025 presTrans.oldLayout = presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2026 presTrans.srcQueueFamilyIndex = gfxQueueFamilyIdx;
2027 presTrans.dstQueueFamilyIndex = presQueueFamilyIdx;
2028 presTrans.image = image.image;
2029 presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2030 presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
2031 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2032 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2033 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
2034 0, 0, nullptr, 0, nullptr,
2035 1, &presTrans);
2036 }
2037
2038 // When grabbing a frame, add a readback at the end and skip presenting.
2039 if (frameGrabbing)
2040 addReadback();
2041
2042 VkResult err = devFuncs->vkEndCommandBuffer(frame.cmdBuf);
2043 if (err != VK_SUCCESS) {
2044 if (!checkDeviceLost(err))
2045 qWarning("QVulkanWindow: Failed to end frame command buffer: %d", err);
2046 return;
2047 }
2048
2049 // submit draw calls
2050 VkSubmitInfo submitInfo;
2051 memset(&submitInfo, 0, sizeof(submitInfo));
2052 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2053 submitInfo.commandBufferCount = 1;
2054 submitInfo.pCommandBuffers = &frame.cmdBuf;
2055 if (frame.imageSemWaitable) {
2056 submitInfo.waitSemaphoreCount = 1;
2057 submitInfo.pWaitSemaphores = &frame.imageSem;
2058 }
2059 if (!frameGrabbing) {
2060 submitInfo.signalSemaphoreCount = 1;
2061 submitInfo.pSignalSemaphores = &frame.drawSem;
2062 }
2063 VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
2064 submitInfo.pWaitDstStageMask = &psf;
2065
2066 Q_ASSERT(!frame.cmdFenceWaitable);
2067
2068 err = devFuncs->vkQueueSubmit(gfxQueue, 1, &submitInfo, frame.cmdFence);
2069 if (err == VK_SUCCESS) {
2070 frame.imageSemWaitable = false;
2071 frame.cmdFenceWaitable = true;
2072 } else {
2073 if (!checkDeviceLost(err))
2074 qWarning("QVulkanWindow: Failed to submit to graphics queue: %d", err);
2075 return;
2076 }
2077
2078 // block and then bail out when grabbing
2079 if (frameGrabbing) {
2080 finishBlockingReadback();
2081 frameGrabbing = false;
2082 // Leave frame.imageAcquired set to true.
2083 // Do not change currentFrame.
2084 emit q->frameGrabbed(frameGrabTargetImage);
2085 return;
2086 }
2087
2088 if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
2089 // Submit the swapchain image acquire to the present queue.
2090 submitInfo.pWaitSemaphores = &frame.drawSem;
2091 submitInfo.pSignalSemaphores = &frame.presTransSem;
2092 submitInfo.pCommandBuffers = &image.presTransCmdBuf; // must be USAGE_SIMULTANEOUS
2093 err = devFuncs->vkQueueSubmit(presQueue, 1, &submitInfo, VK_NULL_HANDLE);
2094 if (err != VK_SUCCESS) {
2095 if (!checkDeviceLost(err))
2096 qWarning("QVulkanWindow: Failed to submit to present queue: %d", err);
2097 return;
2098 }
2099 }
2100
2101 // queue present
2102 VkPresentInfoKHR presInfo;
2103 memset(&presInfo, 0, sizeof(presInfo));
2104 presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
2105 presInfo.swapchainCount = 1;
2106 presInfo.pSwapchains = &swapChain;
2107 presInfo.pImageIndices = &currentImage;
2108 presInfo.waitSemaphoreCount = 1;
2109 presInfo.pWaitSemaphores = gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem;
2110
2111 // Do platform-specific WM notification. F.ex. essential on Wayland in
2112 // order to circumvent driver frame callbacks
2113 inst->presentAboutToBeQueued(q);
2114
2115 err = vkQueuePresentKHR(presQueue, &presInfo);
2116 if (err != VK_SUCCESS) {
2117 if (err == VK_ERROR_OUT_OF_DATE_KHR) {
2118 recreateSwapChain();
2119 q->requestUpdate();
2120 return;
2121 } else if (err != VK_SUBOPTIMAL_KHR) {
2122 if (!checkDeviceLost(err))
2123 qWarning("QVulkanWindow: Failed to present: %d", err);
2124 return;
2125 }
2126 }
2127
2128 frame.imageAcquired = false;
2129
2130 inst->presentQueued(q);
2131
2132 currentFrame = (currentFrame + 1) % frameLag;
2133}
2134
2135/*!
2136 This function must be called exactly once in response to each invocation of
2137 the QVulkanWindowRenderer::startNextFrame() implementation. At the time of
2138 this call, the main command buffer, exposed via currentCommandBuffer(),
2139 must have all necessary rendering commands added to it since this function
2140 will trigger submitting the commands and queuing the present command.
2141
2142 \note This function must only be called from the gui/main thread, which is
2143 where QVulkanWindowRenderer's functions are invoked and where the
2144 QVulkanWindow instance lives.
2145
2146 \sa QVulkanWindowRenderer::startNextFrame()
2147 */
2148void QVulkanWindow::frameReady()
2149{
2150 Q_ASSERT_X(QThread::isMainThread(),
2151 "QVulkanWindow", "frameReady() can only be called from the GUI (main) thread");
2152
2153 Q_D(QVulkanWindow);
2154
2155 if (!d->framePending) {
2156 qWarning("QVulkanWindow: frameReady() called without a corresponding startNextFrame()");
2157 return;
2158 }
2159
2160 d->framePending = false;
2161
2162 d->endFrame();
2163}
2164
2165bool QVulkanWindowPrivate::checkDeviceLost(VkResult err)
2166{
2167 if (err == VK_ERROR_DEVICE_LOST) {
2168 qWarning("QVulkanWindow: Device lost");
2169 if (renderer)
2170 renderer->logicalDeviceLost();
2171 qCDebug(lcGuiVk, "Releasing all resources due to device lost");
2172 releaseSwapChain();
2173 reset();
2174 qCDebug(lcGuiVk, "Restarting");
2175 ensureStarted();
2176 return true;
2177 }
2178 return false;
2179}
2180
2181void QVulkanWindowPrivate::addReadback()
2182{
2183 VkImageCreateInfo imageInfo;
2184 memset(&imageInfo, 0, sizeof(imageInfo));
2185 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2186 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2187 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2188 imageInfo.extent.width = frameGrabTargetImage.width();
2189 imageInfo.extent.height = frameGrabTargetImage.height();
2190 imageInfo.extent.depth = 1;
2191 imageInfo.mipLevels = 1;
2192 imageInfo.arrayLayers = 1;
2193 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2194 imageInfo.tiling = VK_IMAGE_TILING_LINEAR;
2195 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
2196 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2197
2198 VkResult err = devFuncs->vkCreateImage(dev, &imageInfo, nullptr, &frameGrabImage);
2199 if (err != VK_SUCCESS) {
2200 qWarning("QVulkanWindow: Failed to create image for readback: %d", err);
2201 return;
2202 }
2203
2204 VkMemoryRequirements memReq;
2205 devFuncs->vkGetImageMemoryRequirements(dev, frameGrabImage, &memReq);
2206
2207 VkMemoryAllocateInfo allocInfo = {
2208 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
2209 nullptr,
2210 memReq.size,
2211 hostVisibleMemIndex
2212 };
2213
2214 err = devFuncs->vkAllocateMemory(dev, &allocInfo, nullptr, &frameGrabImageMem);
2215 if (err != VK_SUCCESS) {
2216 qWarning("QVulkanWindow: Failed to allocate memory for readback image: %d", err);
2217 return;
2218 }
2219
2220 err = devFuncs->vkBindImageMemory(dev, frameGrabImage, frameGrabImageMem, 0);
2221 if (err != VK_SUCCESS) {
2222 qWarning("QVulkanWindow: Failed to bind readback image memory: %d", err);
2223 return;
2224 }
2225
2226 FrameResources &frame(frameRes[currentFrame]);
2227 ImageResources &image(imageRes[currentImage]);
2228
2229 VkImageMemoryBarrier barrier;
2230 memset(&barrier, 0, sizeof(barrier));
2231 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2232 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2233 barrier.subresourceRange.levelCount = barrier.subresourceRange.layerCount = 1;
2234
2235 barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2236 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
2237 barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2238 barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2239 barrier.image = image.image;
2240
2241 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2242 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
2243 VK_PIPELINE_STAGE_TRANSFER_BIT,
2244 0, 0, nullptr, 0, nullptr,
2245 1, &barrier);
2246
2247 barrier.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2248 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2249 barrier.srcAccessMask = 0;
2250 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2251 barrier.image = frameGrabImage;
2252
2253 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2254 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
2255 VK_PIPELINE_STAGE_TRANSFER_BIT,
2256 0, 0, nullptr, 0, nullptr,
2257 1, &barrier);
2258
2259 VkImageCopy copyInfo;
2260 memset(&copyInfo, 0, sizeof(copyInfo));
2261 copyInfo.srcSubresource.aspectMask = copyInfo.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2262 copyInfo.srcSubresource.layerCount = copyInfo.dstSubresource.layerCount = 1;
2263 copyInfo.extent.width = frameGrabTargetImage.width();
2264 copyInfo.extent.height = frameGrabTargetImage.height();
2265 copyInfo.extent.depth = 1;
2266
2267 devFuncs->vkCmdCopyImage(frame.cmdBuf, image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
2268 frameGrabImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyInfo);
2269
2270 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2271 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
2272 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2273 barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
2274 barrier.image = frameGrabImage;
2275
2276 devFuncs->vkCmdPipelineBarrier(frame.cmdBuf,
2277 VK_PIPELINE_STAGE_TRANSFER_BIT,
2278 VK_PIPELINE_STAGE_HOST_BIT,
2279 0, 0, nullptr, 0, nullptr,
2280 1, &barrier);
2281}
2282
2283void QVulkanWindowPrivate::finishBlockingReadback()
2284{
2285 // Block until the current frame is done. Normally this wait would only be
2286 // done in current + concurrentFrameCount().
2287 FrameResources &frame(frameRes[currentFrame]);
2288 if (frame.cmdFenceWaitable) {
2289 devFuncs->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
2290 devFuncs->vkResetFences(dev, 1, &frame.cmdFence);
2291 frame.cmdFenceWaitable = false;
2292 }
2293
2294 VkImageSubresource subres = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 };
2295 VkSubresourceLayout layout;
2296 devFuncs->vkGetImageSubresourceLayout(dev, frameGrabImage, &subres, &layout);
2297
2298 uchar *p;
2299 VkResult err = devFuncs->vkMapMemory(dev, frameGrabImageMem, layout.offset, layout.size, 0, reinterpret_cast<void **>(&p));
2300 if (err != VK_SUCCESS) {
2301 qWarning("QVulkanWindow: Failed to map readback image memory after transfer: %d", err);
2302 return;
2303 }
2304
2305 for (int y = 0; y < frameGrabTargetImage.height(); ++y) {
2306 memcpy(frameGrabTargetImage.scanLine(y), p, frameGrabTargetImage.width() * 4);
2307 p += layout.rowPitch;
2308 }
2309
2310 devFuncs->vkUnmapMemory(dev, frameGrabImageMem);
2311
2312 devFuncs->vkDestroyImage(dev, frameGrabImage, nullptr);
2313 frameGrabImage = VK_NULL_HANDLE;
2314 devFuncs->vkFreeMemory(dev, frameGrabImageMem, nullptr);
2315 frameGrabImageMem = VK_NULL_HANDLE;
2316}
2317
2318/*!
2319 Returns the active physical device.
2320
2321 \note Calling this function is only valid from the invocation of
2322 QVulkanWindowRenderer::preInitResources() up until
2323 QVulkanWindowRenderer::releaseResources().
2324 */
2325VkPhysicalDevice QVulkanWindow::physicalDevice() const
2326{
2327 Q_D(const QVulkanWindow);
2328 if (d->physDevIndex < d->physDevs.size())
2329 return d->physDevs[d->physDevIndex];
2330 qWarning("QVulkanWindow: Physical device not available");
2331 return VK_NULL_HANDLE;
2332}
2333
2334/*!
2335 Returns a pointer to the properties for the active physical device.
2336
2337 \note Calling this function is only valid from the invocation of
2338 QVulkanWindowRenderer::preInitResources() up until
2339 QVulkanWindowRenderer::releaseResources().
2340 */
2341const VkPhysicalDeviceProperties *QVulkanWindow::physicalDeviceProperties() const
2342{
2343 Q_D(const QVulkanWindow);
2344 if (d->physDevIndex < d->physDevProps.size())
2345 return &d->physDevProps[d->physDevIndex];
2346 qWarning("QVulkanWindow: Physical device properties not available");
2347 return nullptr;
2348}
2349
2350/*!
2351 Returns the active logical device.
2352
2353 \note Calling this function is only valid from the invocation of
2354 QVulkanWindowRenderer::initResources() up until
2355 QVulkanWindowRenderer::releaseResources().
2356 */
2357VkDevice QVulkanWindow::device() const
2358{
2359 Q_D(const QVulkanWindow);
2360 return d->dev;
2361}
2362
2363/*!
2364 Returns the active graphics queue.
2365
2366 \note Calling this function is only valid from the invocation of
2367 QVulkanWindowRenderer::initResources() up until
2368 QVulkanWindowRenderer::releaseResources().
2369 */
2370VkQueue QVulkanWindow::graphicsQueue() const
2371{
2372 Q_D(const QVulkanWindow);
2373 return d->gfxQueue;
2374}
2375
2376/*!
2377 Returns the family index of the active graphics queue.
2378
2379 \note Calling this function is only valid from the invocation of
2380 QVulkanWindowRenderer::initResources() up until
2381 QVulkanWindowRenderer::releaseResources(). Implementations of
2382 QVulkanWindowRenderer::updateQueueCreateInfo() can also call this
2383 function.
2384
2385 \since 5.15
2386 */
2387uint32_t QVulkanWindow::graphicsQueueFamilyIndex() const
2388{
2389 Q_D(const QVulkanWindow);
2390 return d->gfxQueueFamilyIdx;
2391}
2392
2393/*!
2394 Returns the active graphics command pool.
2395
2396 \note Calling this function is only valid from the invocation of
2397 QVulkanWindowRenderer::initResources() up until
2398 QVulkanWindowRenderer::releaseResources().
2399 */
2400VkCommandPool QVulkanWindow::graphicsCommandPool() const
2401{
2402 Q_D(const QVulkanWindow);
2403 return d->cmdPool;
2404}
2405
2406/*!
2407 Returns a host visible memory type index suitable for general use.
2408
2409 The returned memory type will be both host visible and coherent. In
2410 addition, it will also be cached, if possible.
2411
2412 \note Calling this function is only valid from the invocation of
2413 QVulkanWindowRenderer::initResources() up until
2414 QVulkanWindowRenderer::releaseResources().
2415 */
2416uint32_t QVulkanWindow::hostVisibleMemoryIndex() const
2417{
2418 Q_D(const QVulkanWindow);
2419 return d->hostVisibleMemIndex;
2420}
2421
2422/*!
2423 Returns a device local memory type index suitable for general use.
2424
2425 \note Calling this function is only valid from the invocation of
2426 QVulkanWindowRenderer::initResources() up until
2427 QVulkanWindowRenderer::releaseResources().
2428
2429 \note It is not guaranteed that this memory type is always suitable. The
2430 correct, cross-implementation solution - especially for device local images
2431 - is to manually pick a memory type after checking the mask returned from
2432 \c{vkGetImageMemoryRequirements}.
2433 */
2434uint32_t QVulkanWindow::deviceLocalMemoryIndex() const
2435{
2436 Q_D(const QVulkanWindow);
2437 return d->deviceLocalMemIndex;
2438}
2439
2440/*!
2441 Returns a typical render pass with one sub-pass.
2442
2443 \note Applications are not required to use this render pass. However, they
2444 are then responsible for ensuring the current swap chain and depth-stencil
2445 images get transitioned from \c{VK_IMAGE_LAYOUT_UNDEFINED} to
2446 \c{VK_IMAGE_LAYOUT_PRESENT_SRC_KHR} and
2447 \c{VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL} either via the
2448 application's custom render pass or by other means.
2449
2450 \note Stencil read/write is not enabled in this render pass.
2451
2452 \note Calling this function is only valid from the invocation of
2453 QVulkanWindowRenderer::initResources() up until
2454 QVulkanWindowRenderer::releaseResources().
2455
2456 \sa currentFramebuffer()
2457 */
2458VkRenderPass QVulkanWindow::defaultRenderPass() const
2459{
2460 Q_D(const QVulkanWindow);
2461 return d->defaultRenderPass;
2462}
2463
2464/*!
2465 Returns the color buffer format used by the swapchain.
2466
2467 \note Calling this function is only valid from the invocation of
2468 QVulkanWindowRenderer::initResources() up until
2469 QVulkanWindowRenderer::releaseResources().
2470
2471 \sa setPreferredColorFormats()
2472 */
2473VkFormat QVulkanWindow::colorFormat() const
2474{
2475 Q_D(const QVulkanWindow);
2476 return d->colorFormat;
2477}
2478
2479/*!
2480 Returns the format used by the depth-stencil buffer(s).
2481
2482 \note Calling this function is only valid from the invocation of
2483 QVulkanWindowRenderer::initResources() up until
2484 QVulkanWindowRenderer::releaseResources().
2485 */
2486VkFormat QVulkanWindow::depthStencilFormat() const
2487{
2488 Q_D(const QVulkanWindow);
2489 return d->dsFormat;
2490}
2491
2492/*!
2493 Returns the image size of the swapchain.
2494
2495 This usually matches the size of the window, but may also differ in case
2496 \c vkGetPhysicalDeviceSurfaceCapabilitiesKHR reports a fixed size.
2497
2498 In addition, it has been observed on some platforms that the
2499 Vulkan-reported surface size is different with high DPI scaling active,
2500 meaning the QWindow-reported
2501 \l{QWindow::}{size()} multiplied with the \l{QWindow::}{devicePixelRatio()}
2502 was 1 pixel less or more when compared to the value returned from here,
2503 presumably due to differences in rounding. Rendering code should be aware
2504 of this, and any related rendering logic must be based in the value returned
2505 from here, never on the QWindow-reported size. Regardless of which pixel size
2506 is correct in theory, Vulkan rendering must only ever rely on the Vulkan
2507 API-reported surface size. Otherwise validation errors may occur, e.g. when
2508 setting the viewport, because the application-provided values may become
2509 out-of-bounds from Vulkan's perspective.
2510
2511 \note Calling this function is only valid from the invocation of
2512 QVulkanWindowRenderer::initSwapChainResources() up until
2513 QVulkanWindowRenderer::releaseSwapChainResources().
2514 */
2515QSize QVulkanWindow::swapChainImageSize() const
2516{
2517 Q_D(const QVulkanWindow);
2518 return d->swapChainImageSize;
2519}
2520
2521/*!
2522 Returns The active command buffer for the current swap chain frame.
2523 Implementations of QVulkanWindowRenderer::startNextFrame() are expected to
2524 add commands to this command buffer.
2525
2526 \note This function must only be called from within startNextFrame() and, in
2527 case of asynchronous command generation, up until the call to frameReady().
2528 */
2529VkCommandBuffer QVulkanWindow::currentCommandBuffer() const
2530{
2531 Q_D(const QVulkanWindow);
2532 if (!d->framePending) {
2533 qWarning("QVulkanWindow: Attempted to call currentCommandBuffer() without an active frame");
2534 return VK_NULL_HANDLE;
2535 }
2536 return d->frameRes[d->currentFrame].cmdBuf;
2537}
2538
2539/*!
2540 Returns a VkFramebuffer for the current swapchain image using the default
2541 render pass.
2542
2543 The framebuffer has two attachments (color, depth-stencil) when
2544 multisampling is not in use, and three (color resolve, depth-stencil,
2545 multisample color) when sampleCountFlagBits() is greater than
2546 \c{VK_SAMPLE_COUNT_1_BIT}. Renderers must take this into account, for
2547 example when providing clear values.
2548
2549 \note Applications are not required to use this framebuffer in case they
2550 provide their own render pass instead of using the one returned from
2551 defaultRenderPass().
2552
2553 \note This function must only be called from within startNextFrame() and, in
2554 case of asynchronous command generation, up until the call to frameReady().
2555
2556 \sa defaultRenderPass()
2557 */
2558VkFramebuffer QVulkanWindow::currentFramebuffer() const
2559{
2560 Q_D(const QVulkanWindow);
2561 if (!d->framePending) {
2562 qWarning("QVulkanWindow: Attempted to call currentFramebuffer() without an active frame");
2563 return VK_NULL_HANDLE;
2564 }
2565 return d->imageRes[d->currentImage].fb;
2566}
2567
2568/*!
2569 Returns the current frame index in the range [0, concurrentFrameCount() - 1].
2570
2571 Renderer implementations will have to ensure that uniform data and other
2572 dynamic resources exist in multiple copies, in order to prevent frame N
2573 altering the data used by the still-active frames N - 1, N - 2, ... N -
2574 concurrentFrameCount() + 1.
2575
2576 To avoid relying on dynamic array sizes, applications can use
2577 MAX_CONCURRENT_FRAME_COUNT when declaring arrays. This is guaranteed to be
2578 always equal to or greater than the value returned from
2579 concurrentFrameCount(). Such arrays can then be indexed by the value
2580 returned from this function.
2581
2582 \snippet code/src_gui_vulkan_qvulkanwindow.cpp 1
2583
2584 \note This function must only be called from within startNextFrame() and, in
2585 case of asynchronous command generation, up until the call to frameReady().
2586
2587 \sa concurrentFrameCount()
2588 */
2589int QVulkanWindow::currentFrame() const
2590{
2591 Q_D(const QVulkanWindow);
2592 if (!d->framePending)
2593 qWarning("QVulkanWindow: Attempted to call currentFrame() without an active frame");
2594 return d->currentFrame;
2595}
2596
2597/*!
2598 \variable QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT
2599
2600 \brief A constant value that is always equal to or greater than the maximum value
2601 of concurrentFrameCount().
2602 */
2603
2604/*!
2605 Returns the number of frames that can be potentially active at the same time.
2606
2607 \note The value is constant for the entire lifetime of the QVulkanWindow.
2608
2609 \snippet code/src_gui_vulkan_qvulkanwindow.cpp 2
2610
2611 \sa currentFrame()
2612 */
2613int QVulkanWindow::concurrentFrameCount() const
2614{
2615 Q_D(const QVulkanWindow);
2616 return d->frameLag;
2617}
2618
2619/*!
2620 Returns the number of images in the swap chain.
2621
2622 \note Accessing this is necessary when providing a custom render pass and
2623 framebuffer. The framebuffer is specific to the current swapchain image and
2624 hence the application must provide multiple framebuffers.
2625
2626 \note Calling this function is only valid from the invocation of
2627 QVulkanWindowRenderer::initSwapChainResources() up until
2628 QVulkanWindowRenderer::releaseSwapChainResources().
2629 */
2630int QVulkanWindow::swapChainImageCount() const
2631{
2632 Q_D(const QVulkanWindow);
2633 return d->swapChainBufferCount;
2634}
2635
2636/*!
2637 Returns the current swap chain image index in the range [0, swapChainImageCount() - 1].
2638
2639 \note This function must only be called from within startNextFrame() and, in
2640 case of asynchronous command generation, up until the call to frameReady().
2641 */
2642int QVulkanWindow::currentSwapChainImageIndex() const
2643{
2644 Q_D(const QVulkanWindow);
2645 if (!d->framePending)
2646 qWarning("QVulkanWindow: Attempted to call currentSwapChainImageIndex() without an active frame");
2647 return d->currentImage;
2648}
2649
2650/*!
2651 Returns the specified swap chain image.
2652
2653 \a idx must be in the range [0, swapChainImageCount() - 1].
2654
2655 \note Calling this function is only valid from the invocation of
2656 QVulkanWindowRenderer::initSwapChainResources() up until
2657 QVulkanWindowRenderer::releaseSwapChainResources().
2658 */
2659VkImage QVulkanWindow::swapChainImage(int idx) const
2660{
2661 Q_D(const QVulkanWindow);
2662 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].image : VK_NULL_HANDLE;
2663}
2664
2665/*!
2666 Returns the specified swap chain image view.
2667
2668 \a idx must be in the range [0, swapChainImageCount() - 1].
2669
2670 \note Calling this function is only valid from the invocation of
2671 QVulkanWindowRenderer::initSwapChainResources() up until
2672 QVulkanWindowRenderer::releaseSwapChainResources().
2673 */
2674VkImageView QVulkanWindow::swapChainImageView(int idx) const
2675{
2676 Q_D(const QVulkanWindow);
2677 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].imageView : VK_NULL_HANDLE;
2678}
2679
2680/*!
2681 Returns the depth-stencil image.
2682
2683 \note Calling this function is only valid from the invocation of
2684 QVulkanWindowRenderer::initSwapChainResources() up until
2685 QVulkanWindowRenderer::releaseSwapChainResources().
2686 */
2687VkImage QVulkanWindow::depthStencilImage() const
2688{
2689 Q_D(const QVulkanWindow);
2690 return d->dsImage;
2691}
2692
2693/*!
2694 Returns the depth-stencil image view.
2695
2696 \note Calling this function is only valid from the invocation of
2697 QVulkanWindowRenderer::initSwapChainResources() up until
2698 QVulkanWindowRenderer::releaseSwapChainResources().
2699 */
2700VkImageView QVulkanWindow::depthStencilImageView() const
2701{
2702 Q_D(const QVulkanWindow);
2703 return d->dsView;
2704}
2705
2706/*!
2707 Returns the current sample count as a \c VkSampleCountFlagBits value.
2708
2709 When targeting the default render target, the \c rasterizationSamples field
2710 of \c VkPipelineMultisampleStateCreateInfo must be set to this value.
2711
2712 \sa setSampleCount(), supportedSampleCounts()
2713 */
2714VkSampleCountFlagBits QVulkanWindow::sampleCountFlagBits() const
2715{
2716 Q_D(const QVulkanWindow);
2717 return d->sampleCount;
2718}
2719
2720/*!
2721 Returns the specified multisample color image, or \c{VK_NULL_HANDLE} if
2722 multisampling is not in use.
2723
2724 \a idx must be in the range [0, swapChainImageCount() - 1].
2725
2726 \note Calling this function is only valid from the invocation of
2727 QVulkanWindowRenderer::initSwapChainResources() up until
2728 QVulkanWindowRenderer::releaseSwapChainResources().
2729 */
2730VkImage QVulkanWindow::msaaColorImage(int idx) const
2731{
2732 Q_D(const QVulkanWindow);
2733 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].msaaImage : VK_NULL_HANDLE;
2734}
2735
2736/*!
2737 Returns the specified multisample color image view, or \c{VK_NULL_HANDLE} if
2738 multisampling is not in use.
2739
2740 \a idx must be in the range [0, swapChainImageCount() - 1].
2741
2742 \note Calling this function is only valid from the invocation of
2743 QVulkanWindowRenderer::initSwapChainResources() up until
2744 QVulkanWindowRenderer::releaseSwapChainResources().
2745 */
2746VkImageView QVulkanWindow::msaaColorImageView(int idx) const
2747{
2748 Q_D(const QVulkanWindow);
2749 return idx >= 0 && idx < d->swapChainBufferCount ? d->imageRes[idx].msaaImageView : VK_NULL_HANDLE;
2750}
2751
2752/*!
2753 Returns true if the swapchain supports usage as transfer source, meaning
2754 grab() is functional.
2755
2756 \note Calling this function is only valid from the invocation of
2757 QVulkanWindowRenderer::initSwapChainResources() up until
2758 QVulkanWindowRenderer::releaseSwapChainResources().
2759 */
2760bool QVulkanWindow::supportsGrab() const
2761{
2762 Q_D(const QVulkanWindow);
2763 return d->swapChainSupportsReadBack;
2764}
2765
2766/*!
2767 \fn void QVulkanWindow::frameGrabbed(const QImage &image)
2768
2769 This signal is emitted when the \a image is ready.
2770*/
2771
2772/*!
2773 Builds and renders the next frame without presenting it, then performs a
2774 blocking readback of the image content.
2775
2776 Returns the image if the renderer's
2777 \l{QVulkanWindowRenderer::startNextFrame()}{startNextFrame()}
2778 implementation calls back frameReady() directly. Otherwise, returns an
2779 incomplete image, that has the correct size but not the content yet. The
2780 content will be delivered via the frameGrabbed() signal in the latter case.
2781
2782 The returned QImage always has a format of QImage::Format_RGBA8888. If the
2783 colorFormat() is \c VK_FORMAT_B8G8R8A8_UNORM, the red and blue channels are
2784 swapped automatically since this format is commonly used as the default
2785 choice for swapchain color buffers. With any other color buffer format,
2786 there is no conversion performed by this function.
2787
2788 \note This function should not be called when a frame is in progress
2789 (that is, frameReady() has not yet been called back by the application).
2790
2791 \note This function is potentially expensive due to the additional,
2792 blocking readback.
2793
2794 \note This function currently requires that the swapchain supports usage as
2795 a transfer source (\c{VK_IMAGE_USAGE_TRANSFER_SRC_BIT}), and will fail otherwise.
2796 */
2797QImage QVulkanWindow::grab()
2798{
2799 Q_D(QVulkanWindow);
2800 if (!d->swapChain) {
2801 qWarning("QVulkanWindow: Attempted to call grab() without a swapchain");
2802 return QImage();
2803 }
2804 if (d->framePending) {
2805 qWarning("QVulkanWindow: Attempted to call grab() while a frame is still pending");
2806 return QImage();
2807 }
2808 if (!d->swapChainSupportsReadBack) {
2809 qWarning("QVulkanWindow: Attempted to call grab() with a swapchain that does not support usage as transfer source");
2810 return QImage();
2811 }
2812
2813 d->frameGrabbing = true;
2814 d->beginFrame();
2815
2816 if (d->colorFormat == VK_FORMAT_B8G8R8A8_UNORM)
2817 d->frameGrabTargetImage = std::move(d->frameGrabTargetImage).rgbSwapped();
2818
2819 return d->frameGrabTargetImage;
2820}
2821
2822/*!
2823 Returns a QMatrix4x4 that can be used to correct for coordinate
2824 system differences between OpenGL and Vulkan.
2825
2826 By pre-multiplying the projection matrix with this matrix, applications can
2827 continue to assume that Y is pointing upwards, and can set minDepth and
2828 maxDepth in the viewport to 0 and 1, respectively, without having to do any
2829 further corrections to the vertex Z positions. Geometry from OpenGL
2830 applications can then be used as-is, assuming a rasterization state matching
2831 the OpenGL culling and front face settings.
2832 */
2833QMatrix4x4 QVulkanWindow::clipCorrectionMatrix()
2834{
2835 Q_D(QVulkanWindow);
2836 if (d->m_clipCorrect.isIdentity()) {
2837 // NB the ctor takes row-major
2838 d->m_clipCorrect = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
2839 0.0f, -1.0f, 0.0f, 0.0f,
2840 0.0f, 0.0f, 0.5f, 0.5f,
2841 0.0f, 0.0f, 0.0f, 1.0f);
2842 }
2843 return d->m_clipCorrect;
2844}
2845
2846QT_END_NAMESPACE
2847
2848#include "moc_qvulkanwindow.cpp"
Combined button and popup list for selecting options.
VkSampleCountFlagBits mask
int count
static VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)