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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/gui/vulkan/qvulkanwindow.cpp