summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/qvulkan/tst_qvulkan.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/gui/qvulkan/tst_qvulkan.cpp')
-rw-r--r--tests/auto/gui/qvulkan/tst_qvulkan.cpp435
1 files changed, 435 insertions, 0 deletions
diff --git a/tests/auto/gui/qvulkan/tst_qvulkan.cpp b/tests/auto/gui/qvulkan/tst_qvulkan.cpp
new file mode 100644
index 0000000000..8027935003
--- /dev/null
+++ b/tests/auto/gui/qvulkan/tst_qvulkan.cpp
@@ -0,0 +1,435 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtGui/QVulkanInstance>
+#include <QtGui/QVulkanFunctions>
+#include <QtGui/QVulkanWindow>
+
+#include <QtTest/QtTest>
+
+#include <QSignalSpy>
+
+class tst_QVulkan : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void vulkanInstance();
+ void vulkanCheckSupported();
+ void vulkanPlainWindow();
+ void vulkanVersionRequest();
+ void vulkanWindow();
+ void vulkanWindowRenderer();
+ void vulkanWindowGrab();
+};
+
+void tst_QVulkan::vulkanInstance()
+{
+ QVulkanInstance inst;
+ if (!inst.create())
+ QSKIP("Vulkan init failed; skip");
+
+ QVERIFY(inst.isValid());
+ QVERIFY(inst.vkInstance() != VK_NULL_HANDLE);
+ QVERIFY(inst.functions());
+ QVERIFY(!inst.flags().testFlag(QVulkanInstance::NoDebugOutputRedirect));
+
+ inst.destroy();
+
+ QVERIFY(!inst.isValid());
+ QVERIFY(inst.handle() == nullptr);
+
+ inst.setFlags(QVulkanInstance::NoDebugOutputRedirect);
+ // pass a bogus layer and extension
+ inst.setExtensions(QByteArrayList() << "abcdefg" << "notanextension");
+ inst.setLayers(QByteArrayList() << "notalayer");
+ QVERIFY(inst.create());
+
+ QVERIFY(inst.isValid());
+ QVERIFY(inst.vkInstance() != VK_NULL_HANDLE);
+ QVERIFY(inst.handle() != nullptr);
+ QVERIFY(inst.functions());
+ QVERIFY(inst.flags().testFlag(QVulkanInstance::NoDebugOutputRedirect));
+ QVERIFY(!inst.extensions().contains("abcdefg"));
+ QVERIFY(!inst.extensions().contains("notanextension"));
+ QVERIFY(!inst.extensions().contains("notalayer"));
+ // at least the surface extensions should be there however
+ QVERIFY(inst.extensions().contains("VK_KHR_surface"));
+
+ QVERIFY(inst.getInstanceProcAddr("vkGetDeviceQueue"));
+}
+
+void tst_QVulkan::vulkanCheckSupported()
+{
+ // Test the early calls to supportedLayers/extensions that need the library
+ // and some basics, but do not initialize the instance.
+ QVulkanInstance inst;
+ QVERIFY(!inst.isValid());
+
+ QVulkanInfoVector<QVulkanLayer> vl = inst.supportedLayers();
+ qDebug() << vl;
+ QVERIFY(!inst.isValid());
+
+ QVulkanInfoVector<QVulkanExtension> ve = inst.supportedExtensions();
+ qDebug() << ve;
+ QVERIFY(!inst.isValid());
+
+ if (inst.create()) { // skip the rest when Vulkan is not supported at all
+ QVERIFY(!ve.isEmpty());
+ QVERIFY(ve == inst.supportedExtensions());
+ }
+}
+
+void tst_QVulkan::vulkanPlainWindow()
+{
+ QVulkanInstance inst;
+ if (!inst.create())
+ QSKIP("Vulkan init failed; skip");
+
+ QWindow w;
+ w.setSurfaceType(QSurface::VulkanSurface);
+ w.setVulkanInstance(&inst);
+ w.resize(1024, 768);
+ w.show();
+ QTest::qWaitForWindowExposed(&w);
+
+ QCOMPARE(w.vulkanInstance(), &inst);
+
+ VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(&w);
+ QVERIFY(surface != VK_NULL_HANDLE);
+
+ // exercise supportsPresent (and QVulkanFunctions) a bit
+ QVulkanFunctions *f = inst.functions();
+ VkPhysicalDevice physDev;
+ uint32_t count = 1;
+ VkResult err = f->vkEnumeratePhysicalDevices(inst.vkInstance(), &count, &physDev);
+ if (err != VK_SUCCESS)
+ QSKIP("No physical devices; skip");
+
+ VkPhysicalDeviceProperties physDevProps;
+ f->vkGetPhysicalDeviceProperties(physDev, &physDevProps);
+ qDebug("Device name: %s Driver version: %d.%d.%d", physDevProps.deviceName,
+ VK_VERSION_MAJOR(physDevProps.driverVersion), VK_VERSION_MINOR(physDevProps.driverVersion),
+ VK_VERSION_PATCH(physDevProps.driverVersion));
+
+ bool supports = inst.supportsPresent(physDev, 0, &w);
+ qDebug("queue family 0 supports presenting to window = %d", supports);
+}
+
+void tst_QVulkan::vulkanVersionRequest()
+{
+ QVulkanInstance inst;
+ if (!inst.create())
+ QSKIP("Vulkan init failed; skip");
+
+ // Now that we know Vulkan is functional, check the requested apiVersion is
+ // passed to vkCreateInstance as expected.
+
+ inst.destroy();
+
+ inst.setApiVersion(QVersionNumber(10, 0, 0));
+ QVERIFY(!inst.create());
+ QCOMPARE(inst.errorCode(), VK_ERROR_INCOMPATIBLE_DRIVER);
+}
+
+static void waitForUnexposed(QWindow *w)
+{
+ QElapsedTimer timer;
+ timer.start();
+ while (w->isExposed()) {
+ int remaining = 5000 - int(timer.elapsed());
+ if (remaining <= 0)
+ break;
+ QCoreApplication::processEvents(QEventLoop::AllEvents, remaining);
+ QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete);
+ QTest::qSleep(10);
+ }
+}
+
+void tst_QVulkan::vulkanWindow()
+{
+ QVulkanInstance inst;
+ if (!inst.create())
+ QSKIP("Vulkan init failed; skip");
+
+ // First let's forget to set the instance.
+ QVulkanWindow w;
+ QVERIFY(!w.isValid());
+ w.resize(1024, 768);
+ w.show();
+ QTest::qWaitForWindowExposed(&w);
+ QVERIFY(!w.isValid());
+
+ // Now set it. A simple hide - show should be enough to correct, this, no
+ // need for a full destroy - create.
+ w.hide();
+ waitForUnexposed(&w);
+ w.setVulkanInstance(&inst);
+ QVector<VkPhysicalDeviceProperties> pdevs = w.availablePhysicalDevices();
+ if (pdevs.isEmpty())
+ QSKIP("No Vulkan physical devices; skip");
+ w.show();
+ QTest::qWaitForWindowExposed(&w);
+ QVERIFY(w.isValid());
+ QCOMPARE(w.vulkanInstance(), &inst);
+ QVulkanInfoVector<QVulkanExtension> exts = w.supportedDeviceExtensions();
+
+ // Now destroy and recreate.
+ w.destroy();
+ waitForUnexposed(&w);
+ QVERIFY(!w.isValid());
+ // check that flags can be set between a destroy() - show()
+ w.setFlags(QVulkanWindow::PersistentResources);
+ // supported lists can be queried before expose too
+ QVERIFY(w.supportedDeviceExtensions() == exts);
+ w.show();
+ QTest::qWaitForWindowExposed(&w);
+ QVERIFY(w.isValid());
+ QVERIFY(w.flags().testFlag(QVulkanWindow::PersistentResources));
+
+ QVERIFY(w.physicalDevice() != VK_NULL_HANDLE);
+ QVERIFY(w.physicalDeviceProperties() != nullptr);
+ QVERIFY(w.device() != VK_NULL_HANDLE);
+ QVERIFY(w.graphicsQueue() != VK_NULL_HANDLE);
+ QVERIFY(w.graphicsCommandPool() != VK_NULL_HANDLE);
+ QVERIFY(w.defaultRenderPass() != VK_NULL_HANDLE);
+
+ QVERIFY(w.concurrentFrameCount() > 0);
+ QVERIFY(w.concurrentFrameCount() <= QVulkanWindow::MAX_CONCURRENT_FRAME_COUNT);
+}
+
+class TestVulkanRenderer;
+
+class TestVulkanWindow : public QVulkanWindow
+{
+public:
+ QVulkanWindowRenderer *createRenderer() override;
+
+private:
+ TestVulkanRenderer *m_renderer = nullptr;
+};
+
+struct TestVulkan {
+ int preInitResCount = 0;
+ int initResCount = 0;
+ int initSwcResCount = 0;
+ int releaseResCount = 0;
+ int releaseSwcResCount = 0;
+ int startNextFrameCount = 0;
+} testVulkan;
+
+class TestVulkanRenderer : public QVulkanWindowRenderer
+{
+public:
+ TestVulkanRenderer(QVulkanWindow *w) : m_window(w) { }
+
+ void preInitResources() override;
+ void initResources() override;
+ void initSwapChainResources() override;
+ void releaseSwapChainResources() override;
+ void releaseResources() override;
+
+ void startNextFrame() override;
+
+private:
+ QVulkanWindow *m_window;
+ QVulkanDeviceFunctions *m_devFuncs;
+};
+
+void TestVulkanRenderer::preInitResources()
+{
+ if (testVulkan.initResCount) {
+ qWarning("initResources called before preInitResources?!");
+ testVulkan.preInitResCount = -1;
+ return;
+ }
+
+ // Ensure the physical device and the surface are available at this stage.
+ VkPhysicalDevice physDev = m_window->physicalDevice();
+ if (physDev == VK_NULL_HANDLE) {
+ qWarning("No physical device in preInitResources");
+ testVulkan.preInitResCount = -1;
+ return;
+ }
+ VkSurfaceKHR surface = m_window->vulkanInstance()->surfaceForWindow(m_window);
+ if (surface == VK_NULL_HANDLE) {
+ qWarning("No surface in preInitResources");
+ testVulkan.preInitResCount = -1;
+ return;
+ }
+
+ ++testVulkan.preInitResCount;
+}
+
+void TestVulkanRenderer::initResources()
+{
+ m_devFuncs = m_window->vulkanInstance()->deviceFunctions(m_window->device());
+ ++testVulkan.initResCount;
+}
+
+void TestVulkanRenderer::initSwapChainResources()
+{
+ ++testVulkan.initSwcResCount;
+}
+
+void TestVulkanRenderer::releaseSwapChainResources()
+{
+ ++testVulkan.releaseSwcResCount;
+}
+
+void TestVulkanRenderer::releaseResources()
+{
+ ++testVulkan.releaseResCount;
+}
+
+void TestVulkanRenderer::startNextFrame()
+{
+ ++testVulkan.startNextFrameCount;
+
+ VkClearColorValue clearColor = { 0, 1, 0, 1 };
+ VkClearDepthStencilValue clearDS = { 1, 0 };
+ VkClearValue clearValues[2];
+ memset(clearValues, 0, sizeof(clearValues));
+ clearValues[0].color = clearColor;
+ clearValues[1].depthStencil = clearDS;
+
+ VkRenderPassBeginInfo rpBeginInfo;
+ memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
+ rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ rpBeginInfo.renderPass = m_window->defaultRenderPass();
+ rpBeginInfo.framebuffer = m_window->currentFramebuffer();
+ const QSize sz = m_window->swapChainImageSize();
+ rpBeginInfo.renderArea.extent.width = sz.width();
+ rpBeginInfo.renderArea.extent.height = sz.height();
+ rpBeginInfo.clearValueCount = 2;
+ rpBeginInfo.pClearValues = clearValues;
+ VkCommandBuffer cmdBuf = m_window->currentCommandBuffer();
+ m_devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+ m_devFuncs->vkCmdEndRenderPass(cmdBuf);
+
+ m_window->frameReady();
+}
+
+QVulkanWindowRenderer *TestVulkanWindow::createRenderer()
+{
+ Q_ASSERT(!m_renderer);
+ m_renderer = new TestVulkanRenderer(this);
+ return m_renderer;
+}
+
+void tst_QVulkan::vulkanWindowRenderer()
+{
+ QVulkanInstance inst;
+ if (!inst.create())
+ QSKIP("Vulkan init failed; skip");
+
+ testVulkan = TestVulkan();
+
+ TestVulkanWindow w;
+ w.setVulkanInstance(&inst);
+ w.resize(1024, 768);
+ w.show();
+ QTest::qWaitForWindowExposed(&w);
+
+ if (w.availablePhysicalDevices().isEmpty())
+ QSKIP("No Vulkan physical devices; skip");
+
+ QVERIFY(testVulkan.preInitResCount == 1);
+ QVERIFY(testVulkan.initResCount == 1);
+ QVERIFY(testVulkan.initSwcResCount == 1);
+ // this has to be QTRY due to the async update in QVulkanWindowPrivate::ensureStarted()
+ QTRY_VERIFY(testVulkan.startNextFrameCount >= 1);
+
+ QVERIFY(!w.swapChainImageSize().isEmpty());
+ QVERIFY(w.colorFormat() != VK_FORMAT_UNDEFINED);
+ QVERIFY(w.depthStencilFormat() != VK_FORMAT_UNDEFINED);
+
+ w.destroy();
+ waitForUnexposed(&w);
+ QVERIFY(testVulkan.releaseSwcResCount == 1);
+ QVERIFY(testVulkan.releaseResCount == 1);
+}
+
+void tst_QVulkan::vulkanWindowGrab()
+{
+ QVulkanInstance inst;
+ inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
+ if (!inst.create())
+ QSKIP("Vulkan init failed; skip");
+
+ testVulkan = TestVulkan();
+
+ TestVulkanWindow w;
+ w.setVulkanInstance(&inst);
+ w.resize(1024, 768);
+ w.show();
+ QTest::qWaitForWindowExposed(&w);
+
+ if (w.availablePhysicalDevices().isEmpty())
+ QSKIP("No Vulkan physical devices; skip");
+
+ if (!w.supportsGrab())
+ QSKIP("No grab support; skip");
+
+ QVERIFY(!w.swapChainImageSize().isEmpty());
+
+ QImage img1 = w.grab();
+ QImage img2 = w.grab();
+ QImage img3 = w.grab();
+
+ QVERIFY(!img1.isNull());
+ QVERIFY(!img2.isNull());
+ QVERIFY(!img3.isNull());
+
+ QCOMPARE(img1.size(), w.swapChainImageSize());
+ QCOMPARE(img2.size(), w.swapChainImageSize());
+ QCOMPARE(img3.size(), w.swapChainImageSize());
+
+ QRgb a = img1.pixel(10, 20);
+ QRgb b = img2.pixel(5, 5);
+ QRgb c = img3.pixel(50, 30);
+
+ QCOMPARE(a, b);
+ QCOMPARE(b, c);
+ QRgb refPixel = qRgb(0, 255, 0);
+
+ int redFuzz = qAbs(qRed(a) - qRed(refPixel));
+ int greenFuzz = qAbs(qGreen(a) - qGreen(refPixel));
+ int blueFuzz = qAbs(qBlue(a) - qBlue(refPixel));
+
+ QVERIFY(redFuzz <= 1);
+ QVERIFY(blueFuzz <= 1);
+ QVERIFY(greenFuzz <= 1);
+
+ w.destroy();
+}
+
+QTEST_MAIN(tst_QVulkan)
+
+#include "tst_qvulkan.moc"