Browse Source

vulkan: improve module initialization code to exit to other backends if there's a device but it's not suitable for love.

Disallow software rendering (CPU devices) with vulkan by default. The environment variable LOVE_GRAPHICS_VULKAN_ALLOW_SOFTWARE can be set to 1 to override that behaviour.
Sasha Szpakowski 7 months ago
parent
commit
652b012b02

+ 2 - 0
.github/workflows/main.yml

@@ -110,6 +110,7 @@ jobs:
     - name: Run Test Suite (vulkan)
       env:
         LOVE_GRAPHICS_DEBUG: 1
+        LOVE_GRAPHICS_VULKAN_ALLOW_SOFTWARE: 1
       run: |
         ./love-${{ github.sha }}.AppImage love2d-${{ github.sha }}/testing/main.lua --all --isRunner --renderers vulkan
     - name: Love Test Report (vulkan)
@@ -390,6 +391,7 @@ jobs:
 #      if: steps.vars.outputs.arch != 'ARM64'
 #      env:
 #        LOVE_GRAPHICS_DEBUG: 1
+#        LOVE_GRAPHICS_VULKAN_ALLOW_SOFTWARE: 1
 #      run: |
 #        powershell.exe ./install/lovec.exe ./megasource/libs/love/testing/main.lua --all --isRunner --renderers vulkan
 #    - name: Love Test Report (vulkan)

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

@@ -30,6 +30,7 @@
 #include "Vulkan.h"
 
 #include <SDL3/SDL_vulkan.h>
+#include <SDL3/SDL_hints.h>
 
 #include <algorithm>
 #include <vector>
@@ -100,7 +101,10 @@ Graphics::Graphics()
 	volkInitializeCustom((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr());
 
 	if (isDebugEnabled() && !checkValidationSupport())
+	{
+		SDL_Vulkan_UnloadLibrary();
 		throw love::Exception("validation layers requested, but not available");
+	}
 
 	VkApplicationInfo appInfo{};
 	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
@@ -120,7 +124,10 @@ Graphics::Graphics()
 	unsigned int count = 0;
 	char const* const* extensions_string = SDL_Vulkan_GetInstanceExtensions(&count);
 	if (extensions_string == nullptr)
+	{
+		SDL_Vulkan_UnloadLibrary();
 		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+	}
 
 	std::vector<const char*> extensions(extensions_string, extensions_string + count);
 
@@ -141,9 +148,38 @@ Graphics::Graphics()
 	}
 
 	if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
+	{
+		SDL_Vulkan_UnloadLibrary();
 		throw love::Exception("couldn't create vulkan instance");
+	}
 
 	volkLoadInstance(instance);
+
+	uint32_t deviceCount = 0;
+	vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
+
+	int maxScore = 0;
+
+	if (deviceCount > 0)
+	{
+		std::vector<VkPhysicalDevice> devices(deviceCount);
+		vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
+
+		// This is imperfect because we can't query a swap chain at this point.
+		// In theory it could cause a device to have a non-zero score here and
+		// no devices to have a non-zero score later in setMode, but hopefully
+		// that won't happen in practice...
+		for (const auto &device : devices)
+			maxScore = std::max(maxScore, rateDeviceSuitability(device, false));
+	}
+
+	// Exit here if there are no suitable devices, to let other backends take over.
+	if (maxScore == 0)
+	{
+		vkDestroyInstance(instance, nullptr);
+		SDL_Vulkan_UnloadLibrary();
+		throw love::Exception("no suitable vulkan physical devices found");
+	}
 }
 
 Graphics::~Graphics()
@@ -1535,7 +1571,7 @@ void Graphics::pickPhysicalDevice()
 
 	for (const auto &device : devices)
 	{
-		int score = rateDeviceSuitability(device);
+		int score = rateDeviceSuitability(device, true);
 		candidates.insert(std::make_pair(score, device));
 	}
 
@@ -1583,7 +1619,7 @@ bool Graphics::checkDeviceExtensionSupport(VkPhysicalDevice device)
 // if the score is nonzero then the device is suitable.
 // A higher rating means generally better performance
 // if the score is 0 the device is unsuitable
-int Graphics::rateDeviceSuitability(VkPhysicalDevice device)
+int Graphics::rateDeviceSuitability(VkPhysicalDevice device, bool querySwapChain)
 {
 	VkPhysicalDeviceProperties deviceProperties;
 	VkPhysicalDeviceFeatures deviceFeatures;
@@ -1603,15 +1639,21 @@ int Graphics::rateDeviceSuitability(VkPhysicalDevice device)
 
 	// definitely needed
 
+	if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU)
+	{
+		if (!SDL_GetHintBoolean("LOVE_GRAPHICS_VULKAN_ALLOW_SOFTWARE", false))
+			score = 0;
+	}
+
 	QueueFamilyIndices indices = findQueueFamilies(device);
-	if (!indices.isComplete())
+	if (!indices.isComplete() && (querySwapChain || !indices.graphicsFamily.hasValue))
 		score = 0;
 
 	bool extensionsSupported = checkDeviceExtensionSupport(device);
 	if (!extensionsSupported)
 		score = 0;
 
-	if (extensionsSupported)
+	if (extensionsSupported && querySwapChain)
 	{
 		auto swapChainSupport = querySwapChainSupport(device);
 		bool swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
@@ -1644,11 +1686,14 @@ QueueFamilyIndices Graphics::findQueueFamilies(VkPhysicalDevice device)
 		if ((queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) && (queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT))
 			indices.graphicsFamily = i;
 
-		VkBool32 presentSupport = false;
-		vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
+		if (surface != VK_NULL_HANDLE)
+		{
+			VkBool32 presentSupport = false;
+			vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
 
-		if (presentSupport)
-			indices.presentFamily = i;
+			if (presentSupport)
+				indices.presentFamily = i;
+		}
 
 		if (indices.isComplete())
 			break;

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

@@ -300,7 +300,7 @@ protected:
 private:
 	bool checkValidationSupport();
 	void pickPhysicalDevice();
-	int rateDeviceSuitability(VkPhysicalDevice device);
+	int rateDeviceSuitability(VkPhysicalDevice device, bool querySwapChain);
 	QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
 	void createLogicalDevice();
 	void createPipelineCache();