f1a23a5467
For Android, Windows and xcb. Verified on Win10 with NVIDIA, Win10 with AMD, Android with Tegra K1, Android aarch64 with Tegra X1, and Linux aarch64 with Tegra X1 (Jetson TX1, L4T). Introduce QPA-based Vulkan library loader, core function resolver, and instance creation support. In addition to creating a new VkInstance, adopting an existing one from an external engine is supported as well. The WSI specifics are hidden in the platform plugins. Vulkan-capable windows use the new surface type VulkanSurface and are associated with a QVulkanInstance. On Windows VULKAN_SDK is picked up automatically so finding vulkan.h needs no additional manual steps once the LunarG SDK is installed. [ChangeLog][QtGui] Added support for rendering to QWindow via the Vulkan graphics API. Task-number: QTBUG-55981 Change-Id: I50fa92d313fa440e0cc73939c6d7510ca317fbc9 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io> Reviewed-by: Andy Nichols <andy.nichols@qt.io>
714 lines
26 KiB
C++
714 lines
26 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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 <QGuiApplication>
|
|
#include <QVulkanInstance>
|
|
#include <QVulkanFunctions>
|
|
#include <QWindow>
|
|
#include <QLoggingCategory>
|
|
#include <qevent.h>
|
|
|
|
static const int SWAPCHAIN_BUFFER_COUNT = 2;
|
|
static const int FRAME_LAG = 2;
|
|
|
|
class VWindow : public QWindow
|
|
{
|
|
public:
|
|
VWindow() { setSurfaceType(VulkanSurface); }
|
|
~VWindow() { releaseResources(); }
|
|
|
|
private:
|
|
void exposeEvent(QExposeEvent *) override;
|
|
void resizeEvent(QResizeEvent *) override;
|
|
bool event(QEvent *) override;
|
|
|
|
void init();
|
|
void releaseResources();
|
|
void recreateSwapChain();
|
|
void createDefaultRenderPass();
|
|
void releaseSwapChain();
|
|
void render();
|
|
void buildDrawCalls();
|
|
|
|
bool m_inited = false;
|
|
VkSurfaceKHR m_vkSurface;
|
|
VkPhysicalDevice m_vkPhysDev;
|
|
VkPhysicalDeviceProperties m_physDevProps;
|
|
VkDevice m_vkDev = 0;
|
|
QVulkanDeviceFunctions *m_devFuncs;
|
|
VkQueue m_vkGfxQueue;
|
|
VkQueue m_vkPresQueue;
|
|
VkCommandPool m_vkCmdPool = 0;
|
|
|
|
PFN_vkCreateSwapchainKHR m_vkCreateSwapchainKHR = nullptr;
|
|
PFN_vkDestroySwapchainKHR m_vkDestroySwapchainKHR;
|
|
PFN_vkGetSwapchainImagesKHR m_vkGetSwapchainImagesKHR;
|
|
PFN_vkAcquireNextImageKHR m_vkAcquireNextImageKHR;
|
|
PFN_vkQueuePresentKHR m_vkQueuePresentKHR;
|
|
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR m_vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
|
|
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR m_vkGetPhysicalDeviceSurfaceFormatsKHR;
|
|
|
|
QSize m_swapChainImageSize;
|
|
VkFormat m_colorFormat;
|
|
VkSwapchainKHR m_swapChain = 0;
|
|
uint32_t m_swapChainBufferCount = 0;
|
|
|
|
struct ImageResources {
|
|
VkImage image = 0;
|
|
VkImageView imageView = 0;
|
|
VkCommandBuffer cmdBuf = 0;
|
|
VkFence cmdFence = 0;
|
|
bool cmdFenceWaitable = false;
|
|
VkFramebuffer fb = 0;
|
|
} m_imageRes[SWAPCHAIN_BUFFER_COUNT];
|
|
|
|
uint32_t m_currentImage;
|
|
|
|
struct FrameResources {
|
|
VkFence fence = 0;
|
|
bool fenceWaitable = false;
|
|
VkSemaphore imageSem = 0;
|
|
VkSemaphore drawSem = 0;
|
|
} m_frameRes[FRAME_LAG];
|
|
|
|
uint32_t m_currentFrame;
|
|
|
|
VkRenderPass m_defaultRenderPass = 0;
|
|
};
|
|
|
|
void VWindow::exposeEvent(QExposeEvent *)
|
|
{
|
|
if (isExposed() && !m_inited) {
|
|
qDebug("initializing");
|
|
m_inited = true;
|
|
init();
|
|
recreateSwapChain();
|
|
render();
|
|
}
|
|
|
|
// Release everything when unexposed - the meaning of which is platform specific.
|
|
// Can be essential on mobile, to release resources while in background.
|
|
#if 1
|
|
if (!isExposed() && m_inited) {
|
|
m_inited = false;
|
|
releaseSwapChain();
|
|
releaseResources();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void VWindow::resizeEvent(QResizeEvent *)
|
|
{
|
|
// Nothing to do here - recreating the swapchain is handled in render(),
|
|
// in fact calling recreateSwapChain() from here leads to problems.
|
|
}
|
|
|
|
bool VWindow::event(QEvent *e)
|
|
{
|
|
switch (e->type()) {
|
|
case QEvent::UpdateRequest:
|
|
render();
|
|
break;
|
|
|
|
// Now the fun part: the swapchain must be destroyed before the surface as per
|
|
// spec. This is not ideal for us because the surface is managed by the
|
|
// QPlatformWindow which may be gone already when the unexpose comes, making the
|
|
// validation layer scream. The solution is to listen to the PlatformSurface events.
|
|
case QEvent::PlatformSurface:
|
|
if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
|
|
releaseSwapChain();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QWindow::event(e);
|
|
}
|
|
|
|
void VWindow::init()
|
|
{
|
|
m_vkSurface = QVulkanInstance::surfaceForWindow(this);
|
|
if (!m_vkSurface)
|
|
qFatal("Failed to get surface for window");
|
|
|
|
QVulkanInstance *inst = vulkanInstance();
|
|
QVulkanFunctions *f = inst->functions();
|
|
uint32_t devCount = 0;
|
|
f->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, nullptr);
|
|
qDebug("%d physical devices", devCount);
|
|
if (!devCount)
|
|
qFatal("No physical devices");
|
|
|
|
// Just pick the first physical device for now.
|
|
devCount = 1;
|
|
VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &devCount, &m_vkPhysDev);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to enumerate physical devices: %d", err);
|
|
|
|
f->vkGetPhysicalDeviceProperties(m_vkPhysDev, &m_physDevProps);
|
|
qDebug("Device name: %s Driver version: %d.%d.%d", m_physDevProps.deviceName,
|
|
VK_VERSION_MAJOR(m_physDevProps.driverVersion), VK_VERSION_MINOR(m_physDevProps.driverVersion),
|
|
VK_VERSION_PATCH(m_physDevProps.driverVersion));
|
|
|
|
uint32_t queueCount = 0;
|
|
f->vkGetPhysicalDeviceQueueFamilyProperties(m_vkPhysDev, &queueCount, nullptr);
|
|
QVector<VkQueueFamilyProperties> queueFamilyProps(queueCount);
|
|
f->vkGetPhysicalDeviceQueueFamilyProperties(m_vkPhysDev, &queueCount, queueFamilyProps.data());
|
|
int gfxQueueFamilyIdx = -1;
|
|
int presQueueFamilyIdx = -1;
|
|
// First look for a queue that supports both.
|
|
for (int i = 0; i < queueFamilyProps.count(); ++i) {
|
|
qDebug("queue family %d: flags=0x%x count=%d", i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount);
|
|
if (gfxQueueFamilyIdx == -1
|
|
&& (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
|
&& inst->supportsPresent(m_vkPhysDev, i, this))
|
|
gfxQueueFamilyIdx = i;
|
|
}
|
|
if (gfxQueueFamilyIdx != -1) {
|
|
presQueueFamilyIdx = gfxQueueFamilyIdx;
|
|
} else {
|
|
// Separate queues then.
|
|
qDebug("No queue with graphics+present; trying separate queues");
|
|
for (int i = 0; i < queueFamilyProps.count(); ++i) {
|
|
if (gfxQueueFamilyIdx == -1 && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
|
|
gfxQueueFamilyIdx = i;
|
|
if (presQueueFamilyIdx == -1 && inst->supportsPresent(m_vkPhysDev, i, this))
|
|
presQueueFamilyIdx = i;
|
|
}
|
|
}
|
|
if (gfxQueueFamilyIdx == -1)
|
|
qFatal("No graphics queue family found");
|
|
if (presQueueFamilyIdx == -1)
|
|
qFatal("No present queue family found");
|
|
|
|
VkDeviceQueueCreateInfo queueInfo[2];
|
|
const float prio[] = { 0 };
|
|
memset(queueInfo, 0, sizeof(queueInfo));
|
|
queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
queueInfo[0].queueFamilyIndex = gfxQueueFamilyIdx;
|
|
queueInfo[0].queueCount = 1;
|
|
queueInfo[0].pQueuePriorities = prio;
|
|
if (gfxQueueFamilyIdx != presQueueFamilyIdx) {
|
|
queueInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
queueInfo[1].queueFamilyIndex = presQueueFamilyIdx;
|
|
queueInfo[1].queueCount = 1;
|
|
queueInfo[1].pQueuePriorities = prio;
|
|
}
|
|
|
|
QVector<const char *> devLayers;
|
|
if (inst->layers().contains("VK_LAYER_LUNARG_standard_validation"))
|
|
devLayers.append("VK_LAYER_LUNARG_standard_validation");
|
|
|
|
QVector<const char *> devExts;
|
|
devExts.append("VK_KHR_swapchain");
|
|
|
|
VkDeviceCreateInfo devInfo;
|
|
memset(&devInfo, 0, sizeof(devInfo));
|
|
devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
devInfo.queueCreateInfoCount = gfxQueueFamilyIdx == presQueueFamilyIdx ? 1 : 2;
|
|
devInfo.pQueueCreateInfos = queueInfo;
|
|
devInfo.enabledLayerCount = devLayers.count();
|
|
devInfo.ppEnabledLayerNames = devLayers.constData();
|
|
devInfo.enabledExtensionCount = devExts.count();
|
|
devInfo.ppEnabledExtensionNames = devExts.constData();
|
|
|
|
err = f->vkCreateDevice(m_vkPhysDev, &devInfo, nullptr, &m_vkDev);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create device: %d", err);
|
|
|
|
m_devFuncs = inst->deviceFunctions(m_vkDev);
|
|
|
|
m_devFuncs->vkGetDeviceQueue(m_vkDev, gfxQueueFamilyIdx, 0, &m_vkGfxQueue);
|
|
if (gfxQueueFamilyIdx == presQueueFamilyIdx)
|
|
m_vkPresQueue = m_vkGfxQueue;
|
|
else
|
|
m_devFuncs->vkGetDeviceQueue(m_vkDev, presQueueFamilyIdx, 0, &m_vkPresQueue);
|
|
|
|
VkCommandPoolCreateInfo poolInfo;
|
|
memset(&poolInfo, 0, sizeof(poolInfo));
|
|
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
|
|
err = m_devFuncs->vkCreateCommandPool(m_vkDev, &poolInfo, nullptr, &m_vkCmdPool);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create command pool: %d", err);
|
|
|
|
m_colorFormat = VK_FORMAT_B8G8R8A8_UNORM; // may get changed later when setting up the swapchain
|
|
}
|
|
|
|
void VWindow::releaseResources()
|
|
{
|
|
if (!m_vkDev)
|
|
return;
|
|
|
|
m_devFuncs->vkDeviceWaitIdle(m_vkDev);
|
|
|
|
if (m_vkCmdPool) {
|
|
m_devFuncs->vkDestroyCommandPool(m_vkDev, m_vkCmdPool, nullptr);
|
|
m_vkCmdPool = 0;
|
|
}
|
|
|
|
if (m_vkDev) {
|
|
m_devFuncs->vkDestroyDevice(m_vkDev, nullptr);
|
|
|
|
// Play nice and notify QVulkanInstance that the QVulkanDeviceFunctions
|
|
// for m_vkDev needs to be invalidated.
|
|
vulkanInstance()->resetDeviceFunctions(m_vkDev);
|
|
|
|
m_vkDev = 0;
|
|
}
|
|
|
|
m_vkSurface = 0;
|
|
}
|
|
|
|
void VWindow::recreateSwapChain()
|
|
{
|
|
m_swapChainImageSize = size();
|
|
|
|
if (m_swapChainImageSize.isEmpty())
|
|
return;
|
|
|
|
QVulkanInstance *inst = vulkanInstance();
|
|
QVulkanFunctions *f = inst->functions();
|
|
m_devFuncs->vkDeviceWaitIdle(m_vkDev);
|
|
|
|
if (!m_vkCreateSwapchainKHR) {
|
|
m_vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
|
|
inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
|
|
m_vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
|
|
inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
|
|
// note: device-specific functions
|
|
m_vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkCreateSwapchainKHR"));
|
|
m_vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkDestroySwapchainKHR"));
|
|
m_vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkGetSwapchainImagesKHR"));
|
|
m_vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkAcquireNextImageKHR"));
|
|
m_vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(m_vkDev, "vkQueuePresentKHR"));
|
|
}
|
|
|
|
VkColorSpaceKHR colorSpace = VkColorSpaceKHR(0);
|
|
uint32_t formatCount = 0;
|
|
m_vkGetPhysicalDeviceSurfaceFormatsKHR(m_vkPhysDev, m_vkSurface, &formatCount, nullptr);
|
|
if (formatCount) {
|
|
QVector<VkSurfaceFormatKHR> formats(formatCount);
|
|
m_vkGetPhysicalDeviceSurfaceFormatsKHR(m_vkPhysDev, m_vkSurface, &formatCount, formats.data());
|
|
if (formats[0].format != VK_FORMAT_UNDEFINED) {
|
|
m_colorFormat = formats[0].format;
|
|
colorSpace = formats[0].colorSpace;
|
|
}
|
|
}
|
|
|
|
VkSurfaceCapabilitiesKHR surfaceCaps;
|
|
m_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_vkPhysDev, m_vkSurface, &surfaceCaps);
|
|
uint32_t reqBufferCount = SWAPCHAIN_BUFFER_COUNT;
|
|
if (surfaceCaps.maxImageCount)
|
|
reqBufferCount = qBound(surfaceCaps.minImageCount, reqBufferCount, surfaceCaps.maxImageCount);
|
|
|
|
VkExtent2D bufferSize = surfaceCaps.currentExtent;
|
|
if (bufferSize.width == uint32_t(-1))
|
|
bufferSize.width = m_swapChainImageSize.width();
|
|
if (bufferSize.height == uint32_t(-1))
|
|
bufferSize.height = m_swapChainImageSize.height();
|
|
|
|
VkSurfaceTransformFlagBitsKHR preTransform =
|
|
(surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
|
|
? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
|
|
: surfaceCaps.currentTransform;
|
|
|
|
VkCompositeAlphaFlagBitsKHR compositeAlpha =
|
|
(surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
|
|
? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
|
|
: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
|
|
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
VkSwapchainKHR oldSwapChain = m_swapChain;
|
|
VkSwapchainCreateInfoKHR swapChainInfo;
|
|
memset(&swapChainInfo, 0, sizeof(swapChainInfo));
|
|
swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
|
swapChainInfo.surface = m_vkSurface;
|
|
swapChainInfo.minImageCount = reqBufferCount;
|
|
swapChainInfo.imageFormat = m_colorFormat;
|
|
swapChainInfo.imageColorSpace = colorSpace;
|
|
swapChainInfo.imageExtent = bufferSize;
|
|
swapChainInfo.imageArrayLayers = 1;
|
|
swapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
swapChainInfo.preTransform = preTransform;
|
|
swapChainInfo.compositeAlpha = compositeAlpha;
|
|
swapChainInfo.presentMode = presentMode;
|
|
swapChainInfo.clipped = true;
|
|
swapChainInfo.oldSwapchain = oldSwapChain;
|
|
|
|
qDebug("creating new swap chain of %d buffers, size %dx%d", reqBufferCount, bufferSize.width, bufferSize.height);
|
|
|
|
VkSwapchainKHR newSwapChain;
|
|
VkResult err = m_vkCreateSwapchainKHR(m_vkDev, &swapChainInfo, nullptr, &newSwapChain);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create swap chain: %d", err);
|
|
|
|
if (oldSwapChain)
|
|
releaseSwapChain();
|
|
|
|
m_swapChain = newSwapChain;
|
|
|
|
m_swapChainBufferCount = 0;
|
|
err = m_vkGetSwapchainImagesKHR(m_vkDev, m_swapChain, &m_swapChainBufferCount, nullptr);
|
|
if (err != VK_SUCCESS || m_swapChainBufferCount < 2)
|
|
qFatal("Failed to get swapchain images: %d (count=%d)", err, m_swapChainBufferCount);
|
|
|
|
qDebug("actual swap chain buffer count: %d", m_swapChainBufferCount);
|
|
Q_ASSERT(m_swapChainBufferCount <= SWAPCHAIN_BUFFER_COUNT);
|
|
|
|
VkImage swapChainImages[SWAPCHAIN_BUFFER_COUNT];
|
|
err = m_vkGetSwapchainImagesKHR(m_vkDev, m_swapChain, &m_swapChainBufferCount, swapChainImages);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to get swapchain images: %d", err);
|
|
|
|
// Now that we know m_colorFormat, create the default renderpass, the framebuffers will need it.
|
|
createDefaultRenderPass();
|
|
|
|
VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT };
|
|
|
|
for (uint32_t i = 0; i < m_swapChainBufferCount; ++i) {
|
|
ImageResources &image(m_imageRes[i]);
|
|
image.image = swapChainImages[i];
|
|
|
|
VkImageViewCreateInfo imgViewInfo;
|
|
memset(&imgViewInfo, 0, sizeof(imgViewInfo));
|
|
imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
imgViewInfo.image = swapChainImages[i];
|
|
imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
imgViewInfo.format = m_colorFormat;
|
|
imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
|
|
err = m_devFuncs->vkCreateImageView(m_vkDev, &imgViewInfo, nullptr, &image.imageView);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create swapchain image view %d: %d", i, err);
|
|
|
|
err = m_devFuncs->vkCreateFence(m_vkDev, &fenceInfo, nullptr, &image.cmdFence);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create command buffer fence: %d", err);
|
|
image.cmdFenceWaitable = true;
|
|
|
|
VkImageView views[1] = { image.imageView };
|
|
VkFramebufferCreateInfo fbInfo;
|
|
memset(&fbInfo, 0, sizeof(fbInfo));
|
|
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
fbInfo.renderPass = m_defaultRenderPass;
|
|
fbInfo.attachmentCount = 1;
|
|
fbInfo.pAttachments = views;
|
|
fbInfo.width = m_swapChainImageSize.width();
|
|
fbInfo.height = m_swapChainImageSize.height();
|
|
fbInfo.layers = 1;
|
|
VkResult err = m_devFuncs->vkCreateFramebuffer(m_vkDev, &fbInfo, nullptr, &image.fb);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create framebuffer: %d", err);
|
|
}
|
|
|
|
m_currentImage = 0;
|
|
|
|
VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
|
|
for (uint32_t i = 0; i < FRAME_LAG; ++i) {
|
|
FrameResources &frame(m_frameRes[i]);
|
|
m_devFuncs->vkCreateFence(m_vkDev, &fenceInfo, nullptr, &frame.fence);
|
|
frame.fenceWaitable = true;
|
|
m_devFuncs->vkCreateSemaphore(m_vkDev, &semInfo, nullptr, &frame.imageSem);
|
|
m_devFuncs->vkCreateSemaphore(m_vkDev, &semInfo, nullptr, &frame.drawSem);
|
|
}
|
|
|
|
m_currentFrame = 0;
|
|
}
|
|
|
|
void VWindow::createDefaultRenderPass()
|
|
{
|
|
VkAttachmentDescription attDesc[1];
|
|
memset(attDesc, 0, sizeof(attDesc));
|
|
attDesc[0].format = m_colorFormat;
|
|
attDesc[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attDesc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attDesc[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
|
|
|
|
VkSubpassDescription subPassDesc;
|
|
memset(&subPassDesc, 0, sizeof(subPassDesc));
|
|
subPassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subPassDesc.colorAttachmentCount = 1;
|
|
subPassDesc.pColorAttachments = &colorRef;
|
|
|
|
VkRenderPassCreateInfo rpInfo;
|
|
memset(&rpInfo, 0, sizeof(rpInfo));
|
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
rpInfo.attachmentCount = 1;
|
|
rpInfo.pAttachments = attDesc;
|
|
rpInfo.subpassCount = 1;
|
|
rpInfo.pSubpasses = &subPassDesc;
|
|
VkResult err = m_devFuncs->vkCreateRenderPass(m_vkDev, &rpInfo, nullptr, &m_defaultRenderPass);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to create renderpass: %d", err);
|
|
}
|
|
|
|
void VWindow::releaseSwapChain()
|
|
{
|
|
if (!m_vkDev)
|
|
return;
|
|
|
|
m_devFuncs->vkDeviceWaitIdle(m_vkDev);
|
|
|
|
if (m_defaultRenderPass) {
|
|
m_devFuncs->vkDestroyRenderPass(m_vkDev, m_defaultRenderPass, nullptr);
|
|
m_defaultRenderPass = 0;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < FRAME_LAG; ++i) {
|
|
FrameResources &frame(m_frameRes[i]);
|
|
if (frame.fence) {
|
|
if (frame.fenceWaitable)
|
|
m_devFuncs->vkWaitForFences(m_vkDev, 1, &frame.fence, VK_TRUE, UINT64_MAX);
|
|
m_devFuncs->vkDestroyFence(m_vkDev, frame.fence, nullptr);
|
|
frame.fence = 0;
|
|
frame.fenceWaitable = false;
|
|
}
|
|
if (frame.imageSem) {
|
|
m_devFuncs->vkDestroySemaphore(m_vkDev, frame.imageSem, nullptr);
|
|
frame.imageSem = 0;
|
|
}
|
|
if (frame.drawSem) {
|
|
m_devFuncs->vkDestroySemaphore(m_vkDev, frame.drawSem, nullptr);
|
|
frame.drawSem = 0;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < m_swapChainBufferCount; ++i) {
|
|
ImageResources &image(m_imageRes[i]);
|
|
if (image.cmdFence) {
|
|
if (image.cmdFenceWaitable)
|
|
m_devFuncs->vkWaitForFences(m_vkDev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
|
|
m_devFuncs->vkDestroyFence(m_vkDev, image.cmdFence, nullptr);
|
|
image.cmdFence = 0;
|
|
image.cmdFenceWaitable = false;
|
|
}
|
|
if (image.fb) {
|
|
m_devFuncs->vkDestroyFramebuffer(m_vkDev, image.fb, nullptr);
|
|
image.fb = 0;
|
|
}
|
|
if (image.imageView) {
|
|
m_devFuncs->vkDestroyImageView(m_vkDev, image.imageView, nullptr);
|
|
image.imageView = 0;
|
|
}
|
|
if (image.cmdBuf) {
|
|
m_devFuncs->vkFreeCommandBuffers(m_vkDev, m_vkCmdPool, 1, &image.cmdBuf);
|
|
image.cmdBuf = 0;
|
|
}
|
|
}
|
|
|
|
if (m_swapChain) {
|
|
m_vkDestroySwapchainKHR(m_vkDev, m_swapChain, nullptr);
|
|
m_swapChain = 0;
|
|
}
|
|
}
|
|
|
|
void VWindow::render()
|
|
{
|
|
if (!m_swapChain)
|
|
return;
|
|
|
|
if (size() != m_swapChainImageSize) {
|
|
recreateSwapChain();
|
|
if (!m_swapChain)
|
|
return;
|
|
}
|
|
|
|
FrameResources &frame(m_frameRes[m_currentFrame]);
|
|
|
|
// Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate
|
|
// (note that we are using FIFO mode -> vsync)
|
|
if (frame.fenceWaitable) {
|
|
m_devFuncs->vkWaitForFences(m_vkDev, 1, &frame.fence, VK_TRUE, UINT64_MAX);
|
|
m_devFuncs->vkResetFences(m_vkDev, 1, &frame.fence);
|
|
}
|
|
|
|
// move on to next swapchain image
|
|
VkResult err = m_vkAcquireNextImageKHR(m_vkDev, m_swapChain, UINT64_MAX,
|
|
frame.imageSem, frame.fence, &m_currentImage);
|
|
if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
|
|
frame.fenceWaitable = true;
|
|
} else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
frame.fenceWaitable = false;
|
|
recreateSwapChain();
|
|
requestUpdate();
|
|
return;
|
|
} else {
|
|
qWarning("Failed to acquire next swapchain image: %d", err);
|
|
frame.fenceWaitable = false;
|
|
requestUpdate();
|
|
return;
|
|
}
|
|
|
|
// make sure the previous draw for the same image has finished
|
|
ImageResources &image(m_imageRes[m_currentImage]);
|
|
if (image.cmdFenceWaitable) {
|
|
m_devFuncs->vkWaitForFences(m_vkDev, 1, &image.cmdFence, VK_TRUE, UINT64_MAX);
|
|
m_devFuncs->vkResetFences(m_vkDev, 1, &image.cmdFence);
|
|
}
|
|
|
|
// build new draw command buffer
|
|
buildDrawCalls();
|
|
|
|
// submit draw calls
|
|
VkSubmitInfo submitInfo;
|
|
memset(&submitInfo, 0, sizeof(submitInfo));
|
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &image.cmdBuf;
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = &frame.imageSem;
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &frame.drawSem;
|
|
VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
submitInfo.pWaitDstStageMask = &psf;
|
|
|
|
err = m_devFuncs->vkQueueSubmit(m_vkGfxQueue, 1, &submitInfo, image.cmdFence);
|
|
if (err == VK_SUCCESS) {
|
|
image.cmdFenceWaitable = true;
|
|
} else {
|
|
qWarning("Failed to submit to command queue: %d", err);
|
|
image.cmdFenceWaitable = false;
|
|
}
|
|
|
|
// queue present
|
|
VkPresentInfoKHR presInfo;
|
|
memset(&presInfo, 0, sizeof(presInfo));
|
|
presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
|
presInfo.swapchainCount = 1;
|
|
presInfo.pSwapchains = &m_swapChain;
|
|
presInfo.pImageIndices = &m_currentImage;
|
|
presInfo.waitSemaphoreCount = 1;
|
|
presInfo.pWaitSemaphores = &frame.drawSem;
|
|
|
|
// we do not currently handle the case when the present queue is separate
|
|
Q_ASSERT(m_vkGfxQueue == m_vkPresQueue);
|
|
|
|
err = m_vkQueuePresentKHR(m_vkGfxQueue, &presInfo);
|
|
if (err != VK_SUCCESS) {
|
|
if (err == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
recreateSwapChain();
|
|
requestUpdate();
|
|
return;
|
|
} else if (err != VK_SUBOPTIMAL_KHR) {
|
|
qWarning("Failed to present: %d", err);
|
|
}
|
|
}
|
|
|
|
vulkanInstance()->presentQueued(this);
|
|
|
|
m_currentFrame = (m_currentFrame + 1) % FRAME_LAG;
|
|
requestUpdate();
|
|
}
|
|
|
|
void VWindow::buildDrawCalls()
|
|
{
|
|
ImageResources &image(m_imageRes[m_currentImage]);
|
|
|
|
if (image.cmdBuf) {
|
|
m_devFuncs->vkFreeCommandBuffers(m_vkDev, m_vkCmdPool, 1, &image.cmdBuf);
|
|
image.cmdBuf = 0;
|
|
}
|
|
|
|
VkCommandBufferAllocateInfo cmdBufInfo = {
|
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, m_vkCmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
|
|
VkResult err = m_devFuncs->vkAllocateCommandBuffers(m_vkDev, &cmdBufInfo, &image.cmdBuf);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to allocate frame command buffer: %d", err);
|
|
|
|
VkCommandBufferBeginInfo cmdBufBeginInfo = {
|
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, 0, nullptr };
|
|
err = m_devFuncs->vkBeginCommandBuffer(image.cmdBuf, &cmdBufBeginInfo);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to begin frame command buffer: %d", err);
|
|
|
|
static float g = 0;
|
|
g += 0.005f;
|
|
if (g > 1.0f)
|
|
g = 0.0f;
|
|
VkClearColorValue clearColor = { 0.0f, g, 0.0f, 1.0f };
|
|
VkClearValue clearValues[1];
|
|
clearValues[0].color = clearColor;
|
|
|
|
VkRenderPassBeginInfo rpBeginInfo;
|
|
memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
|
|
rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
rpBeginInfo.renderPass = m_defaultRenderPass;
|
|
rpBeginInfo.framebuffer = image.fb;
|
|
rpBeginInfo.renderArea.extent.width = m_swapChainImageSize.width();
|
|
rpBeginInfo.renderArea.extent.height = m_swapChainImageSize.height();
|
|
rpBeginInfo.clearValueCount = 1;
|
|
rpBeginInfo.pClearValues = clearValues;
|
|
m_devFuncs->vkCmdBeginRenderPass(image.cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
m_devFuncs->vkCmdEndRenderPass(image.cmdBuf);
|
|
|
|
err = m_devFuncs->vkEndCommandBuffer(image.cmdBuf);
|
|
if (err != VK_SUCCESS)
|
|
qFatal("Failed to end frame command buffer: %d", err);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
QGuiApplication app(argc, argv);
|
|
|
|
QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true"));
|
|
|
|
QVulkanInstance inst;
|
|
// Test the early queries for supported layers/exts.
|
|
qDebug() << inst.supportedLayers() << inst.supportedExtensions();
|
|
|
|
// Enable validation layer, if supported.
|
|
inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
|
|
|
|
bool ok = inst.create();
|
|
qDebug("QVulkanInstance::create() returned %d", ok);
|
|
if (!ok)
|
|
return 1;
|
|
|
|
VWindow w;
|
|
w.setVulkanInstance(&inst);
|
|
w.resize(1024, 768);
|
|
w.show();
|
|
|
|
return app.exec();
|
|
}
|