Browse Source

vulkan: fix crash when using love.window.close()

niki 1 year ago
parent
commit
8f8afe3de5

+ 144 - 130
src/modules/graphics/vulkan/Graphics.cpp

@@ -65,22 +65,88 @@ const char *Graphics::getName() const
 	return "love.graphics.vulkan";
 }
 
-const VkDevice Graphics::getDevice() const
+VkDevice Graphics::getDevice() const
 {
 	return device;
 }
 
-const VmaAllocator Graphics::getVmaAllocator() const
+VmaAllocator Graphics::getVmaAllocator() const
 {
 	return vmaAllocator;
 }
 
+static void checkOptionalInstanceExtensions(OptionalInstanceExtensions& ext)
+{
+	uint32_t count;
+
+	vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
+
+	std::vector<VkExtensionProperties> extensions(count);
+
+	vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data());
+
+	for (const auto& extension : extensions)
+	{
+		if (strcmp(extension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0)
+			ext.physicalDeviceProperties2 = true;
+	}
+}
+
 Graphics::Graphics()
 {
 	if (SDL_Vulkan_LoadLibrary(nullptr))
 		throw love::Exception("could not find vulkan");
 
 	volkInitializeCustom((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr());
+
+	if (isDebugEnabled() && !checkValidationSupport())
+		throw love::Exception("validation layers requested, but not available");
+
+	VkApplicationInfo appInfo{};
+	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+	appInfo.pApplicationName = "LOVE";
+	appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);	// get this version from somewhere else?
+	appInfo.pEngineName = "LOVE Game Framework";
+	appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
+	appInfo.apiVersion = VK_API_VERSION_1_3;
+
+	VkInstanceCreateInfo createInfo{};
+	createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+	createInfo.pApplicationInfo = &appInfo;
+	createInfo.pNext = nullptr;
+
+	// GetInstanceExtensions works with a null window parameter as long as
+	// SDL_Vulkan_LoadLibrary has been called (which we do earlier).
+	unsigned int count;
+	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, nullptr) != SDL_TRUE)
+		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+
+	std::vector<const char*> extensions = {};
+
+	checkOptionalInstanceExtensions(optionalInstanceExtensions);
+
+	if (optionalInstanceExtensions.physicalDeviceProperties2)
+		extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+
+	size_t additional_extension_count = extensions.size();
+	extensions.resize(additional_extension_count + count);
+
+	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, extensions.data() + additional_extension_count) != SDL_TRUE)
+		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+
+	createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
+	createInfo.ppEnabledExtensionNames = extensions.data();
+
+	if (isDebugEnabled())
+	{
+		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+		createInfo.ppEnabledLayerNames = validationLayers.data();
+	}
+
+	if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
+		throw love::Exception("couldn't create vulkan instance");
+
+	volkLoadInstance(instance);
 }
 
 Graphics::~Graphics()
@@ -88,6 +154,10 @@ Graphics::~Graphics()
 	defaultConstantColor.set(nullptr);
 	defaultTexture.set(nullptr);
 
+	Volatile::unloadAll();
+	cleanup();
+	vkDestroyInstance(instance, nullptr);
+
 	SDL_Vulkan_UnloadLibrary();
 }
 
@@ -302,14 +372,14 @@ void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
 	startRenderPass();
 }
 
-void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
+void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallbackData)
 {
 	flushBatchedDraws();
 
 	if (renderPassState.active)
 		endRenderPass();
 
-	if (present)
+	if (submitMode == SUBMIT_PRESENT)
 	{
 		if (pendingScreenshotCallbacks.empty())
 			Vulkan::cmdTransitionImageLayout(
@@ -435,7 +505,7 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 
 	VkFence fence = VK_NULL_HANDLE;
 
-	if (present)
+	if (submitMode == SUBMIT_PRESENT)
 	{
 		submitInfo.signalSemaphoreCount = 1;
 		submitInfo.pSignalSemaphores = signalSemaphores;
@@ -447,7 +517,7 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 	if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS)
 		throw love::Exception("failed to submit draw command buffer");
 	
-	if (!present)
+	if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART)
 	{
 		vkQueueWaitIdle(graphicsQueue);
 
@@ -458,7 +528,8 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 			callbacks.clear();
 		}
 
-		startRecordingGraphicsCommands();
+		if (submitMode == SUBMIT_RESTART)
+			startRecordingGraphicsCommands();
 	}
 }
 
