/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#define NOMINMAX
#include "context.hpp"
#include "limits.hpp"
#include "small_vector.hpp"
#include "environment.hpp"
#include "bitops.hpp"
#include <vector>
#include <mutex>
#include <algorithm>
#include <string.h>

#ifndef _WIN32
#include <dlfcn.h>
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#if defined(ANDROID) && defined(HAVE_SWAPPY)
#include "swappy/swappyVk.h"
#endif

#ifdef HAVE_GRANITE_VULKAN_POST_MORTEM
#include "post_mortem.hpp"
#endif

//#undef VULKAN_DEBUG

#ifdef GRANITE_VULKAN_PROFILES
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wshadow"
#endif

// We need to make sure profiles implementation sees volk symbols, not loader.
#include "vulkan/vulkan_profiles.cpp"
// Ideally there would be a vpQueryProfile which returns a const pointer to static VpProfileProperties,
// so we wouldn't have to do this.
struct ProfileHolder
{
	explicit ProfileHolder(const std::string &name)
	{
		if (name.empty())
			return;

		uint32_t count;
		vpGetProfiles(&count, nullptr);
		props.resize(count);
		vpGetProfiles(&count, props.data());

		for (auto &prop : props)
			if (name == prop.profileName)
				profile = &prop;
	}
	Util::SmallVector<VpProfileProperties> props;
	const VpProfileProperties *profile = nullptr;
};

#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#endif

#define NV_DRIVER_VERSION_MAJOR(v) (uint32_t(v) >> 22)

namespace Vulkan
{
static constexpr ContextCreationFlags video_context_flags =
	CONTEXT_CREATION_ENABLE_VIDEO_DECODE_BIT |
	CONTEXT_CREATION_ENABLE_VIDEO_ENCODE_BIT |
	CONTEXT_CREATION_ENABLE_VIDEO_FEATURE_ONLY_BIT;

void Context::set_instance_factory(InstanceFactory *factory)
{
	instance_factory = factory;
}

void Context::set_device_factory(DeviceFactory *factory)
{
	device_factory = factory;
}

void Context::set_application_info(const VkApplicationInfo *app_info)
{
	user_application_info.copy_assign(app_info);
	VK_ASSERT(!app_info || app_info->apiVersion >= VK_API_VERSION_1_1);
}

CopiedApplicationInfo::CopiedApplicationInfo()
{
	set_default_app();
}

void CopiedApplicationInfo::set_default_app()
{
	engine.clear();
	application.clear();
	app = {
		VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr,
		"Granite", 0, "Granite", 0,
		VK_API_VERSION_1_1,
	};
}

void CopiedApplicationInfo::copy_assign(const VkApplicationInfo *info)
{
	if (info)
	{
		app = *info;

		if (info->pApplicationName)
		{
			application = info->pApplicationName;
			app.pApplicationName = application.c_str();
		}
		else
			application.clear();

		if (info->pEngineName)
		{
			engine = info->pEngineName;
			app.pEngineName = engine.c_str();
		}
		else
			engine.clear();
	}
	else
	{
		set_default_app();
	}
}

const VkApplicationInfo &CopiedApplicationInfo::get_application_info() const
{
	return app;
}

bool Context::init_instance(const char * const *instance_ext, uint32_t instance_ext_count, ContextCreationFlags flags)
{
	destroy_device();
	destroy_instance();

	owned_instance = !instance_factory || !instance_factory->factory_owns_created_instance();
	if (!create_instance(instance_ext, instance_ext_count, flags))
	{
		destroy_instance();
		LOGE("Failed to create Vulkan instance.\n");
		return false;
	}

	return true;
}

bool Context::init_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface_compat, const char *const *device_ext,
                          uint32_t device_ext_count, ContextCreationFlags flags)
{
	owned_device = !device_factory || !device_factory->factory_owns_created_device();
	VkPhysicalDeviceFeatures features = {};
	if (!create_device(gpu_, surface_compat, device_ext, device_ext_count, &features, flags))
	{
		destroy_device();
		LOGE("Failed to create Vulkan device.\n");
		return false;
	}

	return true;
}

bool Context::init_instance_and_device(const char * const *instance_ext, uint32_t instance_ext_count,
                                       const char * const *device_ext, uint32_t device_ext_count,
                                       ContextCreationFlags flags)
{
	if (!init_instance(instance_ext, instance_ext_count, flags))
		return false;
	if (!init_device(VK_NULL_HANDLE, VK_NULL_HANDLE, device_ext, device_ext_count, flags))
		return false;
	return true;
}

static std::mutex loader_init_lock;
static bool loader_init_once;
static PFN_vkGetInstanceProcAddr instance_proc_addr;

PFN_vkGetInstanceProcAddr Context::get_instance_proc_addr()
{
	return instance_proc_addr;
}

bool Context::init_loader(PFN_vkGetInstanceProcAddr addr, bool force_reload)
{
	std::lock_guard<std::mutex> holder(loader_init_lock);
	if (loader_init_once && !force_reload && !addr)
		return true;

	if (!addr)
	{
#ifndef _WIN32
		static void *module;
		if (!module)
		{
			auto vulkan_path = Util::get_environment_string("GRANITE_VULKAN_LIBRARY", "");
			if (!vulkan_path.empty())
				module = dlopen(vulkan_path.c_str(), RTLD_LOCAL | RTLD_LAZY);
#ifdef __APPLE__
			if (!module)
				module = dlopen("libvulkan.1.dylib", RTLD_LOCAL | RTLD_LAZY);
			if (!module)
				module = dlopen("libMoltenVK.dylib", RTLD_LOCAL | RTLD_LAZY);
#else
			if (!module)
				module = dlopen("libvulkan.so.1", RTLD_LOCAL | RTLD_LAZY);
			if (!module)
				module = dlopen("libvulkan.so", RTLD_LOCAL | RTLD_LAZY);
#endif
			if (!module)
				return false;
		}

		addr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(module, "vkGetInstanceProcAddr"));
		if (!addr)
			return false;
#else
		static HMODULE module;
		if (!module)
		{
			module = LoadLibraryA("vulkan-1.dll");
			if (!module)
				return false;
		}

		// Ugly pointer warning workaround.
		auto ptr = GetProcAddress(module, "vkGetInstanceProcAddr");
		static_assert(sizeof(ptr) == sizeof(addr), "Mismatch pointer type.");
		memcpy(&addr, &ptr, sizeof(ptr));

		if (!addr)
			return false;
#endif
	}

	instance_proc_addr = addr;
	volkInitializeCustom(addr);
	loader_init_once = true;
	return true;
}

bool Context::init_device_from_instance(VkInstance instance_, VkPhysicalDevice gpu_, VkSurfaceKHR surface,
                                        const char **required_device_extensions, unsigned num_required_device_extensions,
                                        const VkPhysicalDeviceFeatures *required_features,
                                        ContextCreationFlags flags)
{
	destroy_device();
	destroy_instance();

	instance = instance_;
	owned_instance = false;
	owned_device = true;

	if (!create_instance(nullptr, 0, flags))
		return false;

	if (!create_device(gpu_, surface, required_device_extensions, num_required_device_extensions, required_features, flags))
	{
		destroy_device();
		LOGE("Failed to create Vulkan device.\n");
		return false;
	}

	return true;
}

void Context::destroy_device()
{
	if (device != VK_NULL_HANDLE)
		device_table.vkDeviceWaitIdle(device);

#if defined(ANDROID) && defined(HAVE_SWAPPY)
	if (device != VK_NULL_HANDLE)
		SwappyVk_destroyDevice(device);
#endif

#ifdef HAVE_GRANITE_VULKAN_POST_MORTEM
	PostMortem::deinit();
#endif

	if (owned_device && device != VK_NULL_HANDLE)
	{
		device_table.vkDestroyDevice(device, nullptr);
		device = VK_NULL_HANDLE;
		owned_device = false;
	}
}

void Context::destroy_instance()
{
#ifdef VULKAN_DEBUG
	if (debug_messenger)
		vkDestroyDebugUtilsMessengerEXT(instance, debug_messenger, nullptr);
	debug_messenger = VK_NULL_HANDLE;
#endif

	if (owned_instance && instance != VK_NULL_HANDLE)
	{
		vkDestroyInstance(instance, nullptr);
		instance = VK_NULL_HANDLE;
		owned_instance = false;
	}
}

Context::Context()
{
}

Context::~Context()
{
	destroy_device();
	destroy_instance();
}

const VkApplicationInfo &Context::get_application_info() const
{
	return user_application_info.get_application_info();
}

void Context::notify_validation_error(const char *msg)
{
	if (message_callback)
		message_callback(msg);
}

void Context::set_notification_callback(std::function<void(const char *)> func)
{
	message_callback = std::move(func);
}

