From f798b1307925d4d6984a08960612c9e001b5d08c Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Mon, 17 Jan 2022 11:44:54 +0100 Subject: [PATCH] Vulkan with simple sprite --- QtGameMaker.pro | 4 +- src/engine/gameengine.cpp | 32 +- src/engine/gameengine.h | 7 + src/engine/glgamewindow.cpp | 10 +- src/engine/glgamewindow.h | 4 +- src/engine/resources_engine.qrc | 6 +- src/engine/vulkan_shader_modules/texture.frag | 12 + src/engine/vulkan_shader_modules/texture.vert | 18 + .../vulkan_shader_modules/texture_frag.spv | Bin 0 -> 556 bytes .../vulkan_shader_modules/texture_vert.spv | Bin 0 -> 968 bytes src/engine/vulkangamerenderer.cpp | 464 ++++++++++++++++-- src/engine/vulkangamerenderer.h | 24 +- src/engine/vulkangamewindow.cpp | 8 +- src/engine/vulkangamewindow.h | 3 +- src/main.cpp | 5 + 15 files changed, 533 insertions(+), 64 deletions(-) create mode 100644 src/engine/vulkan_shader_modules/texture.frag create mode 100644 src/engine/vulkan_shader_modules/texture.vert create mode 100644 src/engine/vulkan_shader_modules/texture_frag.spv create mode 100644 src/engine/vulkan_shader_modules/texture_vert.spv diff --git a/QtGameMaker.pro b/QtGameMaker.pro index 3f4a96c..5733027 100644 --- a/QtGameMaker.pro +++ b/QtGameMaker.pro @@ -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 diff --git a/src/engine/gameengine.cpp b/src/engine/gameengine.cpp index fd2ecb0..868b283 100644 --- a/src/engine/gameengine.cpp +++ b/src/engine/gameengine.cpp @@ -2,14 +2,16 @@ #include #include +#include #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); +} diff --git a/src/engine/gameengine.h b/src/engine/gameengine.h index ad8cd14..6d031cc 100644 --- a/src/engine/gameengine.h +++ b/src/engine/gameengine.h @@ -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; }; diff --git a/src/engine/glgamewindow.cpp b/src/engine/glgamewindow.cpp index b2743c8..c33b59a 100644 --- a/src/engine/glgamewindow.cpp +++ b/src/engine/glgamewindow.cpp @@ -8,9 +8,10 @@ #include #include -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() diff --git a/src/engine/glgamewindow.h b/src/engine/glgamewindow.h index e577598..98b8438 100644 --- a/src/engine/glgamewindow.h +++ b/src/engine/glgamewindow.h @@ -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; }; diff --git a/src/engine/resources_engine.qrc b/src/engine/resources_engine.qrc index c0479d3..5df7fe0 100644 --- a/src/engine/resources_engine.qrc +++ b/src/engine/resources_engine.qrc @@ -1,8 +1,10 @@ - vulkan_shader_modules/color_frag.spv - vulkan_shader_modules/color_vert.spv opengl_shaders/color.frag opengl_shaders/color.vert + vulkan_shader_modules/color_frag.spv + vulkan_shader_modules/color_vert.spv + vulkan_shader_modules/texture_frag.spv + vulkan_shader_modules/texture_vert.spv diff --git a/src/engine/vulkan_shader_modules/texture.frag b/src/engine/vulkan_shader_modules/texture.frag new file mode 100644 index 0000000..e6021fe --- /dev/null +++ b/src/engine/vulkan_shader_modules/texture.frag @@ -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); +} diff --git a/src/engine/vulkan_shader_modules/texture.vert b/src/engine/vulkan_shader_modules/texture.vert new file mode 100644 index 0000000..de486cb --- /dev/null +++ b/src/engine/vulkan_shader_modules/texture.vert @@ -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; +} diff --git a/src/engine/vulkan_shader_modules/texture_frag.spv b/src/engine/vulkan_shader_modules/texture_frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..7521ef6eef30b34c59cc2126de9f3c3246fc7a82 GIT binary patch literal 556 zcmZQ(Qf6mhU}Ruq;9wA8fB-=TCI&_Z1_o{hHZbk(6YQf`T#}+^Vrl?V!N2x1DGwwz|6qLz`&ruz`!8Rz`_7>H;51Np8^95 z0|(e23}A64Kh!)>xPjCQK>Y#|2dM|~L4E?6Z^pn1_5;YRApIaW{a|2XU}a!nuw`HY v+sntm01i_SA7q9QG@im4Siyc0W?*0dsR4=GF))L}7bNzVfsw(2fsp|K9UU72 literal 0 HcmV?d00001 diff --git a/src/engine/vulkan_shader_modules/texture_vert.spv b/src/engine/vulkan_shader_modules/texture_vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..6292c0de310a9e457964a205047a2ea36f2e5125 GIT binary patch literal 968 zcmZQ(Qf6mhU}Ruq;9yW-fB-=TCI&_Z1_o{hHZbk(6YQf`T#}+^Vrl?V!Natd^C56>LIT zd`W6Wa(;eM3P=qr14utY1SH1Bz{kMAke(ACkXjU$T2zu+0kVsY0U{1!gXHpyGfOh_ z^B7nen8Egg#6fHk1_p+t(liD(1{MYo8zi1vRseR77y|=CDM*q5WDhIY%>^)n7#JAb z8Cbw>lmlsk@C_!N3Z38%P=?4x-%|n89Koe|sR=$;!aM08$TggEB}lnixAY z>_BFK)Pc+ZxdG%(C8&BBUlEB93Tu#?KtjR{OyDqA1cyEYm@US@%)rLL0FGyI1{Ma8 zJs>{Fd<6y;1`cpsF@VLbp?pwWf?^va&&2>rDF}HGALL(97=X;3vX%+iMPg+Lx*U|^77U||quU|;~51v48IE+8?G z*`f>(|AE9nW`p>ku!8wZ94Ze}F9r1n$UKmG5FZqFAiF{4fx-~vE|9z#11mV}K=y*1 z0}}tiz{CKGM_XtZ@__RS1A`p{GdP`q_>K&$3?O?Y7#J8pZUOlPq#oo3NwA+77(jfG u9Wu~-GLeB5>_=G!1_qECkT}R3kbC5zX#!*p$Q}?Mr0y>RBZCD4BLe_ntTYn< literal 0 HcmV?d00001 diff --git a/src/engine/vulkangamerenderer.cpp b/src/engine/vulkangamerenderer.cpp index c598723..c975661 100644 --- a/src/engine/vulkangamerenderer.cpp +++ b/src/engine/vulkangamerenderer.cpp @@ -3,14 +3,18 @@ #include #include +#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(&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); diff --git a/src/engine/vulkangamerenderer.h b/src/engine/vulkangamerenderer.h index 822aaa4..020c636 100644 --- a/src/engine/vulkangamerenderer.h +++ b/src/engine/vulkangamerenderer.h @@ -1,13 +1,14 @@ #pragma once #include +#include 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; }; diff --git a/src/engine/vulkangamewindow.cpp b/src/engine/vulkangamewindow.cpp index 9baace9..7e8fbaa 100644 --- a/src/engine/vulkangamewindow.cpp +++ b/src/engine/vulkangamewindow.cpp @@ -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 } diff --git a/src/engine/vulkangamewindow.h b/src/engine/vulkangamewindow.h index 65a3c4f..44ad09f 100644 --- a/src/engine/vulkangamewindow.h +++ b/src/engine/vulkangamewindow.h @@ -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; }; diff --git a/src/main.cpp b/src/main.cpp index 58f25be..7093d93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,11 @@ #include #include +#include #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);