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