Vulkan with simple sprite

This commit is contained in:
2022-01-17 11:44:54 +01:00
parent 0b09db7b48
commit f798b13079
15 changed files with 533 additions and 64 deletions

View File

@ -166,4 +166,6 @@ RESOURCES += \
OTHER_FILES += \
src/engine/vulkan_shader_modules/color.frag \
src/engine/vulkan_shader_modules/color.vert
src/engine/vulkan_shader_modules/color.vert \
src/engine/vulkan_shader_modules/texture.frag \
src/engine/vulkan_shader_modules/texture.vert

View File

@ -2,14 +2,16 @@
#include <QVulkanInstance>
#include <QEventLoop>
#include <QTimer>
#include "closeeventfilter.h"
GameEngine::GameEngine(const ProjectContainer &project, QObject *parent) :
QObject{parent},
m_project{project},
m_glGameWindow{m_project},
m_vulkanGameWindow{m_project}
m_glGameWindow{m_project, m_rotation},
m_vulkanGameWindow{m_project, m_rotation},
m_timerId(startTimer(1000/60, Qt::PreciseTimer))
{
m_vulkanInstance.setLayers(QByteArrayList { "VK_LAYER_LUNARG_standard_validation" });
@ -17,6 +19,9 @@ GameEngine::GameEngine(const ProjectContainer &project, QObject *parent) :
qFatal("Failed to create Vulkan instance: %d", m_vulkanInstance.errorCode());
m_vulkanGameWindow.setVulkanInstance(&m_vulkanInstance);
m_glGameWindow.setModality(Qt::WindowModal);
m_vulkanGameWindow.setModality(Qt::WindowModal);
}
void GameEngine::run()
@ -36,5 +41,28 @@ void GameEngine::run()
m_glGameWindow.show();
m_vulkanGameWindow.show();
QTimer::singleShot(100, &m_glGameWindow, [this](){
auto geometry = m_glGameWindow.geometry();
qDebug() << geometry;
geometry.moveLeft(geometry.left() - (geometry.width() / 2));
m_glGameWindow.setGeometry(geometry);
});
QTimer::singleShot(100, &m_vulkanGameWindow, [this](){
auto geometry = m_vulkanGameWindow.geometry();
qDebug() << geometry;
geometry.moveLeft(geometry.left() + (geometry.width() / 2));
m_vulkanGameWindow.setGeometry(geometry);
});
eventLoop.exec();
}
void GameEngine::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_timerId)
{
m_rotation += 1.f;
}
else
QObject::timerEvent(event);
}

View File

@ -16,11 +16,18 @@ public:
void run();
protected:
void timerEvent(QTimerEvent *event) override;
private:
const ProjectContainer &m_project;
QVulkanInstance m_vulkanInstance;
float m_rotation{};
GlGameWindow m_glGameWindow;
VulkanGameWindow m_vulkanGameWindow;
const int m_timerId;
};

View File

@ -8,9 +8,10 @@
#include <QKeyEvent>
#include <QDebug>
GlGameWindow::GlGameWindow(const ProjectContainer &project, QWindow *parent) :
GlGameWindow::GlGameWindow(const ProjectContainer &project, const float &rotation, QWindow *parent) :
QWindow{parent},
m_project{project}
m_project{project},
m_rotation{rotation}
{
setSurfaceType(QWindow::OpenGLSurface);
@ -18,6 +19,7 @@ GlGameWindow::GlGameWindow(const ProjectContainer &project, QWindow *parent) :
setMaximumWidth(640);
setMinimumHeight(480);
setMaximumHeight(480);
resize(640, 480);
}
GlGameWindow::~GlGameWindow() = default;
@ -54,7 +56,7 @@ void GlGameWindow::render()
QMatrix4x4 matrix;
matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
matrix.translate(0, 0, -2);
matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0);
matrix.rotate(m_rotation, 0, 1, 0);
m_program->setUniformValue(m_matrixUniform, matrix);
@ -82,8 +84,6 @@ void GlGameWindow::render()
glDisableVertexAttribArray(m_posAttr);
m_program->release();
++m_frame;
}
void GlGameWindow::renderLater()

View File

