瀏覽代碼

vulkan: clean up framebuffers when textures they reference are deleted

Fixes corruption/crashing in some situations if a new texture's image view has the same pointer as a previously deleted texture's image view.
Sasha Szpakowski 8 月之前
父節點
當前提交
b87aed937c

+ 53 - 40
src/modules/graphics/vulkan/Graphics.cpp

@@ -59,8 +59,6 @@ static const std::vector<const char*> deviceExtensions = {
 	VK_KHR_SWAPCHAIN_EXTENSION_NAME,
 };
 
-constexpr uint32_t USAGES_POLL_INTERVAL = 5000;
-
 constexpr int DEFAULT_VERTEX_BUFFER_BINDING = 0;
 constexpr int VERTEX_BUFFER_BINDING_START = 1;
 
@@ -577,7 +575,6 @@ void Graphics::present(void *screenshotCallbackdata)
 	updatePendingReadbacks();
 	updateTemporaryResources();
 
-	frameCounter++;
 	currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
 
 	beginFrame();
@@ -703,7 +700,6 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 		createQuadIndexBuffer();
 		createFanIndexBuffer();
 
-		frameCounter = 0;
 		currentFrame = 0;
 	}
 
@@ -1314,12 +1310,6 @@ void Graphics::beginFrame()
 {
 	vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
 
-	if (frameCounter >= USAGES_POLL_INTERVAL)
-	{
-		cleanupUnusedObjects();
-		frameCounter = 0;
-	}
-
 	if (swapChain != VK_NULL_HANDLE)
 	{
 		while (true)
@@ -2152,6 +2142,54 @@ VkFramebuffer Graphics::getFramebuffer(FramebufferConfiguration &configuration)
 	return framebuffer;
 }
 
+void Graphics::cleanupFramebuffers(VkImageView imageView, PixelFormat format)
+{
+	bool depthstencil = isPixelFormatDepthStencil(format);
+
+	for (auto it = framebuffers.begin(); it != framebuffers.end();)
+	{
+		bool foundView = false;
+
+		if (depthstencil)
+		{
+			if (it->first.staticData.depthView == imageView)
+				foundView = true;
+		}
+		else
+		{
+			for (VkImageView view : it->first.colorViews)
+			{
+				if (view == imageView)
+				{
+					foundView = true;
+					break;
+				}
+			}
+			if (!foundView)
+			{
+				for (VkImageView view : it->first.colorResolveViews)
+				{
+					if (view == imageView)
+					{
+						foundView = true;
+						break;
+					}
+				}
+			}
+		}
+
+		if (foundView)
+		{
+			vkDestroyFramebuffer(device, it->second, nullptr);
+			it = framebuffers.erase(it);
+		}
+		else
+		{
+			++it;
+		}
+	}
+}
+
 void Graphics::createDefaultShaders()
 {
 	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
@@ -2737,36 +2775,6 @@ VkSampler Graphics::createSampler(const SamplerState &samplerState)
 	return sampler;
 }
 
-template<typename Configuration, typename ObjectHandle, typename ConfigurationHasher, typename Deleter>
-static void eraseUnusedObjects(
-	std::unordered_map<Configuration, ObjectHandle, ConfigurationHasher> &objects,
-	std::unordered_map<ObjectHandle, bool> &usages,
-	Deleter deleter,
-	VkDevice device)
-{
-	std::vector<Configuration> deletionKeys;
-
-	for (const auto &entry : objects)
-	{
-		if (!usages[entry.second])
-		{
-			deletionKeys.push_back(entry.first);
-			usages.erase(entry.second);
-			deleter(device, entry.second, nullptr);
-		}
-		else
-			usages[entry.second] = false;
-	}
-
-	for (const auto &key : deletionKeys)
-		objects.erase(key);
-}
-
-void Graphics::cleanupUnusedObjects()
-{
-	eraseUnusedObjects(framebuffers, framebufferUsages, vkDestroyFramebuffer, device);
-}
-
 void Graphics::requestSwapchainRecreation()
 {
 	if (swapChain != VK_NULL_HANDLE)
@@ -3242,16 +3250,21 @@ void Graphics::cleanupSwapChain()
 {
 	if (colorImage)
 	{
+		cleanupFramebuffers(colorImageView, swapChainPixelFormat);
 		vkDestroyImageView(device, colorImageView, nullptr);
 		vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation);
 	}
 	if (depthImage)
 	{
+		cleanupFramebuffers(depthImageView, swapChainPixelFormat);
 		vkDestroyImageView(device, depthImageView, nullptr);
 		vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation);
 	}
 	for (const auto &swapChainImageView : swapChainImageViews)
+	{
+		cleanupFramebuffers(swapChainImageView, swapChainPixelFormat);
 		vkDestroyImageView(device, swapChainImageView, nullptr);
+	}
 	swapChainImageViews.clear();
 	vkDestroySwapchainKHR(device, swapChain, nullptr);
 	swapChainImages.clear();

+ 2 - 2
src/modules/graphics/vulkan/Graphics.h

@@ -281,6 +281,8 @@ public:
 	int getVsync() const;
 	void mapLocalUniformData(void *data, size_t size, VkDescriptorBufferInfo &bufferInfo);
 
+	void cleanupFramebuffers(VkImageView imageView, PixelFormat format);
+
 	VkPipeline createGraphicsPipeline(Shader *shader, const GraphicsPipelineConfigurationCore &configuration, const GraphicsPipelineConfigurationNoDynamicState *noDynamicStateConfiguration);
 
 	uint32 getDeviceApiVersion() const { return deviceApiVersion; }
@@ -346,7 +348,6 @@ private:
 	void endRenderPass();
 	void applyScissor();
 	VkSampler createSampler(const SamplerState &sampler);
-	void cleanupUnusedObjects();
 	void requestSwapchainRecreation();
 
 	VkInstance instance = VK_NULL_HANDLE;
@@ -389,7 +390,6 @@ private:
 	int vsync = 1;
 	VkDeviceSize minUniformBufferOffsetAlignment = 0;
 	bool imageRequested = false;
-	uint32_t frameCounter = 0;
 	size_t currentFrame = 0;
 	uint32_t imageIndex = 0;
 	bool swapChainRecreationRequested = false;

+ 8 - 1
src/modules/graphics/vulkan/Texture.cpp

@@ -301,9 +301,16 @@ void Texture::unloadVolatile()
 		vgfx->queueCleanUp([
 			device = device,
 			allocator = allocator,
-			imageData = std::move(*data)] () {
+			imageData = *data,
+			vgfx = vgfx,
+			renderTarget = renderTarget,
+			format = format] () {
 			if (imageData.imageView != VK_NULL_HANDLE)
+			{
+				if (renderTarget)
+					vgfx->cleanupFramebuffers(imageData.imageView, format);
 				vkDestroyImageView(device, imageData.imageView, nullptr);
+			}
 			if (imageData.allocation != VK_NULL_HANDLE)
 				vmaDestroyImage(allocator, imageData.image, imageData.allocation);
 			for (const auto &views : imageData.renderTargetImageViews)