From fbb64a07afbaa81e1cf82a1dfba74c8cb1a7a4e1 Mon Sep 17 00:00:00 2001 From: niki Date: Tue, 9 Aug 2022 00:57:07 +0200 Subject: [PATCH] vulkan: partially implement mipmaps There is no real effect yet when rendering, however debugging the application with renderdoc reveals that the mipmap generation is already correct. There's probably something weird going on with the sampling. I'll have to look into that next. --- src/modules/graphics/vulkan/Graphics.cpp | 42 +------ src/modules/graphics/vulkan/Graphics.h | 9 +- src/modules/graphics/vulkan/Shader.cpp | 2 +- src/modules/graphics/vulkan/Texture.cpp | 149 ++++++++++++++++++----- src/modules/graphics/vulkan/Texture.h | 20 +-- 5 files changed, 140 insertions(+), 82 deletions(-) diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 1ce0db314..bff4881ac 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -563,38 +563,6 @@ void Graphics::queueCleanUp(std::function cleanUp) { cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp)); } -VkCommandBuffer Graphics::beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandPool = commandPool; - allocInfo.commandBufferCount = 1; - - VkCommandBuffer commandBuffer; - 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; - - vkBeginCommandBuffer(commandBuffer, &beginInfo); - - return commandBuffer; -} - -void Graphics::endSingleTimeCommands(VkCommandBuffer commandBuffer) { - vkEndCommandBuffer(commandBuffer); - - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - - vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); - vkQueueWaitIdle(graphicsQueue); - vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); -} - graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() { love::graphics::Shader::BuiltinUniformData data; @@ -641,9 +609,9 @@ void Graphics::createVulkanInstance() { VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "LOVE"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); //todo, get this version from somewhere else? + appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); //todo, get this version from somewhere else? appInfo.pEngineName = "LOVE Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REV); + appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV); appInfo.apiVersion = vulkanApiVersion; VkInstanceCreateInfo createInfo{}; @@ -1283,8 +1251,8 @@ VkSampler Graphics::createSampler(const SamplerState& samplerState) { } samplerInfo.mipmapMode = Vulkan::getMipMapMode(samplerState.mipmapFilter); samplerInfo.mipLodBias = samplerState.lodBias; - samplerInfo.minLod = 0.0f; // fixme: samplerState.minLod - samplerInfo.maxLod = 0.0f; // fixme: samplerState.maxLod + samplerInfo.minLod = static_cast(samplerState.minLod); + samplerInfo.maxLod = static_cast(samplerState.maxLod); VkSampler sampler; if (vkCreateSampler(device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) { @@ -1505,6 +1473,8 @@ void Graphics::createSyncObjects() { void Graphics::createDefaultTexture() { Texture::Settings settings; standardTexture.reset((Texture*)newTexture(settings)); + uint8_t whitePixels[] = {255, 255, 255, 255}; + standardTexture->replacePixels(whitePixels, sizeof(whitePixels), 0, 0, { 0, 0, 1, 1 }, false); } void Graphics::cleanup() { diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 027004fe0..360692464 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -138,9 +138,6 @@ public: VkCommandBuffer getDataTransferCommandBuffer(); void queueCleanUp(std::function cleanUp); - VkCommandBuffer beginSingleTimeCommands(); - void endSingleTimeCommands(VkCommandBuffer); - uint32_t getNumImagesInFlight() const; const PFN_vkCmdPushDescriptorSetKHR getVkCmdPushDescriptorSetKHRFunctionPointer() const; const VkDeviceSize getMinUniformBufferOffsetAlignment() const; @@ -222,8 +219,8 @@ private: std::vector renderFinishedSemaphores; std::vector inFlightFences; std::vector imagesInFlight; - VkDeviceSize minUniformBufferOffsetAlignment; - PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet; + VkDeviceSize minUniformBufferOffsetAlignment = 0; + PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet = nullptr; size_t currentFrame = 0; uint32_t imageIndex = 0; bool framebufferResized = false; @@ -239,7 +236,7 @@ private: // render pass variables. VkFormat currentFramebufferOutputFormat = VK_FORMAT_UNDEFINED; - Texture* renderTargetTexture; + Texture* renderTargetTexture = nullptr; float currentViewportWidth = 0; float currentViewportHeight = 0; }; diff --git a/src/modules/graphics/vulkan/Shader.cpp b/src/modules/graphics/vulkan/Shader.cpp index eecfb8813..db42763fc 100644 --- a/src/modules/graphics/vulkan/Shader.cpp +++ b/src/modules/graphics/vulkan/Shader.cpp @@ -174,7 +174,7 @@ void Shader::unloadVolatile() { vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); }); - for (const auto streamBufferVector : streamBuffers) { + for (const auto &streamBufferVector : streamBuffers) { for (const auto streamBuffer : streamBufferVector) { delete streamBuffer; } diff --git a/src/modules/graphics/vulkan/Texture.cpp b/src/modules/graphics/vulkan/Texture.cpp index 76b9dc421..eeea2aa48 100644 --- a/src/modules/graphics/vulkan/Texture.cpp +++ b/src/modules/graphics/vulkan/Texture.cpp @@ -11,7 +11,11 @@ namespace love { namespace graphics { namespace vulkan { Texture::Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data) - : love::graphics::Texture(gfx, settings, data), gfx(gfx), data(data) { + : love::graphics::Texture(gfx, settings, data), gfx(gfx), slices(settings.type) { + if (data) { + slices = *data; + } + loadVolatile(); } @@ -42,8 +46,8 @@ bool Texture::loadVolatile() { imageInfo.extent.width = static_cast(width); imageInfo.extent.height = static_cast(height); imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = layerCount; + imageInfo.mipLevels = static_cast(getMipmapCount()); + imageInfo.arrayLayers = static_cast(layerCount); imageInfo.format = vulkanFormat.internalFormat; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -60,32 +64,34 @@ bool Texture::loadVolatile() { auto commandBuffer = vgfx->getDataTransferCommandBuffer(); // fixme: we probably should select a different default layout when the texture is not readable, instead of VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL - Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, 1, 0, layerCount); + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + 0, static_cast(getMipmapCount()), + 0, static_cast(layerCount)); - if (data) { - for (int slice = 0; slice < layerCount; slice++) { - auto sliceData = data->get(slice, 0); - auto size = sliceData->getSize(); - auto dataPtr = sliceData->getData(); - Rect rect{}; - rect.x = 0; - rect.y = 0; - rect.w = sliceData->getWidth(); - rect.h = sliceData->getHeight(); + bool hasdata = slices.get(0, 0) != nullptr; - uploadByteData(format, dataPtr, size, 0, slice, rect); + if (hasdata) { + for (int mip = 0; mip < layerCount; mip++) { + // fixme: deal with compressed images. + + for (int slice = 0; slice < slices.getSliceCount(mip); slice++) { + auto* id = slices.get(slice, mip); + if (id != nullptr) { + uploadImageData(id, mip, slice, 0, 0); + } + } } } else { - if (isRenderTarget()) { - clear(false); - } - else { - clear(true); - } + clear(); } createTextureImageView(); textureSampler = vgfx->getCachedSampler(samplerState); + if (slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE) { + generateMipmaps(); + } + return true; } @@ -138,21 +144,25 @@ void Texture::createTextureImageView() { } } -void Texture::clear(bool white) { +void Texture::clear() { auto commandBuffer = vgfx->getDataTransferCommandBuffer(); - auto clearColor = getClearValue(white); + auto clearColor = getClearValue(false); VkImageSubresourceRange range{}; range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - range.layerCount = layerCount; - range.levelCount = 1; + range.layerCount = static_cast(layerCount); + range.levelCount = static_cast(getMipmapCount()); - Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, 1, 0, layerCount); + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 0, range.levelCount, 0, range.layerCount); vkCmdClearColorImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearColor, 1, &range); - Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, 1, 0, layerCount); + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + 0, range.levelCount, 0, range.layerCount); } VkClearColorValue Texture::getClearValue(bool white) { @@ -206,6 +216,83 @@ VkClearColorValue Texture::getClearValue(bool white) { return clearColor; } +void Texture::generateMipmapsInternal() { + auto commandBuffer = vgfx->getDataTransferCommandBuffer(); + + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 0, static_cast(getMipmapCount()), 0, static_cast(layerCount)); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = textureImage; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = static_cast(layerCount); + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1u; + + uint32_t mipLevels = static_cast(getMipmapCount()); + + for (uint32_t i = 1; i < mipLevels; i++) { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + VkImageBlit blit{}; + blit.srcOffsets[0] = { 0, 0, 0 }; + blit.srcOffsets[1] = { getWidth(i - 1), getHeight(i - 1), 1 }; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = static_cast(layerCount); + + blit.dstOffsets[0] = { 0, 0, 0 }; + blit.dstOffsets[1] = { getWidth(i), getHeight(i), 1 }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = static_cast(layerCount); + + vkCmdBlitImage(commandBuffer, + textureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + 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; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +} + void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) { VkBuffer stagingBuffer; VmaAllocation vmaAllocation; @@ -242,7 +329,9 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t s auto commandBuffer = vgfx->getDataTransferCommandBuffer(); - Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, level, 1, slice, 1); + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + level, 1, slice, 1); vkCmdCopyBufferToImage( commandBuffer, @@ -253,7 +342,9 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t s ®ion ); - Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, level, 1, slice, 1); + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + level, 1, slice, 1); vgfx->queueCleanUp([allocator = allocator, stagingBuffer, vmaAllocation]() { vmaDestroyBuffer(allocator, stagingBuffer, vmaAllocation); diff --git a/src/modules/graphics/vulkan/Texture.h b/src/modules/graphics/vulkan/Texture.h index 108fd3a85..8b0ba8ad8 100644 --- a/src/modules/graphics/vulkan/Texture.h +++ b/src/modules/graphics/vulkan/Texture.h @@ -19,7 +19,7 @@ public: virtual bool loadVolatile() override; virtual void unloadVolatile() override; - + void setSamplerState(const SamplerState &s) override; void copyFromBuffer(graphics::Buffer* source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect& rect) override; @@ -30,25 +30,25 @@ public: void uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) override; - void generateMipmapsInternal() override { }; + void generateMipmapsInternal() override; int getMSAA() const override { return 0; }; ptrdiff_t getHandle() const override { return (ptrdiff_t)textureImage; } private: void createTextureImageView(); - void clear(bool white); + void clear(); VkClearColorValue getClearValue(bool white); - graphics::Graphics* gfx; - VkDevice device; - VmaAllocator allocator; + graphics::Graphics* gfx = nullptr; + VkDevice device = VK_NULL_HANDLE; + VmaAllocator allocator = VK_NULL_HANDLE; VkImage textureImage = VK_NULL_HANDLE; - VmaAllocation textureImageAllocation; - VkImageView textureImageView; - VkSampler textureSampler; - const Slices* data; + VmaAllocation textureImageAllocation = VK_NULL_HANDLE; + VkImageView textureImageView = VK_NULL_HANDLE; + VkSampler textureSampler = VK_NULL_HANDLE; + Slices slices; int layerCount = 0; }; } // vulkan