@@ -475,7 +546,7 @@ void Graphics::present(void *screenshotCallbackdata)
 
 	deprecations.draw(this);
 
-	submitGpuCommands(true, screenshotCallbackdata);
+	submitGpuCommands(SUBMIT_PRESENT, screenshotCallbackdata);
 
 	VkPresentInfoKHR presentInfo{};
 	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
@@ -540,64 +611,80 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	readbackCallbacks.clear();
 	readbackCallbacks.resize(MAX_FRAMES_IN_FLIGHT);
 
-	createVulkanInstance();
+	bool createBaseObjects = physicalDevice == VK_NULL_HANDLE;
+
 	createSurface();
-	pickPhysicalDevice();
-	createLogicalDevice();
-	createPipelineCache();
-	initVMA();
-	initCapabilities();
+
+	if (createBaseObjects)
+	{
+		pickPhysicalDevice();
+		createLogicalDevice();
+		createPipelineCache();
+		initVMA();
+		initCapabilities();
+	}
+
+	msaaSamples = getMsaaCount(requestedMsaa);
+
 	createSwapChain();
 	createImageViews();
 	createScreenshotCallbackBuffers();
-	createSyncObjects();
 	createColorResources();
 	createDepthResources();
 	transitionColorDepthLayouts = true;
-	createCommandPool();
-	createCommandBuffers();
-
-	beginFrame();
 
-	if (batchedDrawState.vb[0] == nullptr)
+	if (createBaseObjects)
 	{
-		// Initial sizes that should be good enough for most cases. It will
-		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1);
-		batchedDrawState.indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		createCommandPool();
+		createCommandBuffers();
+		createSyncObjects();
 	}
 
-	// sometimes the VertexTexCoord is not set, so we manually adjust it to (0, 0)
-	if (defaultConstantTexCoord == nullptr)
-	{
-		float zeroTexCoord[2] = { 0.0f, 0.0f };
-		Buffer::DataDeclaration format("ConstantTexCoord", DATAFORMAT_FLOAT_VEC2);
-		Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
-		defaultConstantTexCoord = newBuffer(settings, { format }, zeroTexCoord, sizeof(zeroTexCoord), 1);
-	}
+	beginFrame();
 
-	// sometimes the VertexColor is not set, so we manually adjust it to white color
-	if (defaultConstantColor == nullptr)
+	if (createBaseObjects)
 	{
-		uint8 whiteColor[] = { 255, 255, 255, 255 };
-		Buffer::DataDeclaration format("ConstantColor", DATAFORMAT_UNORM8_VEC4);
-		Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
-		defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1);
-	}
+		if (batchedDrawState.vb[0] == nullptr)
+		{
+			// Initial sizes that should be good enough for most cases. It will
+			// resize to fit if needed, later.
+			batchedDrawState.vb[0] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
+			batchedDrawState.vb[1] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1);
+			batchedDrawState.indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		}
 
-	createDefaultTexture();
-	createDefaultShaders();
-	Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT];
-	createQuadIndexBuffer();
-	createFanIndexBuffer();
+		// sometimes the VertexTexCoord is not set, so we manually adjust it to (0, 0)
+		if (defaultConstantTexCoord == nullptr)
+		{
+			float zeroTexCoord[2] = { 0.0f, 0.0f };
+			Buffer::DataDeclaration format("ConstantTexCoord", DATAFORMAT_FLOAT_VEC2);
+			Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
+			defaultConstantTexCoord = newBuffer(settings, { format }, zeroTexCoord, sizeof(zeroTexCoord), 1);
+		}
+
+		// sometimes the VertexColor is not set, so we manually adjust it to white color
+		if (defaultConstantColor == nullptr)
+		{
+			uint8 whiteColor[] = { 255, 255, 255, 255 };
+			Buffer::DataDeclaration format("ConstantColor", DATAFORMAT_UNORM8_VEC4);
+			Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
+			defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1);
+		}
+
+		createDefaultTexture();
+		createDefaultShaders();
+		Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT];
+		createQuadIndexBuffer();
+		createFanIndexBuffer();
+
+		frameCounter = 0;
+		currentFrame = 0;
+	}
 
 	restoreState(states.back());
 
 	Vulkan::resetShaderSwitches();
 
