From 4251b9df33a128348505449883714d1a9abda10e Mon Sep 17 00:00:00 2001 From: niki Date: Sat, 12 Oct 2024 19:22:05 +0200 Subject: [PATCH] vulkan: implement defragmentation --- src/modules/graphics/vulkan/Buffer.cpp | 60 ++++++-- src/modules/graphics/vulkan/Buffer.h | 7 +- src/modules/graphics/vulkan/Graphics.cpp | 137 ++++++++++++++++++- src/modules/graphics/vulkan/Graphics.h | 11 +- src/modules/graphics/vulkan/Shader.cpp | 82 +++++++++++ src/modules/graphics/vulkan/Shader.h | 1 + src/modules/graphics/vulkan/StreamBuffer.cpp | 27 +++- src/modules/graphics/vulkan/StreamBuffer.h | 5 + src/modules/graphics/vulkan/Texture.cpp | 128 ++++++++++++----- src/modules/graphics/vulkan/Texture.h | 9 +- src/modules/graphics/vulkan/Vulkan.h | 15 ++ 11 files changed, 429 insertions(+), 53 deletions(-) diff --git a/src/modules/graphics/vulkan/Buffer.cpp b/src/modules/graphics/vulkan/Buffer.cpp index a09b2e889..d3f5cfcd0 100644 --- a/src/modules/graphics/vulkan/Buffer.cpp +++ b/src/modules/graphics/vulkan/Buffer.cpp @@ -61,6 +61,7 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st , initialData(data) , vgfx(dynamic_cast(gfx)) , usageFlags(settings.usageFlags) + , graphicsResource(GRAPHICSRESOURCETYPE_BUFFER, this) { // All buffers can be copied to and from. barrierDstAccessFlags = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; @@ -99,7 +100,7 @@ bool Buffer::loadVolatile() { allocator = vgfx->getVmaAllocator(); - VkBufferCreateInfo bufferInfo{}; + memset(&bufferInfo, 0, sizeof(bufferInfo)); bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = getSize(); bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | getVulkanUsageFlags(usageFlags); @@ -113,6 +114,8 @@ bool Buffer::loadVolatile() if (result != VK_SUCCESS) throw love::Exception("failed to create buffer"); + vmaSetAllocationUserData(allocator, allocation, &graphicsResource); + if (zeroInitialize) { auto cmd = vgfx->getCommandBufferForDataTransfer(); @@ -124,16 +127,7 @@ bool Buffer::loadVolatile() fill(0, size, initialData); if (usageFlags & BUFFERUSAGEFLAG_TEXEL) - { - VkBufferViewCreateInfo bufferViewInfo{}; - bufferViewInfo.buffer = buffer; - bufferViewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; - bufferViewInfo.format = Vulkan::getVulkanVertexFormat(getDataMember(0).decl.format); - bufferViewInfo.range = VK_WHOLE_SIZE; - - if (vkCreateBufferView(vgfx->getDevice(), &bufferViewInfo, nullptr, &bufferView) != VK_SUCCESS) - throw love::Exception("failed to create texel buffer view"); - } + createBufferView(); VkMemoryPropertyFlags memoryProperties; vmaGetAllocationMemoryProperties(allocator, allocation, &memoryProperties); @@ -154,6 +148,8 @@ bool Buffer::loadVolatile() vkSetDebugUtilsObjectNameEXT(device, &nameInfo); } + vgfx->requestDefragmentation(); + return true; } @@ -162,6 +158,8 @@ void Buffer::unloadVolatile() if (buffer == VK_NULL_HANDLE) return; + vmaSetAllocationUserData(allocator, allocation, nullptr); + auto device = vgfx->getDevice(); vgfx->queueCleanUp( @@ -170,6 +168,7 @@ void Buffer::unloadVolatile() vmaDestroyBuffer(allocator, buffer, allocation); if (bufferView) vkDestroyBufferView(device, bufferView, nullptr); + return true; }); buffer = VK_NULL_HANDLE; @@ -276,6 +275,7 @@ bool Buffer::fill(size_t offset, size_t size, const void *data) vgfx->queueCleanUp([allocator = allocator, fillBuffer = fillBuffer, fillAllocation = fillAllocation]() { vmaDestroyBuffer(allocator, fillBuffer, fillAllocation); + return true; }); return true; @@ -302,6 +302,7 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize) vgfx->queueCleanUp([allocator = allocator, stagingBuffer = stagingBuffer, stagingAllocation = stagingAllocation]() { vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + return true; }); } } @@ -337,6 +338,43 @@ void Buffer::postGPUWriteBarrier(VkCommandBuffer cmd) vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, barrierDstStageFlags, 0, 1, &barrier, 0, nullptr, 0, nullptr); } +void Buffer::createBufferView() +{ + VkBufferViewCreateInfo bufferViewInfo{}; + bufferViewInfo.buffer = buffer; + bufferViewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; + bufferViewInfo.format = Vulkan::getVulkanVertexFormat(getDataMember(0).decl.format); + bufferViewInfo.range = VK_WHOLE_SIZE; + + if (vkCreateBufferView(vgfx->getDevice(), &bufferViewInfo, nullptr, &bufferView) != VK_SUCCESS) + throw love::Exception("failed to create texel buffer view"); +} + +VkBuffer Buffer::performDefragmentationMove(VkCommandBuffer commandBuffer, VmaAllocator allocator, VmaAllocation dstAllocation) +{ + VkDevice device = vgfx->getDevice(); + VkBuffer oldBuffer = buffer; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) + throw love::Exception("could not recreate stream buffer"); + + if (vmaBindBufferMemory(allocator, dstAllocation, buffer)) + throw love::Exception("could not bind the buffer memory"); + + VkBufferCopy region{}; + region.size = bufferInfo.size; + + vkCmdCopyBuffer(commandBuffer, oldBuffer, buffer, 1, ®ion); + + if (usageFlags & BUFFERUSAGEFLAG_TEXEL) + { + vkDestroyBufferView(device, bufferView, nullptr); + createBufferView(); + } + + return oldBuffer; +} + } // vulkan } // graphics } // love diff --git a/src/modules/graphics/vulkan/Buffer.h b/src/modules/graphics/vulkan/Buffer.h index f1d9c23c6..b579da72e 100644 --- a/src/modules/graphics/vulkan/Buffer.h +++ b/src/modules/graphics/vulkan/Buffer.h @@ -26,6 +26,7 @@ #include "graphics/Volatile.h" #include "VulkanWrapper.h" +#include "Vulkan.h" namespace love @@ -60,12 +61,15 @@ class Buffer final VkAccessFlags getBarrierDstAccessFlags() const { return barrierDstAccessFlags; } VkPipelineStageFlags getBarrierDstStageFlags() const { return barrierDstStageFlags; } -private: + VkBuffer performDefragmentationMove(VkCommandBuffer commandBuffer, VmaAllocator allocator, VmaAllocation dstAllocation); +private: + void createBufferView(); void clearInternal(size_t offset, size_t size) override; bool zeroInitialize; const void *initialData; + VkBufferCreateInfo bufferInfo; VkBuffer buffer = VK_NULL_HANDLE; VkBuffer stagingBuffer = VK_NULL_HANDLE; VkBufferView bufferView = VK_NULL_HANDLE; @@ -78,6 +82,7 @@ class Buffer final BufferUsageFlags usageFlags; Range mappedRange; bool coherent; + GraphicsResource graphicsResource; VkAccessFlags barrierDstAccessFlags = 0; VkPipelineStageFlags barrierDstStageFlags = 0; diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index c07433546..c1f92284a 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -463,7 +463,7 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS) throw love::Exception("failed to submit draw command buffer"); - if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART || screenshotBuffer != VK_NULL_HANDLE) + if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART || screenshotBuffer != VK_NULL_HANDLE || defragmentationRequested) { vkQueueWaitIdle(graphicsQueue); @@ -532,6 +532,9 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback pendingScreenshotCallbacks.clear(); } + if (defragmentationRequested) + runDefragmentation(); + if (submitMode == SUBMIT_RESTART) startRecordingGraphicsCommands(); } @@ -1372,8 +1375,11 @@ void Graphics::beginFrame() readbackCallback(); readbackCallbacks.at(currentFrame).clear(); - for (auto &cleanUpFn : cleanUpFunctions.at(currentFrame)) - cleanUpFn(); + for (auto& cleanUpFn : cleanUpFunctions.at(currentFrame)) + { + if (cleanUpFn()) + defragmentationRequested = true; + } cleanUpFunctions.at(currentFrame).clear(); startRecordingGraphicsCommands(); @@ -1471,7 +1477,7 @@ VkCommandBuffer Graphics::getCommandBufferForDataTransfer() return commandBuffers.at(currentFrame); } -void Graphics::queueCleanUp(std::function cleanUp) +void Graphics::queueCleanUp(std::function cleanUp) { cleanUpFunctions.at(currentFrame).push_back(cleanUp); } @@ -2759,6 +2765,107 @@ void Graphics::requestSwapchainRecreation() } } +void Graphics::runDefragmentation() +{ + VmaDefragmentationInfo defragInfo{}; + defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT; + + VmaDefragmentationContext defragCtx; + if (vmaBeginDefragmentation(vmaAllocator, &defragInfo, &defragCtx) != VK_SUCCESS) + throw love::Exception("could not create defragmentation context"); + + VkResult res; + for (;;) + { + VmaDefragmentationPassMoveInfo pass; + res = vmaBeginDefragmentationPass(vmaAllocator, defragCtx, &pass); + if (res == VK_SUCCESS) + break; + else if (res != VK_INCOMPLETE) + throw love::Exception("could not begin defragmentation pass"); + + std::cout << "defragmentation pass, #moves = " << pass.moveCount << std::endl; + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + if (vkBeginCommandBuffer(defragmentationCommandBuffer, &beginInfo) != VK_SUCCESS) + throw love::Exception("could not start defragmentation command buffer"); + + std::vector buffers; + std::vector images; + + std::vector> postMoves; + + for (uint32_t i = 0; i < pass.moveCount; i++) + { + VmaAllocationInfo srcAllocInfo; + vmaGetAllocationInfo(vmaAllocator, pass.pMoves[i].srcAllocation, &srcAllocInfo); + if (srcAllocInfo.pUserData == nullptr) + pass.pMoves[i].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE; + else + { + GraphicsResource *resource = (GraphicsResource*)srcAllocInfo.pUserData; + switch (resource->type) { + case GRAPHICSRESOURCETYPE_STREAMBUFFER: { + auto streamBuffer = static_cast(resource->object); + buffers.push_back(streamBuffer->performDefragmentationMove(defragmentationCommandBuffer, vmaAllocator, pass.pMoves[i].dstTmpAllocation)); + break; + } + case GRAPHICSRESOURCETYPE_TEXTURE: { + auto texture = static_cast(resource->object); + images.push_back(texture->performDefragmentationMove(defragmentationCommandBuffer, vmaAllocator, pass.pMoves[i].dstTmpAllocation)); + break; + } + case GRAPHICSRESOURCETYPE_BUFFER: { + auto buffer = static_cast(resource->object); + buffers.push_back(buffer->performDefragmentationMove(defragmentationCommandBuffer, vmaAllocator, pass.pMoves[i].dstTmpAllocation)); + break; + } + default: + throw love::Exception("unknown graphics resource type"); + } + } + } + + if (vkEndCommandBuffer(defragmentationCommandBuffer) != VK_SUCCESS) + throw love::Exception("could not end defragmentation command buffer"); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &defragmentationCommandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, defragmentationFence) != VK_SUCCESS) + throw love::Exception("could not submit defragmentation queue"); + + if (vkWaitForFences(device, 1, &defragmentationFence, VK_TRUE, UINT64_MAX) != VK_SUCCESS) + throw love::Exception("could not wait for defragmentation fence"); + + if (vkResetFences(device, 1, &defragmentationFence) != VK_SUCCESS) + throw love::Exception("could not reset defragmentation fence"); + + for (const auto& postMove : postMoves) + postMove(); + + for (VkBuffer buffer : buffers) + vkDestroyBuffer(device, buffer, nullptr); + for (VkImage image : images) + vkDestroyImage(device, image, nullptr); + + res = vmaEndDefragmentationPass(vmaAllocator, defragCtx, &pass); + if (res == VK_SUCCESS) + break; + else if (res != VK_INCOMPLETE) + throw love::Exception("could not end defragmentation pass"); + } + + vmaEndDefragmentation(vmaAllocator, defragCtx, nullptr); + + defragmentationCount++; +} + VkSampler Graphics::getCachedSampler(const SamplerState &samplerState) { auto samplerkey = samplerState.toKey(); @@ -3002,6 +3109,11 @@ void Graphics::mapLocalUniformData(void *data, size_t size, VkDescriptorBufferIn localUniformBuffer->markUsed(alignedSize); } +void Graphics::requestDefragmentation() +{ + defragmentationRequested = true; +} + void Graphics::createColorResources() { if (msaaSamples & VK_SAMPLE_COUNT_1_BIT) @@ -3157,6 +3269,15 @@ void Graphics::createCommandBuffers() if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) throw love::Exception("failed to allocate command buffers"); + + VkCommandBufferAllocateInfo defragmentationAllocInfo{}; + defragmentationAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + defragmentationAllocInfo.commandPool = commandPool; + defragmentationAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + defragmentationAllocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &defragmentationAllocInfo, &defragmentationCommandBuffer) != VK_SUCCESS) + throw love::Exception("failed to allocate command buffers"); } void Graphics::createSyncObjects() @@ -3178,6 +3299,12 @@ void Graphics::createSyncObjects() vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores.at(i)) != VK_SUCCESS || vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences.at(i)) != VK_SUCCESS) throw love::Exception("failed to create synchronization objects for a frame!"); + + VkFenceCreateInfo defragmentationFenceInfo{}; + defragmentationFenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + if (vkCreateFence(device, &defragmentationFenceInfo, nullptr, &defragmentationFence) != VK_SUCCESS) + throw love::Exception("could not create defragmentation fence"); } void Graphics::cleanup() @@ -3194,8 +3321,10 @@ void Graphics::cleanup() vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); vkDestroyFence(device, inFlightFences[i], nullptr); } + vkDestroyFence(device, defragmentationFence, nullptr); vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, commandBuffers.data()); + vkFreeCommandBuffers(device, commandPool, 1, &defragmentationCommandBuffer); for (auto const &p : samplers) vkDestroySampler(device, p.second, nullptr); diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index f2b714bca..9b4668b53 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -266,7 +266,7 @@ class Graphics final : public love::graphics::Graphics VkDevice getDevice() const; VmaAllocator getVmaAllocator() const; VkCommandBuffer getCommandBufferForDataTransfer(); - void queueCleanUp(std::function cleanUp); + void queueCleanUp(std::function cleanUp); void addReadbackCallback(std::function callback); void submitGpuCommands(SubmitMode, void *screenshotCallbackData = nullptr); VkSampler getCachedSampler(const SamplerState &sampler); @@ -277,9 +277,11 @@ class Graphics final : public love::graphics::Graphics void setVsync(int vsync); int getVsync() const; void mapLocalUniformData(void *data, size_t size, VkDescriptorBufferInfo &bufferInfo); + void requestDefragmentation(); VkPipeline createGraphicsPipeline(Shader *shader, const GraphicsPipelineConfigurationCore &configuration, const GraphicsPipelineConfigurationNoDynamicState *noDynamicStateConfiguration); + unsigned long getDefragmentationCount() const { return defragmentationCount; } uint32 getDeviceApiVersion() const { return deviceApiVersion; } protected: @@ -345,6 +347,7 @@ class Graphics final : public love::graphics::Graphics VkSampler createSampler(const SamplerState &sampler); void cleanupUnusedObjects(); void requestSwapchainRecreation(); + void runDefragmentation(); VkInstance instance = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -383,12 +386,16 @@ class Graphics final : public love::graphics::Graphics std::vector renderFinishedSemaphores; std::vector inFlightFences; std::vector imagesInFlight; + VkCommandBuffer defragmentationCommandBuffer; + VkFence defragmentationFence; int vsync = 1; VkDeviceSize minUniformBufferOffsetAlignment = 0; bool imageRequested = false; uint32_t frameCounter = 0; size_t currentFrame = 0; uint32_t imageIndex = 0; + unsigned long defragmentationCount = 0; + bool defragmentationRequested = false; bool swapChainRecreationRequested = false; bool transitionColorDepthLayouts = false; VmaAllocator vmaAllocator = VK_NULL_HANDLE; @@ -396,7 +403,7 @@ class Graphics final : public love::graphics::Graphics StrongRef localUniformBuffer; // functions that need to be called to cleanup objects that were needed for rendering a frame. // We need a vector for each frame in flight. - std::vector>> cleanUpFunctions; + std::vector>> cleanUpFunctions; std::vector>> readbackCallbacks; std::set> usedShadersInFrame; RenderpassState renderPassState; diff --git a/src/modules/graphics/vulkan/Shader.cpp b/src/modules/graphics/vulkan/Shader.cpp index 34bfe5c85..d7a70b4fd 100644 --- a/src/modules/graphics/vulkan/Shader.cpp +++ b/src/modules/graphics/vulkan/Shader.cpp @@ -218,6 +218,7 @@ void Shader::unloadVolatile() vkDestroyPipeline(device, kvp.second, nullptr); for (const auto &kvp : graphicsPipelinesFull) vkDestroyPipeline(device, kvp.second, nullptr); + return false; }); shaderModules.clear(); @@ -304,6 +305,87 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind imageInfo.sampler = sampler; resourceDescriptorsDirty = true; } + VkImageView view = vkTexture != nullptr ? (VkImageView)vkTexture->getRenderTargetHandle() : VK_NULL_HANDLE; + if (view != imageInfo.imageView) + { + imageInfo.imageView = view; + resourceDescriptorsDirty = true; + } + } + } + + if (lastDefragmentationCount != vgfx->getDefragmentationCount()) + { + lastDefragmentationCount = vgfx->getDefragmentationCount(); + + for (const auto& u : reflection.storageTextures) + { + const auto& info = u.second; + if (!info.active) + continue; + + for (int i = 0; i < info.count; i++) + { + auto vkTexture = dynamic_cast(activeTextures[info.resourceIndex + i]); + + if (vkTexture == nullptr) + throw love::Exception("uniform variable %s is not set", info.name.c_str()); + + VkDescriptorImageInfo& imageInfo = descriptorImages[info.bindingStartIndex + i]; + + VkImageView view = vkTexture != nullptr ? (VkImageView)vkTexture->getRenderTargetHandle() : VK_NULL_HANDLE; + if (view != imageInfo.imageView) + { + imageInfo.imageView = view; + resourceDescriptorsDirty = true; + } + } + } + + for (const auto& u : reflection.texelBuffers) + { + const auto& info = u.second; + if (!info.active) + continue; + + for (int i = 0; i < info.count; i++) + { + auto vkBuffer = dynamic_cast(activeBuffers[info.resourceIndex + i]); + + if (vkBuffer == nullptr) + throw love::Exception("uniform variable %s is not set", info.name.c_str()); + + VkDescriptorBufferInfo bufferInfo = descriptorBuffers[info.bindingStartIndex + i]; + VkBufferView bufferView = (VkBufferView)vkBuffer->getTexelBufferHandle(); + if (descriptorBufferViews[info.bindingStartIndex + i] != bufferView) + { + descriptorBufferViews[info.bindingStartIndex + i] = bufferView; + resourceDescriptorsDirty = true; + } + } + } + + for (const auto& u : reflection.storageBuffers) + { + const auto& info = u.second; + if (!info.active) + continue; + + for (int i = 0; i < info.count; i++) + { + auto vkBuffer = dynamic_cast(activeBuffers[info.resourceIndex + i]); + + if (vkBuffer == nullptr) + throw love::Exception("uniform variable %s is not set", info.name.c_str()); + + VkDescriptorBufferInfo bufferInfo = descriptorBuffers[info.bindingStartIndex + i]; + VkBuffer buffer = (VkBuffer)vkBuffer->getHandle(); + if (bufferInfo.buffer != buffer) + { + bufferInfo.buffer = buffer; + resourceDescriptorsDirty = true; + } + } } } diff --git a/src/modules/graphics/vulkan/Shader.h b/src/modules/graphics/vulkan/Shader.h index b395c8c31..a3b8b549c 100644 --- a/src/modules/graphics/vulkan/Shader.h +++ b/src/modules/graphics/vulkan/Shader.h @@ -229,6 +229,7 @@ class Shader final uint32_t currentFrame = 0; uint32_t currentDescriptorPool = 0; + unsigned long lastDefragmentationCount = 0; }; } diff --git a/src/modules/graphics/vulkan/StreamBuffer.cpp b/src/modules/graphics/vulkan/StreamBuffer.cpp index 517a69eaa..38cf2d384 100644 --- a/src/modules/graphics/vulkan/StreamBuffer.cpp +++ b/src/modules/graphics/vulkan/StreamBuffer.cpp @@ -1,4 +1,5 @@ /** +* * Copyright (c) 2006-2024 LOVE Development Team * * This software is provided 'as-is', without any express or implied @@ -44,6 +45,7 @@ static VkBufferUsageFlags getUsageFlags(BufferUsage mode) StreamBuffer::StreamBuffer(graphics::Graphics *gfx, BufferUsage mode, size_t size) : love::graphics::StreamBuffer(mode, size) , vgfx(dynamic_cast(gfx)) + , graphicsResource(GRAPHICSRESOURCETYPE_STREAMBUFFER, this) { loadVolatile(); } @@ -52,9 +54,9 @@ bool StreamBuffer::loadVolatile() { allocator = vgfx->getVmaAllocator(); - VkBufferCreateInfo bufferInfo{}; + memset(&bufferInfo, 0, sizeof(bufferInfo)); bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = getSize() * MAX_FRAMES_IN_FLIGHT; // TODO: Is this sufficient or should it be +1? + bufferInfo.size = getSize() * MAX_FRAMES_IN_FLIGHT; // TODO: Is this sufficient or should it be +1? bufferInfo.usage = getUsageFlags(mode); bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; @@ -65,6 +67,8 @@ bool StreamBuffer::loadVolatile() if (vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, &allocInfo) != VK_SUCCESS) throw love::Exception("Cannot create stream buffer: out of graphics memory."); + vmaSetAllocationUserData(allocator, allocation, &graphicsResource); + VkMemoryPropertyFlags properties; vmaGetAllocationMemoryProperties(allocator, allocation, &properties); if (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) @@ -80,8 +84,11 @@ void StreamBuffer::unloadVolatile() if (buffer == VK_NULL_HANDLE) return; + vmaSetAllocationUserData(allocator, allocation, nullptr); + vgfx->queueCleanUp([allocator=allocator, buffer=buffer, allocation=allocation](){ vmaDestroyBuffer(allocator, buffer, allocation); + return true; }); buffer = VK_NULL_HANDLE; } @@ -132,6 +139,22 @@ void StreamBuffer::nextFrame() frameGPUReadOffset = 0; } +VkBuffer StreamBuffer::performDefragmentationMove(VkCommandBuffer commandBuffer, VmaAllocator allocator, VmaAllocation dstAllocation) +{ + VkDevice device = vgfx->getDevice(); + VkBuffer oldBuffer = buffer; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) + throw love::Exception("could not recreate stream buffer"); + + if (vmaBindBufferMemory(allocator, dstAllocation, buffer)) + throw love::Exception("could not bind the buffer memory"); + + // no copy here, stream buffers don't need to preserve the data. + + return oldBuffer; +} + } // vulkan } // graphics } // love diff --git a/src/modules/graphics/vulkan/StreamBuffer.h b/src/modules/graphics/vulkan/StreamBuffer.h index c32533d13..2158660c7 100644 --- a/src/modules/graphics/vulkan/StreamBuffer.h +++ b/src/modules/graphics/vulkan/StreamBuffer.h @@ -25,6 +25,7 @@ #include "graphics/Graphics.h" #include "VulkanWrapper.h" +#include "Vulkan.h" namespace love { @@ -56,14 +57,18 @@ class StreamBuffer final ptrdiff_t getHandle() const override; + VkBuffer performDefragmentationMove(VkCommandBuffer commandBuffer, VmaAllocator allocator, VmaAllocation dstAllocation); + private: Graphics *vgfx = nullptr; + VkBufferCreateInfo bufferInfo; VmaAllocator allocator; VmaAllocation allocation; VmaAllocationInfo allocInfo; VkBuffer buffer = VK_NULL_HANDLE; int frameIndex = 0; bool coherent; + GraphicsResource graphicsResource; }; diff --git a/src/modules/graphics/vulkan/Texture.cpp b/src/modules/graphics/vulkan/Texture.cpp index 2f64905c0..67726864d 100644 --- a/src/modules/graphics/vulkan/Texture.cpp +++ b/src/modules/graphics/vulkan/Texture.cpp @@ -37,6 +37,7 @@ Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const , vgfx(dynamic_cast(gfx)) , slices(settings.type) , imageAspect(0) + , graphicsResource(GRAPHICSRESOURCETYPE_TEXTURE, this) { if (data) slices = *data; @@ -53,6 +54,7 @@ Texture::Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, c , vgfx(dynamic_cast(gfx)) , slices(viewsettings.type.get(base->getTextureType())) , imageAspect(0) + , graphicsResource(GRAPHICSRESOURCETYPE_TEXTURE, this) { loadVolatile(); } @@ -118,7 +120,7 @@ bool Texture::loadVolatile() msaaSamples = vgfx->getMsaaCount(requestedMSAA); - VkImageCreateInfo imageInfo{}; + memset(&imageInfo, 0, sizeof(imageInfo)); imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.flags = createFlags; imageInfo.imageType = Vulkan::getImageType(getTextureType()); @@ -134,7 +136,7 @@ bool Texture::loadVolatile() imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.samples = msaaSamples; - VkImageFormatListCreateInfo viewFormatsInfo{}; + memset(&viewFormatsInfo, 0, sizeof(viewFormatsInfo)); viewFormatsInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO; if (!vkviewformats.empty() && vgfx->getDeviceApiVersion() >= VK_API_VERSION_1_2) @@ -156,6 +158,8 @@ bool Texture::loadVolatile() if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS) throw love::Exception("failed to create image"); + vmaSetAllocationUserData(allocator, textureImageAllocation, &graphicsResource); + auto commandBuffer = vgfx->getCommandBufferForDataTransfer(); if (computeWrite) @@ -213,36 +217,7 @@ bool Texture::loadVolatile() generateMipmaps(); if (renderTarget) - { - renderTargetImageViews.resize(getMipmapCount()); - for (int mip = 0; mip < getMipmapCount(); mip++) - { - renderTargetImageViews.at(mip).resize(layerCount); - - for (int slice = 0; slice < layerCount; slice++) - { - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = textureImage; - viewInfo.viewType = Vulkan::getImageViewType(getTextureType()); - if (viewInfo.viewType == VK_IMAGE_VIEW_TYPE_CUBE) - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = vulkanFormat.internalFormat; - viewInfo.subresourceRange.aspectMask = imageAspect; - viewInfo.subresourceRange.baseMipLevel = mip + rootView.startMipmap; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = slice + rootView.startLayer; - viewInfo.subresourceRange.layerCount = 1; - viewInfo.components.r = vulkanFormat.swizzleR; - viewInfo.components.g = vulkanFormat.swizzleG; - viewInfo.components.b = vulkanFormat.swizzleB; - viewInfo.components.a = vulkanFormat.swizzleA; - - if (vkCreateImageView(device, &viewInfo, nullptr, &renderTargetImageViews.at(mip).at(slice)) != VK_SUCCESS) - throw love::Exception("could not create render target image view"); - } - } - } + createRendertargetViews(); if (!debugName.empty()) { @@ -275,6 +250,8 @@ void Texture::unloadVolatile() if (textureImage == VK_NULL_HANDLE) return; + vmaSetAllocationUserData(allocator, textureImageAllocation, nullptr); + vgfx->queueCleanUp([ device = device, textureImageView = textureImageView, @@ -288,6 +265,7 @@ void Texture::unloadVolatile() for (const auto &views : textureImageViews) for (const auto &view : views) vkDestroyImageView(device, view, nullptr); + return true; }); textureImage = VK_NULL_HANDLE; @@ -342,6 +320,41 @@ VkImageLayout Texture::getImageLayout() const return imageLayout; } + +void Texture::createRendertargetViews() +{ + auto vulkanFormat = Vulkan::getTextureFormat(getPixelFormat()); + + renderTargetImageViews.resize(getMipmapCount()); + for (int mip = 0; mip < getMipmapCount(); mip++) + { + renderTargetImageViews.at(mip).resize(layerCount); + + for (int slice = 0; slice < layerCount; slice++) + { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = textureImage; + viewInfo.viewType = Vulkan::getImageViewType(getTextureType()); + if (viewInfo.viewType == VK_IMAGE_VIEW_TYPE_CUBE) + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = vulkanFormat.internalFormat; + viewInfo.subresourceRange.aspectMask = imageAspect; + viewInfo.subresourceRange.baseMipLevel = mip + rootView.startMipmap; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = slice + rootView.startLayer; + viewInfo.subresourceRange.layerCount = 1; + viewInfo.components.r = vulkanFormat.swizzleR; + viewInfo.components.g = vulkanFormat.swizzleG; + viewInfo.components.b = vulkanFormat.swizzleB; + viewInfo.components.a = vulkanFormat.swizzleA; + + if (vkCreateImageView(device, &viewInfo, nullptr, &renderTargetImageViews.at(mip).at(slice)) != VK_SUCCESS) + throw love::Exception("could not create render target image view"); + } + } +} + void Texture::createTextureImageView() { auto vulkanFormat = Vulkan::getTextureFormat(format); @@ -618,6 +631,7 @@ void Texture::uploadByteData(const void *data, size_t size, int level, int slice vgfx->queueCleanUp([allocator = allocator, stagingBuffer, vmaAllocation]() { vmaDestroyBuffer(allocator, stagingBuffer, vmaAllocation); + return true; }); } @@ -689,6 +703,56 @@ void Texture::copyToBuffer(graphics::Buffer *dest, int slice, int mipmap, const ((Buffer *)dest)->postGPUWriteBarrier(commandBuffer); } +VkImage Texture::performDefragmentationMove(VkCommandBuffer commandBuffer, VmaAllocator allocator, VmaAllocation dstAllocation) +{ + VkImage oldImage = textureImage; + if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) + throw love::Exception("could not re-create texture"); + + if (vmaBindImageMemory(allocator, dstAllocation, textureImage) != VK_SUCCESS) + throw love::Exception("could not rebind texture memory"); + + Vulkan::cmdTransitionImageLayout(commandBuffer, oldImage, getPixelFormat(), isRenderTarget(), imageLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, getPixelFormat(), isRenderTarget(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + for (uint32_t mipLevel = rootView.startMipmap; mipLevel < mipmapCount; mipLevel++) + { + VkImageSubresourceLayers subResource{}; + subResource.aspectMask = imageAspect; + subResource.baseArrayLayer = rootView.startLayer; + subResource.layerCount = layerCount - rootView.startLayer; + subResource.mipLevel = mipLevel; + + VkImageCopy region{}; + region.extent.width = pixelWidth; + region.extent.height = pixelHeight; + region.extent.depth = depth; + region.srcSubresource = subResource; + region.dstSubresource = subResource; + + vkCmdCopyImage(commandBuffer, oldImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + } + + Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, getPixelFormat(), isRenderTarget(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageLayout); + + Vulkan::cmdTransitionImageLayout(commandBuffer, oldImage, getPixelFormat(), isRenderTarget(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, imageLayout); + + vkDestroyImageView(device, textureImageView, nullptr); + createTextureImageView(); + if (isRenderTarget()) + { + for (const auto& views : renderTargetImageViews) + { + for (const auto view : views) + vkDestroyImageView(device, view, nullptr); + } + createRendertargetViews(); + } + + return oldImage; +} + } // vulkan } // graphics } // love diff --git a/src/modules/graphics/vulkan/Texture.h b/src/modules/graphics/vulkan/Texture.h index e9dac8be7..9d6e1009c 100644 --- a/src/modules/graphics/vulkan/Texture.h +++ b/src/modules/graphics/vulkan/Texture.h @@ -24,7 +24,9 @@ #include "graphics/Volatile.h" #include "VulkanWrapper.h" +#include "Vulkan.h" +#include namespace love { @@ -40,7 +42,6 @@ class Texture final , public Volatile { public: - Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data); Texture(love::graphics::Graphics *gfx, love::graphics::Texture *base, const Texture::ViewSettings &viewsettings); virtual ~Texture(); @@ -70,7 +71,10 @@ class Texture final static VkClearColorValue getClearColor(love::graphics::Texture *texture, const ColorD &color); + VkImage performDefragmentationMove(VkCommandBuffer commandBuffer, VmaAllocator allocator, VmaAllocation dstAllocation); + private: + void createRendertargetViews(); void createTextureImageView(); void clear(); @@ -78,6 +82,8 @@ class Texture final VkDevice device = VK_NULL_HANDLE; VkImageAspectFlags imageAspect; VmaAllocator allocator = VK_NULL_HANDLE; + VkImageCreateInfo imageInfo; + VkImageFormatListCreateInfo viewFormatsInfo; VkImage textureImage = VK_NULL_HANDLE; VkImageLayout imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; VmaAllocation textureImageAllocation = VK_NULL_HANDLE; @@ -87,6 +93,7 @@ class Texture final Slices slices; int layerCount = 0; VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; + GraphicsResource graphicsResource; }; } // vulkan diff --git a/src/modules/graphics/vulkan/Vulkan.h b/src/modules/graphics/vulkan/Vulkan.h index 8b182f61e..7e351c56c 100644 --- a/src/modules/graphics/vulkan/Vulkan.h +++ b/src/modules/graphics/vulkan/Vulkan.h @@ -49,6 +49,21 @@ struct TextureFormat VkComponentSwizzle swizzleA = VK_COMPONENT_SWIZZLE_IDENTITY; }; +enum GraphicsResourceType { + GRAPHICSRESOURCETYPE_BUFFER, + GRAPHICSRESOURCETYPE_STREAMBUFFER, + GRAPHICSRESOURCETYPE_TEXTURE, + GRAPHICSRESOURCETYPE_MAX_ENUM, +}; + +struct GraphicsResource { + GraphicsResourceType type; + void *object; + + GraphicsResource(GraphicsResourceType type, void* obj) + : type(type), object(obj) {} +}; + constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; class Vulkan