VkSurfaceKHR becomes invalid for no reason?

Hello,

A short while ago I tried to learn Vulkan, and so followed the tutorial at https://vulkan-tutorial.com but when I recently came back to it, I found that rather than running as it used to, it was crashing.

After some debugging and improvements to error checking, I figured out that I was getting a VK_ERROR_SURFACE_LOST_KHR error when I tried to call vkGetPhysicalDeviceSurfaceCapabilitiesKHR (on line 339), but I have no idea why.

I also figured out that the change in behaviour was due to an update to the Vulkan ICD Loader. I use Manjaro Linux, and after vulkan-icd-loader got updated from 1.2.203-1 to 1.3.207-1, this error appeared. If I downgrade, it goes away again. Also interestingly, this error only occurs when using the (default) amdlvk driver - it runs perfectly if I override AMD_VULKAN_ICD=RADV to make it use the vulkan-radeon / Mesa driver.

I have a bunch of vulkan sample programs from GitHub - KhronosGroup/Vulkan-Samples: One stop solution for all Vulkan samples and they seem to run fine with both drivers and with the newer ICD loader, so I think the error must be in my code - but I have no clue what I’ve messed up.

I’ve stripped back my code to just the setup of Vulkan up to the point at which it errors, which should hopefully make it at least a little easier to navigate…
It prints a clear error message when it gets to the point at which VK_ERROR_SURFACE_LOST_KHR is returned.

Could anybody help me out?

compiling with clang++ -g -Wall -Wextra -Wpedantic -std=c++20 -O2 -o main main.cpp -lglfw -lvulkan -lpthread

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <vector>
#include <cstring>
#include <map>
#include <optional>
#include <set>
#include <cstdint> // Necessary for UINT32_MAX
#include <algorithm> // Necessary for std::min/std::max
#include <assert.h>
#include <glm/glm.hpp>
#include <array>
#include <sys/time.h>

// #define NDEBUG
#define VULKAN_VERBOSE false

#ifdef NDEBUG
#define VK_CHECK_RESULT(f) ((void)f)
#else

static std::string VkResultString(VkResult err) {
#define STRCASE(v) case v: return #v;
	switch(err) {
		STRCASE(VK_SUCCESS)
		STRCASE(VK_NOT_READY)
		STRCASE(VK_TIMEOUT)
		STRCASE(VK_EVENT_SET)
		STRCASE(VK_EVENT_RESET)
		STRCASE(VK_INCOMPLETE)
		STRCASE(VK_ERROR_OUT_OF_HOST_MEMORY)
		STRCASE(VK_ERROR_OUT_OF_DEVICE_MEMORY)
		STRCASE(VK_ERROR_INITIALIZATION_FAILED)
		STRCASE(VK_ERROR_DEVICE_LOST)
		STRCASE(VK_ERROR_MEMORY_MAP_FAILED)
		STRCASE(VK_ERROR_LAYER_NOT_PRESENT)
		STRCASE(VK_ERROR_EXTENSION_NOT_PRESENT)
		STRCASE(VK_ERROR_FEATURE_NOT_PRESENT)
		STRCASE(VK_ERROR_INCOMPATIBLE_DRIVER)
		STRCASE(VK_ERROR_TOO_MANY_OBJECTS)
		STRCASE(VK_ERROR_FORMAT_NOT_SUPPORTED)
		STRCASE(VK_ERROR_FRAGMENTED_POOL)
		STRCASE(VK_ERROR_UNKNOWN)
		STRCASE(VK_ERROR_OUT_OF_POOL_MEMORY)
		STRCASE(VK_ERROR_INVALID_EXTERNAL_HANDLE)
		STRCASE(VK_ERROR_FRAGMENTATION)
		STRCASE(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS)
		STRCASE(VK_PIPELINE_COMPILE_REQUIRED)
		STRCASE(VK_ERROR_SURFACE_LOST_KHR)
		STRCASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR)
		STRCASE(VK_SUBOPTIMAL_KHR)
		STRCASE(VK_ERROR_OUT_OF_DATE_KHR)
		STRCASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR)
		STRCASE(VK_ERROR_VALIDATION_FAILED_EXT)
		STRCASE(VK_ERROR_INVALID_SHADER_NV)
		STRCASE(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT)
		STRCASE(VK_ERROR_NOT_PERMITTED_KHR)
		STRCASE(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
		STRCASE(VK_THREAD_IDLE_KHR)
		STRCASE(VK_THREAD_DONE_KHR)
		STRCASE(VK_OPERATION_DEFERRED_KHR)
		STRCASE(VK_OPERATION_NOT_DEFERRED_KHR)
		STRCASE(VK_RESULT_MAX_ENUM)
	}
	return "[Other VkResult]";
}