#ifdef VULKAN_DEBUG
static VKAPI_ATTR VkBool32 VKAPI_CALL vulkan_messenger_cb(
		VkDebugUtilsMessageSeverityFlagBitsEXT           messageSeverity,
		VkDebugUtilsMessageTypeFlagsEXT                  messageType,
		const VkDebugUtilsMessengerCallbackDataEXT*      pCallbackData,
		void *pUserData)
{
	auto *context = static_cast<Context *>(pUserData);

	switch (messageSeverity)
	{
	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
		if (messageType == VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
		{
			LOGE("[Vulkan]: Validation Error: %s - %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage);
			context->notify_validation_error(pCallbackData->pMessage);
		}
		else
			LOGE("[Vulkan]: Other Error: %s\n", pCallbackData->pMessage);
		break;

	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
		if (messageType == VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
			LOGW("[Vulkan]: Validation Warning: %s - %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage);
		else
			LOGW("[Vulkan]: Other Warning: %s\n", pCallbackData->pMessage);
		break;

#if 0
	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
	case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
		if (messageType == VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
			LOGI("[Vulkan]: Validation Info: %s\n", pCallbackData->pMessage);
		else
			LOGI("[Vulkan]: Other Info: %s\n", pCallbackData->pMessage);
		break;
#endif

	default:
		return VK_FALSE;
	}

	bool log_object_names = false;
	for (uint32_t i = 0; i < pCallbackData->objectCount; i++)
	{
		auto *name = pCallbackData->pObjects[i].pObjectName;
		if (name)
		{
			log_object_names = true;
			break;
		}
	}

	if (log_object_names)
	{
		for (uint32_t i = 0; i < pCallbackData->objectCount; i++)
		{
			auto *name = pCallbackData->pObjects[i].pObjectName;
			LOGI("  Object #%u: %s\n", i, name ? name : "N/A");
		}
	}

	return VK_FALSE;
}
#endif

void Context::set_required_profile(const char *profile, bool strict)
{
	if (profile)
		required_profile = profile;
	else
		required_profile.clear();
	required_profile_strict = strict;
}

bool Context::init_profile()
{
#ifdef GRANITE_VULKAN_PROFILES
	if (required_profile.empty())
	{
		if (Util::get_environment("GRANITE_VULKAN_PROFILE", required_profile))
			LOGI("Overriding profile: %s\n", required_profile.c_str());

		required_profile_strict = Util::get_environment_bool("GRANITE_VULKAN_PROFILE_STRICT", false);
		if (required_profile_strict)
			LOGI("Using profile strictness.\n");
	}

	if (required_profile.empty())
		return true;

	ProfileHolder profile{required_profile};

	if (!profile.profile)
	{
		LOGW("No profile matches %s.\n", required_profile.c_str());
		return false;
	}

	VkBool32 supported = VK_FALSE;
	if (vpGetInstanceProfileSupport(nullptr, profile.profile, &supported) != VK_SUCCESS || !supported)
	{
		LOGE("Profile %s is not supported.\n", required_profile.c_str());
		return false;
	}
#endif

	return true;
}

VkResult Context::create_instance_from_profile(const VkInstanceCreateInfo &info, VkInstance *pInstance)
{
#ifdef GRANITE_VULKAN_PROFILES
	ProfileHolder holder{required_profile};
	if (!holder.profile)
		return VK_ERROR_INITIALIZATION_FAILED;

	if (instance_factory)
	{
		// Can override vkGetInstanceProcAddr (macro define) and override vkCreateInstance
		// to a TLS magic trampoline if we really have to.
		LOGE("Instance factory currently not supported with profiles.\n");
		return VK_ERROR_INITIALIZATION_FAILED;
	}

	VpInstanceCreateInfo vp_info = {};
	vp_info.pCreateInfo = &info;
	vp_info.pProfile = holder.profile;
	// Any extra extensions we add for instances are essential, like WSI stuff.
	vp_info.flags = VP_INSTANCE_CREATE_MERGE_EXTENSIONS_BIT;

	VkResult result;
	if ((result = vpCreateInstance(&vp_info, nullptr, pInstance)) != VK_SUCCESS)
		LOGE("Failed to create instance from profile.\n");

	return result;
#else
	(void)info;
	(void)pInstance;
	return VK_ERROR_INITIALIZATION_FAILED;
#endif
}

VkResult Context::create_device_from_profile(const VkDeviceCreateInfo &info, VkDevice *pDevice)
{
#ifdef GRANITE_VULKAN_PROFILES
	ProfileHolder holder{required_profile};
	if (!holder.profile)
		return VK_ERROR_INITIALIZATION_FAILED;

	if (device_factory)
	{
		// Need TLS hackery like instance.
		LOGE("Device factory currently not supported with profiles.\n");
		return VK_ERROR_INITIALIZATION_FAILED;
	}

	auto tmp_info = info;

	VpDeviceCreateInfo vp_info = {};
	vp_info.pProfile = holder.profile;
	vp_info.pCreateInfo = &tmp_info;
	vp_info.flags |= VP_DEVICE_CREATE_DISABLE_ROBUST_ACCESS;

	if (required_profile_strict)
	{
		tmp_info.enabledExtensionCount = 0;
		tmp_info.ppEnabledExtensionNames = nullptr;
		tmp_info.pNext = nullptr;
		tmp_info.pEnabledFeatures = nullptr;
	}
	else
	{
		vp_info.flags = VP_DEVICE_CREATE_MERGE_EXTENSIONS_BIT | VP_DEVICE_CREATE_OVERRIDE_FEATURES_BIT;
	}

	VkResult result;
	if ((result = vpCreateDevice(gpu, &vp_info, nullptr, pDevice)) != VK_SUCCESS)
		LOGE("Failed to create device from profile.\n");
	return result;
#else
	(void)info;
	(void)pDevice;
	return VK_ERROR_INITIALIZATION_FAILED;
#endif
}

VkApplicationInfo Context::get_promoted_application_info() const
{
	auto *inherit_info = instance_factory ? instance_factory->get_existing_create_info() : nullptr;
	auto app_info = get_application_info();

	VK_ASSERT(!inherit_info || inherit_info->pApplicationInfo);
	uint32_t supported_instance_version =
			inherit_info ? inherit_info->pApplicationInfo->apiVersion : volkGetInstanceVersion();

	// Granite min-req is 1.1.
	app_info.apiVersion = std::max(VK_API_VERSION_1_1, app_info.apiVersion);

	// Target Vulkan 1.4 if available,
	// but the tooling ecosystem isn't quite ready for this yet, so stick to 1.3 for the time being.
	app_info.apiVersion = std::max(app_info.apiVersion, std::min(VK_API_VERSION_1_4, supported_instance_version));

	return app_info;
}

bool Context::create_instance(const char * const *instance_ext, uint32_t instance_ext_count, ContextCreationFlags flags)
{
	VkInstanceCreateInfo info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
	auto app_info = get_promoted_application_info();
	auto *inherit_info = instance_factory ? instance_factory->get_existing_create_info() : nullptr;
	uint32_t supported_instance_version =
			inherit_info ? inherit_info->pApplicationInfo->apiVersion : volkGetInstanceVersion();

	if (supported_instance_version < app_info.apiVersion)
	{
		LOGE("Vulkan loader does not support required Vulkan version.\n");
		return false;
	}

	if (inherit_info)
	{
		instance_ext = inherit_info->ppEnabledExtensionNames;
		instance_ext_count = inherit_info->enabledExtensionCount;
	}

	info.pApplicationInfo = &app_info;

	std::vector<const char *> instance_exts;
	std::vector<const char *> instance_layers;
	for (uint32_t i = 0; i < instance_ext_count; i++)
		instance_exts.push_back(instance_ext[i]);

	uint32_t ext_count = 0;
	if (!inherit_info)
		vkEnumerateInstanceExtensionProperties(nullptr, &ext_count, nullptr);
	std::vector<VkExtensionProperties> queried_extensions(ext_count);
	if (ext_count && !inherit_info)
		vkEnumerateInstanceExtensionProperties(nullptr, &ext_count, queried_extensions.data());

	uint32_t layer_count = 0;
	if (!inherit_info)
		vkEnumerateInstanceLayerProperties(&layer_count, nullptr);
	std::vector<VkLayerProperties> queried_layers(layer_count);
	if (layer_count && !inherit_info)
		vkEnumerateInstanceLayerProperties(&layer_count, queried_layers.data());

	if (!inherit_info)
	{
		LOGI("Layer count: %u\n", layer_count);
		for (auto &layer: queried_layers)
			LOGI("Found layer: %s.\n", layer.layerName);
	}

	const auto has_extension = [&](const char *name) -> bool {
		if (inherit_info)
		{
			for (uint32_t i = 0; i < inherit_info->enabledExtensionCount; i++)
				if (strcmp(inherit_info->ppEnabledExtensionNames[i], name) == 0)
					return true;
			return false;
		}

		auto itr = find_if(begin(queried_extensions), end(queried_extensions), [name](const VkExtensionProperties &e) -> bool {
			return strcmp(e.extensionName, name) == 0;
		});
		return itr != end(queried_extensions);
	};

	if (!inherit_info)
		for (uint32_t i = 0; i < instance_ext_count; i++)
			if (!has_extension(instance_ext[i]))
				return false;

	if (has_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
	{
		instance_exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
		ext.supports_debug_utils = true;
	}

	auto itr = std::find_if(instance_ext, instance_ext + instance_ext_count, [](const char *name) {
		return strcmp(name, VK_KHR_SURFACE_EXTENSION_NAME) == 0;
	});
	bool has_surface_extension = itr != (instance_ext + instance_ext_count);

	if (has_surface_extension && has_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME))
	{
		instance_exts.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
		ext.supports_surface_capabilities2 = true;
	}

	if ((flags & CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT) != 0 &&
	    has_surface_extension &&
	    has_extension(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME))
	{
		instance_exts.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
		ext.supports_swapchain_colorspace = true;
	}

#ifdef VULKAN_DEBUG
	const auto has_layer = [&](const char *name) -> bool {
		if (inherit_info)
		{
			for (uint32_t i = 0; i < inherit_info->enabledLayerCount; i++)
				if (strcmp(inherit_info->ppEnabledLayerNames[i], name) == 0)
					return true;
			return false;
		}

		auto layer_itr = find_if(begin(queried_layers), end(queried_layers), [name](const VkLayerProperties &e) -> bool {
			return strcmp(e.layerName, name) == 0;
		});
		return layer_itr != end(queried_layers);
	};

	force_no_validation = Util::get_environment_bool("GRANITE_VULKAN_NO_VALIDATION", false);
	VkValidationFeaturesEXT validation_features = { VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT };

	if (!force_no_validation && has_layer("VK_LAYER_KHRONOS_validation"))
	{
		instance_layers.push_back("VK_LAYER_KHRONOS_validation");
		LOGI("Enabling VK_LAYER_KHRONOS_validation.\n");

		uint32_t layer_ext_count = 0;
		vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &layer_ext_count, nullptr);
		std::vector<VkExtensionProperties> layer_exts(layer_ext_count);
		vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &layer_ext_count, layer_exts.data());

		if (Util::get_environment_bool("GRANITE_VULKAN_SYNC_VALIDATION", false))
		{
			// Tons of false positives around timeline semaphores atm, so don't bother.
			if (find_if(begin(layer_exts), end(layer_exts), [](const VkExtensionProperties &e)
			{
				return strcmp(e.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) == 0;
			}) != end(layer_exts))
			{
				instance_exts.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
				static const VkValidationFeatureEnableEXT validation_sync_features[1] = {
					VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT,
				};
				LOGI("Enabling VK_EXT_validation_features for synchronization validation.\n");
				validation_features.enabledValidationFeatureCount = 1;
				validation_features.pEnabledValidationFeatures = validation_sync_features;
				info.pNext = &validation_features;
			}
		}

		if (!ext.supports_debug_utils &&
		    find_if(begin(layer_exts), end(layer_exts), [](const VkExtensionProperties &e) {
			    return strcmp(e.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0;
		    }) != end(layer_exts))
		{
			instance_exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
			ext.supports_debug_utils = true;
		}
	}
#endif

	if (ext.supports_surface_capabilities2)
	{
		bool supports_khr = has_extension(VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
		bool supports_ext = has_extension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME);

		if (supports_khr || supports_ext)
		{
			instance_exts.push_back(
					supports_khr ? VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME
					             : VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
			ext.supports_surface_maintenance1 = true;
		}
	}

	if (has_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME))
	{
		// Permit MoltenVK (and other portability drivers) when enumerating drivers via the Vulkan loader.
		info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
		instance_exts.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);

		// To use drivers which support VK_KHR_portability_subset, we have to enable the portability_subset
		// extension as well, but since that's a provisional extension, it's not safe to ship that.
		// Just ignore this requirement, since only VVL is likely to complain here.
	}

	if (inherit_info)
	{
		info.enabledExtensionCount = inherit_info->enabledExtensionCount;
		info.ppEnabledExtensionNames = inherit_info->ppEnabledExtensionNames;
		info.enabledLayerCount = inherit_info->enabledLayerCount;
		info.ppEnabledLayerNames = inherit_info->ppEnabledLayerNames;
	}
	else
	{
		info.enabledExtensionCount = instance_exts.size();
		info.ppEnabledExtensionNames = instance_exts.empty() ? nullptr : instance_exts.data();
		info.enabledLayerCount = instance_layers.size();
		info.ppEnabledLayerNames = instance_layers.empty() ? nullptr : instance_layers.data();
	}

	for (uint32_t i = 0; i < info.enabledExtensionCount; i++)
		LOGI("Enabling instance extension: %s.\n", info.ppEnabledExtensionNames[i]);

#ifdef GRANITE_VULKAN_PROFILES
	if (!init_profile())
	{
		LOGE("Profile is not supported.\n");
		return false;
	}

	if (instance == VK_NULL_HANDLE && !required_profile.empty())
		if (create_instance_from_profile(info, &instance) != VK_SUCCESS)
			return false;
#endif

	// instance != VK_NULL_HANDLE here is deprecated and somewhat broken.
	// For libretro Vulkan context negotiation v1.
	if (instance == VK_NULL_HANDLE)
	{
		if (instance_factory)
		{
			instance = instance_factory->create_instance(&info);
			if (instance == VK_NULL_HANDLE)
				return false;
		}
		else if (vkCreateInstance(&info, nullptr, &instance) != VK_SUCCESS)
			return false;

		// If we have a pre-existing instance, we can only assume Vulkan 1.1 in legacy interface.
		ext.instance_api_core_version = app_info.apiVersion;
	}

	if (inherit_info)
	{
		ext.instance_extensions = inherit_info->ppEnabledExtensionNames;
		ext.num_instance_extensions = inherit_info->enabledExtensionCount;
	}
	else
	{
		enabled_instance_extensions = std::move(instance_exts);
		ext.instance_extensions = enabled_instance_extensions.data();
		ext.num_instance_extensions = uint32_t(enabled_instance_extensions.size());
	}

	volkLoadInstance(instance);

#if defined(VULKAN_DEBUG)
	if (ext.supports_debug_utils)
	{
		VkDebugUtilsMessengerCreateInfoEXT debug_info = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT };
		debug_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
		                             VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
		                             VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
		                             VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
		debug_info.pfnUserCallback = vulkan_messenger_cb;
		debug_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
		                         VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT |
		                         VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
		debug_info.pUserData = this;

		// For some reason, this segfaults Android, sigh ... We get relevant output in logcat anyways.
		if (vkCreateDebugUtilsMessengerEXT)
			vkCreateDebugUtilsMessengerEXT(instance, &debug_info, nullptr, &debug_messenger);
	}
#endif

	return true;
}

static unsigned device_score(VkPhysicalDevice &gpu)
{
	VkPhysicalDeviceProperties props = {};
	vkGetPhysicalDeviceProperties(gpu, &props);

	if (props.apiVersion < VK_API_VERSION_1_1)
		return 0;

	switch (props.deviceType)
	{
	case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
		return 3;
	case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
		return 2;
	case VK_PHYSICAL_DEVICE_TYPE_CPU:
		return 1;
	default:
		return 0;
	}
}

QueueInfo::QueueInfo()
{
	for (auto &index : family_indices)
		index = VK_QUEUE_FAMILY_IGNORED;
}

bool Context::physical_device_supports_surface_and_profile(VkPhysicalDevice candidate_gpu, VkSurfaceKHR surface) const
{
#ifdef GRANITE_VULKAN_PROFILES
	if (!required_profile.empty())
	{
		ProfileHolder holder{required_profile};
		if (!holder.profile)
			return false;

		VkBool32 supported = VK_FALSE;
		if (vpGetPhysicalDeviceProfileSupport(instance, candidate_gpu, holder.profile, &supported) != VK_SUCCESS ||
		    !supported)
		{
			return false;
		}
	}
#endif

	if (surface == VK_NULL_HANDLE)
		return true;

	VkPhysicalDeviceProperties dev_props;
	vkGetPhysicalDeviceProperties(candidate_gpu, &dev_props);
	if (dev_props.limits.maxUniformBufferRange < VULKAN_MAX_UBO_SIZE)
	{
		LOGW("Device does not support 64 KiB UBOs. Must be *ancient* mobile driver.\n");
		return false;
	}

	if (dev_props.apiVersion < VK_API_VERSION_1_1)
	{
		LOGW("Device does not support Vulkan 1.1. Skipping.\n");
		return false;
	}

	uint32_t family_count = 0;
	vkGetPhysicalDeviceQueueFamilyProperties(candidate_gpu, &family_count, nullptr);
	Util::SmallVector<VkQueueFamilyProperties> props(family_count);
	vkGetPhysicalDeviceQueueFamilyProperties(candidate_gpu, &family_count, props.data());

	for (uint32_t i = 0; i < family_count; i++)
	{
		// A graphics queue candidate must support present for us to select it.
		if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
		{
			VkBool32 supported = VK_FALSE;
			if (vkGetPhysicalDeviceSurfaceSupportKHR(candidate_gpu, i, surface, &supported) == VK_SUCCESS && supported)
				return true;
		}
	}

	return false;
}

bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface,
                            const char * const *required_device_extensions, uint32_t num_required_device_extensions,
                            const VkPhysicalDeviceFeatures *required_features,
                            ContextCreationFlags flags)
{
	gpu = gpu_;
	if (gpu == VK_NULL_HANDLE)
	{
		uint32_t gpu_count = 0;
		if (vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr) != VK_SUCCESS)
			return false;

		if (gpu_count == 0)
			return false;

		std::vector<VkPhysicalDevice> gpus(gpu_count);
		if (vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data()) != VK_SUCCESS)
			return false;

		for (auto &g : gpus)
		{
			VkPhysicalDeviceProperties props;
			vkGetPhysicalDeviceProperties(g, &props);
			LOGI("Found Vulkan GPU: %s\n", props.deviceName);
			LOGI("    API: %u.%u.%u\n",
			     VK_VERSION_MAJOR(props.apiVersion),
			     VK_VERSION_MINOR(props.apiVersion),
			     VK_VERSION_PATCH(props.apiVersion));
			LOGI("    Driver: %u.%u.%u\n",
			     VK_VERSION_MAJOR(props.driverVersion),
			     VK_VERSION_MINOR(props.driverVersion),
			     VK_VERSION_PATCH(props.driverVersion));
		}

		int gpu_index = Util::get_environment_int("GRANITE_VULKAN_DEVICE_INDEX", -1);
		if (gpu_index >= 0 && gpu_index < int(gpu_count))
			gpu = gpus[gpu_index];

		if (gpu != VK_NULL_HANDLE)
		{
			if (!physical_device_supports_surface_and_profile(gpu, surface))
			{
				LOGE("Selected physical device which does not support surface.\n");
				gpu = VK_NULL_HANDLE;
			}
		}

		if (gpu == VK_NULL_HANDLE)
		{
			unsigned max_score = 0;
			// Prefer earlier entries in list.
			for (size_t i = gpus.size(); i; i--)
			{
				unsigned score = device_score(gpus[i - 1]);
				if (score >= max_score && physical_device_supports_surface_and_profile(gpus[i - 1], surface))
				{
					max_score = score;
					gpu = gpus[i - 1];
				}
			}
		}

		if (gpu == VK_NULL_HANDLE)
		{
			LOGE("Found not GPU which supports surface.\n");
			return false;
		}
	}
	else if (!physical_device_supports_surface_and_profile(gpu, surface))
	{
		LOGE("Selected physical device does not support surface.\n");
		return false;
	}

	auto *inherit_info = device_factory ? device_factory->get_existing_create_info() : nullptr;
	std::vector<VkExtensionProperties> queried_extensions;

	if (inherit_info)
	{
		required_device_extensions = inherit_info->ppEnabledExtensionNames;
		num_required_device_extensions = inherit_info->enabledExtensionCount;
	}

