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