@ -13,7 +13,7 @@ class GlGameWindow : public QWindow, protected QOpenGLFunctions
Q_OBJECT
public:
explicit GlGameWindow(const ProjectContainer &project, QWindow *parent = nullptr);
explicit GlGameWindow(const ProjectContainer &project, const float &m_rotation, QWindow *parent = nullptr);
~GlGameWindow();
void initialize();
@ -41,5 +41,5 @@ private:
GLint m_matrixUniform = 0;
QOpenGLShaderProgram *m_program{};
int m_frame{};
const float &m_rotation;
};

View File

@ -1,8 +1,10 @@
<RCC>
<qresource prefix="/qtgameengine">
<file>vulkan_shader_modules/color_frag.spv</file>
<file>vulkan_shader_modules/color_vert.spv</file>
<file>opengl_shaders/color.frag</file>
<file>opengl_shaders/color.vert</file>
<file>vulkan_shader_modules/color_frag.spv</file>
<file>vulkan_shader_modules/color_vert.spv</file>
<file>vulkan_shader_modules/texture_frag.spv</file>
<file>vulkan_shader_modules/texture_vert.spv</file>
</qresource>
</RCC>

View File

@ -0,0 +1,12 @@
#version 440
layout(location = 0) in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D tex;
void main()
{
fragColor = texture(tex, v_texcoord);
}

View File

@ -0,0 +1,18 @@
#version 440
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texcoord;
layout(location = 0) out vec2 v_texcoord;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
} ubuf;
out gl_PerVertex { vec4 gl_Position; };
void main()
{
v_texcoord = texcoord;
gl_Position = ubuf.mvp * position;
}

Binary file not shown.

Binary file not shown.

View File