#define TERMCOLOR_RESET "\033[0m"
#define TERMCOLOR_BLACK "\033[30m"
#define TERMCOLOR_RED "\033[31m"
#define TERMCOLOR_GREEN "\033[32m"
#define TERMCOLOR_YELLOW "\033[33m"
#define TERMCOLOR_BLUE "\033[34m"
#define TERMCOLOR_MAGENTA "\033[35m"
#define TERMCOLOR_CYAN "\033[36m"
#define TERMCOLOR_WHITE "\033[37m"

#define VK_CHECK_RESULT(f)																					\
{																											\
	VkResult __vk_check_result_res = (f);																	\
	if (__vk_check_result_res != VK_SUCCESS) {																\
		if (__vk_check_result_res == VK_NOT_READY   ||														\
			__vk_check_result_res == VK_TIMEOUT     ||														\
			__vk_check_result_res == VK_EVENT_SET   ||														\
			__vk_check_result_res == VK_EVENT_RESET ||														\
			__vk_check_result_res == VK_INCOMPLETE  )														\
		{																									\
			std::cout << TERMCOLOR_YELLOW "Warning " TERMCOLOR_RESET ": VkResult is \"" TERMCOLOR_YELLOW <<	\
					VkResultString(__vk_check_result_res) << TERMCOLOR_RESET "\" at " <<					\
					TERMCOLOR_BLUE << __FILE__ << TERMCOLOR_RESET ":" TERMCOLOR_YELLOW << __LINE__ <<		\
					TERMCOLOR_RESET " when trying to evaluate '" TERMCOLOR_GREEN << #f << TERMCOLOR_RESET "'\n"; \
		} else {																							\
			std::cout << TERMCOLOR_RED "Fatal " TERMCOLOR_RESET ": VkResult is \"" TERMCOLOR_RED <<			\
					VkResultString(__vk_check_result_res) << TERMCOLOR_RESET "\" at " <<					\
					TERMCOLOR_BLUE << __FILE__ << TERMCOLOR_RESET ":" TERMCOLOR_YELLOW << __LINE__ <<		\
					TERMCOLOR_RESET " when trying to evaluate '" TERMCOLOR_GREEN << #f << TERMCOLOR_RESET "'\n"; \
			assert(__vk_check_result_res == VK_SUCCESS);													\
		}																									\
	}																										\
}
#endif

const char APP_NAME[] = "Bug free sniffle";

const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

#ifdef NDEBUG
    const bool enableValidationLayers = false;
#else
    const bool enableValidationLayers = true;
#endif

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;
	std::optional<uint32_t> presentFamily;
};

struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};

class VulkanApplication {
public:
	void run() {
		initWindow();
		initVulkan();
	}

private:
/* ---------------------------------------------- PRIVATE MEMBERS ---------------------------------------------- */
	GLFWwindow* window;
	VkInstance instance;
	VkDebugUtilsMessengerEXT debugMessenger;
	VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
	VkSurfaceKHR surface;

/* ---------------------------------------------- DEBUG CALLBACK ---------------------------------------------- */
	static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
		VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
		VkDebugUtilsMessageTypeFlagsEXT messageType,
		const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
		void* pUserData) {
			
		(void) messageSeverity;
		(void) messageType;
		(void) pUserData;

		std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;

		return VK_FALSE;
	}

/* ---------------------------------------------- INIT WINDOW ---------------------------------------------- */
	void initWindow() {
		glfwInit();
		
		assert(glfwVulkanSupported() == GLFW_TRUE);

		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
		
		window = glfwCreateWindow(WIDTH, HEIGHT, APP_NAME, nullptr, nullptr);
		
		glfwSetWindowUserPointer(window, this);
	}
	