#ifdef GRANITE_VULKAN_PROFILES
	// Only allow extensions that profile declares.
	ProfileHolder profile{required_profile};
	if (profile.profile && required_profile_strict)
	{
		uint32_t ext_count = 0;
		vpGetProfileDeviceExtensionProperties(profile.profile, &ext_count, nullptr);
		queried_extensions.resize(ext_count);
		if (ext_count)
			vpGetProfileDeviceExtensionProperties(profile.profile, &ext_count, queried_extensions.data());
	}
	else
#endif
	{
		uint32_t ext_count = 0;
		if (!inherit_info)
			vkEnumerateDeviceExtensionProperties(gpu, nullptr, &ext_count, nullptr);
		queried_extensions.resize(ext_count);
		if (ext_count && !inherit_info)
			vkEnumerateDeviceExtensionProperties(gpu, nullptr, &ext_count, queried_extensions.data());
	}

	const auto has_extension = [&](const char *name) -> bool {
		if (inherit_info)
		{
			for (uint32_t i = 0; i < inherit_info->enabledExtensionCount; i++)
				if (strcmp(inherit_info->ppEnabledExtensionNames[i], name) == 0)
					return true;
			return false;
		}

		auto itr = find_if(begin(queried_extensions), end(queried_extensions), [name](const VkExtensionProperties &e) -> bool {
			return strcmp(e.extensionName, name) == 0;
		});
		return itr != end(queried_extensions);
	};

	if (!inherit_info)
		for (uint32_t i = 0; i < num_required_device_extensions; i++)
			if (!has_extension(required_device_extensions[i]))
				return false;

	vkGetPhysicalDeviceProperties(gpu, &gpu_props);
	// We can use core device functionality if enabled VkInstance apiVersion and physical device supports it.
	ext.device_api_core_version = std::min(ext.instance_api_core_version, gpu_props.apiVersion);

	LOGI("Using Vulkan GPU: %s\n", gpu_props.deviceName);

	// FFmpeg integration requires Vulkan 1.3 core for physical device.
	uint32_t minimum_api_version = (flags & video_context_flags) ? VK_API_VERSION_1_3 : VK_API_VERSION_1_1;
	if (ext.device_api_core_version < minimum_api_version && (flags & video_context_flags) != 0)
	{
		LOGW("Requested FFmpeg-enabled context, but Vulkan 1.3 was not supported. Falling back to 1.1 without support.\n");
		minimum_api_version = VK_API_VERSION_1_1;
		flags &= ~video_context_flags;
	}

	if (ext.device_api_core_version < minimum_api_version)
	{
		LOGE("Found no Vulkan GPU which supports Vulkan 1.%u.\n",
		     VK_API_VERSION_MINOR(minimum_api_version));
		return false;
	}

	vkGetPhysicalDeviceMemoryProperties(gpu, &mem_props);

	uint32_t queue_family_count = 0;
	vkGetPhysicalDeviceQueueFamilyProperties2(gpu, &queue_family_count, nullptr);
	Util::SmallVector<VkQueueFamilyProperties2> queue_props(queue_family_count);
	Util::SmallVector<VkQueueFamilyVideoPropertiesKHR> video_queue_props2(queue_family_count);

	if ((flags & video_context_flags) != 0 && has_extension(VK_KHR_VIDEO_QUEUE_EXTENSION_NAME))
		ext.supports_video_queue = true;

	for (uint32_t i = 0; i < queue_family_count; i++)
	{
		queue_props[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
		if (ext.supports_video_queue)
		{
			queue_props[i].pNext = &video_queue_props2[i];
			video_queue_props2[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR;
		}
	}

	Util::SmallVector<uint32_t> queue_offsets(queue_family_count);
	Util::SmallVector<Util::SmallVector<float, QUEUE_INDEX_COUNT>> queue_priorities(queue_family_count);
	vkGetPhysicalDeviceQueueFamilyProperties2(gpu, &queue_family_count, queue_props.data());

	if (inherit_info)
	{
		for (uint32_t i = 0; i < queue_family_count; i++)
		{
			auto itr = std::find_if(inherit_info->pQueueCreateInfos,
			                        inherit_info->pQueueCreateInfos + inherit_info->queueCreateInfoCount,
			                        [&](const VkDeviceQueueCreateInfo &queue) { return queue.queueFamilyIndex == i; });

			if (itr != inherit_info->pQueueCreateInfos + inherit_info->queueCreateInfoCount)
				queue_props[i].queueFamilyProperties.queueCount = itr->queueCount;
			else
				queue_props[i].queueFamilyProperties.queueCount = 0;
		}
	}

	queue_info = {};
	uint32_t queue_indices[QUEUE_INDEX_COUNT] = {};

	const auto find_vacant_queue = [&](uint32_t &family, uint32_t &index,
	                                   VkQueueFlags required, VkQueueFlags ignore_flags,
	                                   float priority) -> bool {
		for (unsigned family_index = 0; family_index < queue_family_count; family_index++)
		{
			if ((queue_props[family_index].queueFamilyProperties.queueFlags & ignore_flags) != 0)
				continue;

			// A graphics queue candidate must support present for us to select it.
			if ((required & VK_QUEUE_GRAPHICS_BIT) != 0 && surface != VK_NULL_HANDLE)
			{
				VkBool32 supported = VK_FALSE;
				if (vkGetPhysicalDeviceSurfaceSupportKHR(gpu, family_index, surface, &supported) != VK_SUCCESS || !supported)
					continue;
			}

			if (queue_props[family_index].queueFamilyProperties.queueCount &&
			    (queue_props[family_index].queueFamilyProperties.queueFlags & required) == required)
			{
				family = family_index;
				queue_props[family_index].queueFamilyProperties.queueCount--;
				index = queue_offsets[family_index]++;
				queue_priorities[family_index].push_back(priority);
				return true;
			}
		}

		return false;
	};

	if (!find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_GRAPHICS],
	                       queue_indices[QUEUE_INDEX_GRAPHICS],
	                       VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0, 0.5f))
	{
		LOGE("Could not find suitable graphics queue.\n");
		return false;
	}

	// XXX: This assumes timestamp valid bits is the same for all queue types.
	queue_info.timestamp_valid_bits =
			queue_props[queue_info.family_indices[QUEUE_INDEX_GRAPHICS]].queueFamilyProperties.timestampValidBits;

	// Prefer standalone compute queue. If not, fall back to another graphics queue.
	if (!find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_COMPUTE], queue_indices[QUEUE_INDEX_COMPUTE],
	                       VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT, 0.5f) &&
	    !find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_COMPUTE], queue_indices[QUEUE_INDEX_COMPUTE],
	                       VK_QUEUE_COMPUTE_BIT, 0, 0.5f))
	{
		// Fallback to the graphics queue if we must.
		queue_info.family_indices[QUEUE_INDEX_COMPUTE] = queue_info.family_indices[QUEUE_INDEX_GRAPHICS];
		queue_indices[QUEUE_INDEX_COMPUTE] = queue_indices[QUEUE_INDEX_GRAPHICS];
	}

	// For transfer, try to find a queue which only supports transfer, e.g. DMA queue.
	// If not, fallback to a dedicated compute queue.
	// Finally, fallback to same queue as compute.
	if (!find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_TRANSFER], queue_indices[QUEUE_INDEX_TRANSFER],
	                       VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0.5f) &&
	    !find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_TRANSFER], queue_indices[QUEUE_INDEX_TRANSFER],
	                       VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT, 0.5f))
	{
		queue_info.family_indices[QUEUE_INDEX_TRANSFER] = queue_info.family_indices[QUEUE_INDEX_COMPUTE];
		queue_indices[QUEUE_INDEX_TRANSFER] = queue_indices[QUEUE_INDEX_COMPUTE];
	}

	if (ext.supports_video_queue)
	{
		if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_DECODE_BIT) != 0)
		{
			if (!find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE],
			                       queue_indices[QUEUE_INDEX_VIDEO_DECODE],
			                       VK_QUEUE_VIDEO_DECODE_BIT_KHR, 0, 0.5f))
			{
				queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE] = VK_QUEUE_FAMILY_IGNORED;
				queue_indices[QUEUE_INDEX_VIDEO_DECODE] = UINT32_MAX;
			}
		}

		if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_ENCODE_BIT) != 0)
		{
			if (!find_vacant_queue(queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE],
			                       queue_indices[QUEUE_INDEX_VIDEO_ENCODE],
			                       VK_QUEUE_VIDEO_ENCODE_BIT_KHR, 0, 0.5f))
			{
				queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE] = VK_QUEUE_FAMILY_IGNORED;
				queue_indices[QUEUE_INDEX_VIDEO_ENCODE] = UINT32_MAX;
			}
		}
	}

	VkDeviceCreateInfo device_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };

	Util::SmallVector<VkDeviceQueueCreateInfo> queue_infos;
	for (uint32_t family_index = 0; family_index < queue_family_count; family_index++)
	{
		if (queue_offsets[family_index] == 0)
			continue;

		VkDeviceQueueCreateInfo info = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
		info.queueFamilyIndex = family_index;
		info.queueCount = queue_offsets[family_index];
		info.pQueuePriorities = queue_priorities[family_index].data();
		queue_infos.push_back(info);
	}

	if (inherit_info)
	{
		device_info.pQueueCreateInfos = inherit_info->pQueueCreateInfos;
		device_info.queueCreateInfoCount = inherit_info->queueCreateInfoCount;
	}
	else
	{
		device_info.pQueueCreateInfos = queue_infos.data();
		device_info.queueCreateInfoCount = uint32_t(queue_infos.size());
	}

	std::vector<const char *> enabled_extensions;

	bool requires_swapchain = false;
	for (uint32_t i = 0; i < num_required_device_extensions; i++)
	{
		enabled_extensions.push_back(required_device_extensions[i]);
		if (strcmp(required_device_extensions[i], VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0)
			requires_swapchain = true;
		else if (strcmp(required_device_extensions[i], VK_KHR_PRESENT_ID_EXTENSION_NAME) == 0 ||
		         strcmp(required_device_extensions[i], VK_KHR_PRESENT_ID_2_EXTENSION_NAME) == 0 ||
		         strcmp(required_device_extensions[i], VK_KHR_PRESENT_WAIT_EXTENSION_NAME) == 0 ||
		         strcmp(required_device_extensions[i], VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME) == 0 ||
		         strcmp(required_device_extensions[i], VK_EXT_HDR_METADATA_EXTENSION_NAME) == 0 ||
		         strcmp(required_device_extensions[i], VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME) == 0 ||
		         strcmp(required_device_extensions[i], VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME) == 0)
		{
			flags |= CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT;
		}
	}

#if defined(ANDROID) && defined(HAVE_SWAPPY)
	// Enable additional extensions required by SwappyVk.
	std::unique_ptr<char[]> swappy_str_buffer;
	if (requires_swapchain)
	{
		uint32_t required_swappy_extension_count = 0;

		// I'm really not sure why the API just didn't return static const char * strings here,
		// but oh well.
		SwappyVk_determineDeviceExtensions(gpu, uint32_t(queried_extensions.size()),
		                                   queried_extensions.data(),
		                                   &required_swappy_extension_count,
		                                   nullptr);
		swappy_str_buffer.reset(new char[required_swappy_extension_count * (VK_MAX_EXTENSION_NAME_SIZE + 1)]);

		std::vector<char *> extension_buffer;
		extension_buffer.reserve(required_swappy_extension_count);
		for (uint32_t i = 0; i < required_swappy_extension_count; i++)
			extension_buffer.push_back(swappy_str_buffer.get() + i * (VK_MAX_EXTENSION_NAME_SIZE + 1));
		SwappyVk_determineDeviceExtensions(gpu, uint32_t(queried_extensions.size()),
		                                   queried_extensions.data(),
		                                   &required_swappy_extension_count,
		                                   extension_buffer.data());

		for (auto *required_ext : extension_buffer)
			enabled_extensions.push_back(required_ext);
	}
#endif

#ifdef _WIN32
	if (ext.supports_surface_capabilities2 && has_extension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME))
	{
		ext.supports_full_screen_exclusive = true;
		enabled_extensions.push_back(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME);
	}