@ -3,14 +3,18 @@
#include <QVulkanFunctions>
#include <QFile>
#include "projectcontainer.h"
// 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 = CCW
0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f
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);
@ -20,9 +24,10 @@ static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
return (v + byteAlign - 1) & ~(byteAlign - 1);
}
VulkanGameRenderer::VulkanGameRenderer(const ProjectContainer &project, QVulkanWindow *w, bool msaa) :
VulkanGameRenderer::VulkanGameRenderer(const ProjectContainer &project, const float &rotation, QVulkanWindow *w, bool msaa) :
m_window{w},
m_project{project}
m_project{project},
m_rotation{rotation}
{
if (msaa)
{
@ -69,6 +74,281 @@ VkShaderModule VulkanGameRenderer::createShader(const QString &name)
return shaderModule;
}
bool VulkanGameRenderer::createTextureFromFile(const QString &name)
{
QImage img{name};
if (img.isNull())
{
qWarning("Failed to load image %s", qPrintable(name));
return false;
}
return createTexture(img);
}
bool VulkanGameRenderer::createTexture(const QImage &image)
{
// Convert to byte ordered RGBA8. Use premultiplied alpha, see pColorBlendState in the pipeline.
const auto img = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
QVulkanFunctions *f = m_window->vulkanInstance()->functions();
VkDevice dev = m_window->device();
const bool srgb = false; //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 VulkanGameRenderer::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 VulkanGameRenderer::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 VulkanGameRenderer::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 VulkanGameRenderer::initResources()
{
qDebug("initResources");
@ -153,14 +433,14 @@ void VulkanGameRenderer::initResources()
{ // position
0, // location
0, // binding
VK_FORMAT_R32G32_SFLOAT,
VK_FORMAT_R32G32B32_SFLOAT,
0
},
{ // color
{ // texcoord
1,
0,
VK_FORMAT_R32G32B32_SFLOAT,
2 * sizeof(float)
VK_FORMAT_R32G32_SFLOAT,
3 * sizeof(float)
}
};
@ -173,31 +453,68 @@ void VulkanGameRenderer::initResources()
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);
if (m_project.sprites.empty() ||
m_project.sprites.front().pixmaps.empty() ||
m_project.sprites.front().pixmaps.front().isNull())
qFatal("no sprites in game project!");
// Texture.
//if (!createTextureFromFile(QStringLiteral(":/qtgameengine/qt256.png")))
// qFatal("Failed to create texture");
if (!createTexture(m_project.sprites.front().pixmaps.front().toImage()))
qFatal("Failed to create texture");
// Set up descriptor set and its layout.
VkDescriptorPoolSize descPoolSizes = { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32_t(concurrentFrameCount) };
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 = 1;
descPoolInfo.pPoolSizes = &descPoolSizes;
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 {
0, // binding
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1,
VK_SHADER_STAGE_VERTEX_BIT,
nullptr
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,
1,
&layoutBinding
2, // bindingCount
layoutBinding
};
err = m_devFuncs->vkCreateDescriptorSetLayout(dev, &descLayoutInfo, nullptr, &m_descSetLayout);
if (err != VK_SUCCESS)
@ -216,14 +533,29 @@ void VulkanGameRenderer::initResources()
if (err != VK_SUCCESS)
qFatal("Failed to allocate descriptor set: %d", err);
VkWriteDescriptorSet descWrite;
memset(&descWrite, 0, sizeof(descWrite));
descWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descWrite.dstSet = m_descSet[i];
descWrite.descriptorCount = 1;
descWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descWrite.pBufferInfo = &m_uniformBufInfo[i];
m_devFuncs->vkUpdateDescriptorSets(dev, 1, &descWrite, 0, nullptr);
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
@ -245,8 +577,8 @@ void VulkanGameRenderer::initResources()
qFatal("Failed to create pipeline layout: %d", err);
// Shaders
VkShaderModule vertShaderModule = createShader(QStringLiteral(":/qtgameengine/vulkan_shader_modules/color_vert.spv"));
VkShaderModule fragShaderModule = createShader(QStringLiteral(":/qtgameengine/vulkan_shader_modules/color_frag.spv"));
VkShaderModule vertShaderModule = createShader(QStringLiteral(":/qtgameengine/vulkan_shader_modules/texture_vert.spv"));
VkShaderModule fragShaderModule = createShader(QStringLiteral(":/qtgameengine/vulkan_shader_modules/texture_frag.spv"));
// Graphics pipeline
VkGraphicsPipelineCreateInfo pipelineInfo;
@ -281,7 +613,7 @@ void VulkanGameRenderer::initResources()
VkPipelineInputAssemblyStateCreateInfo ia;
memset(&ia, 0, sizeof(ia));
ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
pipelineInfo.pInputAssemblyState = &ia;
// The viewport and scissor will be set dynamically via vkCmdSetViewport/Scissor.
@ -297,16 +629,15 @@ void VulkanGameRenderer::initResources()
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_NONE; // we want the back face as well
rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
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;
// Enable multisampling.
ms.rasterizationSamples = m_window->sampleCountFlagBits();
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
pipelineInfo.pMultisampleState = &ms;
VkPipelineDepthStencilStateCreateInfo ds;
@ -320,10 +651,17 @@ void VulkanGameRenderer::initResources()
VkPipelineColorBlendStateCreateInfo cb;
memset(&cb, 0, sizeof(cb));
cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
// no blend, write out all of rgba
// 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;
@ -371,6 +709,42 @@ void VulkanGameRenderer::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);
@ -420,11 +794,14 @@ void VulkanGameRenderer::startNextFrame()
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[3];
VkClearValue clearValues[2];
memset(clearValues, 0, sizeof(clearValues));
clearValues[0].color = clearValues[2].color = clearColor;
clearValues[0].color = clearColor;
clearValues[1].depthStencil = clearDS;
VkRenderPassBeginInfo rpBeginInfo;
@ -434,7 +811,7 @@ void VulkanGameRenderer::startNextFrame()
rpBeginInfo.framebuffer = m_window->currentFramebuffer();
rpBeginInfo.renderArea.extent.width = sz.width();
rpBeginInfo.renderArea.extent.height = sz.height();
rpBeginInfo.clearValueCount = m_window->sampleCountFlagBits() > VK_SAMPLE_COUNT_1_BIT ? 3 : 2;
rpBeginInfo.clearValueCount = 2;
rpBeginInfo.pClearValues = clearValues;
VkCommandBuffer cmdBuf = m_window->currentCommandBuffer();
m_devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
@ -445,13 +822,10 @@ void VulkanGameRenderer::startNextFrame()
if (err != VK_SUCCESS)
qFatal("Failed to map memory: %d", err);
QMatrix4x4 m = m_proj;
m.rotate(m_rotation, 0, 1, 0);
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);
@ -472,7 +846,7 @@ void VulkanGameRenderer::startNextFrame()
scissor.extent.height = viewport.height;
m_devFuncs->vkCmdSetScissor(cb, 0, 1, &scissor);
m_devFuncs->vkCmdDraw(cb, 3, 1, 0, 0);
m_devFuncs->vkCmdDraw(cb, 4, 1, 0, 0);
m_devFuncs->vkCmdEndRenderPass(cmdBuf);