/* ---------------------------------------------- CREATE VULKAN INSTANCE ---------------------------------------------- */
	bool checkValidationLayerSupport() {
		uint32_t layerCount;
		VK_CHECK_RESULT(vkEnumerateInstanceLayerProperties(&layerCount, nullptr));

		std::vector<VkLayerProperties> availableLayers(layerCount);
		VK_CHECK_RESULT(vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()));

		for (const char* layerName : validationLayers) {
			bool layerFound = false;

			for (const auto& layerProperties : availableLayers) {
				if (strcmp(layerName, layerProperties.layerName) == 0) {
					layerFound = true;
					break;
				}
			}

			if (!layerFound) {
				return false;
			}
		}

		return true;
	}

	std::vector<const char*> getRequiredExtensions() {
		uint32_t glfwExtensionCount = 0;
		const char** glfwExtensions;
		glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

		std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

		if (enableValidationLayers) {
			extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
		}

		return extensions;
	}

	
	void createInstance() {
		if (enableValidationLayers && !checkValidationLayerSupport()) {
			throw std::runtime_error("Validation layers requested, but not available.");
		}
		
		VkApplicationInfo appInfo{};
		appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
		appInfo.pApplicationName = APP_NAME;
		appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
		appInfo.pEngineName = "No Engine";
		appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
		appInfo.apiVersion = VK_API_VERSION_1_0;
		
		VkInstanceCreateInfo createInfo{};
		createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
		createInfo.pApplicationInfo = &appInfo;
		
		auto extensions = getRequiredExtensions();
		createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
		createInfo.ppEnabledExtensionNames = extensions.data();
		
		VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
		if (enableValidationLayers) {
			createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
			createInfo.ppEnabledLayerNames = validationLayers.data();
			
			populateDebugMessengerCreateInfo(debugCreateInfo);
        	createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
		} else {
			createInfo.enabledLayerCount = 0;
			
			createInfo.pNext = nullptr;
		}
		
		VK_CHECK_RESULT(vkCreateInstance(&createInfo, nullptr, &instance));
		
		uint32_t supportedExtensionCount = 0;
		VK_CHECK_RESULT(vkEnumerateInstanceExtensionProperties(nullptr, &supportedExtensionCount, nullptr));
		std::vector<VkExtensionProperties> supportedExtensions(supportedExtensionCount);
		VK_CHECK_RESULT(vkEnumerateInstanceExtensionProperties(nullptr, &supportedExtensionCount, supportedExtensions.data()));
		
#ifndef NDEBUG
		std::cout << "available extensions:\n";
		for (const auto& extension : supportedExtensions) {
			std::cout << '\t' << extension.extensionName << '\n';
		}
#endif
	}
	
/* ---------------------------------------------- FIND QUEUE FAMILIES ---------------------------------------------- */
	QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
		QueueFamilyIndices indices;

		uint32_t queueFamilyCount = 0;
		vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

		std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
		vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
		
		int i = 0;
		for (const auto& queueFamily : queueFamilies) {
			if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
				indices.graphicsFamily = i;
			} // can add support for more queue families here (after adding a corresponding field to the struct)
			
			VkBool32 presentSupport = false;
			VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport));
			if (presentSupport) {
				indices.presentFamily = i;
			}
			
			i++;
		}
		
		return indices;
	}

/* ---------------------------------------------- DEBUG INFO & MESSENGER ---------------------------------------------- */
	void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
		createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
		createInfo.messageSeverity = (VULKAN_VERBOSE ? VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT : 0) | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
		createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
		createInfo.pfnUserCallback = debugCallback;
	}

	VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
		auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
		if (func != nullptr) {
			return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
		} else {
			return VK_ERROR_EXTENSION_NOT_PRESENT;
		}
	}

	void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
		auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
		if (func != nullptr) {
			func(instance, debugMessenger, pAllocator);
		}
	}

	void setupDebugMessenger() {
		if (!enableValidationLayers) return;

		VkDebugUtilsMessengerCreateInfoEXT createInfo;
		populateDebugMessengerCreateInfo(createInfo);

		if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
			throw std::runtime_error("Failed to set up debug messenger.");
		}
	}

