2019-08-26 09:45:54 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
**
|
|
|
|
** This file is part of the demonstration applications of the Qt Toolkit.
|
|
|
|
**
|
|
|
|
** $QT_BEGIN_LICENSE:BSD$
|
|
|
|
** 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.
|
|
|
|
**
|
|
|
|
** BSD License Usage
|
|
|
|
** Alternatively, you may use this file under the terms of the BSD license
|
|
|
|
** as follows:
|
|
|
|
**
|
|
|
|
** "Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions are
|
|
|
|
** met:
|
|
|
|
** * Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** * Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in
|
|
|
|
** the documentation and/or other materials provided with the
|
|
|
|
** distribution.
|
|
|
|
** * Neither the name of The Qt Company Ltd nor the names of its
|
|
|
|
** contributors may be used to endorse or promote products derived
|
|
|
|
** from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
|
|
|
**
|
|
|
|
** $QT_END_LICENSE$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include "vulkansquircle.h"
|
|
|
|
#include <QtCore/QRunnable>
|
|
|
|
#include <QtQuick/QQuickWindow>
|
|
|
|
|
|
|
|
#include <QVulkanInstance>
|
|
|
|
#include <QVulkanFunctions>
|
|
|
|
|
|
|
|
class SquircleRenderer : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
~SquircleRenderer();
|
|
|
|
|
|
|
|
void setT(qreal t) { m_t = t; }
|
|
|
|
void setViewportSize(const QSize &size) { m_viewportSize = size; }
|
|
|
|
void setWindow(QQuickWindow *window) { m_window = window; }
|
|
|
|
|
|
|
|
public slots:
|
|
|
|
void frameStart();
|
|
|
|
void mainPassRecordingStart();
|
|
|
|
|
|
|
|
private:
|
|
|
|
enum Stage {
|
|
|
|
VertexStage,
|
|
|
|
FragmentStage
|
|
|
|
};
|
|
|
|
void prepareShader(Stage stage);
|
|
|
|
void init(int framesInFlight);
|
|
|
|
|
|
|
|
QSize m_viewportSize;
|
|
|
|
qreal m_t = 0;
|
|
|
|
QQuickWindow *m_window;
|
|
|
|
|
|
|
|
QByteArray m_vert;
|
|
|
|
QByteArray m_frag;
|
|
|
|
|
|
|
|
bool m_initialized = false;
|
|
|
|
VkPhysicalDevice m_physDev = VK_NULL_HANDLE;
|
|
|
|
VkDevice m_dev = VK_NULL_HANDLE;
|
|
|
|
QVulkanDeviceFunctions *m_devFuncs = nullptr;
|
|
|
|
QVulkanFunctions *m_funcs = nullptr;
|
|
|
|
|
|
|
|
VkBuffer m_vbuf = VK_NULL_HANDLE;
|
|
|
|
VkDeviceMemory m_vbufMem = VK_NULL_HANDLE;
|
|
|
|
VkBuffer m_ubuf = VK_NULL_HANDLE;
|
|
|
|
VkDeviceMemory m_ubufMem = VK_NULL_HANDLE;
|
|
|
|
VkDeviceSize m_allocPerUbuf = 0;
|
|
|
|
|
|
|
|
VkPipelineCache m_pipelineCache = VK_NULL_HANDLE;
|
|
|
|
|
|
|
|
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
|
|
|
|
VkDescriptorSetLayout m_resLayout = VK_NULL_HANDLE;
|
|
|
|
VkPipeline m_pipeline = VK_NULL_HANDLE;
|
|
|
|
|
|
|
|
VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE;
|
|
|
|
VkDescriptorSet m_ubufDescriptor = VK_NULL_HANDLE;
|
|
|
|
};
|
|
|
|
|
|
|
|
VulkanSquircle::VulkanSquircle()
|
|
|
|
{
|
|
|
|
connect(this, &QQuickItem::windowChanged, this, &VulkanSquircle::handleWindowChanged);
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanSquircle::setT(qreal t)
|
|
|
|
{
|
|
|
|
if (t == m_t)
|
|
|
|
return;
|
|
|
|
m_t = t;
|
|
|
|
emit tChanged();
|
|
|
|
if (window())
|
|
|
|
window()->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanSquircle::handleWindowChanged(QQuickWindow *win)
|
|
|
|
{
|
|
|
|
if (win) {
|
|
|
|
connect(win, &QQuickWindow::beforeSynchronizing, this, &VulkanSquircle::sync, Qt::DirectConnection);
|
|
|
|
connect(win, &QQuickWindow::sceneGraphInvalidated, this, &VulkanSquircle::cleanup, Qt::DirectConnection);
|
|
|
|
|
|
|
|
// Ensure we start with cleared to black. The squircle's blend mode relies on this.
|
|
|
|
win->setColor(Qt::black);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The safe way to release custom graphics resources is to both connect to
|
|
|
|
// sceneGraphInvalidated() and implement releaseResources(). To support
|
|
|
|
// threaded render loops the latter performs the SquircleRenderer destruction
|
|
|
|
// via scheduleRenderJob(). Note that the VulkanSquircle may be gone by the time
|
|
|
|
// the QRunnable is invoked.
|
|
|
|
|
|
|
|
void VulkanSquircle::cleanup()
|
|
|
|
{
|
|
|
|
delete m_renderer;
|
|
|
|
m_renderer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
class CleanupJob : public QRunnable
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { }
|
|
|
|
void run() override { delete m_renderer; }
|
|
|
|
private:
|
|
|
|
SquircleRenderer *m_renderer;
|
|
|
|
};
|
|
|
|
|
|
|
|
void VulkanSquircle::releaseResources()
|
|
|
|
{
|
|
|
|
window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage);
|
|
|
|
m_renderer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
SquircleRenderer::~SquircleRenderer()
|
|
|
|
{
|
|
|
|
qDebug("cleanup");
|
|
|
|
if (!m_devFuncs)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_devFuncs->vkDestroyPipeline(m_dev, m_pipeline, nullptr);
|
|
|
|
m_devFuncs->vkDestroyPipelineLayout(m_dev, m_pipelineLayout, nullptr);
|
|
|
|
m_devFuncs->vkDestroyDescriptorSetLayout(m_dev, m_resLayout, nullptr);
|
|
|
|
|
|
|
|
m_devFuncs->vkDestroyDescriptorPool(m_dev, m_descriptorPool, nullptr);
|
|
|
|
|
|
|
|
m_devFuncs->vkDestroyPipelineCache(m_dev, m_pipelineCache, nullptr);
|
|
|
|
|
|
|
|
m_devFuncs->vkDestroyBuffer(m_dev, m_vbuf, nullptr);
|
|
|
|
m_devFuncs->vkFreeMemory(m_dev, m_vbufMem, nullptr);
|
|
|
|
|
|
|
|
m_devFuncs->vkDestroyBuffer(m_dev, m_ubuf, nullptr);
|
|
|
|
m_devFuncs->vkFreeMemory(m_dev, m_ubufMem, nullptr);
|
|
|
|
|
|
|
|
qDebug("released");
|
|
|
|
}
|
|
|
|
|
|
|
|
void VulkanSquircle::sync()
|
|
|
|
{
|
|
|
|
if (!m_renderer) {
|
|
|
|
m_renderer = new SquircleRenderer;
|
2019-08-30 11:13:54 +00:00
|
|
|
// Initializing resources is done before starting to record the
|
|
|
|
// renderpass, regardless of wanting an underlay or overlay.
|
2019-08-26 09:45:54 +00:00
|
|
|
connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::frameStart, Qt::DirectConnection);
|
2019-08-30 11:13:54 +00:00
|
|
|
// Here we want an underlay and therefore connect to
|
|
|
|
// beforeRenderPassRecording. Changing to afterRenderPassRecording
|
|
|
|
// would render the squircle on top (overlay).
|
2019-08-26 09:45:54 +00:00
|
|
|
connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::mainPassRecordingStart, Qt::DirectConnection);
|
|
|
|
}
|
|
|
|
m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio());
|
|
|
|
m_renderer->setT(m_t);
|
|
|
|
m_renderer->setWindow(window());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SquircleRenderer::frameStart()
|
|
|
|
{
|
|
|
|
QSGRendererInterface *rif = m_window->rendererInterface();
|
|
|
|
|
|
|
|
// We are not prepared for anything other than running with the RHI and its Vulkan backend.
|
|
|
|
Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::VulkanRhi);
|
|
|
|
|
|
|
|
if (m_vert.isEmpty())
|
|
|
|
prepareShader(VertexStage);
|
|
|
|
if (m_frag.isEmpty())
|
|
|
|
prepareShader(FragmentStage);
|
|
|
|
|
|
|
|
if (!m_initialized)
|
|
|
|
init(m_window->graphicsStateInfo()->framesInFlight);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const float vertices[] = {
|
|
|
|
-1, -1,
|
|
|
|
1, -1,
|
|
|
|
-1, 1,
|
|
|
|
1, 1
|
|
|
|
};
|
|
|
|
|
|
|
|
const int UBUF_SIZE = 4;
|
|
|
|
|
|
|
|
void SquircleRenderer::mainPassRecordingStart()
|
|
|
|
{
|
|
|
|
// This example demonstrates the simple case: prepending some commands to
|
|
|
|
// the scenegraph's main renderpass. It does not create its own passes,
|
|
|
|
// rendertargets, etc. so no synchronization is needed.
|
|
|
|
|
|
|
|
const QQuickWindow::GraphicsStateInfo *stateInfo = m_window->graphicsStateInfo();
|
|
|
|
QSGRendererInterface *rif = m_window->rendererInterface();
|
|
|
|
|
|
|
|
VkDeviceSize ubufOffset = stateInfo->currentFrameSlot * m_allocPerUbuf;
|
|
|
|
void *p = nullptr;
|
|
|
|
VkResult err = m_devFuncs->vkMapMemory(m_dev, m_ubufMem, ubufOffset, m_allocPerUbuf, 0, &p);
|
|
|
|
if (err != VK_SUCCESS || !p)
|
|
|
|
qFatal("Failed to map uniform buffer memory: %d", err);
|
|
|
|
float t = m_t;
|
|
|
|
memcpy(p, &t, 4);
|
|
|
|
m_devFuncs->vkUnmapMemory(m_dev, m_ubufMem);
|
|
|
|
|
|
|
|
m_window->beginExternalCommands();
|
|
|
|
|
2019-08-28 13:18:44 +00:00
|
|
|
// Must query the command buffer _after_ beginExternalCommands(), this is
|
|
|
|
// actually important when running on Vulkan because what we get here is a
|
|
|
|
// new secondary command buffer, not the primary one.
|
|
|
|
VkCommandBuffer cb = *reinterpret_cast<VkCommandBuffer *>(
|
|
|
|
rif->getResource(m_window, QSGRendererInterface::CommandListResource));
|
|
|
|
Q_ASSERT(cb);
|
|
|
|
|
|
|
|
// Do not assume any state persists on the command buffer. (it may be a
|
|
|
|
// brand new one that just started recording)
|
|
|
|
|
2019-08-26 09:45:54 +00:00
|
|
|
m_devFuncs->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline);
|
|
|
|
|
|
|
|
VkDeviceSize vbufOffset = 0;
|
|
|
|
m_devFuncs->vkCmdBindVertexBuffers(cb, 0, 1, &m_vbuf, &vbufOffset);
|
|
|
|
|
|
|
|
uint32_t dynamicOffset = m_allocPerUbuf * stateInfo->currentFrameSlot;
|
|
|
|
m_devFuncs->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1,
|
|
|
|
&m_ubufDescriptor, 1, &dynamicOffset);
|
|
|
|
|
|
|
|
VkViewport vp = { 0, 0, float(m_viewportSize.width()), float(m_viewportSize.height()), 0.0f, 1.0f };
|
|
|
|
m_devFuncs->vkCmdSetViewport(cb, 0, 1, &vp);
|
|
|
|
VkRect2D scissor = { { 0, 0 }, { uint32_t(m_viewportSize.width()), uint32_t(m_viewportSize.height()) } };
|
|
|
|
m_devFuncs->vkCmdSetScissor(cb, 0, 1, &scissor);
|
|
|
|
|
|
|
|
m_devFuncs->vkCmdDraw(cb, 4, 1, 0, 0);
|
|
|
|
|
|
|
|
m_window->endExternalCommands();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SquircleRenderer::prepareShader(Stage stage)
|
|
|
|
{
|
|
|
|
QString filename;
|
|
|
|
if (stage == VertexStage) {
|
|
|
|
filename = QLatin1String(":/scenegraph/vulkanunderqml/squircle.vert.spv");
|
|
|
|
} else {
|
|
|
|
Q_ASSERT(stage == FragmentStage);
|
|
|
|
filename = QLatin1String(":/scenegraph/vulkanunderqml/squircle.frag.spv");
|
|
|
|
}
|
|
|
|
QFile f(filename);
|
|
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
|
|
qFatal("Failed to read shader %s", qPrintable(filename));
|
|
|
|
|
|
|
|
const QByteArray contents = f.readAll();
|
|
|
|
|
|
|
|
if (stage == VertexStage) {
|
|
|
|
m_vert = contents;
|
|
|
|
Q_ASSERT(!m_vert.isEmpty());
|
|
|
|
} else {
|
|
|
|
m_frag = contents;
|
|
|
|
Q_ASSERT(!m_frag.isEmpty());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
|
|
|
|
{
|
|
|
|
return (v + byteAlign - 1) & ~(byteAlign - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SquircleRenderer::init(int framesInFlight)
|
|
|
|
{
|
|
|
|
qDebug("init");
|
|
|
|
|
|
|
|
Q_ASSERT(framesInFlight <= 3);
|
|
|
|
m_initialized = true;
|
|
|
|
|
|
|
|
QSGRendererInterface *rif = m_window->rendererInterface();
|
|
|
|
QVulkanInstance *inst = reinterpret_cast<QVulkanInstance *>(
|
|
|
|
rif->getResource(m_window, QSGRendererInterface::VulkanInstanceResource));
|
|
|
|
Q_ASSERT(inst && inst->isValid());
|
|
|
|
|
|
|
|
m_physDev = *reinterpret_cast<VkPhysicalDevice *>(rif->getResource(m_window, QSGRendererInterface::PhysicalDeviceResource));
|
|
|
|
m_dev = *reinterpret_cast<VkDevice *>(rif->getResource(m_window, QSGRendererInterface::DeviceResource));
|
|
|
|
Q_ASSERT(m_physDev && m_dev);
|
|
|
|
|
|
|
|
m_devFuncs = inst->deviceFunctions(m_dev);
|
|
|
|
m_funcs = inst->functions();
|
|
|
|
Q_ASSERT(m_devFuncs && m_funcs);
|
|
|
|
|
|
|
|
VkRenderPass rp = *reinterpret_cast<VkRenderPass *>(
|
|
|
|
rif->getResource(m_window, QSGRendererInterface::RenderPassResource));
|
|
|
|
Q_ASSERT(rp);
|
|
|
|
|
|
|
|
// For simplicity we just use host visible buffers instead of device local + staging.
|
|
|
|
|
2019-08-28 13:18:44 +00:00
|
|
|
VkPhysicalDeviceProperties physDevProps;
|
|
|
|
m_funcs->vkGetPhysicalDeviceProperties(m_physDev, &physDevProps);
|
|
|
|
|
2019-08-26 09:45:54 +00:00
|
|
|
VkPhysicalDeviceMemoryProperties physDevMemProps;
|
|
|
|
m_funcs->vkGetPhysicalDeviceMemoryProperties(m_physDev, &physDevMemProps);
|
|
|
|
|
|
|
|
VkBufferCreateInfo bufferInfo;
|
|
|
|
memset(&bufferInfo, 0, sizeof(bufferInfo));
|
|
|
|
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
|
|
bufferInfo.size = sizeof(vertices);
|
|
|
|
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
|
|
|
VkResult err = m_devFuncs->vkCreateBuffer(m_dev, &bufferInfo, nullptr, &m_vbuf);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create vertex buffer: %d", err);
|
|
|
|
|
|
|
|
VkMemoryRequirements memReq;
|
|
|
|
m_devFuncs->vkGetBufferMemoryRequirements(m_dev, m_vbuf, &memReq);
|
|
|
|
VkMemoryAllocateInfo allocInfo;
|
|
|
|
memset(&allocInfo, 0, sizeof(allocInfo));
|
|
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
|
|
allocInfo.allocationSize = memReq.size;
|
|
|
|
|
|
|
|
uint32_t memTypeIndex = uint32_t(-1);
|
|
|
|
const VkMemoryType *memType = physDevMemProps.memoryTypes;
|
|
|
|
for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
|
|
|
|
if (memReq.memoryTypeBits & (1 << i)) {
|
|
|
|
if ((memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
|
|
|
|
&& (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
|
|
|
|
{
|
|
|
|
memTypeIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (memTypeIndex < 0)
|
|
|
|
qFatal("Failed to find host visible and coherent memory type");
|
|
|
|
|
|
|
|
allocInfo.memoryTypeIndex = memTypeIndex;
|
|
|
|
err = m_devFuncs->vkAllocateMemory(m_dev, &allocInfo, nullptr, &m_vbufMem);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to allocate vertex buffer memory of size %llu: %d", allocInfo.allocationSize, err);
|
|
|
|
|
|
|
|
void *p = nullptr;
|
|
|
|
err = m_devFuncs->vkMapMemory(m_dev, m_vbufMem, 0, allocInfo.allocationSize, 0, &p);
|
|
|
|
if (err != VK_SUCCESS || !p)
|
|
|
|
qFatal("Failed to map vertex buffer memory: %d", err);
|
|
|
|
memcpy(p, vertices, sizeof(vertices));
|
|
|
|
m_devFuncs->vkUnmapMemory(m_dev, m_vbufMem);
|
|
|
|
err = m_devFuncs->vkBindBufferMemory(m_dev, m_vbuf, m_vbufMem, 0);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to bind vertex buffer memory: %d", err);
|
|
|
|
|
|
|
|
// Now have a uniform buffer with enough space for the buffer data for each
|
|
|
|
// (potentially) in-flight frame. (as we will write the contents every
|
|
|
|
// frame, and so would need to wait for command buffer completion if there
|
|
|
|
// was only one, and that would not be nice)
|
|
|
|
|
|
|
|
// Could have three buffers and three descriptor sets, or one buffer and
|
|
|
|
// one descriptor set and dynamic offset. We chose the latter in this
|
|
|
|
// example.
|
|
|
|
|
|
|
|
// We use one memory allocation for all uniform buffers, but then have to
|
|
|
|
// watch out for the buffer offset aligment requirement, which may be as
|
|
|
|
// large as 256 bytes.
|
|
|
|
|
2019-08-28 13:18:44 +00:00
|
|
|
m_allocPerUbuf = aligned(UBUF_SIZE, physDevProps.limits.minUniformBufferOffsetAlignment);
|
2019-08-26 09:45:54 +00:00
|
|
|
|
|
|
|
bufferInfo.size = framesInFlight * m_allocPerUbuf;
|
|
|
|
bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
|
|
|
err = m_devFuncs->vkCreateBuffer(m_dev, &bufferInfo, nullptr, &m_ubuf);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create uniform buffer: %d", err);
|
|
|
|
m_devFuncs->vkGetBufferMemoryRequirements(m_dev, m_ubuf, &memReq);
|
|
|
|
memTypeIndex = -1;
|
|
|
|
for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) {
|
|
|
|
if (memReq.memoryTypeBits & (1 << i)) {
|
|
|
|
if ((memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
|
|
|
|
&& (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
|
|
|
|
{
|
|
|
|
memTypeIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (memTypeIndex < 0)
|
|
|
|
qFatal("Failed to find host visible and coherent memory type");
|
|
|
|
|
|
|
|
allocInfo.allocationSize = framesInFlight * m_allocPerUbuf;
|
|
|
|
allocInfo.memoryTypeIndex = memTypeIndex;
|
|
|
|
err = m_devFuncs->vkAllocateMemory(m_dev, &allocInfo, nullptr, &m_ubufMem);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to allocate uniform buffer memory of size %llu: %d", allocInfo.allocationSize, err);
|
|
|
|
|
|
|
|
err = m_devFuncs->vkBindBufferMemory(m_dev, m_ubuf, m_ubufMem, 0);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to bind uniform buffer memory: %d", err);
|
|
|
|
|
|
|
|
// Now onto the pipeline.
|
|
|
|
|
|
|
|
VkPipelineCacheCreateInfo pipelineCacheInfo;
|
|
|
|
memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo));
|
|
|
|
pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
|
|
|
err = m_devFuncs->vkCreatePipelineCache(m_dev, &pipelineCacheInfo, nullptr, &m_pipelineCache);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create pipeline cache: %d", err);
|
|
|
|
|
|
|
|
VkDescriptorSetLayoutBinding descLayoutBinding;
|
|
|
|
memset(&descLayoutBinding, 0, sizeof(descLayoutBinding));
|
|
|
|
descLayoutBinding.binding = 0;
|
|
|
|
descLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
|
|
|
descLayoutBinding.descriptorCount = 1;
|
|
|
|
descLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
VkDescriptorSetLayoutCreateInfo layoutInfo;
|
|
|
|
memset(&layoutInfo, 0, sizeof(layoutInfo));
|
|
|
|
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
|
|
layoutInfo.bindingCount = 1;
|
|
|
|
layoutInfo.pBindings = &descLayoutBinding;
|
|
|
|
err = m_devFuncs->vkCreateDescriptorSetLayout(m_dev, &layoutInfo, nullptr, &m_resLayout);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create descriptor set layout: %d", err);
|
|
|
|
|
|
|
|
VkPipelineLayoutCreateInfo pipelineLayoutInfo;
|
|
|
|
memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo));
|
|
|
|
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
|
|
pipelineLayoutInfo.setLayoutCount = 1;
|
|
|
|
pipelineLayoutInfo.pSetLayouts = &m_resLayout;
|
|
|
|
err = m_devFuncs->vkCreatePipelineLayout(m_dev, &pipelineLayoutInfo, nullptr, &m_pipelineLayout);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qWarning("Failed to create pipeline layout: %d", err);
|
|
|
|
|
|
|
|
VkGraphicsPipelineCreateInfo pipelineInfo;
|
|
|
|
memset(&pipelineInfo, 0, sizeof(pipelineInfo));
|
|
|
|
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
|
|
|
|
|
|
VkShaderModuleCreateInfo shaderInfo;
|
|
|
|
memset(&shaderInfo, 0, sizeof(shaderInfo));
|
|
|
|
shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
|
|
shaderInfo.codeSize = m_vert.size();
|
|
|
|
shaderInfo.pCode = reinterpret_cast<const quint32 *>(m_vert.constData());
|
|
|
|
VkShaderModule vertShaderModule;
|
|
|
|
err = m_devFuncs->vkCreateShaderModule(m_dev, &shaderInfo, nullptr, &vertShaderModule);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create vertex shader module: %d", err);
|
|
|
|
|
|
|
|
shaderInfo.codeSize = m_frag.size();
|
|
|
|
shaderInfo.pCode = reinterpret_cast<const quint32 *>(m_frag.constData());
|
|
|
|
VkShaderModule fragShaderModule;
|
|
|
|
err = m_devFuncs->vkCreateShaderModule(m_dev, &shaderInfo, nullptr, &fragShaderModule);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create fragment shader module: %d", err);
|
|
|
|
|
|
|
|
VkPipelineShaderStageCreateInfo stageInfo[2];
|
|
|
|
memset(&stageInfo, 0, sizeof(stageInfo));
|
|
|
|
stageInfo[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
|
|
stageInfo[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
|
|
stageInfo[0].module = vertShaderModule;
|
|
|
|
stageInfo[0].pName = "main";
|
|
|
|
stageInfo[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
|
|
stageInfo[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
stageInfo[1].module = fragShaderModule;
|
|
|
|
stageInfo[1].pName = "main";
|
|
|
|
pipelineInfo.stageCount = 2;
|
|
|
|
pipelineInfo.pStages = stageInfo;
|
|
|
|
|
|
|
|
VkVertexInputBindingDescription vertexBinding = {
|
|
|
|
0, // binding
|
|
|
|
2 * sizeof(float), // stride
|
|
|
|
VK_VERTEX_INPUT_RATE_VERTEX
|
|
|
|
};
|
|
|
|
VkVertexInputAttributeDescription vertexAttr = {
|
|
|
|
0, // location
|
|
|
|
0, // binding
|
|
|
|
VK_FORMAT_R32G32_SFLOAT, // 'vertices' only has 2 floats per vertex
|
|
|
|
0 // offset
|
|
|
|
};
|
|
|
|
VkPipelineVertexInputStateCreateInfo vertexInputInfo;
|
|
|
|
memset(&vertexInputInfo, 0, sizeof(vertexInputInfo));
|
|
|
|
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
|
|
vertexInputInfo.vertexBindingDescriptionCount = 1;
|
|
|
|
vertexInputInfo.pVertexBindingDescriptions = &vertexBinding;
|
|
|
|
vertexInputInfo.vertexAttributeDescriptionCount = 1;
|
|
|
|
vertexInputInfo.pVertexAttributeDescriptions = &vertexAttr;
|
|
|
|
pipelineInfo.pVertexInputState = &vertexInputInfo;
|
|
|
|
|
|
|
|
VkDynamicState dynStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
|
|
|
|
VkPipelineDynamicStateCreateInfo dynamicInfo;
|
|
|
|
memset(&dynamicInfo, 0, sizeof(dynamicInfo));
|
|
|
|
dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
|
|
|
dynamicInfo.dynamicStateCount = 2;
|
|
|
|
dynamicInfo.pDynamicStates = dynStates;
|
|
|
|
pipelineInfo.pDynamicState = &dynamicInfo;
|
|
|
|
|
|
|
|
VkPipelineViewportStateCreateInfo viewportInfo;
|
|
|
|
memset(&viewportInfo, 0, sizeof(viewportInfo));
|
|
|
|
viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
|
|
|
viewportInfo.viewportCount = viewportInfo.scissorCount = 1;
|
|
|
|
pipelineInfo.pViewportState = &viewportInfo;
|
|
|
|
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo iaInfo;
|
|
|
|
memset(&iaInfo, 0, sizeof(iaInfo));
|
|
|
|
iaInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
|
|
iaInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
|
|
|
pipelineInfo.pInputAssemblyState = &iaInfo;
|
|
|
|
|
|
|
|
VkPipelineRasterizationStateCreateInfo rsInfo;
|
|
|
|
memset(&rsInfo, 0, sizeof(rsInfo));
|
|
|
|
rsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
|
|
|
rsInfo.lineWidth = 1.0f;
|
|
|
|
pipelineInfo.pRasterizationState = &rsInfo;
|
|
|
|
|
|
|
|
VkPipelineMultisampleStateCreateInfo msInfo;
|
|
|
|
memset(&msInfo, 0, sizeof(msInfo));
|
|
|
|
msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
|
|
msInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
pipelineInfo.pMultisampleState = &msInfo;
|
|
|
|
|
|
|
|
VkPipelineDepthStencilStateCreateInfo dsInfo;
|
|
|
|
memset(&dsInfo, 0, sizeof(dsInfo));
|
|
|
|
dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
|
|
|
pipelineInfo.pDepthStencilState = &dsInfo;
|
|
|
|
|
|
|
|
// SrcAlpha, One
|
|
|
|
VkPipelineColorBlendStateCreateInfo blendInfo;
|
|
|
|
memset(&blendInfo, 0, sizeof(blendInfo));
|
|
|
|
blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
|
|
|
VkPipelineColorBlendAttachmentState blend;
|
|
|
|
memset(&blend, 0, sizeof(blend));
|
|
|
|
blend.blendEnable = true;
|
|
|
|
blend.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
|
|
blend.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
|
|
blend.colorBlendOp = VK_BLEND_OP_ADD;
|
|
|
|
blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
|
|
blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
|
|
blend.alphaBlendOp = VK_BLEND_OP_ADD;
|
|
|
|
blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT
|
|
|
|
| VK_COLOR_COMPONENT_A_BIT;
|
|
|
|
blendInfo.attachmentCount = 1;
|
|
|
|
blendInfo.pAttachments = &blend;
|
|
|
|
pipelineInfo.pColorBlendState = &blendInfo;
|
|
|
|
|
|
|
|
pipelineInfo.layout = m_pipelineLayout;
|
|
|
|
|
|
|
|
pipelineInfo.renderPass = rp;
|
|
|
|
|
|
|
|
err = m_devFuncs->vkCreateGraphicsPipelines(m_dev, m_pipelineCache, 1, &pipelineInfo, nullptr, &m_pipeline);
|
|
|
|
|
|
|
|
m_devFuncs->vkDestroyShaderModule(m_dev, vertShaderModule, nullptr);
|
|
|
|
m_devFuncs->vkDestroyShaderModule(m_dev, fragShaderModule, nullptr);
|
|
|
|
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create graphics pipeline: %d", err);
|
|
|
|
|
|
|
|
// Now just need some descriptors.
|
|
|
|
VkDescriptorPoolSize descPoolSizes[] = {
|
|
|
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1 }
|
|
|
|
};
|
|
|
|
VkDescriptorPoolCreateInfo descPoolInfo;
|
|
|
|
memset(&descPoolInfo, 0, sizeof(descPoolInfo));
|
|
|
|
descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
|
|
descPoolInfo.flags = 0; // won't use vkFreeDescriptorSets
|
|
|
|
descPoolInfo.maxSets = 1;
|
|
|
|
descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]);
|
|
|
|
descPoolInfo.pPoolSizes = descPoolSizes;
|
|
|
|
err = m_devFuncs->vkCreateDescriptorPool(m_dev, &descPoolInfo, nullptr, &m_descriptorPool);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to create descriptor pool: %d", err);
|
|
|
|
|
|
|
|
VkDescriptorSetAllocateInfo descAllocInfo;
|
|
|
|
memset(&descAllocInfo, 0, sizeof(descAllocInfo));
|
|
|
|
descAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
|
|
descAllocInfo.descriptorPool = m_descriptorPool;
|
|
|
|
descAllocInfo.descriptorSetCount = 1;
|
|
|
|
descAllocInfo.pSetLayouts = &m_resLayout;
|
|
|
|
err = m_devFuncs->vkAllocateDescriptorSets(m_dev, &descAllocInfo, &m_ubufDescriptor);
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
qFatal("Failed to allocate descriptor set");
|
|
|
|
|
|
|
|
VkWriteDescriptorSet writeInfo;
|
|
|
|
memset(&writeInfo, 0, sizeof(writeInfo));
|
|
|
|
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
|
|
writeInfo.dstSet = m_ubufDescriptor;
|
|
|
|
writeInfo.dstBinding = 0;
|
|
|
|
writeInfo.descriptorCount = 1;
|
|
|
|
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
|
|
|
VkDescriptorBufferInfo bufInfo;
|
|
|
|
bufInfo.buffer = m_ubuf;
|
|
|
|
bufInfo.offset = 0; // dynamic offset is used so this is ignored
|
|
|
|
bufInfo.range = UBUF_SIZE;
|
|
|
|
writeInfo.pBufferInfo = &bufInfo;
|
|
|
|
m_devFuncs->vkUpdateDescriptorSets(m_dev, 1, &writeInfo, 0, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "vulkansquircle.moc"
|