/* 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 #include #include #include #ifndef _WIN32 #include #elif defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #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 = ∝ } Util::SmallVector 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 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(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 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(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 instance_exts; std::vector 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 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 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 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 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 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 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 queue_props(queue_family_count); Util::SmallVector 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 queue_offsets(queue_family_count); Util::SmallVector> 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 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 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 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 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(&pdf2.pNext); const auto add_chain = [&](void *pnext, size_t size, VkStructureType type, const VkDeviceCreateInfo *device_create_info) { auto *sout = static_cast(pnext); memset(sout, 0, size); sout->sType = type; if (device_create_info) { auto *existing = find_pnext(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(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(&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 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 if (vkCreateDevice(gpu, &device_info, nullptr, &device) != 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(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(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( 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( ext.descriptor_heap_properties.bufferDescriptorAlignment, ext.descriptor_heap_properties.imageDescriptorAlignment); ext.resource_heap_resource_desc_size = std::max( ext.descriptor_heap_properties.bufferDescriptorSize, ext.resource_heap_resource_desc_size); ext.resource_heap_resource_desc_size = std::max( 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 }