1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtGui/QVulkanInstance>
30#include <QtGui/QVulkanFunctions>
31#include <QtGui/QVulkanWindow>
32
33#include <QtTest/QtTest>
34
35#include <QSignalSpy>
36
37class tst_QVulkan : public QObject
38{
39 Q_OBJECT
40
41private slots:
42 void vulkanInstance();
43 void vulkanCheckSupported();
44 void vulkanPlainWindow();
45 void vulkanVersionRequest();
46 void vulkanWindow();
47 void vulkanWindowRenderer();
48 void vulkanWindowGrab();
49};
50
51void tst_QVulkan::vulkanInstance()
52{
53 QVulkanInstance inst;
54 if (!inst.create())
55 QSKIP("Vulkan init failed; skip");
56
57 QVERIFY(inst.isValid());
58 QVERIFY(inst.vkInstance() != VK_NULL_HANDLE);
59 QVERIFY(inst.functions());
60 QVERIFY(!inst.flags().testFlag(QVulkanInstance::NoDebugOutputRedirect));
61
62 inst.destroy();
63
64 QVERIFY(!inst.isValid());
65 QVERIFY(inst.handle() == nullptr);
66
67 inst.setFlags(QVulkanInstance::NoDebugOutputRedirect);
68 // pass a bogus layer and extension
69 inst.setExtensions(QByteArrayList() << "abcdefg" << "notanextension");
70 inst.setLayers(QByteArrayList() << "notalayer");
71 QVERIFY(inst.create());
72
73 QVERIFY(inst.isValid());
74 QVERIFY(inst.vkInstance() != VK_NULL_HANDLE);
75 QVERIFY(inst.handle() != nullptr);
76 QVERIFY(inst.functions());
77 QVERIFY(inst.flags().testFlag(QVulkanInstance::NoDebugOutputRedirect));
78 QVERIFY(!inst.extensions().contains("abcdefg"));
79 QVERIFY(!inst.extensions().contains("notanextension"));
80 QVERIFY(!inst.extensions().contains("notalayer"));
81 // at least the surface extensions should be there however
82 QVERIFY(inst.extensions().contains("VK_KHR_surface"));
83
84 QVERIFY(inst.getInstanceProcAddr("vkGetDeviceQueue"));
85}
86
87void tst_QVulkan::vulkanCheckSupported()
88{
89 // Test the early calls to supportedLayers/extensions that need the library
90 // and some basics, but do not initialize the instance.
91 QVulkanInstance inst;
92 QVERIFY(!inst.isValid());
93
94 QVulkanInfoVector<QVulkanLayer> vl = inst.supportedLayers();
95 qDebug() << vl;
96 QVERIFY(!inst.isValid());
97
98 QVulkanInfoVector<QVulkanExtension> ve = inst.supportedExtensions();
99 qDebug() << ve;
100 QVERIFY(!inst.isValid());
101
102 if (inst.create()) { // skip the rest when Vulkan is not supported at all
103 QVERIFY(!ve.isEmpty());
104 QVERIFY(ve == inst.supportedExtensions());
105 }
106}
107
108void tst_QVulkan::vulkanPlainWindow()
109{
110 QVulkanInstance inst;
111 if (!inst.create())
112 QSKIP("Vulkan init failed; skip");
113
114 QWindow w;
115 w.setSurfaceType(QSurface::VulkanSurface);
116 w.setVulkanInstance(&inst);
117 w.resize(w: 1024, h: 768);
118 w.show();
119 QVERIFY(QTest::qWaitForWindowExposed(&w));
120
121 QCOMPARE(w.vulkanInstance(), &inst);
122
123 VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window: &w);
124 QVERIFY(surface != VK_NULL_HANDLE);
125
126 // exercise supportsPresent (and QVulkanFunctions) a bit
127 QVulkanFunctions *f = inst.functions();
128 VkPhysicalDevice physDev;
129 uint32_t count = 1;
130 VkResult err = f->vkEnumeratePhysicalDevices(inst.vkInstance(), &count, &physDev);
131 if (err != VK_SUCCESS)
132 QSKIP("No physical devices; skip");
133
134 VkPhysicalDeviceProperties physDevProps;
135 f->vkGetPhysicalDeviceProperties(physDev, &physDevProps);
136 qDebug(msg: "Device name: %s Driver version: %d.%d.%d", physDevProps.deviceName,
137 VK_VERSION_MAJOR(physDevProps.driverVersion), VK_VERSION_MINOR(physDevProps.driverVersion),
138 VK_VERSION_PATCH(physDevProps.driverVersion));
139
140 bool supports = inst.supportsPresent(physicalDevice: physDev, queueFamilyIndex: 0, window: &w);
141 qDebug(msg: "queue family 0 supports presenting to window = %d", supports);
142}
143
144void tst_QVulkan::vulkanVersionRequest()
145{
146 QVulkanInstance inst;
147 if (!inst.create())
148 QSKIP("Vulkan init failed; skip");
149
150 // Now that we know Vulkan is functional, check the requested apiVersion is
151 // passed to vkCreateInstance as expected.
152
153 inst.destroy();
154
155 inst.setApiVersion(QVersionNumber(10, 0, 0));
156
157 bool result = inst.create();
158
159 // Starting with Vulkan 1.1 the spec does not allow the implementation to
160 // fail the instance creation. So check for the 1.0 behavior only when
161 // create() failed, skip this verification with 1.1+ (where create() will
162 // succeed for any bogus api version).
163 if (!result)
164 QCOMPARE(inst.errorCode(), VK_ERROR_INCOMPATIBLE_DRIVER);
165}
166
167static void waitForUnexposed(QWindow *w)
168{
169 QElapsedTimer timer;
170 timer.start();
171 while (w->isExposed()) {
172 int remaining = 5000 - int(timer.elapsed());
173 if (remaining <= 0)
174 break;
175 QCoreApplication::processEvents(flags: QEventLoop::AllEvents, maxtime: remaining);
176 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
177 QTest::qSleep(ms: 10);
178 }
179}
180
181void tst_QVulkan::vulkanWindow()
182{
183 QVulkanInstance inst;
184 if (!inst.create())
185 QSKIP("Vulkan init failed; skip");
186
187 // First let's forget to set the instance.
188 QVulkanWindow w;
189 QVERIFY(!w.isValid());
190 w.resize(w: 1024, h: 768);
191 w.show();
192 QVERIFY(QTest::qWaitForWindowExposed(&w));
193 QVERIFY(!w.isValid());
194
195 // Now set it. A simple hide - show should be enough to correct, this, no
196 // need for a full destroy - create.
197 w.hide();
198 waitForUnexposed(w: &w);
199 w.setVulkanInstance(&inst);
200 QVector<VkPhysicalDeviceProperties> pdevs = w.availablePhysicalDevices();
201 if (pdevs.isEmpty())
202 QSKIP("No Vulkan physical devices; skip");
203 w.show();
204 QVERIFY(QTest::qWaitForWindowExposed(&w));
205 QVERIFY(w.isValid());
206 QCOMPARE(w.vulkanInstance(), &inst);
207 QVulkanInfoVector<QVulkanExtension> exts = w.supportedDeviceExtensions();
208
209 // Now destroy and recreate.
210 w.destroy();
211 waitForUnexposed(w: &w);
212 QVERIFY(!w.isValid());
213 // check that flags can be set between a destroy() - show()
214 w.setFlags(QVulkanWindow::PersistentResources);
215 // supported lists can be queried before expose too
216 QVERIFY(w.supportedDeviceExtensions() == exts);
217 w.show();
218 QVERIFY(QTest::qWaitForWindowExposed(&w));
219 QVERIFY(w.isValid());
220 QVERIFY(w.flags().testFlag(QVulkanWindow::PersistentResources));
221
222 QVERIFY(w.physicalDevice() != VK_NULL_HANDLE);
223 QVERIFY(w.physicalDeviceProperties() != nullptr);
224 QVERIFY(w.device() != VK_NULL_HANDLE);
225 QVERIFY(w.graphicsQueue() != VK_NULL_HANDLE);
226 QVERIFY(w.graphicsCommandPool() != VK_NULL_HANDLE);
227 QVERIFY(w.defaultRenderPass() != VK_NULL_HANDLE);
228
229 QVERIFY(w.concurrentFrameCount() > 0);
230 QVERIFY(w.concurrentFrameCount() <= QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT);
231}
232
233class TestVulkanRenderer;
234
235class TestVulkanWindow : public QVulkanWindow
236{
237public:
238 QVulkanWindowRenderer *createRenderer() override;
239
240private:
241 TestVulkanRenderer *m_renderer = nullptr;
242};
243
244struct TestVulkan {
245 int preInitResCount = 0;
246 int initResCount = 0;
247 int initSwcResCount = 0;
248 int releaseResCount = 0;
249 int releaseSwcResCount = 0;
250 int startNextFrameCount = 0;
251} testVulkan;
252
253class TestVulkanRenderer : public QVulkanWindowRenderer
254{
255public:
256 TestVulkanRenderer(QVulkanWindow *w) : m_window(w) { }
257
258 void preInitResources() override;
259 void initResources() override;
260 void initSwapChainResources() override;
261 void releaseSwapChainResources() override;
262 void releaseResources() override;
263
264 void startNextFrame() override;
265
266private:
267 QVulkanWindow *m_window;
268 QVulkanDeviceFunctions *m_devFuncs;
269};
270
271void TestVulkanRenderer::preInitResources()
272{
273 if (testVulkan.initResCount) {
274 qWarning(msg: "initResources called before preInitResources?!");
275 testVulkan.preInitResCount = -1;
276 return;
277 }
278
279 // Ensure the physical device and the surface are available at this stage.
280 VkPhysicalDevice physDev = m_window->physicalDevice();
281 if (physDev == VK_NULL_HANDLE) {
282 qWarning(msg: "No physical device in preInitResources");
283 testVulkan.preInitResCount = -1;
284 return;
285 }
286 VkSurfaceKHR surface = m_window->vulkanInstance()->surfaceForWindow(window: m_window);
287 if (surface == VK_NULL_HANDLE) {
288 qWarning(msg: "No surface in preInitResources");
289 testVulkan.preInitResCount = -1;
290 return;
291 }
292
293 ++testVulkan.preInitResCount;
294}
295
296void TestVulkanRenderer::initResources()
297{
298 m_devFuncs = m_window->vulkanInstance()->deviceFunctions(device: m_window->device());
299 ++testVulkan.initResCount;
300}
301
302void TestVulkanRenderer::initSwapChainResources()
303{
304 ++testVulkan.initSwcResCount;
305}
306
307void TestVulkanRenderer::releaseSwapChainResources()
308{
309 ++testVulkan.releaseSwcResCount;
310}
311
312void TestVulkanRenderer::releaseResources()
313{
314 ++testVulkan.releaseResCount;
315}
316
317void TestVulkanRenderer::startNextFrame()
318{
319 ++testVulkan.startNextFrameCount;
320
321 VkClearColorValue clearColor = { 0, 1, 0, 1 };
322 VkClearDepthStencilValue clearDS = { .depth: 1, .stencil: 0 };
323 VkClearValue clearValues[2];
324 memset(s: clearValues, c: 0, n: sizeof(clearValues));
325 clearValues[0].color = clearColor;
326 clearValues[1].depthStencil = clearDS;
327
328 VkRenderPassBeginInfo rpBeginInfo;
329 memset(s: &rpBeginInfo, c: 0, n: sizeof(rpBeginInfo));
330 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
331 rpBeginInfo.renderPass = m_window->defaultRenderPass();
332 rpBeginInfo.framebuffer = m_window->currentFramebuffer();
333 const QSize sz = m_window->swapChainImageSize();
334 rpBeginInfo.renderArea.extent.width = sz.width();
335 rpBeginInfo.renderArea.extent.height = sz.height();
336 rpBeginInfo.clearValueCount = 2;
337 rpBeginInfo.pClearValues = clearValues;
338 VkCommandBuffer cmdBuf = m_window->currentCommandBuffer();
339 m_devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
340
341 m_devFuncs->vkCmdEndRenderPass(cmdBuf);
342
343 m_window->frameReady();
344}
345
346QVulkanWindowRenderer *TestVulkanWindow::createRenderer()
347{
348 Q_ASSERT(!m_renderer);
349 m_renderer = new TestVulkanRenderer(this);
350 return m_renderer;
351}
352
353void tst_QVulkan::vulkanWindowRenderer()
354{
355 QVulkanInstance inst;
356 if (!inst.create())
357 QSKIP("Vulkan init failed; skip");
358
359 testVulkan = TestVulkan();
360
361 TestVulkanWindow w;
362 w.setVulkanInstance(&inst);
363 w.resize(w: 1024, h: 768);
364 w.show();
365 QVERIFY(QTest::qWaitForWindowExposed(&w));
366
367 if (w.availablePhysicalDevices().isEmpty())
368 QSKIP("No Vulkan physical devices; skip");
369
370 QVERIFY(testVulkan.preInitResCount == 1);
371 QVERIFY(testVulkan.initResCount == 1);
372 QVERIFY(testVulkan.initSwcResCount == 1);
373 // this has to be QTRY due to the async update in QVulkanWindowPrivate::ensureStarted()
374 QTRY_VERIFY(testVulkan.startNextFrameCount >= 1);
375
376 QVERIFY(!w.swapChainImageSize().isEmpty());
377 QVERIFY(w.colorFormat() != VK_FORMAT_UNDEFINED);
378 QVERIFY(w.depthStencilFormat() != VK_FORMAT_UNDEFINED);
379
380 w.destroy();
381 waitForUnexposed(w: &w);
382 QVERIFY(testVulkan.releaseSwcResCount == 1);
383 QVERIFY(testVulkan.releaseResCount == 1);
384}
385
386void tst_QVulkan::vulkanWindowGrab()
387{
388 QVulkanInstance inst;
389 inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
390 if (!inst.create())
391 QSKIP("Vulkan init failed; skip");
392
393 testVulkan = TestVulkan();
394
395 TestVulkanWindow w;
396 w.setVulkanInstance(&inst);
397 w.resize(w: 1024, h: 768);
398 w.show();
399 QVERIFY(QTest::qWaitForWindowExposed(&w));
400
401 if (w.availablePhysicalDevices().isEmpty())
402 QSKIP("No Vulkan physical devices; skip");
403
404 if (!w.supportsGrab())
405 QSKIP("No grab support; skip");
406
407 QVERIFY(!w.swapChainImageSize().isEmpty());
408
409 QImage img1 = w.grab();
410 QImage img2 = w.grab();
411 QImage img3 = w.grab();
412
413 QVERIFY(!img1.isNull());
414 QVERIFY(!img2.isNull());
415 QVERIFY(!img3.isNull());
416
417 QCOMPARE(img1.size(), w.swapChainImageSize());
418 QCOMPARE(img2.size(), w.swapChainImageSize());
419 QCOMPARE(img3.size(), w.swapChainImageSize());
420
421 QRgb a = img1.pixel(x: 10, y: 20);
422 QRgb b = img2.pixel(x: 5, y: 5);
423 QRgb c = img3.pixel(x: 50, y: 30);
424
425 QCOMPARE(a, b);
426 QCOMPARE(b, c);
427 QRgb refPixel = qRgb(r: 0, g: 255, b: 0);
428
429 int redFuzz = qAbs(t: qRed(rgb: a) - qRed(rgb: refPixel));
430 int greenFuzz = qAbs(t: qGreen(rgb: a) - qGreen(rgb: refPixel));
431 int blueFuzz = qAbs(t: qBlue(rgb: a) - qBlue(rgb: refPixel));
432
433 QVERIFY(redFuzz <= 1);
434 QVERIFY(blueFuzz <= 1);
435 QVERIFY(greenFuzz <= 1);
436
437 w.destroy();
438}
439
440QTEST_MAIN(tst_QVulkan)
441
442#include "tst_qvulkan.moc"
443

source code of qtbase/tests/auto/gui/qvulkan/tst_qvulkan.cpp