#endif

	if (
#ifdef _WIN32
	    has_extension(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME) &&
	    has_extension(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME)
#else
	    has_extension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME) &&
	    has_extension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)
#endif
		)
	{
		ext.supports_external = true;
#ifdef _WIN32
		enabled_extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME);
		enabled_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
#else
		enabled_extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
		enabled_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
#endif
	}
	else
		ext.supports_external = false;

	if (ext.supports_external)
	{
		if (has_extension(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME))
			enabled_extensions.push_back(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME);

		if (has_extension(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME);
			ext.supports_drm_modifiers = true;
		}
	}

	if (has_extension(VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME))
	{
		ext.supports_calibrated_timestamps = true;
		enabled_extensions.push_back(VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME);
	}

	if (has_extension(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME);
		ext.supports_conservative_rasterization = true;
	}

	if (ext.device_api_core_version < VK_API_VERSION_1_2)
	{
		if (!has_extension(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME))
		{
			LOGE("VK_KHR_create_renderpass2 is not supported.\n");
			return false;
		}

		enabled_extensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);

		if (has_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME))
		{
			ext.supports_image_format_list = true;
			enabled_extensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
		}
	}
	else
		ext.supports_image_format_list = true;

	// Physical device functionality.
	ext.supports_format_feature_flags2 = ext.device_api_core_version >= VK_API_VERSION_1_3 ||
	                                     has_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME);

	if (has_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME))
		ext.supports_tooling_info = true;

	if (ext.supports_video_queue)
	{
		enabled_extensions.push_back(VK_KHR_VIDEO_QUEUE_EXTENSION_NAME);

		if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_DECODE_BIT) != 0 &&
		    has_extension(VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME);
			ext.supports_video_decode_queue = true;

			if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_H264_BIT) != 0 &&
			    has_extension(VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME);

				if (queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE] != VK_QUEUE_FAMILY_IGNORED)
				{
					ext.supports_video_decode_h264 =
							(video_queue_props2[queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE]].videoCodecOperations &
							 VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR) != 0;
				}
			}

			if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_H265_BIT) != 0 &&
			    has_extension(VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME);

				if (queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE] != VK_QUEUE_FAMILY_IGNORED)
				{
					ext.supports_video_decode_h265 =
							(video_queue_props2[queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE]].videoCodecOperations &
							 VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR) != 0;
				}
			}

			if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_AV1_BIT) != 0 &&
			    has_extension(VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME);

				if (queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE] != VK_QUEUE_FAMILY_IGNORED)
				{
					ext.supports_video_decode_av1 =
							(video_queue_props2[queue_info.family_indices[QUEUE_INDEX_VIDEO_DECODE]].videoCodecOperations &
							 VK_VIDEO_CODEC_OPERATION_DECODE_AV1_BIT_KHR) != 0;
				}
			}
		}
		else if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_FEATURE_ONLY_BIT) != 0 &&
		         has_extension(VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME);
		}

		if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_ENCODE_BIT) != 0 &&
		    has_extension(VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME);
			ext.supports_video_encode_queue = true;

			if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_H264_BIT) != 0 &&
			    has_extension(VK_KHR_VIDEO_ENCODE_H264_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_VIDEO_ENCODE_H264_EXTENSION_NAME);

				if (queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE] != VK_QUEUE_FAMILY_IGNORED)
				{
					ext.supports_video_encode_h264 =
							(video_queue_props2[queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE]].videoCodecOperations &
							 VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR) != 0;
				}
			}

			if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_H265_BIT) != 0 &&
			    has_extension(VK_KHR_VIDEO_ENCODE_H265_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_VIDEO_ENCODE_H265_EXTENSION_NAME);

				if (queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE] != VK_QUEUE_FAMILY_IGNORED)
				{
					ext.supports_video_encode_h265 =
							(video_queue_props2[queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE]].videoCodecOperations &
							 VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR) != 0;
				}
			}

			if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_AV1_BIT) != 0 &&
			    has_extension(VK_KHR_VIDEO_ENCODE_AV1_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_VIDEO_ENCODE_AV1_EXTENSION_NAME);

				if (queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE] != VK_QUEUE_FAMILY_IGNORED)
				{
					ext.supports_video_encode_av1 =
							(video_queue_props2[queue_info.family_indices[QUEUE_INDEX_VIDEO_ENCODE]].videoCodecOperations &
							 VK_VIDEO_CODEC_OPERATION_ENCODE_AV1_BIT_KHR) != 0;
				}
			}
		}
		else if ((flags & CONTEXT_CREATION_ENABLE_VIDEO_FEATURE_ONLY_BIT) != 0 &&
		         has_extension(VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME);
		}
	}

	pdf2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
	auto **ppNext = reinterpret_cast<VkBaseOutStructure **>(&pdf2.pNext);

	const auto add_chain = [&](void *pnext, size_t size,
	                           VkStructureType type, const VkDeviceCreateInfo *device_create_info) {
		auto *sout = static_cast<VkBaseOutStructure *>(pnext);
		memset(sout, 0, size);
		sout->sType = type;

		if (device_create_info)
		{
			auto *existing = find_pnext<VkBaseOutStructure>(device_create_info->pNext, type);
			if (existing)
				memcpy(sout + 1, existing + 1, size - sizeof(*sout));
		}

		*ppNext = sout;
		ppNext = &sout->pNext;
	};