-	frameCounter = 0;
-	currentFrame = 0;
 	created = true;
 	drawCalls = 0;
 	drawCallsBatched = 0;
@@ -659,14 +746,12 @@ void Graphics::getAPIStats(int &shaderswitches) const
 
 void Graphics::unSetMode()
 {
-	renderPassUsages.clear();
-	framebufferUsages.clear();
-	pipelineUsages.clear();
-	
+	submitGpuCommands(SUBMIT_NOPRESENT);
+
 	created = false;
-	vkDeviceWaitIdle(device);
-	Volatile::unloadAll();
-	cleanup();
+
+	cleanupSwapChain();
+	vkDestroySurfaceKHR(instance, surface, nullptr);
 }
 
 void Graphics::setActive(bool enable)
@@ -1332,75 +1417,6 @@ const OptionalDeviceExtensions &Graphics::getEnabledOptionalDeviceExtensions() c
 	return optionalDeviceExtensions;
 }
 
-static void checkOptionalInstanceExtensions(OptionalInstanceExtensions &ext)
-{
-	uint32_t count;
-
-	vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
-
-	std::vector<VkExtensionProperties> extensions(count);
-
-	vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data());
-
-	for (const auto &extension : extensions)
-	{
-		if (strcmp(extension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0)
-			ext.physicalDeviceProperties2 = true;
-	}
-}
-
-void Graphics::createVulkanInstance()
-{
-	if (isDebugEnabled() && !checkValidationSupport())
-		throw love::Exception("validation layers requested, but not available");
-
-	VkApplicationInfo appInfo{};
-	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
-	appInfo.pApplicationName = "LOVE";
-	appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);	// get this version from somewhere else?
-	appInfo.pEngineName = "LOVE Game Framework";
-	appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
-	appInfo.apiVersion = VK_API_VERSION_1_3;
-
-	VkInstanceCreateInfo createInfo{};
-	createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
-	createInfo.pApplicationInfo = &appInfo;
-	createInfo.pNext = nullptr;
-
-	// GetInstanceExtensions works with a null window parameter as long as
-	// SDL_Vulkan_LoadLibrary has been called (which we do earlier).
-	unsigned int count;
-	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, nullptr) != SDL_TRUE)
-		throw love::Exception("couldn't retrieve sdl vulkan extensions");
-
-	std::vector<const char*> extensions = {};
-
-	checkOptionalInstanceExtensions(optionalInstanceExtensions);
-
-	if (optionalInstanceExtensions.physicalDeviceProperties2)
-		extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
-
-	size_t additional_extension_count = extensions.size();
-	extensions.resize(additional_extension_count + count);
-
-	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, extensions.data() + additional_extension_count) != SDL_TRUE)
-		throw love::Exception("couldn't retrieve sdl vulkan extensions");
-
-	createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
-	createInfo.ppEnabledExtensionNames = extensions.data();
-
-	if (isDebugEnabled())
-	{
-		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
-		createInfo.ppEnabledLayerNames = validationLayers.data();
-	}
-
-	if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
-		throw love::Exception("couldn't create vulkan instance");
-
-	volkLoadInstance(instance);
-}
-
 bool Graphics::checkValidationSupport()
 {
 	uint32_t layerCount;
@@ -1458,7 +1474,6 @@ void Graphics::pickPhysicalDevice()
 	minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment;
 	deviceApiVersion = properties.apiVersion;
 
-	msaaSamples = getMsaaCount(requestedMsaa);
 	depthStencilFormat = findDepthFormat();
 }
 
