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

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