#define ADD_CHAIN(s, type) add_chain(&(s), sizeof(s), VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ ## type, inherit_info)

	if (ext.supports_video_encode_av1)
		ADD_CHAIN(ext.av1_features, VIDEO_ENCODE_AV1_FEATURES_KHR);

	if (ext.device_api_core_version >= VK_API_VERSION_1_2)
	{
		ADD_CHAIN(ext.vk11_features, VULKAN_1_1_FEATURES);
		ADD_CHAIN(ext.vk12_features, VULKAN_1_2_FEATURES);
	}
	else
	{
		if (has_extension(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME))
		{
			ADD_CHAIN(ext.host_query_reset_features, HOST_QUERY_RESET_FEATURES);
			enabled_extensions.push_back(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
		}

		if (has_extension(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME))
		{
			ADD_CHAIN(ext.float16_int8_features, FLOAT16_INT8_FEATURES_KHR);
			enabled_extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
		}

		if (has_extension(VK_KHR_16BIT_STORAGE_EXTENSION_NAME))
		{
			ADD_CHAIN(ext.storage_16bit_features, 16BIT_STORAGE_FEATURES_KHR);
			enabled_extensions.push_back(VK_KHR_16BIT_STORAGE_EXTENSION_NAME);
		}

		if (has_extension(VK_KHR_8BIT_STORAGE_EXTENSION_NAME))
		{
			ADD_CHAIN(ext.storage_8bit_features, 8BIT_STORAGE_FEATURES_KHR);
			enabled_extensions.push_back(VK_KHR_8BIT_STORAGE_EXTENSION_NAME);
		}
	}

	if (ext.device_api_core_version >= VK_API_VERSION_1_3)
	{
		ADD_CHAIN(ext.vk13_features, VULKAN_1_3_FEATURES);
	}
	else
	{
		if (has_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME))
		{
			ADD_CHAIN(ext.subgroup_size_control_features, SUBGROUP_SIZE_CONTROL_FEATURES_EXT);
			enabled_extensions.push_back(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
		}

		if (has_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME))
		{
			ADD_CHAIN(ext.sync2_features, SYNCHRONIZATION_2_FEATURES_KHR);
			enabled_extensions.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
		}
		else
		{
			LOGE("KHR_synchronization2 is not supported. This is a hard requirement for Granite.\n");
			return false;
		}
	}

	bool supports_khr_push_descriptor = false;

	if (ext.device_api_core_version >= VK_API_VERSION_1_4)
	{
		ADD_CHAIN(ext.vk14_features, VULKAN_1_4_FEATURES);
	}
	else
	{
		if ((flags & CONTEXT_CREATION_ENABLE_PUSH_DESCRIPTOR_BIT) != 0 && has_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
			supports_khr_push_descriptor = true;
		}

		if (has_extension(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME);
			ADD_CHAIN(ext.index_type_uint8_features, INDEX_TYPE_UINT8_FEATURES_EXT);
		}

		if (has_extension(VK_KHR_MAINTENANCE_5_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
			ADD_CHAIN(ext.maintenance5_features, MAINTENANCE_5_FEATURES_KHR);
		}
	}

	if (has_extension(VK_KHR_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME);
		ADD_CHAIN(ext.compute_shader_derivative_features, COMPUTE_SHADER_DERIVATIVES_FEATURES_KHR);
	}

	if (has_extension(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME);
		ADD_CHAIN(ext.performance_query_features, PERFORMANCE_QUERY_FEATURES_KHR);
	}

	if (has_extension(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME);
		ADD_CHAIN(ext.memory_priority_features, MEMORY_PRIORITY_FEATURES_EXT);
	}

	if (has_extension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
		ext.supports_memory_budget = true;
	}

	if (has_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME))
	{
		ext.supports_astc_decode_mode = true;
		enabled_extensions.push_back(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME);
		ADD_CHAIN(ext.astc_decode_features, ASTC_DECODE_FEATURES_EXT);
	}

	if (has_extension(VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_EXTENSION_NAME);
		ADD_CHAIN(ext.pageable_device_local_memory_features, PAGEABLE_DEVICE_LOCAL_MEMORY_FEATURES_EXT);
	}

	if (has_extension(VK_EXT_DEVICE_GENERATED_COMMANDS_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_DEVICE_GENERATED_COMMANDS_EXTENSION_NAME);
		ADD_CHAIN(ext.device_generated_commands_features, DEVICE_GENERATED_COMMANDS_FEATURES_EXT);
	}

	if (has_extension(VK_NV_DESCRIPTOR_POOL_OVERALLOCATION_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_NV_DESCRIPTOR_POOL_OVERALLOCATION_EXTENSION_NAME);
		ADD_CHAIN(ext.descriptor_pool_overallocation_features, DESCRIPTOR_POOL_OVERALLOCATION_FEATURES_NV);
	}

	if (has_extension(VK_EXT_MESH_SHADER_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_MESH_SHADER_EXTENSION_NAME);
		ADD_CHAIN(ext.mesh_shader_features, MESH_SHADER_FEATURES_EXT);
	}

	if (has_extension(VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME);
		ADD_CHAIN(ext.rgba10x6_formats_features, RGBA10X6_FORMATS_FEATURES_EXT);
	}

	if (has_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME))
	{
		ext.supports_external_memory_host = true;
		enabled_extensions.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
	}

	if (has_extension(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
		ADD_CHAIN(ext.barycentric_features, FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR);
	}

	if (ext.supports_video_queue && has_extension(VK_KHR_VIDEO_MAINTENANCE_1_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_VIDEO_MAINTENANCE_1_EXTENSION_NAME);
		ADD_CHAIN(ext.video_maintenance1_features, VIDEO_MAINTENANCE_1_FEATURES_KHR);
	}

	if (has_extension(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME);
		ADD_CHAIN(ext.image_compression_control_features, IMAGE_COMPRESSION_CONTROL_FEATURES_EXT);
	}

	if (has_extension(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME);
		ADD_CHAIN(ext.image_compression_control_swapchain_features, IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT);
	}

	if (has_extension(VK_NV_LOW_LATENCY_2_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_NV_LOW_LATENCY_2_EXTENSION_NAME);
		ext.supports_low_latency2_nv = true;
	}

	if (has_extension(VK_AMD_ANTI_LAG_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_AMD_ANTI_LAG_EXTENSION_NAME);
		ADD_CHAIN(ext.anti_lag_features, ANTI_LAG_FEATURES_AMD);
	}

	if (has_extension(VK_KHR_RAY_QUERY_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME);
		enabled_extensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME);
		enabled_extensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
		ADD_CHAIN(ext.ray_query_features, RAY_QUERY_FEATURES_KHR);
		ADD_CHAIN(ext.rtas_features, ACCELERATION_STRUCTURE_FEATURES_KHR);
	}

	if (ext.device_api_core_version >= VK_API_VERSION_1_3)
	{
		ext.supports_store_op_none = true;
	}
	else if (has_extension(VK_KHR_LOAD_STORE_OP_NONE_EXTENSION_NAME))
	{
		ext.supports_store_op_none = true;
		enabled_extensions.push_back(VK_KHR_LOAD_STORE_OP_NONE_EXTENSION_NAME);
	}
	else if (has_extension(VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME))
	{
		ext.supports_store_op_none = true;
		enabled_extensions.push_back(VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME);
	}

	// Pipeline binaries are currently borked in VVL.
	if ((flags & CONTEXT_CREATION_ENABLE_PIPELINE_BINARY_BIT) != 0 &&
	    has_extension(VK_KHR_PIPELINE_BINARY_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_PIPELINE_BINARY_EXTENSION_NAME);
		ADD_CHAIN(ext.pipeline_binary_features, PIPELINE_BINARY_FEATURES_KHR);
	}

	if ((flags & CONTEXT_CREATION_ENABLE_ROBUSTNESS_2_BIT) != 0 && has_extension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
		ADD_CHAIN(ext.robustness2_features, ROBUSTNESS_2_FEATURES_EXT);
	}