@@ -3040,8 +3055,6 @@ void Graphics::createDefaultTexture()
 
 void Graphics::cleanup()
 {
-	cleanupSwapChain();
-
 	for (auto &cleanUpFns : cleanUpFunctions)
 		for (auto &cleanUpFn : cleanUpFns)
 			cleanUpFn();
@@ -3076,8 +3089,6 @@ void Graphics::cleanup()
 	vkDestroyCommandPool(device, commandPool, nullptr);
 	vkDestroyPipelineCache(device, pipelineCache, nullptr);
 	vkDestroyDevice(device, nullptr);
-	vkDestroySurfaceKHR(instance, surface, nullptr);
-	vkDestroyInstance(instance, nullptr);
 }
 
 void Graphics::cleanupSwapChain()
@@ -3087,8 +3098,11 @@ void Graphics::cleanupSwapChain()
 		vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
 		vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);
 	}
-	vkDestroyImageView(device, colorImageView, nullptr);
-	vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation);
+	if (colorImage)
+	{
+		vkDestroyImageView(device, colorImageView, nullptr);
+		vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation);
+	}
 	vkDestroyImageView(device, depthImageView, nullptr);
 	vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation);
 	for (const auto &swapChainImageView : swapChainImageViews)

+ 11 - 4
src/modules/graphics/vulkan/Graphics.h

@@ -256,6 +256,14 @@ struct ScreenshotReadbackBuffer
 	VmaAllocation imageAllocation;
 };
 
+enum SubmitMode
+{
+	SUBMIT_PRESENT,
+	SUBMIT_NOPRESENT,
+	SUBMIT_RESTART,
+	SUBMIT_MAXENUM,
+};
+
 class Graphics final : public love::graphics::Graphics
 {
 public:
@@ -300,12 +308,12 @@ public:
 
 	// internal functions.
 
-	const VkDevice getDevice() const;
-	const VmaAllocator getVmaAllocator() const;
+	VkDevice getDevice() const;
+	VmaAllocator getVmaAllocator() const;
 	VkCommandBuffer getCommandBufferForDataTransfer();
 	void queueCleanUp(std::function<void()> cleanUp);
 	void addReadbackCallback(std::function<void()> callback);
-	void submitGpuCommands(bool present, void *screenshotCallbackData = nullptr);
+	void submitGpuCommands(SubmitMode, void *screenshotCallbackData = nullptr);
 	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
 	VkSampler getCachedSampler(const SamplerState &sampler);
 	void setComputeShader(Shader *computeShader);
@@ -326,7 +334,6 @@ protected:
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
 
 private:
-	void createVulkanInstance();
 	bool checkValidationSupport();
 	void pickPhysicalDevice();
 	int rateDeviceSuitability(VkPhysicalDevice device);

+ 3 - 3
src/modules/graphics/vulkan/GraphicsReadback.cpp

@@ -44,7 +44,7 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod
 
 	if (method == READBACK_IMMEDIATE)
 	{
-		vgfx->submitGpuCommands(false);
+		vgfx->submitGpuCommands(SUBMIT_RESTART);
 		if (stagingBuffer.get()) {
 			status = readbackBuffer(stagingBuffer, 0, size);
 			gfx->releaseTemporaryBuffer(stagingBuffer);
@@ -79,7 +79,7 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod
 	});
 
 	if (method == READBACK_IMMEDIATE)
-		vgfx->submitGpuCommands(false);
+		vgfx->submitGpuCommands(SUBMIT_RESTART);
 }
 
 GraphicsReadback::~GraphicsReadback()
@@ -89,7 +89,7 @@ GraphicsReadback::~GraphicsReadback()
 void GraphicsReadback::wait()
 {
 	if (status == STATUS_WAITING)
-		vgfx->submitGpuCommands(false);
+		vgfx->submitGpuCommands(SUBMIT_RESTART);
 }
 
 void GraphicsReadback::update()