diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index afda2f4..3b38c29 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -36,7 +36,7 @@ jobs: matrix: os: - windows-latest - - macos-latest + # - macos-latest - ubuntu-latest qt_ver: - 6.6.1 @@ -97,6 +97,11 @@ jobs: with: fetch-depth: 1 + - name: Install Vulkan SDK + uses: humbletim/install-vulkan-sdk@v1.1.1 + with: + cache: true + - name: Enable Developer Command Prompt if: startsWith(matrix.os, 'windows') uses: ilammy/msvc-dev-cmd@v1.12.1 diff --git a/.github/workflows/qmake.yml b/.github/workflows/qmake.yml index 9a4baf4..49dac67 100644 --- a/.github/workflows/qmake.yml +++ b/.github/workflows/qmake.yml @@ -36,8 +36,8 @@ jobs: matrix: os: - windows-2019 - - macos-latest - - ubuntu-latest + # - macos-latest + # - ubuntu-latest qt_ver: - 6.6.1 libs: @@ -93,6 +93,11 @@ jobs: with: fetch-depth: 1 + - name: Install Vulkan SDK + uses: humbletim/install-vulkan-sdk@v1.1.1 + with: + cache: true + - name: msvc-build if: startsWith(matrix.os, 'windows') shell: cmd diff --git a/.github/workflows/toolchain.yml b/.github/workflows/toolchain.yml index 3687fe3..3fc3f60 100644 --- a/.github/workflows/toolchain.yml +++ b/.github/workflows/toolchain.yml @@ -26,6 +26,16 @@ jobs: - breakpad giflib steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install Vulkan SDK + uses: humbletim/install-vulkan-sdk@v1.1.1 + with: + cache: true + - name: Install Qt uses: jurplel/install-qt-action@v3 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fcae80..be0258d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,10 @@ find_package(GIF REQUIRED) if(GIF_FOUND) message(STATUS "found GIF") endif() +find_package(Vulkan REQUIRED) +if(Vulkan_FOUND) + message(STATUS "found Vulkan") +endif() add_subdirectory(utils) add_subdirectory(3rdparty) diff --git a/gpugraphics/CMakeLists.txt b/gpugraphics/CMakeLists.txt index ed643b3..852510f 100644 --- a/gpugraphics/CMakeLists.txt +++ b/gpugraphics/CMakeLists.txt @@ -1,5 +1,13 @@ -set(PROJECT_SOURCES gpugraphics_global.hpp openglshaderprogram.cc - openglshaderprogram.hpp openglview.cc openglview.hpp) +set(PROJECT_SOURCES + gpugraphics_global.hpp + openglshaderprogram.cc + openglshaderprogram.hpp + openglview.cc + openglview.hpp + texturerenderer.cc + texturerenderer.hpp + vulkanview.cc + vulkanview.hpp) qt_add_resources(SOURCES shader.qrc) diff --git a/gpugraphics/gpugraphics.pro b/gpugraphics/gpugraphics.pro index f77d9cc..3563d24 100644 --- a/gpugraphics/gpugraphics.pro +++ b/gpugraphics/gpugraphics.pro @@ -8,11 +8,15 @@ TARGET = $$replaceLibName(gpugraphics) HEADERS += \ gpugraphics_global.hpp \ openglshaderprogram.hpp \ - openglview.hpp + openglview.hpp \ + texturerenderer.hpp \ + vulkanview.hpp SOURCES += \ openglshaderprogram.cc \ - openglview.cc + openglview.cc \ + texturerenderer.cc \ + vulkanview.cc RESOURCES += \ shader.qrc diff --git a/gpugraphics/texturerenderer.cc b/gpugraphics/texturerenderer.cc new file mode 100644 index 0000000..5450f5d --- /dev/null +++ b/gpugraphics/texturerenderer.cc @@ -0,0 +1,1004 @@ +#include "texturerenderer.hpp" + +#include +#include + +namespace GpuGraphics { + +static std::vector vertices = { + // positions // texture coords + 1.0F, 1.0F, 0.0F, 1.0F, 1.0F, // top right + 1.0F, -1.0F, 0.0F, 1.0F, 0.0F, // bottom right + -1.0F, -1.0F, 0.0F, 0.0F, 0.0F, // bottom left + -1.0F, 1.0F, 0.0F, 0.0F, 1.0F // top left +}; + +static const std::vector indices = {0, 1, 2, 2, 3, 0}; + +struct Texture +{ + void cleanup(QVulkanDeviceFunctions *vulkanDeviceFunctions, const VkDevice &device) + { + if (textureImageView != VK_NULL_HANDLE) { + vulkanDeviceFunctions->vkDestroyImageView(device, textureImageView, nullptr); + textureImageView = VK_NULL_HANDLE; + } + if (textureImage != VK_NULL_HANDLE) { + vulkanDeviceFunctions->vkDestroyImage(device, textureImage, nullptr); + textureImage = VK_NULL_HANDLE; + } + if (textureImageMemory != VK_NULL_HANDLE) { + vulkanDeviceFunctions->vkFreeMemory(device, textureImageMemory, nullptr); + textureImageMemory = VK_NULL_HANDLE; + } + } + + VkFormat format = VK_FORMAT_R8G8B8A8_SRGB; + VkImage textureImage = VK_NULL_HANDLE; + VkDeviceMemory textureImageMemory = VK_NULL_HANDLE; + VkImageView textureImageView = VK_NULL_HANDLE; +}; + +class TextureRenderer::TextureRendererPrivate +{ +public: + TextureRendererPrivate(TextureRenderer *q) + : q_ptr(q) + {} + + VkShaderModule createShader(const QString &name) + { + QFile file(name); + if (!file.open(QIODevice::ReadOnly)) { + qWarning("Failed to read shader %s", qPrintable(name)); + return VK_NULL_HANDLE; + } + auto blob = file.readAll(); + file.close(); + + VkShaderModuleCreateInfo shaderInfo; + memset(&shaderInfo, 0, sizeof(shaderInfo)); + shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + shaderInfo.codeSize = blob.size(); + shaderInfo.pCode = reinterpret_cast(blob.constData()); + VkShaderModule shaderModule; + auto err = vulkanDeviceFunctions->vkCreateShaderModule(vulkanWindow->device(), + &shaderInfo, + nullptr, + &shaderModule); + if (err != VK_SUCCESS) { + qWarning("Failed to create shader module: %d", err); + return VK_NULL_HANDLE; + } + + return shaderModule; + } + + void createBuffer(VkDeviceSize size, + VkBufferUsageFlags usage, + VkMemoryPropertyFlags properties, + VkBuffer &buffer, + VkDeviceMemory &bufferMemory) + { + auto device = vulkanWindow->device(); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + auto err = vulkanDeviceFunctions->vkCreateBuffer(device, &bufferInfo, nullptr, &buffer); + if (err != VK_SUCCESS) { + qFatal("Failed to create buffer: %d", err); + } + + VkMemoryRequirements memReq; + vulkanDeviceFunctions->vkGetBufferMemoryRequirements(device, buffer, &memReq); + + VkMemoryAllocateInfo allocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + nullptr, + memReq.size, + findMemoryType(memReq.memoryTypeBits, properties)}; + + err = vulkanDeviceFunctions->vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory); + if (err != VK_SUCCESS) { + qFatal("Failed to allocate memory: %d", err); + } + + err = vulkanDeviceFunctions->vkBindBufferMemory(device, buffer, bufferMemory, 0); + if (err != VK_SUCCESS) { + qFatal("Failed to bind buffer memory: %d", err); + } + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) + { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vulkanDeviceFunctions->vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + auto findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t + { + VkPhysicalDeviceMemoryProperties memProperties; + vulkanWindow->vulkanInstance() + ->functions() + ->vkGetPhysicalDeviceMemoryProperties(vulkanWindow->physicalDevice(), &memProperties); + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if (((typeFilter & (1 << i)) != 0U) + && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + auto beginSingleTimeCommands() -> VkCommandBuffer + { + auto device = vulkanWindow->device(); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = vulkanWindow->graphicsCommandPool(); + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vulkanDeviceFunctions->vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vulkanDeviceFunctions->vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) + { + auto device = vulkanWindow->device(); + + vulkanDeviceFunctions->vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vulkanDeviceFunctions->vkQueueSubmit(vulkanWindow->graphicsQueue(), + 1, + &submitInfo, + VK_NULL_HANDLE); + vulkanDeviceFunctions->vkQueueWaitIdle(vulkanWindow->graphicsQueue()); + + vulkanDeviceFunctions->vkFreeCommandBuffers(device, + vulkanWindow->graphicsCommandPool(), + 1, + &commandBuffer); + } + + bool createImage(const QSize &size, + VkFormat format, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkMemoryPropertyFlags properties, + VkImage &image, + VkDeviceMemory &imageMemor) + { + auto device = vulkanWindow->device(); + + VkImageCreateInfo imageInfo; + memset(&imageInfo, 0, sizeof(imageInfo)); + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = texture.format; + 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; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + auto err = vulkanDeviceFunctions->vkCreateImage(device, &imageInfo, nullptr, &image); + if (err != VK_SUCCESS) { + qWarning("Failed to create linear image for texture: %d", err); + return false; + } + + VkMemoryRequirements memReq; + vulkanDeviceFunctions->vkGetImageMemoryRequirements(device, image, &memReq); + + VkMemoryAllocateInfo allocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + nullptr, + memReq.size, + findMemoryType(memReq.memoryTypeBits, properties)}; + qDebug("allocating %u bytes for texture image", uint32_t(memReq.size)); + + err = vulkanDeviceFunctions->vkAllocateMemory(device, &allocInfo, nullptr, &imageMemor); + if (err != VK_SUCCESS) { + qWarning("Failed to allocate memory for linear image: %d", err); + return false; + } + + err = vulkanDeviceFunctions->vkBindImageMemory(device, image, imageMemor, 0); + if (err != VK_SUCCESS) { + qWarning("Failed to bind linear image memory: %d", err); + return false; + } + + return true; + } + + void transitionImageLayout(VkImage image, + VkFormat format, + VkImageLayout oldLayout, + VkImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED + && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vulkanDeviceFunctions->vkCmdPipelineBarrier( + commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, const QSize &size) + { + auto commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = {static_cast(size.width()), + static_cast(size.height()), + 1}; + + vulkanDeviceFunctions->vkCmdCopyBufferToImage(commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion); + + endSingleTimeCommands(commandBuffer); + } + + auto createImageView(VkImage image, VkFormat format) -> VkImageView + { + auto device = vulkanWindow->device(); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vulkanDeviceFunctions->vkCreateImageView(device, &viewInfo, nullptr, &imageView) + != VK_SUCCESS) { + throw std::runtime_error("failed to create texture image view!"); + } + + return imageView; + } + + void updateUniformBuffer(uint32_t currentImage) + { + transform.setToIdentity(); + memcpy(uniformBuffersMapped[currentImage], transform.constData(), 16 * sizeof(float)); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer) + { + const QSize sz = vulkanWindow->swapChainImageSize(); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vulkanDeviceFunctions->vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = vulkanWindow->defaultRenderPass(); + renderPassInfo.framebuffer = vulkanWindow->currentFramebuffer(); + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = {static_cast(sz.width()), + static_cast(sz.height())}; + + VkClearValue clearColor = {{{0.0F, 0.0F, 0.0F, 1.0F}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vulkanDeviceFunctions->vkCmdBeginRenderPass(commandBuffer, + &renderPassInfo, + VK_SUBPASS_CONTENTS_INLINE); + + vulkanDeviceFunctions->vkCmdBindPipeline(commandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0F; + viewport.y = 0.0F; + viewport.width = static_cast(sz.width()); + viewport.height = static_cast(sz.height()); + viewport.minDepth = 0.0F; + viewport.maxDepth = 1.0F; + vulkanDeviceFunctions->vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = {static_cast(sz.width()), static_cast(sz.height())}; + vulkanDeviceFunctions->vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vulkanDeviceFunctions->vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vulkanDeviceFunctions->vkCmdBindIndexBuffer(commandBuffer, + indexBuffer, + 0, + VK_INDEX_TYPE_UINT16); + + vulkanDeviceFunctions->vkCmdBindDescriptorSets(commandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout, + 0, + 1, + &descriptorSets[vulkanWindow->currentFrame()], + 0, + nullptr); + + vulkanDeviceFunctions + ->vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vulkanDeviceFunctions->vkCmdEndRenderPass(commandBuffer); + + if (vulkanDeviceFunctions->vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + TextureRenderer *q_ptr; + + QVulkanWindow *vulkanWindow; + QVulkanDeviceFunctions *vulkanDeviceFunctions; + + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + VkPipelineCache pipelineCache = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline graphicsPipeline; + + Texture texture; + VkSampler sampler = VK_NULL_HANDLE; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + QMatrix4x4 transform; + const qreal scaleFactor = 1.2; +}; + +TextureRenderer::TextureRenderer(QVulkanWindow *w) + : d_ptr(new TextureRendererPrivate(this)) +{ + d_ptr->vulkanWindow = w; +} + +TextureRenderer::~TextureRenderer() {} + +void TextureRenderer::initResources() +{ + auto device = d_ptr->vulkanWindow->device(); + d_ptr->vulkanDeviceFunctions = d_ptr->vulkanWindow->vulkanInstance()->deviceFunctions(device); + + createDescriptorSetLayout(); + createGraphicsPipeline(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); +} + +void TextureRenderer::initSwapChainResources() +{ + d_ptr->transform = d_ptr->vulkanWindow + ->clipCorrectionMatrix(); // adjust for Vulkan-OpenGL clip space differences + const auto sz = d_ptr->vulkanWindow->swapChainImageSize(); + d_ptr->transform.perspective(45.0f, sz.width() / (float) sz.height(), 0.01f, 100.0f); + d_ptr->transform.translate(0, 0, -4); +} + +void TextureRenderer::releaseSwapChainResources() {} + +void TextureRenderer::releaseResources() +{ + auto device = d_ptr->vulkanWindow->device(); + const auto concurrentFrameCount = d_ptr->vulkanWindow->concurrentFrameCount(); + + d_ptr->vulkanDeviceFunctions->vkDestroyPipeline(device, d_ptr->graphicsPipeline, nullptr); + d_ptr->vulkanDeviceFunctions->vkDestroyPipelineCache(device, d_ptr->pipelineCache, nullptr); + d_ptr->vulkanDeviceFunctions->vkDestroyPipelineLayout(device, d_ptr->pipelineLayout, nullptr); + + for (size_t i = 0; i < concurrentFrameCount; i++) { + d_ptr->vulkanDeviceFunctions->vkDestroyBuffer(device, d_ptr->uniformBuffers[i], nullptr); + d_ptr->vulkanDeviceFunctions->vkFreeMemory(device, d_ptr->uniformBuffersMemory[i], nullptr); + } + + d_ptr->vulkanDeviceFunctions->vkDestroyDescriptorPool(device, d_ptr->descriptorPool, nullptr); + d_ptr->vulkanDeviceFunctions->vkDestroySampler(device, d_ptr->sampler, nullptr); + + d_ptr->texture.cleanup(d_ptr->vulkanDeviceFunctions, device); + + d_ptr->vulkanDeviceFunctions->vkDestroyDescriptorSetLayout(device, + d_ptr->descriptorSetLayout, + nullptr); + + d_ptr->vulkanDeviceFunctions->vkDestroyDescriptorSetLayout(device, + d_ptr->descriptorSetLayout, + nullptr); + + d_ptr->vulkanDeviceFunctions->vkDestroyBuffer(device, d_ptr->indexBuffer, nullptr); + d_ptr->vulkanDeviceFunctions->vkFreeMemory(device, d_ptr->indexBufferMemory, nullptr); + + d_ptr->vulkanDeviceFunctions->vkDestroyBuffer(device, d_ptr->vertexBuffer, nullptr); + d_ptr->vulkanDeviceFunctions->vkFreeMemory(device, d_ptr->vertexBufferMemory, nullptr); +} + +void TextureRenderer::startNextFrame() +{ + d_ptr->updateUniformBuffer(d_ptr->vulkanWindow->currentFrame()); + + // vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + d_ptr->recordCommandBuffer(d_ptr->vulkanWindow->currentCommandBuffer()); + + d_ptr->vulkanWindow->frameReady(); + d_ptr->vulkanWindow->requestUpdate(); // render continuously, throttled by the presentation rate +} + +void TextureRenderer::setImageUrl(const QString &imageUrl) +{ + auto device = d_ptr->vulkanWindow->device(); + d_ptr->vulkanDeviceFunctions->vkDeviceWaitIdle(device); + + d_ptr->texture.cleanup(d_ptr->vulkanDeviceFunctions, d_ptr->vulkanWindow->device()); + + createTexture(imageUrl); + createTextureImageView(); + + updateDescriptorSets(); + + d_ptr->vulkanDeviceFunctions->vkDeviceWaitIdle(device); +} + +void TextureRenderer::createDescriptorSetLayout() +{ + auto device = d_ptr->vulkanWindow->device(); + VkDescriptorSetLayoutBinding layoutBinding[2] = {{0, // binding + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, // descriptorCount + VK_SHADER_STAGE_VERTEX_BIT, + nullptr}, + {1, // binding + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + 1, // descriptorCount + VK_SHADER_STAGE_FRAGMENT_BIT, + nullptr}}; + VkDescriptorSetLayoutCreateInfo descLayoutInfo + = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + nullptr, + 0, + 2, // bindingCount + layoutBinding}; + auto err = d_ptr->vulkanDeviceFunctions->vkCreateDescriptorSetLayout(device, + &descLayoutInfo, + nullptr, + &d_ptr->descriptorSetLayout); + if (err != VK_SUCCESS) { + qFatal("Failed to create descriptor set layout: %d", err); + } +} + +void TextureRenderer::createGraphicsPipeline() +{ + auto device = d_ptr->vulkanWindow->device(); + + VkPipelineCacheCreateInfo pipelineCacheInfo; + memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo)); + pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + auto err = d_ptr->vulkanDeviceFunctions->vkCreatePipelineCache(device, + &pipelineCacheInfo, + nullptr, + &d_ptr->pipelineCache); + if (err != VK_SUCCESS) + qFatal("Failed to create pipeline cache: %d", err); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo; + memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &d_ptr->descriptorSetLayout; + err = d_ptr->vulkanDeviceFunctions->vkCreatePipelineLayout(device, + &pipelineLayoutInfo, + nullptr, + &d_ptr->pipelineLayout); + if (err != VK_SUCCESS) { + qFatal("Failed to create pipeline layout: %d", err); + } + + // Shaders + auto vertShaderModule = d_ptr->createShader(QStringLiteral(":/texture_vert.spv")); + auto fragShaderModule = d_ptr->createShader(QStringLiteral(":/texture_frag.spv")); + + VkPipelineShaderStageCreateInfo shaderStages[2] + = {{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_VERTEX_BIT, + vertShaderModule, + "main", + nullptr}, + {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_FRAGMENT_BIT, + fragShaderModule, + "main", + nullptr}}; + + VkVertexInputBindingDescription vertexBindingDesc = {0, // binding + 5 * sizeof(float), + VK_VERTEX_INPUT_RATE_VERTEX}; + VkVertexInputAttributeDescription vertexAttrDesc[] = {{ // position + 0, // location + 0, // binding + VK_FORMAT_R32G32B32_SFLOAT, + 0}, + {// texcoord + 1, + 0, + VK_FORMAT_R32G32_SFLOAT, + 3 * sizeof(float)}}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.pNext = nullptr; + vertexInputInfo.flags = 0; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &vertexBindingDesc; + vertexInputInfo.vertexAttributeDescriptionCount = 2; + vertexInputInfo.pVertexAttributeDescriptions = vertexAttrDesc; + + VkPipelineInputAssemblyStateCreateInfo ia; + memset(&ia, 0, sizeof(ia)); + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + + VkPipelineViewportStateCreateInfo vp; + memset(&vp, 0, sizeof(vp)); + vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; + vp.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rs; + memset(&rs, 0, sizeof(rs)); + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_CLOCKWISE; + rs.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms; + memset(&ms, 0, sizeof(ms)); + ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendStateCreateInfo cb; + memset(&cb, 0, sizeof(cb)); + cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + // assume pre-multiplied alpha, blend, write out all of rgba + VkPipelineColorBlendAttachmentState att; + memset(&att, 0, sizeof(att)); + att.colorWriteMask = 0xF; + att.blendEnable = VK_TRUE; + att.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + att.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + att.colorBlendOp = VK_BLEND_OP_ADD; + att.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + att.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + att.alphaBlendOp = VK_BLEND_OP_ADD; + cb.attachmentCount = 1; + cb.pAttachments = &att; + + VkDynamicState dynEnable[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dyn; + memset(&dyn, 0, sizeof(dyn)); + dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dyn.dynamicStateCount = sizeof(dynEnable) / sizeof(VkDynamicState); + dyn.pDynamicStates = dynEnable; + + // Graphics pipeline + VkGraphicsPipelineCreateInfo pipelineInfo; + memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &ia; + pipelineInfo.pViewportState = &vp; + pipelineInfo.pRasterizationState = &rs; + pipelineInfo.pMultisampleState = &ms; + pipelineInfo.pColorBlendState = &cb; + pipelineInfo.pDynamicState = &dyn; + pipelineInfo.layout = d_ptr->pipelineLayout; + pipelineInfo.renderPass = d_ptr->vulkanWindow->defaultRenderPass(); + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + err = d_ptr->vulkanDeviceFunctions->vkCreateGraphicsPipelines(device, + d_ptr->pipelineCache, + 1, + &pipelineInfo, + nullptr, + &d_ptr->graphicsPipeline); + if (err != VK_SUCCESS) + qFatal("Failed to create graphics pipeline: %d", err); + + if (vertShaderModule) { + d_ptr->vulkanDeviceFunctions->vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + if (fragShaderModule) { + d_ptr->vulkanDeviceFunctions->vkDestroyShaderModule(device, fragShaderModule, nullptr); + } +} + +void TextureRenderer::createTextureSampler() +{ + auto device = d_ptr->vulkanWindow->device(); + + 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.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = d_ptr->vulkanWindow->physicalDeviceProperties() + ->limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + auto err = d_ptr->vulkanDeviceFunctions->vkCreateSampler(device, + &samplerInfo, + nullptr, + &d_ptr->sampler); + if (err != VK_SUCCESS) { + qFatal("Failed to create sampler: %d", err); + } +} + +void TextureRenderer::createVertexBuffer() +{ + auto device = d_ptr->vulkanWindow->device(); + + auto bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + d_ptr->createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + stagingBuffer, + stagingBufferMemory); + + void *data; + d_ptr->vulkanDeviceFunctions->vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), static_cast(bufferSize)); + d_ptr->vulkanDeviceFunctions->vkUnmapMemory(device, stagingBufferMemory); + + d_ptr->createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + d_ptr->vertexBuffer, + d_ptr->vertexBufferMemory); + + d_ptr->copyBuffer(stagingBuffer, d_ptr->vertexBuffer, bufferSize); + + d_ptr->vulkanDeviceFunctions->vkDestroyBuffer(device, stagingBuffer, nullptr); + d_ptr->vulkanDeviceFunctions->vkFreeMemory(device, stagingBufferMemory, nullptr); +} + +void TextureRenderer::createIndexBuffer() +{ + auto device = d_ptr->vulkanWindow->device(); + + auto bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + d_ptr->createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + stagingBuffer, + stagingBufferMemory); + + void *data; + d_ptr->vulkanDeviceFunctions->vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), static_cast(bufferSize)); + d_ptr->vulkanDeviceFunctions->vkUnmapMemory(device, stagingBufferMemory); + + d_ptr->createBuffer(bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + d_ptr->indexBuffer, + d_ptr->indexBufferMemory); + + d_ptr->copyBuffer(stagingBuffer, d_ptr->indexBuffer, bufferSize); + + d_ptr->vulkanDeviceFunctions->vkDestroyBuffer(device, stagingBuffer, nullptr); + d_ptr->vulkanDeviceFunctions->vkFreeMemory(device, stagingBufferMemory, nullptr); +} + +void TextureRenderer::createUniformBuffers() +{ + auto device = d_ptr->vulkanWindow->device(); + const auto concurrentFrameCount = d_ptr->vulkanWindow->concurrentFrameCount(); + + VkDeviceSize bufferSize = 16 * sizeof(float); + + d_ptr->uniformBuffers.resize(concurrentFrameCount); + d_ptr->uniformBuffersMemory.resize(concurrentFrameCount); + d_ptr->uniformBuffersMapped.resize(concurrentFrameCount); + + for (size_t i = 0; i < concurrentFrameCount; i++) { + d_ptr->createBuffer(bufferSize, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + d_ptr->uniformBuffers[i], + d_ptr->uniformBuffersMemory[i]); + + d_ptr->vulkanDeviceFunctions->vkMapMemory(device, + d_ptr->uniformBuffersMemory[i], + 0, + bufferSize, + 0, + &d_ptr->uniformBuffersMapped[i]); + } +} + +void TextureRenderer::createDescriptorPool() +{ + auto device = d_ptr->vulkanWindow->device(); + const auto concurrentFrameCount = d_ptr->vulkanWindow->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 = 2; + descPoolInfo.pPoolSizes = descPoolSizes; + auto err = d_ptr->vulkanDeviceFunctions->vkCreateDescriptorPool(device, + &descPoolInfo, + nullptr, + &d_ptr->descriptorPool); + if (err != VK_SUCCESS) { + qFatal("Failed to create descriptor pool: %d", err); + } +} + +void TextureRenderer::createDescriptorSets() +{ + auto device = d_ptr->vulkanWindow->device(); + const auto concurrentFrameCount = d_ptr->vulkanWindow->concurrentFrameCount(); + + std::vector layouts(concurrentFrameCount, d_ptr->descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = d_ptr->descriptorPool; + allocInfo.descriptorSetCount = static_cast(concurrentFrameCount); + allocInfo.pSetLayouts = layouts.data(); + + d_ptr->descriptorSets.resize(concurrentFrameCount); + auto err = d_ptr->vulkanDeviceFunctions->vkAllocateDescriptorSets(device, + &allocInfo, + d_ptr->descriptorSets.data()); + if (err != VK_SUCCESS) { + qFatal("Failed to allocate descriptor set: %d", err); + } +} + +void TextureRenderer::updateDescriptorSets() +{ + auto device = d_ptr->vulkanWindow->device(); + const auto concurrentFrameCount = d_ptr->vulkanWindow->concurrentFrameCount(); + + for (size_t i = 0; i < concurrentFrameCount; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = d_ptr->uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = 16 * sizeof(float); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = d_ptr->texture.textureImageView; + imageInfo.sampler = d_ptr->sampler; + + std::array descriptorWrites{}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = d_ptr->descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = d_ptr->descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + d_ptr->vulkanDeviceFunctions->vkUpdateDescriptorSets(device, + static_cast( + descriptorWrites.size()), + descriptorWrites.data(), + 0, + nullptr); + } +} + +bool TextureRenderer::createTexture(const QString &name) +{ + auto device = d_ptr->vulkanWindow->device(); + auto *f = d_ptr->vulkanWindow->vulkanInstance()->functions(); + + QImage img(name); + if (img.isNull()) { + qWarning("Failed to load image %s", qPrintable(name)); + return false; + } + + // Convert to byte ordered RGBA8. Use premultiplied alpha, see pColorBlendState in the pipeline. + img = img.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + auto imageSize = img.sizeInBytes(); + + // 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(d_ptr->vulkanWindow->physicalDevice(), + d_ptr->texture.format, + &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; + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + d_ptr->createBuffer(imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + stagingBuffer, + stagingBufferMemory); + + void *data; + d_ptr->vulkanDeviceFunctions->vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, img.constBits(), static_cast(imageSize)); + d_ptr->vulkanDeviceFunctions->vkUnmapMemory(device, stagingBufferMemory); + + d_ptr->createImage(img.size(), + d_ptr->texture.format, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + d_ptr->texture.textureImage, + d_ptr->texture.textureImageMemory); + + d_ptr->transitionImageLayout(d_ptr->texture.textureImage, + d_ptr->texture.format, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + d_ptr->copyBufferToImage(stagingBuffer, d_ptr->texture.textureImage, img.size()); + d_ptr->transitionImageLayout(d_ptr->texture.textureImage, + d_ptr->texture.format, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + d_ptr->vulkanDeviceFunctions->vkDestroyBuffer(device, stagingBuffer, nullptr); + d_ptr->vulkanDeviceFunctions->vkFreeMemory(device, stagingBufferMemory, nullptr); + + return true; +} + +void TextureRenderer::createTextureImageView() +{ + d_ptr->texture.textureImageView = d_ptr->createImageView(d_ptr->texture.textureImage, + d_ptr->texture.format); +} + +} // namespace GpuGraphics diff --git a/gpugraphics/texturerenderer.hpp b/gpugraphics/texturerenderer.hpp new file mode 100644 index 0000000..a8ddd27 --- /dev/null +++ b/gpugraphics/texturerenderer.hpp @@ -0,0 +1,45 @@ +#ifndef TEXTURERENDERER_HPP +#define TEXTURERENDERER_HPP + +#include "gpugraphics_global.hpp" + +#include + +namespace GpuGraphics { + +class GPUAPHICS TextureRenderer : public QVulkanWindowRenderer +{ +public: + TextureRenderer(QVulkanWindow *w); + ~TextureRenderer() override; + + void initResources() override; + void initSwapChainResources() override; + void releaseSwapChainResources() override; + void releaseResources() override; + + void startNextFrame() override; + + void setImageUrl(const QString &imageUrl); + +private: + void createDescriptorSetLayout(); + void createGraphicsPipeline(); + void createTextureSampler(); + void createVertexBuffer(); + void createIndexBuffer(); + void createUniformBuffers(); + void createDescriptorPool(); + void createDescriptorSets(); + void updateDescriptorSets(); + + bool createTexture(const QString &name); + void createTextureImageView(); + + class TextureRendererPrivate; + QScopedPointer d_ptr; +}; + +} // namespace GpuGraphics + +#endif // TEXTURERENDERER_HPP diff --git a/gpugraphics/vulkanview.cc b/gpugraphics/vulkanview.cc new file mode 100644 index 0000000..ae5766b --- /dev/null +++ b/gpugraphics/vulkanview.cc @@ -0,0 +1,44 @@ +#include "vulkanview.hpp" +#include "texturerenderer.hpp" + +#include + +namespace GpuGraphics { + +class VulkanView::VulkanViewPrivate +{ +public: + VulkanViewPrivate(VulkanView *q) + : q_ptr(q) + , textureRenderer(new TextureRenderer(q_ptr)) + {} + + VulkanView *q_ptr; + + TextureRenderer *textureRenderer; +}; + +VulkanView::VulkanView(QWindow *parent) + : QVulkanWindow(parent) + , d_ptr(new VulkanViewPrivate(this)) +{} + +VulkanView::~VulkanView() {} + +QVulkanWindowRenderer *VulkanView::createRenderer() +{ + return d_ptr->textureRenderer; +} + +TextureRenderer *VulkanView::textureRenderer() +{ + return d_ptr->textureRenderer; +} + +QWidget *VulkanView::widget() +{ + static QWidget *widget = QWidget::createWindowContainer(this); + return widget; +} + +} // namespace GpuGraphics diff --git a/gpugraphics/vulkanview.hpp b/gpugraphics/vulkanview.hpp new file mode 100644 index 0000000..c01c56a --- /dev/null +++ b/gpugraphics/vulkanview.hpp @@ -0,0 +1,31 @@ +#ifndef VULKANVIEW_HPP +#define VULKANVIEW_HPP + +#include "gpugraphics_global.hpp" + +#include + +namespace GpuGraphics { + +class TextureRenderer; + +class GPUAPHICS VulkanView : public QVulkanWindow +{ +public: + explicit VulkanView(QWindow *parent = nullptr); + ~VulkanView() override; + + QVulkanWindowRenderer *createRenderer() override; + + TextureRenderer *textureRenderer(); + + QWidget *widget(); + +private: + class VulkanViewPrivate; + QScopedPointer d_ptr; +}; + +} // namespace GpuGraphics + +#endif // VULKANVIEW_HPP