#if !defined(VULKAN_DEBUG)
	if ((flags & CONTEXT_CREATION_ENABLE_DESCRIPTOR_HEAP_BIT) != 0 &&
	    has_extension(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME))
	{
		ADD_CHAIN(ext.descriptor_heap_features, DESCRIPTOR_HEAP_FEATURES_EXT);
		enabled_extensions.push_back(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME);
		ADD_CHAIN(ext.untyped_pointers_features, SHADER_UNTYPED_POINTERS_FEATURES_KHR);
		enabled_extensions.push_back(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME);
	}
	else if ((flags & CONTEXT_CREATION_ENABLE_DESCRIPTOR_BUFFER_BIT) != 0 &&
	         has_extension(VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME))
	{
		ADD_CHAIN(ext.descriptor_buffer_features, DESCRIPTOR_BUFFER_FEATURES_EXT);
		enabled_extensions.push_back(VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME);
	}
#endif

	if ((flags & CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT) != 0 && requires_swapchain)
	{
		if (has_extension(VK_KHR_PRESENT_ID_2_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_PRESENT_ID_2_EXTENSION_NAME);
			ADD_CHAIN(ext.present_id2_features, PRESENT_ID_2_FEATURES_KHR);
		}
		else if (has_extension(VK_KHR_PRESENT_ID_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_PRESENT_ID_EXTENSION_NAME);
			ADD_CHAIN(ext.present_id_features, PRESENT_ID_FEATURES_KHR);
		}

		if (has_extension(VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME);
			ADD_CHAIN(ext.present_wait2_features, PRESENT_WAIT_2_FEATURES_KHR);
		}
		else if (has_extension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME);
			ADD_CHAIN(ext.present_wait_features, PRESENT_WAIT_FEATURES_KHR);
		}

#if !defined(ANDROID) || !defined(HAVE_SWAPPY)
		// Assume that swappy takes care of all this on Android.
		if (has_extension(VK_EXT_PRESENT_TIMING_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_EXT_PRESENT_TIMING_EXTENSION_NAME);
			ADD_CHAIN(ext.present_timing_features, PRESENT_TIMING_FEATURES_EXT);
		}
#endif

		if (ext.supports_surface_maintenance1)
		{
			if (has_extension(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
				ADD_CHAIN(ext.swapchain_maintenance1_features, SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR);
			}
			else if (has_extension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME))
			{
				enabled_extensions.push_back(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
				ADD_CHAIN(ext.swapchain_maintenance1_features, SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR);
			}
		}

		if (ext.supports_swapchain_colorspace && has_extension(VK_EXT_HDR_METADATA_EXTENSION_NAME))
		{
			ext.supports_hdr_metadata = true;
			enabled_extensions.push_back(VK_EXT_HDR_METADATA_EXTENSION_NAME);
		}
	}

	if ((flags & CONTEXT_CREATION_ENABLE_POST_MORTEM_BIT) ||
		Util::get_environment_bool("GRANITE_VULKAN_POST_MORTEM", false))
	{
		if (has_extension(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME);
			ADD_CHAIN(ext.coherent_memory_features, COHERENT_MEMORY_FEATURES_AMD);
		}

		if (has_extension(VK_AMD_BUFFER_MARKER_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_AMD_BUFFER_MARKER_EXTENSION_NAME);
			ext.supports_amd_buffer_marker = true;
		}

		if (has_extension(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME);
			ext.supports_nv_checkpoints = true;
		}

		// The KHR is quite new and not all relevant drivers support the KHR yet.
		if (has_extension(VK_KHR_DEVICE_FAULT_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_KHR_DEVICE_FAULT_EXTENSION_NAME);
			ADD_CHAIN(ext.fault_features_khr, FAULT_FEATURES_KHR);
		}
		else if (has_extension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME))
		{
			enabled_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME);
			ADD_CHAIN(ext.fault_features_ext, FAULT_FEATURES_EXT);
		}

		ext.supports_post_mortem = true;
	}

	if (has_extension(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME);
		ADD_CHAIN(ext.cooperative_matrix_features, COOPERATIVE_MATRIX_FEATURES_KHR);
	}

	if (has_extension(VK_VALVE_SHADER_MIXED_FLOAT_DOT_PRODUCT_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_VALVE_SHADER_MIXED_FLOAT_DOT_PRODUCT_EXTENSION_NAME);
		ADD_CHAIN(ext.shader_mixed_float_dot_product_features, SHADER_MIXED_FLOAT_DOT_PRODUCT_FEATURES_VALVE);
	}

#ifdef GRANITE_VULKAN_PROFILES
	// Override any features in the profile in strict mode.
	if (profile.profile && required_profile_strict)
	{
		vpGetProfileFeatures(profile.profile, &pdf2);
	}
	else
#endif
	{
		if (!inherit_info)
		{
			vkGetPhysicalDeviceFeatures2(gpu, &pdf2);
		}
		else if (inherit_info->pNext)
		{
			auto *features = find_pnext<VkPhysicalDeviceFeatures2>(inherit_info->pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2);
			pdf2.features = features->features;
		}
		else if (inherit_info->pEnabledFeatures)
		{
			pdf2.features = *inherit_info->pEnabledFeatures;
		}
	}

	// Promote fallback features to core structs.
	if (ext.host_query_reset_features.hostQueryReset)
		ext.vk12_features.hostQueryReset = VK_TRUE;

	if (ext.storage_16bit_features.storageBuffer16BitAccess)
		ext.vk11_features.storageBuffer16BitAccess = VK_TRUE;
	if (ext.storage_16bit_features.storageInputOutput16)
		ext.vk11_features.storageInputOutput16 = VK_TRUE;
	if (ext.storage_16bit_features.storagePushConstant16)
		ext.vk11_features.storagePushConstant16 = VK_TRUE;
	if (ext.storage_16bit_features.uniformAndStorageBuffer16BitAccess)
		ext.vk11_features.uniformAndStorageBuffer16BitAccess = VK_TRUE;

	if (ext.storage_8bit_features.storageBuffer8BitAccess)
		ext.vk12_features.storageBuffer8BitAccess = VK_TRUE;
	if (ext.storage_8bit_features.storagePushConstant8)
		ext.vk12_features.storagePushConstant8 = VK_TRUE;
	if (ext.storage_8bit_features.uniformAndStorageBuffer8BitAccess)
		ext.vk12_features.uniformAndStorageBuffer8BitAccess = VK_TRUE;

	if (ext.float16_int8_features.shaderFloat16)
		ext.vk12_features.shaderFloat16 = VK_TRUE;
	if (ext.float16_int8_features.shaderInt8)
		ext.vk12_features.shaderInt8 = VK_TRUE;

	if (ext.subgroup_size_control_features.computeFullSubgroups)
		ext.vk13_features.computeFullSubgroups = VK_TRUE;
	if (ext.subgroup_size_control_features.subgroupSizeControl)
		ext.vk13_features.subgroupSizeControl = VK_TRUE;

	if (ext.sync2_features.synchronization2)
		ext.vk13_features.synchronization2 = VK_TRUE;

	if (ext.maintenance5_features.maintenance5)
		ext.vk14_features.maintenance5 = VK_TRUE;
	if (supports_khr_push_descriptor)
		ext.vk14_features.pushDescriptor = VK_TRUE;
	if (ext.index_type_uint8_features.indexTypeUint8)
		ext.vk14_features.indexTypeUint8 = VK_TRUE;
	///

	ext.vk11_features.multiviewGeometryShader = VK_FALSE;
	ext.vk11_features.multiviewTessellationShader = VK_FALSE;
	ext.vk11_features.protectedMemory = VK_FALSE;
	ext.vk11_features.variablePointers = VK_FALSE;
	ext.vk11_features.variablePointersStorageBuffer = VK_FALSE;

	ext.vk12_features.bufferDeviceAddressCaptureReplay = VK_FALSE;
	ext.vk12_features.bufferDeviceAddressMultiDevice = VK_FALSE;
	ext.vk12_features.imagelessFramebuffer = VK_FALSE;

	ext.vk13_features.descriptorBindingInlineUniformBlockUpdateAfterBind = VK_FALSE;
	ext.vk13_features.inlineUniformBlock = VK_FALSE;
	ext.vk13_features.privateData = VK_FALSE;

	// Might be relevant when we move fully to 1.4.
	ext.vk14_features.dynamicRenderingLocalRead = VK_FALSE;
	ext.vk14_features.globalPriorityQuery = VK_FALSE;
	ext.vk14_features.pipelineProtectedAccess = VK_FALSE;
	ext.vk14_features.pipelineRobustness = VK_FALSE;
	ext.vk14_features.vertexAttributeInstanceRateDivisor = VK_FALSE;
	ext.vk14_features.vertexAttributeInstanceRateZeroDivisor = VK_FALSE;
	ext.vk14_features.stippledBresenhamLines = VK_FALSE;
	ext.vk14_features.stippledRectangularLines = VK_FALSE;
	ext.vk14_features.stippledSmoothLines = VK_FALSE;
	ext.vk14_features.rectangularLines = VK_FALSE;
	ext.vk14_features.smoothLines = VK_FALSE;
	ext.vk14_features.bresenhamLines = VK_FALSE;
	if ((flags & CONTEXT_CREATION_ENABLE_PUSH_DESCRIPTOR_BIT) == 0)
		ext.vk14_features.pushDescriptor = VK_FALSE;

	ext.mesh_shader_features.primitiveFragmentShadingRateMeshShader = VK_FALSE;
	ext.mesh_shader_features.meshShaderQueries = VK_FALSE;
	ext.mesh_shader_features.multiviewMeshShader = VK_FALSE;

	ext.descriptor_buffer_features.descriptorBufferCaptureReplay = VK_FALSE;
	ext.descriptor_buffer_features.descriptorBufferImageLayoutIgnored = VK_FALSE;
	ext.descriptor_buffer_features.descriptorBufferPushDescriptors = VK_FALSE;

	ext.descriptor_heap_features.descriptorHeapCaptureReplay = VK_FALSE;

	// Enable device features we might care about.
	{
		VkPhysicalDeviceFeatures enabled_features = *required_features;
		if (pdf2.features.robustBufferAccess && (flags & CONTEXT_CREATION_ENABLE_ROBUSTNESS_2_BIT) != 0)
			enabled_features.robustBufferAccess = VK_TRUE;
		if (pdf2.features.textureCompressionETC2)
			enabled_features.textureCompressionETC2 = VK_TRUE;
		if (pdf2.features.textureCompressionBC)
			enabled_features.textureCompressionBC = VK_TRUE;
		if (pdf2.features.textureCompressionASTC_LDR)
			enabled_features.textureCompressionASTC_LDR = VK_TRUE;
		if (pdf2.features.fullDrawIndexUint32)
			enabled_features.fullDrawIndexUint32 = VK_TRUE;
		if (pdf2.features.imageCubeArray)
			enabled_features.imageCubeArray = VK_TRUE;
		if (pdf2.features.fillModeNonSolid)
			enabled_features.fillModeNonSolid = VK_TRUE;
		if (pdf2.features.independentBlend)
			enabled_features.independentBlend = VK_TRUE;
		if (pdf2.features.sampleRateShading)
			enabled_features.sampleRateShading = VK_TRUE;
		if (pdf2.features.vertexPipelineStoresAndAtomics)
			enabled_features.vertexPipelineStoresAndAtomics = VK_TRUE;
		if (pdf2.features.fragmentStoresAndAtomics)
			enabled_features.fragmentStoresAndAtomics = VK_TRUE;
		if (pdf2.features.shaderStorageImageExtendedFormats)
			enabled_features.shaderStorageImageExtendedFormats = VK_TRUE;
		if (pdf2.features.shaderStorageImageMultisample)
			enabled_features.shaderStorageImageMultisample = VK_TRUE;
		if (pdf2.features.largePoints)
			enabled_features.largePoints = VK_TRUE;
		if (pdf2.features.shaderInt16)
			enabled_features.shaderInt16 = VK_TRUE;
		if (pdf2.features.shaderInt64)
			enabled_features.shaderInt64 = VK_TRUE;
		if (pdf2.features.shaderStorageImageWriteWithoutFormat)
			enabled_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
		if (pdf2.features.shaderStorageImageReadWithoutFormat)
			enabled_features.shaderStorageImageReadWithoutFormat = VK_TRUE;
		if (pdf2.features.multiDrawIndirect)
			enabled_features.multiDrawIndirect = VK_TRUE;

		if (pdf2.features.shaderSampledImageArrayDynamicIndexing)
			enabled_features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
		if (pdf2.features.shaderUniformBufferArrayDynamicIndexing)
			enabled_features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
		if (pdf2.features.shaderStorageBufferArrayDynamicIndexing)
			enabled_features.shaderStorageBufferArrayDynamicIndexing = VK_TRUE;
		if (pdf2.features.shaderStorageImageArrayDynamicIndexing)
			enabled_features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
		if (pdf2.features.shaderImageGatherExtended)
			enabled_features.shaderImageGatherExtended = VK_TRUE;

		if (pdf2.features.samplerAnisotropy)
			enabled_features.samplerAnisotropy = VK_TRUE;

		pdf2.features = enabled_features;
		ext.enabled_features = enabled_features;
	}

	device_info.pNext = inherit_info ? inherit_info->pNext : &pdf2;