View File

@ -1,13 +1,14 @@
#pragma once
#include <QVulkanWindow>
#include <QImage>
struct ProjectContainer;
class VulkanGameRenderer : public QVulkanWindowRenderer
{
public:
VulkanGameRenderer(const ProjectContainer &project, QVulkanWindow *w, bool msaa = false);
VulkanGameRenderer(const ProjectContainer &project, const float &rotation, QVulkanWindow *w, bool msaa = false);
void initResources() override;
void initSwapChainResources() override;
@ -18,6 +19,12 @@ public:
protected:
VkShaderModule createShader(const QString &name);
bool createTextureFromFile(const QString &name);
bool createTexture(const QImage &image);
bool createTextureImage(const QSize &size, VkImage *image, VkDeviceMemory *mem,
VkImageTiling tiling, VkImageUsageFlags usage, uint32_t memIndex);
bool writeLinearImage(const QImage &img, VkImage image, VkDeviceMemory memory);
void ensureTexture();
QVulkanWindow *m_window;
QVulkanDeviceFunctions *m_devFuncs;
@ -34,8 +41,19 @@ protected:
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
VkPipeline m_pipeline = VK_NULL_HANDLE;
const ProjectContainer &m_project;
VkSampler m_sampler = VK_NULL_HANDLE;
VkImage m_texImage = VK_NULL_HANDLE;
VkDeviceMemory m_texMem = VK_NULL_HANDLE;
bool m_texLayoutPending = false;
VkImageView m_texView = VK_NULL_HANDLE;
VkImage m_texStaging = VK_NULL_HANDLE;
VkDeviceMemory m_texStagingMem = VK_NULL_HANDLE;
bool m_texStagingPending = false;
QSize m_texSize;
VkFormat m_texFormat;
QMatrix4x4 m_proj;
float m_rotation = 0.0f;
const ProjectContainer &m_project;
const float &m_rotation;
};

View File

@ -2,17 +2,19 @@
#include "vulkangamerenderer.h"
VulkanGameWindow::VulkanGameWindow(const ProjectContainer &project, QWindow *parent) :
VulkanGameWindow::VulkanGameWindow(const ProjectContainer &project, const float &rotation, QWindow *parent) :
QVulkanWindow{parent},
m_project{project}
m_project{project},
m_rotation{rotation}
{
setMinimumWidth(640);
setMaximumWidth(640);
setMinimumHeight(480);
setMaximumHeight(480);
resize(640, 480);
}
QVulkanWindowRenderer *VulkanGameWindow::createRenderer()
{
return new VulkanGameRenderer{m_project, this, true}; // try MSAA, when available
return new VulkanGameRenderer{m_project, m_rotation, this, true}; // try MSAA, when available
}

View File

@ -8,10 +8,11 @@ struct ProjectContainer;
class VulkanGameWindow : public QVulkanWindow
{
public:
explicit VulkanGameWindow(const ProjectContainer &project, QWindow *parent = nullptr);
explicit VulkanGameWindow(const ProjectContainer &project, const float &rotation, QWindow *parent = nullptr);
QVulkanWindowRenderer *createRenderer() override;
private:
const ProjectContainer &m_project;
const float &m_rotation;
};

View File

@ -1,8 +1,11 @@
#include <QApplication>
#include <QCommandLineParser>
#include <QLoggingCategory>
#include "mainwindow.h"
Q_LOGGING_CATEGORY(lcVk, "qt.vulkan")
int main(int argc, char *argv[])
{
qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} "
@ -16,6 +19,8 @@ int main(int argc, char *argv[])
"%{function}(): "
"%{message}"));
QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true"));
Q_INIT_RESOURCE(resources_editor);
Q_INIT_RESOURCE(resources_engine);