From bf6915ae73330440fba175ad5350c5a6fb8d32bb Mon Sep 17 00:00:00 2001 From: niki Date: Sun, 18 Sep 2022 02:57:29 +0200 Subject: [PATCH] vulkan: garbage collect unused objects --- src/modules/graphics/vulkan/Graphics.cpp | 138 +++++++++++++++++------ src/modules/graphics/vulkan/Graphics.h | 6 +- 2 files changed, 109 insertions(+), 35 deletions(-) diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 8edccc10f..4339aed29 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -56,7 +56,9 @@ static const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; + +constexpr uint32_t USAGES_POLL_INTERVAL = 5000; const char *Graphics::getName() const { @@ -81,8 +83,6 @@ Graphics::Graphics() volkInitializeCustom((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()); vulkanApiVersion = volkGetInstanceVersion(); - - enableValidationLayers = isDebugEnabled(); } Graphics::~Graphics() @@ -452,6 +452,7 @@ void Graphics::present(void *screenshotCallbackdata) updatePendingReadbacks(); updateTemporaryResources(); + frameCounter++; currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; beginFrame(); @@ -532,6 +533,7 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int Vulkan::resetShaderSwitches(); + frameCounter = 0; currentFrame = 0; created = true; drawCalls = 0; @@ -593,6 +595,10 @@ void Graphics::getAPIStats(int &shaderswitches) const void Graphics::unSetMode() { + renderPassUsages.clear(); + framebufferUsages.clear(); + pipelineUsages.clear(); + created = false; vkDeviceWaitIdle(device); Volatile::unloadAll(); @@ -667,30 +673,39 @@ Graphics::RendererInfo Graphics::getRendererInfo() const vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); Graphics::RendererInfo info; + + if (isDebugEnabled()) + { + std::stringstream ss; + ss << "Vulkan( "; + ss << renderPasses.size() << " "; + ss << framebuffers.size() << " "; + ss << graphicsPipelines.size() << " "; + if (optionalDeviceFeatures.extendedDynamicState) + ss << "eds "; + if (optionalDeviceFeatures.memoryRequirements2) + ss << "mr2 "; + if (optionalDeviceFeatures.dedicatedAllocation) + ss << "da "; + if (optionalDeviceFeatures.bufferDeviceAddress) + ss << "bda "; + if (optionalDeviceFeatures.memoryBudget) + ss << "mb "; + if (optionalDeviceFeatures.shaderFloatControls) + ss << "sfc "; + if (optionalDeviceFeatures.spirv14) + ss << "spv14 "; + ss << ")"; + + info.name = ss.str(); + } + else + info.name = "Vulkan"; + info.device = deviceProperties.deviceName; info.vendor = Vulkan::getVendorName(deviceProperties.vendorID); info.version = Vulkan::getVulkanApiVersion(deviceProperties.apiVersion); - std::stringstream ss; - ss << "Vulkan( "; - if (optionalDeviceFeatures.extendedDynamicState) - ss << "eds "; - if (optionalDeviceFeatures.memoryRequirements2) - ss << "mr2 "; - if (optionalDeviceFeatures.dedicatedAllocation) - ss << "da "; - if (optionalDeviceFeatures.bufferDeviceAddress) - ss << "bda "; - if (optionalDeviceFeatures.memoryBudget) - ss << "mb "; - if (optionalDeviceFeatures.shaderFloatControls) - ss << "sfc "; - if (optionalDeviceFeatures.spirv14) - ss << "spv14 "; - ss << ")"; - - info.name = ss.str(); - return info; } @@ -1080,6 +1095,13 @@ void Graphics::beginFrame() { vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + if (frameCounter >= USAGES_POLL_INTERVAL) + { + vkDeviceWaitIdle(device); + cleanupUnusedObjects(); + frameCounter = 0; + } + while (true) { VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); @@ -1271,7 +1293,7 @@ static void checkOptionalInstanceExtensions(OptionalInstanceExtensions &ext) void Graphics::createVulkanInstance() { - if (enableValidationLayers && !checkValidationSupport()) + if (isDebugEnabled() && !checkValidationSupport()) throw love::Exception("validation layers requested, but not available"); VkApplicationInfo appInfo{}; @@ -1312,7 +1334,7 @@ void Graphics::createVulkanInstance() createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); - if (enableValidationLayers) + if (isDebugEnabled()) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); @@ -1602,7 +1624,7 @@ void Graphics::createLogicalDevice() createInfo.enabledExtensionCount = static_cast(enabledExtensions.size()); createInfo.ppEnabledExtensionNames = enabledExtensions.data(); - if (enableValidationLayers) + if (isDebugEnabled()) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); @@ -1677,6 +1699,12 @@ void Graphics::initVMA() allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions; + allocatorCreateInfo.flags |= VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; + if (optionalDeviceFeatures.dedicatedAllocation) + allocatorCreateInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; + if (optionalDeviceFeatures.memoryBudget) + allocatorCreateInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; + if (vmaCreateAllocator(&allocatorCreateInfo, &vmaAllocator) != VK_SUCCESS) throw love::Exception("failed to create vma allocator"); } @@ -2029,15 +2057,20 @@ VkFramebuffer Graphics::createFramebuffer(FramebufferConfiguration &configuratio VkFramebuffer Graphics::getFramebuffer(FramebufferConfiguration &configuration) { + VkFramebuffer framebuffer; + auto it = framebuffers.find(configuration); if (it != framebuffers.end()) - return it->second; + framebuffer = it->second; else { - VkFramebuffer framebuffer = createFramebuffer(configuration); + framebuffer = createFramebuffer(configuration); framebuffers[configuration] = framebuffer; - return framebuffer; - } + } + + framebufferUsages[framebuffer] = true; + + return framebuffer; } void Graphics::createDefaultShaders() @@ -2409,6 +2442,8 @@ void Graphics::startRenderPass() } renderPassState.beginInfo.renderPass = renderPass; + renderPassUsages[renderPass] = true; + auto &framebufferConfiguration = renderPassState.framebufferConfiguration; framebufferConfiguration.staticData.renderPass = renderPass; renderPassState.beginInfo.framebuffer = getFramebuffer(framebufferConfiguration); @@ -2468,6 +2503,38 @@ VkSampler Graphics::createSampler(const SamplerState &samplerState) return sampler; } +template +static void eraseUnusedObjects( + std::unordered_map &objects, + std::unordered_map &usages, + Deleter deleter, + VkDevice device) +{ + std::vector deletionKeys; + + for (const auto &[key, val] : objects) + { + if (!usages[val]) + { + deletionKeys.push_back(key); + usages.erase(val); + deleter(device, val, nullptr); + } + else + usages[val] = false; + } + + for (const auto &key : deletionKeys) + objects.erase(key); +} + +void Graphics::cleanupUnusedObjects() +{ + eraseUnusedObjects(renderPasses, renderPassUsages, vkDestroyRenderPass, device); + eraseUnusedObjects(framebuffers, framebufferUsages, vkDestroyFramebuffer, device); + eraseUnusedObjects(graphicsPipelines, pipelineUsages, vkDestroyPipeline, device); +} + void Graphics::setComputeShader(Shader *shader) { computeShader = shader; @@ -2653,6 +2720,7 @@ void Graphics::ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration { vkCmdBindPipeline(commandBuffers.at(currentFrame), VK_PIPELINE_BIND_POINT_GRAPHICS, it->second); renderPassState.pipeline = it->second; + pipelineUsages[it->second] = true; } } else @@ -2661,6 +2729,7 @@ void Graphics::ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration graphicsPipelines.insert({configuration, pipeline}); vkCmdBindPipeline(commandBuffers.at(currentFrame), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); renderPassState.pipeline = pipeline; + pipelineUsages[pipeline] = true; } } @@ -2887,8 +2956,12 @@ void Graphics::cleanup() for (const auto &[key, val] : renderPasses) vkDestroyRenderPass(device, val, nullptr); + renderPasses.clear(); - // fixme: maybe we should clean up some pipelines if they haven't been used in a while. + for (const auto &[key, val] : framebuffers) + vkDestroyFramebuffer(device, val, nullptr); + framebuffers.clear(); + for (auto const &p : graphicsPipelines) vkDestroyPipeline(device, p.second, nullptr); graphicsPipelines.clear(); @@ -2913,9 +2986,6 @@ void Graphics::cleanupSwapChain() vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation); vkDestroyImageView(device, depthImageView, nullptr); vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation); - for (const auto &[key, val] : framebuffers) - vkDestroyFramebuffer(device, val, nullptr); - framebuffers.clear(); for (const auto &swapChainImageView : swapChainImageViews) vkDestroyImageView(device, swapChainImageView, nullptr); swapChainImageViews.clear(); diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 1042d31b0..e95d2d161 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -389,9 +389,9 @@ private: void startRenderPass(); void endRenderPass(); VkSampler createSampler(const SamplerState &samplerState); + void cleanupUnusedObjects(); uint32_t vulkanApiVersion = VK_VERSION_1_0; - bool enableValidationLayers = false; VkInstance instance = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; int requestedMsaa = 0; @@ -420,6 +420,9 @@ private: std::unordered_map renderPasses; std::unordered_map framebuffers; std::unordered_map graphicsPipelines; + std::unordered_map renderPassUsages; + std::unordered_map framebufferUsages; + std::unordered_map pipelineUsages; std::unordered_map samplers; VkCommandPool commandPool = VK_NULL_HANDLE; std::vector commandBuffers; @@ -430,6 +433,7 @@ private: std::vector imagesInFlight; VkDeviceSize minUniformBufferOffsetAlignment = 0; bool imageRequested = false; + uint32_t frameCounter = 0; size_t currentFrame = 0; uint32_t imageIndex = 0; bool framebufferResized = false;