hellovulkantexture.cpp Example File
hellovulkantexture/hellovulkantexture.cpp
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples 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 "hellovulkantexture.h"
#include <QVulkanFunctions>
#include <QCoreApplication>
#include <QFile>
// Use a triangle strip to get a quad.
//
// Note that the vertex data and the projection matrix assume OpenGL. With
// Vulkan Y is negated in clip space and the near/far plane is at 0/1 instead
// of -1/1. These will be corrected for by an extra transformation when
// calculating the modelview-projection matrix.
static float vertexData[ ] = { // Y up, front = CW
// x, y, z, u, v
- 1 , - 1 , 0 , 0 , 1 ,
- 1 , 1 , 0 , 0 , 0 ,
1 , - 1 , 0 , 1 , 1 ,
1 , 1 , 0 , 1 , 0
};
static const int UNIFORM_DATA_SIZE = 16 * sizeof (float );
static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
{
return (v + byteAlign - 1 ) & ~ (byteAlign - 1 );
}
QVulkanWindowRenderer * VulkanWindow:: createRenderer()
{
return new VulkanRenderer(this );
}
VulkanRenderer:: VulkanRenderer(QVulkanWindow * w)
: m_window(w)
{
}
VkShaderModule VulkanRenderer:: createShader(const QString & name)
{
QFile file(name);
if (! file. open(QIODevice :: ReadOnly)) {
qWarning ("Failed to read shader %s" , qPrintable (name));
return VK_NULL_HANDLE;
}
QByteArray blob = file. readAll();
file. close();
VkShaderModuleCreateInfo shaderInfo;
memset(& shaderInfo, 0 , sizeof (shaderInfo));
shaderInfo. sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shaderInfo. codeSize = blob. size();
shaderInfo. pCode = reinterpret_cast < const uint32_t * > (blob. constData());
VkShaderModule shaderModule;
VkResult err = m_devFuncs- > vkCreateShaderModule(m_window- > device(), & shaderInfo, nullptr, & shaderModule);
if (err ! = VK_SUCCESS) {
qWarning ("Failed to create shader module: %d" , err);
return VK_NULL_HANDLE;
}
return shaderModule;
}
bool VulkanRenderer:: createTexture(const QString & name)
{
QImage img(name);
if (img. isNull()) {
qWarning ("Failed to load image %s" , qPrintable (name));
return false ;
}
// Convert to byte ordered RGBA8. Use premultiplied alpha, see pColorBlendState in the pipeline.
img = img. convertToFormat(QImage :: Format_RGBA8888_Premultiplied);
QVulkanFunctions * f = m_window- > vulkanInstance()- > functions();
VkDevice dev = m_window- > device();
const bool srgb = QCoreApplication :: arguments(). contains(QStringLiteral ("--srgb" ));
if (srgb)
qDebug ("sRGB swapchain was requested, making texture sRGB too" );
m_texFormat = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
// Now we can either map and copy the image data directly, or have to go
// through a staging buffer to copy and convert into the internal optimal
// tiling format.
VkFormatProperties props;
f- > vkGetPhysicalDeviceFormatProperties(m_window- > physicalDevice(), m_texFormat, & props);
const bool canSampleLinear = (props. linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
const bool canSampleOptimal = (props. optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
if (! canSampleLinear & & ! canSampleOptimal) {
qWarning ("Neither linear nor optimal image sampling is supported for RGBA8" );
return false ;
}
static bool alwaysStage = qEnvironmentVariableIntValue ("QT_VK_FORCE_STAGE_TEX" );
if (canSampleLinear & & ! alwaysStage) {
if (! createTextureImage(img. size(), & m_texImage, & m_texMem,
VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT,
m_window- > hostVisibleMemoryIndex()))
return false ;
if (! writeLinearImage(img, m_texImage, m_texMem))
return false ;
m_texLayoutPending = true ;
} else {
if (! createTextureImage(img. size(), & m_texStaging, & m_texStagingMem,
VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
m_window- > hostVisibleMemoryIndex()))
return false ;
if (! createTextureImage(img. size(), & m_texImage, & m_texMem,
VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
m_window- > deviceLocalMemoryIndex()))
return false ;
if (! writeLinearImage(img, m_texStaging, m_texStagingMem))
return false ;
m_texStagingPending = true ;
}
VkImageViewCreateInfo viewInfo;
memset(& viewInfo, 0 , sizeof (viewInfo));
viewInfo. sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo. image = m_texImage;
viewInfo. viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo. format = m_texFormat;
viewInfo. components. r = VK_COMPONENT_SWIZZLE_R;
viewInfo. components. g = VK_COMPONENT_SWIZZLE_G;
viewInfo. components. b = VK_COMPONENT_SWIZZLE_B;
viewInfo. components. a = VK_COMPONENT_SWIZZLE_A;
viewInfo. subresourceRange. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo. subresourceRange. levelCount = viewInfo. subresourceRange. layerCount = 1 ;
VkResult err = m_devFuncs- > vkCreateImageView(dev, & viewInfo, nullptr, & m_texView);
if (err ! = VK_SUCCESS) {
qWarning ("Failed to create image view for texture: %d" , err);
return false ;
}
m_texSize = img. size();
return true ;
}
bool VulkanRenderer:: createTextureImage(const QSize & size, VkImage * image, VkDeviceMemory * mem,
VkImageTiling tiling, VkImageUsageFlags usage, uint32_t memIndex)
{
VkDevice dev = m_window- > device();
VkImageCreateInfo imageInfo;
memset(& imageInfo, 0 , sizeof (imageInfo));
imageInfo. sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo. imageType = VK_IMAGE_TYPE_2D;
imageInfo. format = m_texFormat;
imageInfo. extent. width = size. width();
imageInfo. extent. height = size. height();
imageInfo. extent. depth = 1 ;
imageInfo. mipLevels = 1 ;
imageInfo. arrayLayers = 1 ;
imageInfo. samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo. tiling = tiling;
imageInfo. usage = usage;
imageInfo. initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
VkResult err = m_devFuncs- > vkCreateImage(dev, & imageInfo, nullptr, image);
if (err ! = VK_SUCCESS) {
qWarning ("Failed to create linear image for texture: %d" , err);
return false ;
}
VkMemoryRequirements memReq;
m_devFuncs- > vkGetImageMemoryRequirements(dev, * image, & memReq);
if (! (memReq. memoryTypeBits & (1 < < memIndex))) {
VkPhysicalDeviceMemoryProperties physDevMemProps;
m_window- > vulkanInstance()- > functions()- > vkGetPhysicalDeviceMemoryProperties(m_window- > physicalDevice(), & physDevMemProps);
for (uint32_t i = 0 ; i < physDevMemProps. memoryTypeCount; + + i) {
if (! (memReq. memoryTypeBits & (1 < < i)))
continue ;
memIndex = i;
}
}
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
nullptr,
memReq. size,
memIndex
};
qDebug ("allocating %u bytes for texture image" , uint32_t(memReq. size));
err = m_devFuncs- > vkAllocateMemory(dev, & allocInfo, nullptr, mem);
if (err ! = VK_SUCCESS) {
qWarning ("Failed to allocate memory for linear image: %d" , err);
return false ;
}
err = m_devFuncs- > vkBindImageMemory(dev, * image, * mem, 0 );
if (err ! = VK_SUCCESS) {
qWarning ("Failed to bind linear image memory: %d" , err);
return false ;
}
return true ;
}
bool VulkanRenderer:: writeLinearImage(const QImage & img, VkImage image, VkDeviceMemory memory)
{
VkDevice dev = m_window- > device();
VkImageSubresource subres = {
VK_IMAGE_ASPECT_COLOR_BIT,
0 , // mip level
0
};
VkSubresourceLayout layout;
m_devFuncs- > vkGetImageSubresourceLayout(dev, image, & subres, & layout);
uchar * p;
VkResult err = m_devFuncs- > vkMapMemory(dev, memory, layout. offset, layout. size, 0 , reinterpret_cast < void * * > (& p));
if (err ! = VK_SUCCESS) {
qWarning ("Failed to map memory for linear image: %d" , err);
return false ;
}
for (int y = 0 ; y < img. height(); + + y) {
const uchar * line = img. constScanLine(y);
memcpy(p, line, img. width() * 4 );
p + = layout. rowPitch;
}
m_devFuncs- > vkUnmapMemory(dev, memory);
return true ;
}
void VulkanRenderer:: ensureTexture()
{
if (! m_texLayoutPending & & ! m_texStagingPending)
return ;
Q_ASSERT(m_texLayoutPending ! = m_texStagingPending);
VkCommandBuffer cb = m_window- > currentCommandBuffer();
VkImageMemoryBarrier barrier;
memset(& barrier, 0 , sizeof (barrier));
barrier. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier. subresourceRange. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier. subresourceRange. levelCount = barrier. subresourceRange. layerCount = 1 ;
if (m_texLayoutPending) {
m_texLayoutPending = false ;
barrier. oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
barrier. newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier. srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
barrier. dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier. image = m_texImage;
m_devFuncs- > vkCmdPipelineBarrier(cb,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0 , 0 , nullptr, 0 , nullptr,
1 , & barrier);
} else {
m_texStagingPending = false ;
barrier. oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
barrier. newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier. srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
barrier. dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier. image = m_texStaging;
m_devFuncs- > vkCmdPipelineBarrier(cb,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0 , 0 , nullptr, 0 , nullptr,
1 , & barrier);
barrier. oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
barrier. newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier. srcAccessMask = 0 ;
barrier. dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier. image = m_texImage;
m_devFuncs- > vkCmdPipelineBarrier(cb,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0 , 0 , nullptr, 0 , nullptr,
1 , & barrier);
VkImageCopy copyInfo;
memset(& copyInfo, 0 , sizeof (copyInfo));
copyInfo. srcSubresource. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyInfo. srcSubresource. layerCount = 1 ;
copyInfo. dstSubresource. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyInfo. dstSubresource. layerCount = 1 ;
copyInfo. extent. width = m_texSize. width();
copyInfo. extent. height = m_texSize. height();
copyInfo. extent. depth = 1 ;
m_devFuncs- > vkCmdCopyImage(cb, m_texStaging, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
m_texImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1 , & copyInfo);
barrier. oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier. newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier. srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier. dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier. image = m_texImage;
m_devFuncs- > vkCmdPipelineBarrier(cb,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0 , 0 , nullptr, 0 , nullptr,
1 , & barrier);
}
}
void VulkanRenderer:: initResources()
{
qDebug ("initResources" );
VkDevice dev = m_window- > device();
m_devFuncs = m_window- > vulkanInstance()- > deviceFunctions(dev);
// The setup is similar to hellovulkantriangle. The difference is the
// presence of a second vertex attribute (texcoord), a sampler, and that we
// need blending.
const int concurrentFrameCount = m_window- > concurrentFrameCount();
const VkPhysicalDeviceLimits * pdevLimits = & m_window- > physicalDeviceProperties()- > limits;
const VkDeviceSize uniAlign = pdevLimits- > minUniformBufferOffsetAlignment;
qDebug ("uniform buffer offset alignment is %u" , (uint ) uniAlign);
VkBufferCreateInfo bufInfo;
memset(& bufInfo, 0 , sizeof (bufInfo));
bufInfo. sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
// Our internal layout is vertex, uniform, uniform, ... with each uniform buffer start offset aligned to uniAlign.
const VkDeviceSize vertexAllocSize = aligned(sizeof (vertexData), uniAlign);
const VkDeviceSize uniformAllocSize = aligned(UNIFORM_DATA_SIZE, uniAlign);
bufInfo. size = vertexAllocSize + concurrentFrameCount * uniformAllocSize;
bufInfo. usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
VkResult err = m_devFuncs- > vkCreateBuffer(dev, & bufInfo, nullptr, & m_buf);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create buffer: %d" , err);
VkMemoryRequirements memReq;
m_devFuncs- > vkGetBufferMemoryRequirements(dev, m_buf, & memReq);
VkMemoryAllocateInfo memAllocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
nullptr,
memReq. size,
m_window- > hostVisibleMemoryIndex()
};
err = m_devFuncs- > vkAllocateMemory(dev, & memAllocInfo, nullptr, & m_bufMem);
if (err ! = VK_SUCCESS)
qFatal ("Failed to allocate memory: %d" , err);
err = m_devFuncs- > vkBindBufferMemory(dev, m_buf, m_bufMem, 0 );
if (err ! = VK_SUCCESS)
qFatal ("Failed to bind buffer memory: %d" , err);
quint8 * p;
err = m_devFuncs- > vkMapMemory(dev, m_bufMem, 0 , memReq. size, 0 , reinterpret_cast < void * * > (& p));
if (err ! = VK_SUCCESS)
qFatal ("Failed to map memory: %d" , err);
memcpy(p, vertexData, sizeof (vertexData));
QMatrix4x4 ident;
memset(m_uniformBufInfo, 0 , sizeof (m_uniformBufInfo));
for (int i = 0 ; i < concurrentFrameCount; + + i) {
const VkDeviceSize offset = vertexAllocSize + i * uniformAllocSize;
memcpy(p + offset, ident. constData(), 16 * sizeof (float ));
m_uniformBufInfo[ i] . buffer = m_buf;
m_uniformBufInfo[ i] . offset = offset;
m_uniformBufInfo[ i] . range = uniformAllocSize;
}
m_devFuncs- > vkUnmapMemory(dev, m_bufMem);
VkVertexInputBindingDescription vertexBindingDesc = {
0 , // binding
5 * sizeof (float ),
VK_VERTEX_INPUT_RATE_VERTEX
};
VkVertexInputAttributeDescription vertexAttrDesc[ ] = {
{ // position
0 , // location
0 , // binding
VK_FORMAT_R32G32B32_SFLOAT,
0
},
{ // texcoord
1 ,
0 ,
VK_FORMAT_R32G32_SFLOAT,
3 * sizeof (float )
}
};
VkPipelineVertexInputStateCreateInfo vertexInputInfo;
vertexInputInfo. sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo. pNext = nullptr;
vertexInputInfo. flags = 0 ;
vertexInputInfo. vertexBindingDescriptionCount = 1 ;
vertexInputInfo. pVertexBindingDescriptions = & vertexBindingDesc;
vertexInputInfo. vertexAttributeDescriptionCount = 2 ;
vertexInputInfo. pVertexAttributeDescriptions = vertexAttrDesc;
// Sampler.
VkSamplerCreateInfo samplerInfo;
memset(& samplerInfo, 0 , sizeof (samplerInfo));
samplerInfo. sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo. magFilter = VK_FILTER_NEAREST;
samplerInfo. minFilter = VK_FILTER_NEAREST;
samplerInfo. addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo. addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo. addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo. maxAnisotropy = 1.0f ;
err = m_devFuncs- > vkCreateSampler(dev, & samplerInfo, nullptr, & m_sampler);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create sampler: %d" , err);
// Texture.
if (! createTexture(QStringLiteral (":/qt256.png" )))
qFatal ("Failed to create texture" );
// Set up descriptor set and its layout.
VkDescriptorPoolSize descPoolSizes[ 2 ] = {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32_t(concurrentFrameCount) },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, uint32_t(concurrentFrameCount) }
};
VkDescriptorPoolCreateInfo descPoolInfo;
memset(& descPoolInfo, 0 , sizeof (descPoolInfo));
descPoolInfo. sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descPoolInfo. maxSets = concurrentFrameCount;
descPoolInfo. poolSizeCount = 2 ;
descPoolInfo. pPoolSizes = descPoolSizes;
err = m_devFuncs- > vkCreateDescriptorPool(dev, & descPoolInfo, nullptr, & m_descPool);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create descriptor pool: %d" , err);
VkDescriptorSetLayoutBinding layoutBinding[ 2 ] =
{
{
0 , // binding
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1 , // descriptorCount
VK_SHADER_STAGE_VERTEX_BIT,
nullptr
},
{
1 , // binding
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1 , // descriptorCount
VK_SHADER_STAGE_FRAGMENT_BIT,
nullptr
}
};
VkDescriptorSetLayoutCreateInfo descLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
nullptr,
0 ,
2 , // bindingCount
layoutBinding
};
err = m_devFuncs- > vkCreateDescriptorSetLayout(dev, & descLayoutInfo, nullptr, & m_descSetLayout);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create descriptor set layout: %d" , err);
for (int i = 0 ; i < concurrentFrameCount; + + i) {
VkDescriptorSetAllocateInfo descSetAllocInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
nullptr,
m_descPool,
1 ,
& m_descSetLayout
};
err = m_devFuncs- > vkAllocateDescriptorSets(dev, & descSetAllocInfo, & m_descSet[ i] );
if (err ! = VK_SUCCESS)
qFatal ("Failed to allocate descriptor set: %d" , err);
VkWriteDescriptorSet descWrite[ 2 ] ;
memset(descWrite, 0 , sizeof (descWrite));
descWrite[ 0 ] . sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descWrite[ 0 ] . dstSet = m_descSet[ i] ;
descWrite[ 0 ] . dstBinding = 0 ;
descWrite[ 0 ] . descriptorCount = 1 ;
descWrite[ 0 ] . descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descWrite[ 0 ] . pBufferInfo = & m_uniformBufInfo[ i] ;
VkDescriptorImageInfo descImageInfo = {
m_sampler,
m_texView,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
};
descWrite[ 1 ] . sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descWrite[ 1 ] . dstSet = m_descSet[ i] ;
descWrite[ 1 ] . dstBinding = 1 ;
descWrite[ 1 ] . descriptorCount = 1 ;
descWrite[ 1 ] . descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descWrite[ 1 ] . pImageInfo = & descImageInfo;
m_devFuncs- > vkUpdateDescriptorSets(dev, 2 , descWrite, 0 , nullptr);
}
// Pipeline cache
VkPipelineCacheCreateInfo pipelineCacheInfo;
memset(& pipelineCacheInfo, 0 , sizeof (pipelineCacheInfo));
pipelineCacheInfo. sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
err = m_devFuncs- > vkCreatePipelineCache(dev, & pipelineCacheInfo, nullptr, & m_pipelineCache);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create pipeline cache: %d" , err);
// Pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo;
memset(& pipelineLayoutInfo, 0 , sizeof (pipelineLayoutInfo));
pipelineLayoutInfo. sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo. setLayoutCount = 1 ;
pipelineLayoutInfo. pSetLayouts = & m_descSetLayout;
err = m_devFuncs- > vkCreatePipelineLayout(dev, & pipelineLayoutInfo, nullptr, & m_pipelineLayout);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create pipeline layout: %d" , err);
// Shaders
VkShaderModule vertShaderModule = createShader(QStringLiteral (":/texture_vert.spv" ));
VkShaderModule fragShaderModule = createShader(QStringLiteral (":/texture_frag.spv" ));
// Graphics pipeline
VkGraphicsPipelineCreateInfo pipelineInfo;
memset(& pipelineInfo, 0 , sizeof (pipelineInfo));
pipelineInfo. sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
VkPipelineShaderStageCreateInfo shaderStages[ 2 ] = {
{
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr,
0 ,
VK_SHADER_STAGE_VERTEX_BIT,
vertShaderModule,
"main" ,
nullptr
},
{
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr,
0 ,
VK_SHADER_STAGE_FRAGMENT_BIT,
fragShaderModule,
"main" ,
nullptr
}
};
pipelineInfo. stageCount = 2 ;
pipelineInfo. pStages = shaderStages;
pipelineInfo. pVertexInputState = & vertexInputInfo;
VkPipelineInputAssemblyStateCreateInfo ia;
memset(& ia, 0 , sizeof (ia));
ia. sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
ia. topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
pipelineInfo. pInputAssemblyState = & ia;
// The viewport and scissor will be set dynamically via vkCmdSetViewport/Scissor.
// This way the pipeline does not need to be touched when resizing the window.
VkPipelineViewportStateCreateInfo vp;
memset(& vp, 0 , sizeof (vp));
vp. sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
vp. viewportCount = 1 ;
vp. scissorCount = 1 ;
pipelineInfo. pViewportState = & vp;
VkPipelineRasterizationStateCreateInfo rs;
memset(& rs, 0 , sizeof (rs));
rs. sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rs. polygonMode = VK_POLYGON_MODE_FILL;
rs. cullMode = VK_CULL_MODE_BACK_BIT;
rs. frontFace = VK_FRONT_FACE_CLOCKWISE;
rs. lineWidth = 1.0f ;
pipelineInfo. pRasterizationState = & rs;
VkPipelineMultisampleStateCreateInfo ms;
memset(& ms, 0 , sizeof (ms));
ms. sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
ms. rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
pipelineInfo. pMultisampleState = & ms;
VkPipelineDepthStencilStateCreateInfo ds;
memset(& ds, 0 , sizeof (ds));
ds. sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
ds. depthTestEnable = VK_TRUE;
ds. depthWriteEnable = VK_TRUE;
ds. depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
pipelineInfo. pDepthStencilState = & ds;
VkPipelineColorBlendStateCreateInfo cb;
memset(& cb, 0 , sizeof (cb));
cb. sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
// assume pre-multiplied alpha, blend, write out all of rgba
VkPipelineColorBlendAttachmentState att;
memset(& att, 0 , sizeof (att));
att. colorWriteMask = 0xF ;
att. blendEnable = VK_TRUE;
att. srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
att. dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
att. colorBlendOp = VK_BLEND_OP_ADD;
att. srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
att. dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
att. alphaBlendOp = VK_BLEND_OP_ADD;
cb. attachmentCount = 1 ;
cb. pAttachments = & att;
pipelineInfo. pColorBlendState = & cb;
VkDynamicState dynEnable[ ] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dyn;
memset(& dyn, 0 , sizeof (dyn));
dyn. sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dyn. dynamicStateCount = sizeof (dynEnable) / sizeof (VkDynamicState);
dyn. pDynamicStates = dynEnable;
pipelineInfo. pDynamicState = & dyn;
pipelineInfo. layout = m_pipelineLayout;
pipelineInfo. renderPass = m_window- > defaultRenderPass();
err = m_devFuncs- > vkCreateGraphicsPipelines(dev, m_pipelineCache, 1 , & pipelineInfo, nullptr, & m_pipeline);
if (err ! = VK_SUCCESS)
qFatal ("Failed to create graphics pipeline: %d" , err);
if (vertShaderModule)
m_devFuncs- > vkDestroyShaderModule(dev, vertShaderModule, nullptr);
if (fragShaderModule)
m_devFuncs- > vkDestroyShaderModule(dev, fragShaderModule, nullptr);
}
void VulkanRenderer:: initSwapChainResources()
{
qDebug ("initSwapChainResources" );
// Projection matrix
m_proj = m_window- > clipCorrectionMatrix(); // adjust for Vulkan-OpenGL clip space differences
const QSize sz = m_window- > swapChainImageSize();
m_proj. perspective(45.0f , sz. width() / (float ) sz. height(), 0.01f , 100.0f );
m_proj. translate(0 , 0 , - 4 );
}
void VulkanRenderer:: releaseSwapChainResources()
{
qDebug ("releaseSwapChainResources" );
}
void VulkanRenderer:: releaseResources()
{
qDebug ("releaseResources" );
VkDevice dev = m_window- > device();
if (m_sampler) {
m_devFuncs- > vkDestroySampler(dev, m_sampler, nullptr);
m_sampler = VK_NULL_HANDLE;
}
if (m_texStaging) {
m_devFuncs- > vkDestroyImage(dev, m_texStaging, nullptr);
m_texStaging = VK_NULL_HANDLE;
}
if (m_texStagingMem) {
m_devFuncs- > vkFreeMemory(dev, m_texStagingMem, nullptr);
m_texStagingMem = VK_NULL_HANDLE;
}
if (m_texView) {
m_devFuncs- > vkDestroyImageView(dev, m_texView, nullptr);
m_texView = VK_NULL_HANDLE;
}
if (m_texImage) {
m_devFuncs- > vkDestroyImage(dev, m_texImage, nullptr);
m_texImage = VK_NULL_HANDLE;
}
if (m_texMem) {
m_devFuncs- > vkFreeMemory(dev, m_texMem, nullptr);
m_texMem = VK_NULL_HANDLE;
}
if (m_pipeline) {
m_devFuncs- > vkDestroyPipeline(dev, m_pipeline, nullptr);
m_pipeline = VK_NULL_HANDLE;
}
if (m_pipelineLayout) {
m_devFuncs- > vkDestroyPipelineLayout(dev, m_pipelineLayout, nullptr);
m_pipelineLayout = VK_NULL_HANDLE;
}
if (m_pipelineCache) {
m_devFuncs- > vkDestroyPipelineCache(dev, m_pipelineCache, nullptr);
m_pipelineCache = VK_NULL_HANDLE;
}
if (m_descSetLayout) {
m_devFuncs- > vkDestroyDescriptorSetLayout(dev, m_descSetLayout, nullptr);
m_descSetLayout = VK_NULL_HANDLE;
}
if (m_descPool) {
m_devFuncs- > vkDestroyDescriptorPool(dev, m_descPool, nullptr);
m_descPool = VK_NULL_HANDLE;
}
if (m_buf) {
m_devFuncs- > vkDestroyBuffer(dev, m_buf, nullptr);
m_buf = VK_NULL_HANDLE;
}
if (m_bufMem) {
m_devFuncs- > vkFreeMemory(dev, m_bufMem, nullptr);
m_bufMem = VK_NULL_HANDLE;
}
}
void VulkanRenderer:: startNextFrame()
{
VkDevice dev = m_window- > device();
VkCommandBuffer cb = m_window- > currentCommandBuffer();
const QSize sz = m_window- > swapChainImageSize();
// Add the necessary barriers and do the host-linear -> device-optimal copy, if not yet done.
ensureTexture();
VkClearColorValue clearColor = {{ 0 , 0 , 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();
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);
quint8 * p;
VkResult err = m_devFuncs- > vkMapMemory(dev, m_bufMem, m_uniformBufInfo[ m_window- > currentFrame()] . offset,
UNIFORM_DATA_SIZE, 0 , reinterpret_cast < void * * > (& p));
if (err ! = VK_SUCCESS)
qFatal ("Failed to map memory: %d" , err);
QMatrix4x4 m = m_proj;
m. rotate(m_rotation, 0 , 0 , 1 );
memcpy(p, m. constData(), 16 * sizeof (float ));
m_devFuncs- > vkUnmapMemory(dev, m_bufMem);
// Not exactly a real animation system, just advance on every frame for now.
m_rotation + = 1.0f ;
m_devFuncs- > vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline);
m_devFuncs- > vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0 , 1 ,
& m_descSet[ m_window- > currentFrame()] , 0 , nullptr);
VkDeviceSize vbOffset = 0 ;
m_devFuncs- > vkCmdBindVertexBuffers(cb, 0 , 1 , & m_buf, & vbOffset);
VkViewport viewport;
viewport. x = viewport. y = 0 ;
viewport. width = sz. width();
viewport. height = sz. height();
viewport. minDepth = 0 ;
viewport. maxDepth = 1 ;
m_devFuncs- > vkCmdSetViewport(cb, 0 , 1 , & viewport);
VkRect2D scissor;
scissor. offset. x = scissor. offset. y = 0 ;
scissor. extent. width = viewport. width;
scissor. extent. height = viewport. height;
m_devFuncs- > vkCmdSetScissor(cb, 0 , 1 , & scissor);
m_devFuncs- > vkCmdDraw(cb, 4 , 1 , 0 , 0 );
m_devFuncs- > vkCmdEndRenderPass(cmdBuf);
m_window- > frameReady();
m_window- > requestUpdate(); // render continuously, throttled by the presentation rate
}