#ifdef HAVE_GRANITE_VULKAN_POST_MORTEM
	VkPhysicalDeviceDiagnosticsConfigFeaturesNV diagnostic_config_nv;
	VkDeviceDiagnosticsConfigCreateInfoNV diagnostic_config_create_nv;
	if (has_extension(VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME))
	{
		enabled_extensions.push_back(VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME);
		diagnostic_config_nv = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV };
		diagnostic_config_create_nv = { VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV };
		diagnostic_config_nv.diagnosticsConfig = VK_TRUE;
		diagnostic_config_create_nv.flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV |
		                                    VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV |
		                                    VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV |
		                                    VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_ERROR_REPORTING_BIT_NV;
		diagnostic_config_nv.pNext = &diagnostic_config_create_nv;
		diagnostic_config_create_nv.pNext = device_info.pNext;
		device_info.pNext = &diagnostic_config_nv;
		PostMortem::init_nv_aftermath();
	}
#endif

	// Only need GetPhysicalDeviceProperties2 for Vulkan 1.1-only code, so don't bother getting KHR variant.
	VkPhysicalDeviceProperties2 props = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 };
	// Fallback, query some important Vulkan 1.1 structs if we cannot use core 1.2 method.
	VkPhysicalDeviceDriverProperties driver_properties = {};
	VkPhysicalDeviceIDProperties id_properties = {};
	VkPhysicalDeviceSubgroupProperties subgroup_properties = {};
	VkPhysicalDeviceSubgroupSizeControlProperties size_control_props = {};

#undef ADD_CHAIN
#define ADD_CHAIN(s, type) add_chain(&(s), sizeof(s), VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ ## type, nullptr)
	ppNext = reinterpret_cast<VkBaseOutStructure **>(&props.pNext);

	if (ext.device_api_core_version >= VK_API_VERSION_1_2)
	{
		ADD_CHAIN(ext.vk11_props, VULKAN_1_1_PROPERTIES);
		ADD_CHAIN(ext.vk12_props, VULKAN_1_2_PROPERTIES);
	}
	else
	{
		if (has_extension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME))
			ADD_CHAIN(driver_properties, DRIVER_PROPERTIES);
		ADD_CHAIN(id_properties, ID_PROPERTIES);
		ADD_CHAIN(subgroup_properties, SUBGROUP_PROPERTIES);
	}

	if (ext.device_api_core_version >= VK_API_VERSION_1_3)
		ADD_CHAIN(ext.vk13_props, VULKAN_1_3_PROPERTIES);
	else if (has_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME))
		ADD_CHAIN(size_control_props, SUBGROUP_SIZE_CONTROL_PROPERTIES);

	if (ext.device_api_core_version >= VK_API_VERSION_1_4)
		ADD_CHAIN(ext.vk14_props, VULKAN_1_4_PROPERTIES);

	if (ext.supports_external_memory_host)
		ADD_CHAIN(ext.host_memory_properties, EXTERNAL_MEMORY_HOST_PROPERTIES_EXT);

	if (has_extension(VK_EXT_DEVICE_GENERATED_COMMANDS_EXTENSION_NAME))
		ADD_CHAIN(ext.device_generated_commands_properties, DEVICE_GENERATED_COMMANDS_PROPERTIES_EXT);

	if (ext.supports_conservative_rasterization)
		ADD_CHAIN(ext.conservative_rasterization_properties, CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT);

	if (has_extension(VK_EXT_MESH_SHADER_EXTENSION_NAME))
		ADD_CHAIN(ext.mesh_shader_properties, MESH_SHADER_PROPERTIES_EXT);

	if ((flags & CONTEXT_CREATION_ENABLE_DESCRIPTOR_HEAP_BIT) != 0 &&
	    has_extension(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME))
	{
		ADD_CHAIN(ext.descriptor_heap_properties, DESCRIPTOR_HEAP_PROPERTIES_EXT);
	}
	else if ((flags & CONTEXT_CREATION_ENABLE_DESCRIPTOR_BUFFER_BIT) != 0 &&
	         has_extension(VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME))
	{
		ADD_CHAIN(ext.descriptor_buffer_properties, DESCRIPTOR_BUFFER_PROPERTIES_EXT);
	}

	if (has_extension(VK_KHR_RAY_QUERY_EXTENSION_NAME))
		ADD_CHAIN(ext.rtas_properties, ACCELERATION_STRUCTURE_PROPERTIES_KHR);

#ifndef HAVE_GRANITE_VULKAN_POST_MORTEM
	if ((flags & CONTEXT_CREATION_ENABLE_PIPELINE_BINARY_BIT) != 0 &&
	    has_extension(VK_KHR_PIPELINE_BINARY_EXTENSION_NAME))
		ADD_CHAIN(ext.pipeline_binary_properties, PIPELINE_BINARY_PROPERTIES_KHR);
#endif

	vkGetPhysicalDeviceProperties2(gpu, &props);

	// If a layer or driver doesn't tell us that internal cache is preferred,
	// go ahead and take full control over the cache.
	if (!ext.pipeline_binary_properties.pipelineBinaryPrefersInternalCache &&
	    ext.pipeline_binary_features.pipelineBinaries &&
	    ext.pipeline_binary_properties.pipelineBinaryInternalCacheControl)
	{
		ext.pipeline_binary_internal_cache_control.sType =
				VK_STRUCTURE_TYPE_DEVICE_PIPELINE_BINARY_INTERNAL_CACHE_CONTROL_KHR;
		ext.pipeline_binary_internal_cache_control.disableInternalCache = VK_TRUE;
		ext.pipeline_binary_internal_cache_control.pNext = device_info.pNext;
		device_info.pNext = &ext.pipeline_binary_internal_cache_control;
	}

	if (ext.device_api_core_version < VK_API_VERSION_1_2)
	{
		ext.driver_id = driver_properties.driverID;
		ext.supports_driver_properties = has_extension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME);
		ext.vk12_props.driverID = ext.driver_id;
		memcpy(ext.vk11_props.deviceUUID, id_properties.deviceUUID, sizeof(id_properties.deviceUUID));
		memcpy(ext.vk11_props.driverUUID, id_properties.driverUUID, sizeof(id_properties.driverUUID));
		memcpy(ext.vk11_props.deviceLUID, id_properties.deviceLUID, sizeof(id_properties.deviceLUID));
		ext.vk11_props.deviceNodeMask = id_properties.deviceNodeMask;
		ext.vk11_props.deviceLUIDValid = id_properties.deviceLUIDValid;
		ext.vk11_props.subgroupQuadOperationsInAllStages = subgroup_properties.quadOperationsInAllStages;
		ext.vk11_props.subgroupSupportedOperations = subgroup_properties.supportedOperations;
		ext.vk11_props.subgroupSupportedStages = subgroup_properties.supportedStages;
		ext.vk11_props.subgroupSize = subgroup_properties.subgroupSize;
	}
	else
	{
		ext.driver_id = ext.vk12_props.driverID;
		ext.supports_driver_properties = true;
	}

	if (ext.device_api_core_version < VK_API_VERSION_1_3)
	{
		ext.vk13_props.minSubgroupSize = size_control_props.minSubgroupSize;
		ext.vk13_props.maxSubgroupSize = size_control_props.maxSubgroupSize;
		ext.vk13_props.requiredSubgroupSizeStages = size_control_props.requiredSubgroupSizeStages;
		ext.vk13_props.maxComputeWorkgroupSubgroups = size_control_props.maxComputeWorkgroupSubgroups;
	}

#ifdef GRANITE_VULKAN_PROFILES
	// Override any properties in the profile in strict mode.
	if (profile.profile && required_profile_strict)
		vpGetProfileProperties(profile.profile, &props);
