diff options
Diffstat (limited to 'tests/auto/gui/qvulkan/tst_qvulkan.cpp')
-rw-r--r-- | tests/auto/gui/qvulkan/tst_qvulkan.cpp | 435 |
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" |