Vulkan with simple sprite
This commit is contained in:
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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>
|
||||
|
12
src/engine/vulkan_shader_modules/texture.frag
Normal file
12
src/engine/vulkan_shader_modules/texture.frag
Normal 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);
|
||||
}
|
18
src/engine/vulkan_shader_modules/texture.vert
Normal file
18
src/engine/vulkan_shader_modules/texture.vert
Normal 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;
|
||||
}
|
BIN
src/engine/vulkan_shader_modules/texture_frag.spv
Normal file
BIN
src/engine/vulkan_shader_modules/texture_frag.spv
Normal file
Binary file not shown.
BIN
src/engine/vulkan_shader_modules/texture_vert.spv
Normal file
BIN
src/engine/vulkan_shader_modules/texture_vert.spv
Normal file
Binary file not shown.
@ -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(©Info, 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, ©Info);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
Reference in New Issue
Block a user