#endif

	// PUNKTFUNK PATCH (patches/0005-global-priority-queue.patch, NOT upstream): request a high
	// global-priority queue so PyroWave's compute-shader encode can PREEMPT a GPU-bound game on
	// the shared shader cores. A WDDM process-priority raise does not help (it only orders packet
	// submission, not hardware preemption); a global-priority queue is the actual NVIDIA compute-
	// preemption lever. `PYROWAVE_QUEUE_PRIORITY` = off | high | realtime (default realtime). The
	// device-create loop below downgrades on NOT_PERMITTED, so a refused class NEVER fails the
	// encoder — it just runs at default priority. Only the fresh encoder device (no inherit_info)
	// is affected; this Granite copy is vendored solely for the PyroWave codec.
	Util::SmallVector<VkQueueGlobalPriorityKHR> pf_priority_candidates;
	Util::SmallVector<VkDeviceQueueGlobalPriorityCreateInfoKHR> pf_global_priority_infos;
	if (!inherit_info)
	{
		const char *pf_gp_ext = nullptr;
		if (has_extension(VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME))
			pf_gp_ext = VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME;
		else if (has_extension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME))
			pf_gp_ext = VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME;
		std::string pf_prio;
		if (!Util::get_environment("PYROWAVE_QUEUE_PRIORITY", pf_prio))
			pf_prio = "realtime";
		for (auto &pf_ch : pf_prio)
			if (pf_ch >= 'A' && pf_ch <= 'Z')
				pf_ch = char(pf_ch + 32);
		if (pf_gp_ext && pf_prio != "off")
		{
			if (pf_prio == "high")
			{
				pf_priority_candidates.push_back(VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR);
			}
			else
			{
				pf_priority_candidates.push_back(VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR);
				pf_priority_candidates.push_back(VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR);
			}
			enabled_extensions.push_back(pf_gp_ext);
			pf_global_priority_infos.resize(queue_infos.size());
			for (size_t pf_i = 0; pf_i < queue_infos.size(); pf_i++)
			{
				pf_global_priority_infos[pf_i] = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR };
				pf_global_priority_infos[pf_i].globalPriority = pf_priority_candidates.front();
				queue_infos[pf_i].pNext = &pf_global_priority_infos[pf_i];
			}
		}
	}

	if (inherit_info)
	{
		device_info.enabledExtensionCount = inherit_info->enabledExtensionCount;
		device_info.ppEnabledExtensionNames = inherit_info->ppEnabledExtensionNames;
	}
	else
	{
		device_info.enabledExtensionCount = enabled_extensions.size();
		device_info.ppEnabledExtensionNames = enabled_extensions.empty() ? nullptr : enabled_extensions.data();
	}

	for (uint32_t i = 0; i < device_info.enabledExtensionCount; i++)
		LOGI("Enabling device extension: %s.\n", device_info.ppEnabledExtensionNames[i]);

#ifdef GRANITE_VULKAN_PROFILES
	if (!required_profile.empty())
	{
		if (create_device_from_profile(device_info, &device) != VK_SUCCESS)
			return false;
	}
	else
#endif
	{
		if (device_factory)
		{
			device = device_factory->create_device(gpu, &device_info);
			if (device == VK_NULL_HANDLE)
				return false;
		}
		else
		{
			// PUNKTFUNK: try the requested global-priority class, downgrade through the
			// candidate list on NOT_PERMITTED, then finally create with no global priority.
			VkResult pf_res;
			if (pf_priority_candidates.empty())
			{
				pf_res = vkCreateDevice(gpu, &device_info, nullptr, &device);
			}
			else
			{
				pf_res = VK_ERROR_NOT_PERMITTED_KHR;
				for (size_t pf_a = 0; pf_a < pf_priority_candidates.size(); pf_a++)
				{
					for (auto &pf_gp : pf_global_priority_infos)
						pf_gp.globalPriority = pf_priority_candidates[pf_a];
					pf_res = vkCreateDevice(gpu, &device_info, nullptr, &device);
					if (pf_res != VK_ERROR_NOT_PERMITTED_KHR)
						break;
					LOGW("PyroWave: global queue priority %u not permitted; downgrading.\n",
						unsigned(pf_priority_candidates[pf_a]));
				}
				if (pf_res == VK_ERROR_NOT_PERMITTED_KHR)
				{
					for (auto &pf_qi : queue_infos)
						pf_qi.pNext = nullptr;
					pf_res = vkCreateDevice(gpu, &device_info, nullptr, &device);
					LOGW("PyroWave: all global queue priorities refused; default priority.\n");
				}
				else
					LOGI("PyroWave: encode device created with an elevated global queue priority.\n");
			}
			if (pf_res != VK_SUCCESS)
				return false;
		}
	}

	if (inherit_info)
	{
		ext.device_extensions = inherit_info->ppEnabledExtensionNames;
		ext.num_device_extensions = inherit_info->enabledExtensionCount;

		if (inherit_info->pNext)
		{
			ext.pdf2 = static_cast<const VkPhysicalDeviceFeatures2 *>(inherit_info->pNext);
			VK_ASSERT(ext.pdf2->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2);
		}
		else
		{
			ext.pdf2 = &pdf2;
		}
	}
	else
	{
		enabled_device_extensions = std::move(enabled_extensions);
		ext.device_extensions = enabled_device_extensions.data();
		ext.num_device_extensions = uint32_t(enabled_device_extensions.size());
		ext.pdf2 = &pdf2;
	}

#ifdef GRANITE_VULKAN_FOSSILIZE
	feature_filter.init(ext.device_api_core_version,
	                    const_cast<const char **>(device_info.ppEnabledExtensionNames),
	                    device_info.enabledExtensionCount,
	                    &pdf2, &props);
	feature_filter.set_device_query_interface(this);
#endif

	volkLoadDeviceTable(&device_table, device);

#define PROMOTE_CALL(n, e) if (!device_table.vk##n) device_table.vk##n = device_table.vk##n##e
	PROMOTE_CALL(CreateRenderPass2, KHR);
	PROMOTE_CALL(QueueSubmit2, KHR);
	PROMOTE_CALL(CmdPipelineBarrier2, KHR);
	PROMOTE_CALL(CmdWriteTimestamp2, KHR);
	PROMOTE_CALL(CmdSetEvent2, KHR);
	PROMOTE_CALL(CmdResetEvent2, KHR);
	PROMOTE_CALL(CmdWaitEvents2, KHR);
	PROMOTE_CALL(ResetQueryPool, EXT);
	PROMOTE_CALL(CmdPushDescriptorSetWithTemplate, KHR);
#undef PROMOTE_CALL

	for (int i = 0; i < QUEUE_INDEX_COUNT; i++)
	{
		if (queue_info.family_indices[i] != VK_QUEUE_FAMILY_IGNORED)
		{
			queue_info.queues[i] = device_factory ?
				device_factory->get_queue(queue_info.family_indices[i], queue_indices[i]) : VK_NULL_HANDLE;

			if (!queue_info.queues[i])
			{
				device_table.vkGetDeviceQueue(device, queue_info.family_indices[i], queue_indices[i],
											  &queue_info.queues[i]);
			}

			queue_info.counts[i] = queue_offsets[queue_info.family_indices[i]];

#if defined(ANDROID) && defined(HAVE_SWAPPY)
			SwappyVk_setQueueFamilyIndex(device, queue_info.queues[i], queue_info.family_indices[i]);
#endif
		}
		else
		{
			queue_info.queues[i] = VK_NULL_HANDLE;
		}
	}

#ifdef VULKAN_DEBUG
	static const char *family_names[QUEUE_INDEX_COUNT] = { "Graphics", "Compute", "Transfer", "Video decode", "Video encode" };
	for (int i = 0; i < QUEUE_INDEX_COUNT; i++)
		if (queue_info.family_indices[i] != VK_QUEUE_FAMILY_IGNORED)
			LOGI("%s queue: family %u, index %u.\n", family_names[i], queue_info.family_indices[i], queue_indices[i]);
#endif

	if (ext.descriptor_buffer_features.descriptorBuffer)
	{
		auto max_heap_size = std::min<VkDeviceSize>(
				ext.descriptor_buffer_properties.maxSamplerDescriptorBufferRange,
				ext.descriptor_buffer_properties.maxResourceDescriptorBufferRange);

		// Only expose this if we can easily linearly allocate samplers and combined image samplers.
		// Cba to deal with planar arrays of combined image samplers either.
		ext.supports_descriptor_buffer =
				ext.descriptor_buffer_properties.samplerDescriptorSize * 512ull * 1024ull <= max_heap_size &&
				ext.descriptor_buffer_properties.sampledImageDescriptorSize * 512ull * 1024ull <= max_heap_size &&
				ext.descriptor_buffer_properties.combinedImageSamplerDescriptorSingleArray;
	}

	ext.supports_descriptor_buffer_or_heap =
		ext.supports_descriptor_buffer || ext.descriptor_heap_features.descriptorHeap;

	if (ext.descriptor_heap_features.descriptorHeap)
	{
		ext.resource_heap_offset_alignment = std::max<uint32_t>(
			ext.descriptor_heap_properties.bufferDescriptorAlignment,
			ext.descriptor_heap_properties.imageDescriptorAlignment);

		ext.resource_heap_resource_desc_size = std::max<uint32_t>(
			ext.descriptor_heap_properties.bufferDescriptorSize,
			ext.resource_heap_resource_desc_size);

		ext.resource_heap_resource_desc_size = std::max<uint32_t>(
			ext.descriptor_heap_properties.imageDescriptorSize,
			ext.resource_heap_resource_desc_size);

		ext.resource_heap_resource_desc_size = Util::next_pow2(ext.resource_heap_resource_desc_size);
		ext.resource_heap_resource_desc_size_log2 = Util::floor_log2(ext.resource_heap_resource_desc_size);
	}
	else
	{
		ext.resource_heap_offset_alignment = ext.descriptor_buffer_properties.descriptorBufferOffsetAlignment;
	}

	return true;
}

const VkDeviceCreateInfo *DeviceFactory::get_existing_create_info()
{
	return nullptr;
}

bool DeviceFactory::factory_owns_created_device()
{
	return false;
}

VkQueue DeviceFactory::get_queue(uint32_t, uint32_t)
{
	return VK_NULL_HANDLE;
}

const VkInstanceCreateInfo *InstanceFactory::get_existing_create_info()
{
	return nullptr;
}

bool InstanceFactory::factory_owns_created_instance()
{
	return false;
}

#ifdef GRANITE_VULKAN_FOSSILIZE
bool Context::format_is_supported(VkFormat format, VkFormatFeatureFlags features)
{
	if (gpu == VK_NULL_HANDLE)
		return false;

	VkFormatProperties props;
	vkGetPhysicalDeviceFormatProperties(gpu, format, &props);
	auto supported = props.bufferFeatures | props.linearTilingFeatures | props.optimalTilingFeatures;
	return (supported & features) == features;
}

bool Context::descriptor_set_layout_is_supported(const VkDescriptorSetLayoutCreateInfo *set_layout)
{
	if (device == VK_NULL_HANDLE)
		return false;

	VkDescriptorSetLayoutSupport support = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT };
	vkGetDescriptorSetLayoutSupport(device, set_layout, &support);
	return support.supported == VK_TRUE;
}

void Context::physical_device_feature_query(VkPhysicalDeviceFeatures2 *pdf2_)
{
	if (gpu && vkGetPhysicalDeviceFeatures2)
		vkGetPhysicalDeviceFeatures2(gpu, pdf2_);
}
#endif
}