/* ---------------------------------------------- CHOOSING PHYSICAL DEVICE ---------------------------------------------- */
	SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
		SwapChainSupportDetails details;
		
		VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities));
		
		uint32_t formatCount;
		VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr));

		if (formatCount != 0) {
			details.formats.resize(formatCount);
			VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()));
		}
		
		uint32_t presentModeCount;
		VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr));

		if (presentModeCount != 0) {
			details.presentModes.resize(presentModeCount);
			VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()));
		}
		
		return details;
	}
	
	bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
		uint32_t extensionCount;
		VK_CHECK_RESULT(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr));

		std::vector<VkExtensionProperties> availableExtensions(extensionCount);
		VK_CHECK_RESULT(vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()));

		std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

		for (const auto& extension : availableExtensions) {
			requiredExtensions.erase(extension.extensionName);
		}

		return requiredExtensions.empty();
	}
	
	int rateDeviceSuitability(VkPhysicalDevice device) {
		QueueFamilyIndices indices = findQueueFamilies(device);
		
		if (!indices.graphicsFamily.has_value()) {
			return -1; // if it does not support graphics, it is not suitable
		}
		
		if (!indices.presentFamily.has_value()) {
			return -1; // if it cannot present to a surface, it is not suitable
		}
		
		if (!checkDeviceExtensionSupport(device)) {
			return -1; // if it cannot work with a swapchain, it is not suitable
		}
		
		SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
    	bool swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
		if (!swapChainAdequate) {
			return -1; // if the swapchain is inadequate, the device is unsuitable
		}
		
		VkPhysicalDeviceProperties deviceProperties;
		vkGetPhysicalDeviceProperties(device, &deviceProperties);
		
		VkPhysicalDeviceFeatures deviceFeatures;
		vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
		
		int score = 0; // 0 = lowest possible score while still being valid - desirable features will increase the score

		// Discrete GPUs have a significant performance advantage
		if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
			score += 1000;
		}
		
		// If the graphics and presentation are supported by the same queue, rendering to the surface will be faster
		if (indices.graphicsFamily.value() == indices.presentFamily.value()) {
			score += 100;
		}

		return score;
	}
	
	void pickPhysicalDevice() {
		uint32_t deviceCount = 0;
		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr));
		
		if (deviceCount == 0) {
			throw std::runtime_error("Failed to find a GPU with Vulkan support.");
		}
		
		std::vector<VkPhysicalDevice> devices(deviceCount);
		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()));
		
		// Use an ordered map to automatically sort candidates by increasing score
		std::multimap<int, VkPhysicalDevice> candidates;

		for (const auto& device : devices) {
			int score = rateDeviceSuitability(device);
			candidates.insert(std::make_pair(score, device));
		}

		// Check if the best candidate is suitable at all
		if (candidates.rbegin()->first >= 0) {
			physicalDevice = candidates.rbegin()->second;
		} else {
			throw std::runtime_error("Failed to find a suitable GPU.");
		}
		
#ifndef NDEBUG
		VkPhysicalDeviceProperties deviceProperties;
		vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
		std::cout << "Selected GPU: " << deviceProperties.deviceName << std::endl;
#endif
	}

/* ---------------------------------------------- CREATING WINDOW SURFACE ---------------------------------------------- */
	void createSurface() {
		VK_CHECK_RESULT(glfwCreateWindowSurface(instance, window, nullptr, &surface));
	}

/* ---------------------------------------------- VULKAN INITIALISATION ---------------------------------------------- */
	void initVulkan() {
		createInstance();
		setupDebugMessenger();
		createSurface();
		pickPhysicalDevice();
	}
};

int main() {
	VulkanApplication app;

	try {
		app.run();
	} catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

Many thanks in advance!

Possibly 1.3.x causes segfaults on AMD hardware · Issue #888 · KhronosGroup/Vulkan-Loader · GitHub

1 Like

That was indeed the issue! Thank you so much. (A fix has now been committed to the master branch of the Vulkan-Loader, and using a loader built from those sources, the weird behaviour disappears!)

So yes - that was the issue, and it is now fixed.