#define __STDC_LIMIT_MACROS #include #include #include #include #include "Common/System/System.h" #include "Common/System/Display.h" #include "Common/Log.h" #include "Common/GPU/Shader.h" #include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanDebug.h" #include "Common/StringUtils.h" #ifdef USE_CRT_DBG #undef new #endif #include "ext/vma/vk_mem_alloc.h" // Change this to 1, 2, and 3 to fake failures in a few places, so that // we can test our fallback-to-GL code. #define SIMULATE_VULKAN_FAILURE 0 #include "ext/glslang/SPIRV/GlslangToSpv.h" #ifdef USE_CRT_DBG #define new DBG_NEW #endif using namespace PPSSPP_VK; VulkanLogOptions g_LogOptions; static const char *validationLayers[] = { "VK_LAYER_KHRONOS_validation", /* // For layers included in the Android NDK. "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation", "VK_LAYER_LUNARG_core_validation", "VK_LAYER_LUNARG_image", "VK_LAYER_LUNARG_object_tracker", "VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects", */ }; std::string VulkanVendorString(uint32_t vendorId) { switch (vendorId) { case VULKAN_VENDOR_INTEL: return "Intel"; case VULKAN_VENDOR_NVIDIA: return "NVIDIA"; case VULKAN_VENDOR_AMD: return "AMD"; case VULKAN_VENDOR_ARM: return "ARM"; case VULKAN_VENDOR_QUALCOMM: return "Qualcomm"; case VULKAN_VENDOR_IMGTEC: return "Imagination"; case VULKAN_VENDOR_APPLE: return "Apple"; default: return StringFromFormat("%08x", vendorId); } } const char *PresentModeString(VkPresentModeKHR presentMode) { switch (presentMode) { case VK_PRESENT_MODE_IMMEDIATE_KHR: return "IMMEDIATE"; case VK_PRESENT_MODE_MAILBOX_KHR: return "MAILBOX"; case VK_PRESENT_MODE_FIFO_KHR: return "FIFO"; case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return "FIFO_RELAXED"; case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: return "SHARED_DEMAND_REFRESH_KHR"; case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: return "SHARED_CONTINUOUS_REFRESH_KHR"; default: return "UNKNOWN"; } } VulkanContext::VulkanContext() { // Do nothing here. } VkResult VulkanContext::CreateInstance(const CreateInfo &info) { if (!vkCreateInstance) { init_error_ = "Vulkan not loaded - can't create instance"; return VK_ERROR_INITIALIZATION_FAILED; } instance_layer_names_.clear(); device_layer_names_.clear(); // We can get the list of layers and extensions without an instance so we can use this information // to enable the extensions we need that are available. GetInstanceLayerProperties(); GetInstanceLayerExtensionList(nullptr, instance_extension_properties_); if (!IsInstanceExtensionAvailable(VK_KHR_SURFACE_EXTENSION_NAME)) { // Cannot create a Vulkan display without VK_KHR_SURFACE_EXTENSION. init_error_ = "Vulkan not loaded - no surface extension"; return VK_ERROR_INITIALIZATION_FAILED; } flags_ = info.flags; // List extensions to try to enable. instance_extensions_enabled_.push_back(VK_KHR_SURFACE_EXTENSION_NAME); #ifdef _WIN32 instance_extensions_enabled_.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif defined(__ANDROID__) instance_extensions_enabled_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #else #if defined(VK_USE_PLATFORM_XLIB_KHR) if (IsInstanceExtensionAvailable(VK_KHR_XLIB_SURFACE_EXTENSION_NAME)) { instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); } #endif //#if defined(VK_USE_PLATFORM_XCB_KHR) // instance_extensions_enabled_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); //#endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) if (IsInstanceExtensionAvailable(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME)) { instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); } #endif #if defined(VK_USE_PLATFORM_DISPLAY_KHR) if (IsInstanceExtensionAvailable(VK_KHR_DISPLAY_EXTENSION_NAME)) { instance_extensions_enabled_.push_back(VK_KHR_DISPLAY_EXTENSION_NAME); } #endif #if defined(VK_USE_PLATFORM_METAL_EXT) if (IsInstanceExtensionAvailable(VK_EXT_METAL_SURFACE_EXTENSION_NAME)) { instance_extensions_enabled_.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); } #endif #endif if (flags_ & VULKAN_FLAG_VALIDATE) { if (IsInstanceExtensionAvailable(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { // Enable the validation layers for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) { instance_layer_names_.push_back(validationLayers[i]); device_layer_names_.push_back(validationLayers[i]); } instance_extensions_enabled_.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); extensionsLookup_.EXT_debug_utils = true; INFO_LOG(G3D, "Vulkan debug_utils validation enabled."); } else { ERROR_LOG(G3D, "Validation layer extension not available - not enabling Vulkan validation."); flags_ &= ~VULKAN_FLAG_VALIDATE; } } // Temporary hack for libretro. For some reason, when we try to load the functions from this extension, // we get null pointers when running libretro. Quite strange. #if !defined(__LIBRETRO__) if (EnableInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) { extensionsLookup_.KHR_get_physical_device_properties2 = true; } #endif if (EnableInstanceExtension(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) { extensionsLookup_.EXT_swapchain_colorspace = true; } // Validate that all the instance extensions we ask for are actually available. for (auto ext : instance_extensions_enabled_) { if (!IsInstanceExtensionAvailable(ext)) WARN_LOG(G3D, "WARNING: Does not seem that instance extension '%s' is available. Trying to proceed anyway.", ext); } // Check which Vulkan version we should request. // Our code is fine with any version from 1.0 to 1.2, we don't know about higher versions. u32 vulkanApiVersion = VK_API_VERSION_1_0; if (vkEnumerateInstanceVersion) { vkEnumerateInstanceVersion(&vulkanApiVersion); vulkanApiVersion &= 0xFFFFF000; // Remove patch version. vulkanApiVersion = std::min(VK_API_VERSION_1_2, vulkanApiVersion); } VkApplicationInfo app_info{ VK_STRUCTURE_TYPE_APPLICATION_INFO }; app_info.pApplicationName = info.app_name; app_info.applicationVersion = info.app_ver; app_info.pEngineName = info.app_name; // Let's increment this when we make major engine/context changes. app_info.engineVersion = 2; app_info.apiVersion = vulkanApiVersion; VkInstanceCreateInfo inst_info{ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; inst_info.flags = 0; inst_info.pApplicationInfo = &app_info; inst_info.enabledLayerCount = (uint32_t)instance_layer_names_.size(); inst_info.ppEnabledLayerNames = instance_layer_names_.size() ? instance_layer_names_.data() : nullptr; inst_info.enabledExtensionCount = (uint32_t)instance_extensions_enabled_.size(); inst_info.ppEnabledExtensionNames = instance_extensions_enabled_.size() ? instance_extensions_enabled_.data() : nullptr; #if SIMULATE_VULKAN_FAILURE == 2 VkResult res = VK_ERROR_INCOMPATIBLE_DRIVER; #else VkResult res = vkCreateInstance(&inst_info, nullptr, &instance_); #endif if (res != VK_SUCCESS) { if (res == VK_ERROR_LAYER_NOT_PRESENT) { WARN_LOG(G3D, "Validation on but instance layer not available - dropping layers"); // Drop the validation layers and try again. instance_layer_names_.clear(); device_layer_names_.clear(); inst_info.enabledLayerCount = 0; inst_info.ppEnabledLayerNames = nullptr; res = vkCreateInstance(&inst_info, nullptr, &instance_); if (res != VK_SUCCESS) ERROR_LOG(G3D, "Failed to create instance even without validation: %d", res); } else { ERROR_LOG(G3D, "Failed to create instance : %d", res); } } if (res != VK_SUCCESS) { init_error_ = "Failed to create Vulkan instance"; return res; } VulkanLoadInstanceFunctions(instance_, extensionsLookup_); if (!CheckLayers(instance_layer_properties_, instance_layer_names_)) { WARN_LOG(G3D, "CheckLayers for instance failed"); // init_error_ = "Failed to validate instance layers"; // return; } uint32_t gpu_count = 1; #if SIMULATE_VULKAN_FAILURE == 3 gpu_count = 0; #else res = vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr); #endif if (gpu_count <= 0) { ERROR_LOG(G3D, "Vulkan driver found but no supported GPU is available"); init_error_ = "No Vulkan physical devices found"; vkDestroyInstance(instance_, nullptr); instance_ = nullptr; return VK_ERROR_INITIALIZATION_FAILED; } _dbg_assert_(gpu_count > 0); physical_devices_.resize(gpu_count); physicalDeviceProperties_.resize(gpu_count); res = vkEnumeratePhysicalDevices(instance_, &gpu_count, physical_devices_.data()); if (res != VK_SUCCESS) { init_error_ = "Failed to enumerate physical devices"; vkDestroyInstance(instance_, nullptr); instance_ = nullptr; return res; } if (extensionsLookup_.KHR_get_physical_device_properties2) { for (uint32_t i = 0; i < gpu_count; i++) { VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; VkPhysicalDevicePushDescriptorPropertiesKHR pushProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR}; VkPhysicalDeviceExternalMemoryHostPropertiesEXT extHostMemProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT}; VkPhysicalDeviceDepthStencilResolveProperties depthStencilResolveProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES}; props2.pNext = &pushProps; pushProps.pNext = &extHostMemProps; extHostMemProps.pNext = &depthStencilResolveProps; vkGetPhysicalDeviceProperties2KHR(physical_devices_[i], &props2); // Don't want bad pointers sitting around. props2.pNext = nullptr; pushProps.pNext = nullptr; extHostMemProps.pNext = nullptr; depthStencilResolveProps.pNext = nullptr; physicalDeviceProperties_[i].properties = props2.properties; physicalDeviceProperties_[i].pushDescriptorProperties = pushProps; physicalDeviceProperties_[i].externalMemoryHostProperties = extHostMemProps; physicalDeviceProperties_[i].depthStencilResolve = depthStencilResolveProps; } } else { for (uint32_t i = 0; i < gpu_count; i++) { vkGetPhysicalDeviceProperties(physical_devices_[i], &physicalDeviceProperties_[i].properties); } } if (extensionsLookup_.EXT_debug_utils) { InitDebugUtilsCallback(); } return VK_SUCCESS; } VulkanContext::~VulkanContext() { _dbg_assert_(instance_ == VK_NULL_HANDLE); } void VulkanContext::DestroyInstance() { if (extensionsLookup_.EXT_debug_utils) { while (utils_callbacks.size() > 0) { vkDestroyDebugUtilsMessengerEXT(instance_, utils_callbacks.back(), nullptr); utils_callbacks.pop_back(); } } vkDestroyInstance(instance_, nullptr); VulkanFree(); instance_ = VK_NULL_HANDLE; } void VulkanContext::BeginFrame(VkCommandBuffer firstCommandBuffer) { FrameData *frame = &frame_[curFrame_]; // Process pending deletes. frame->deleteList.PerformDeletes(this, allocator_); // VK_NULL_HANDLE when profiler is disabled. if (firstCommandBuffer) { frame->profiler.BeginFrame(this, firstCommandBuffer); } } void VulkanContext::EndFrame() { frame_[curFrame_].deleteList.Take(globalDeleteList_); curFrame_++; if (curFrame_ >= inflightFrames_) { curFrame_ = 0; } } void VulkanContext::UpdateInflightFrames(int n) { _dbg_assert_(n >= 1 && n <= MAX_INFLIGHT_FRAMES); inflightFrames_ = n; if (curFrame_ >= inflightFrames_) { curFrame_ = 0; } } void VulkanContext::WaitUntilQueueIdle() { // Should almost never be used vkQueueWaitIdle(gfx_queue_); } bool VulkanContext::MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) { // Search memtypes to find first index with those properties for (uint32_t i = 0; i < 32; i++) { if ((typeBits & 1) == 1) { // Type is available, does it match user properties? if ((memory_properties_.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) { *typeIndex = i; return true; } } typeBits >>= 1; } // No memory types matched, return failure return false; } void VulkanContext::DestroySwapchain() { if (swapchain_ != VK_NULL_HANDLE) { vkDestroySwapchainKHR(device_, swapchain_, nullptr); swapchain_ = VK_NULL_HANDLE; } } void VulkanContext::DestroySurface() { if (surface_ != VK_NULL_HANDLE) { vkDestroySurfaceKHR(instance_, surface_, nullptr); surface_ = VK_NULL_HANDLE; } } VkResult VulkanContext::GetInstanceLayerExtensionList(const char *layerName, std::vector &extensions) { VkResult res; do { uint32_t instance_extension_count; res = vkEnumerateInstanceExtensionProperties(layerName, &instance_extension_count, nullptr); if (res != VK_SUCCESS) return res; if (instance_extension_count == 0) return VK_SUCCESS; extensions.resize(instance_extension_count); res = vkEnumerateInstanceExtensionProperties(layerName, &instance_extension_count, extensions.data()); } while (res == VK_INCOMPLETE); return res; } VkResult VulkanContext::GetInstanceLayerProperties() { /* * It's possible, though very rare, that the number of * instance layers could change. For example, installing something * could include new layers that the loader would pick up * between the initial query for the count and the * request for VkLayerProperties. The loader indicates that * by returning a VK_INCOMPLETE status and will update the * the count parameter. * The count parameter will be updated with the number of * entries loaded into the data pointer - in case the number * of layers went down or is smaller than the size given. */ uint32_t instance_layer_count; std::vector vk_props; VkResult res; do { res = vkEnumerateInstanceLayerProperties(&instance_layer_count, nullptr); if (res != VK_SUCCESS) return res; if (!instance_layer_count) return VK_SUCCESS; vk_props.resize(instance_layer_count); res = vkEnumerateInstanceLayerProperties(&instance_layer_count, vk_props.data()); } while (res == VK_INCOMPLETE); // Now gather the extension list for each instance layer. for (uint32_t i = 0; i < instance_layer_count; i++) { LayerProperties layer_props; layer_props.properties = vk_props[i]; res = GetInstanceLayerExtensionList(layer_props.properties.layerName, layer_props.extensions); if (res != VK_SUCCESS) return res; instance_layer_properties_.push_back(layer_props); } return res; } // Pass layerName == nullptr to get the extension list for the device. VkResult VulkanContext::GetDeviceLayerExtensionList(const char *layerName, std::vector &extensions) { VkResult res; do { uint32_t device_extension_count; res = vkEnumerateDeviceExtensionProperties(physical_devices_[physical_device_], layerName, &device_extension_count, nullptr); if (res != VK_SUCCESS) return res; if (!device_extension_count) return VK_SUCCESS; extensions.resize(device_extension_count); res = vkEnumerateDeviceExtensionProperties(physical_devices_[physical_device_], layerName, &device_extension_count, extensions.data()); } while (res == VK_INCOMPLETE); return res; } VkResult VulkanContext::GetDeviceLayerProperties() { /* * It's possible, though very rare, that the number of * instance layers could change. For example, installing something * could include new layers that the loader would pick up * between the initial query for the count and the * request for VkLayerProperties. The loader indicates that * by returning a VK_INCOMPLETE status and will update the * the count parameter. * The count parameter will be updated with the number of * entries loaded into the data pointer - in case the number * of layers went down or is smaller than the size given. */ uint32_t device_layer_count; std::vector vk_props; VkResult res; do { res = vkEnumerateDeviceLayerProperties(physical_devices_[physical_device_], &device_layer_count, nullptr); if (res != VK_SUCCESS) return res; if (device_layer_count == 0) return VK_SUCCESS; vk_props.resize(device_layer_count); res = vkEnumerateDeviceLayerProperties(physical_devices_[physical_device_], &device_layer_count, vk_props.data()); } while (res == VK_INCOMPLETE); // Gather the list of extensions for each device layer. for (uint32_t i = 0; i < device_layer_count; i++) { LayerProperties layer_props; layer_props.properties = vk_props[i]; res = GetDeviceLayerExtensionList(layer_props.properties.layerName, layer_props.extensions); if (res != VK_SUCCESS) return res; device_layer_properties_.push_back(layer_props); } return res; } // Returns true if all layer names specified in check_names can be found in given layer properties. bool VulkanContext::CheckLayers(const std::vector &layer_props, const std::vector &layer_names) const { uint32_t check_count = (uint32_t)layer_names.size(); uint32_t layer_count = (uint32_t)layer_props.size(); for (uint32_t i = 0; i < check_count; i++) { bool found = false; for (uint32_t j = 0; j < layer_count; j++) { if (!strcmp(layer_names[i], layer_props[j].properties.layerName)) { found = true; } } if (!found) { std::cout << "Cannot find layer: " << layer_names[i] << std::endl; return false; } } return true; } int VulkanContext::GetPhysicalDeviceByName(std::string name) { for (size_t i = 0; i < physical_devices_.size(); i++) { if (physicalDeviceProperties_[i].properties.deviceName == name) return (int)i; } return -1; } int VulkanContext::GetBestPhysicalDevice() { // Rules: Prefer discrete over embedded. // Prefer nVidia over Intel. int maxScore = -1; int best = -1; for (size_t i = 0; i < physical_devices_.size(); i++) { int score = 0; VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(physical_devices_[i], &props); switch (props.deviceType) { case VK_PHYSICAL_DEVICE_TYPE_CPU: score += 1; break; case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: score += 2; break; case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: score += 20; break; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: score += 10; break; default: break; } if (props.vendorID == VULKAN_VENDOR_AMD) { score += 5; } else if (props.vendorID == VULKAN_VENDOR_NVIDIA) { score += 5; } if (score > maxScore) { best = (int)i; maxScore = score; } } return best; } void VulkanContext::ChooseDevice(int physical_device) { physical_device_ = physical_device; INFO_LOG(G3D, "Chose physical device %d: %p", physical_device, physical_devices_[physical_device]); GetDeviceLayerProperties(); if (!CheckLayers(device_layer_properties_, device_layer_names_)) { WARN_LOG(G3D, "CheckLayers for device %d failed", physical_device); } vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, nullptr); _dbg_assert_(queue_count >= 1); queueFamilyProperties_.resize(queue_count); vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, queueFamilyProperties_.data()); _dbg_assert_(queue_count >= 1); // Detect preferred formats, in this order. static const VkFormat depthStencilFormats[] = { VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, }; deviceInfo_.preferredDepthStencilFormat = VK_FORMAT_UNDEFINED; for (size_t i = 0; i < ARRAY_SIZE(depthStencilFormats); i++) { VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(physical_devices_[physical_device_], depthStencilFormats[i], &props); if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { deviceInfo_.preferredDepthStencilFormat = depthStencilFormats[i]; break; } } _assert_msg_(deviceInfo_.preferredDepthStencilFormat != VK_FORMAT_UNDEFINED, "Could not find a usable depth stencil format."); VkFormatProperties preferredProps; vkGetPhysicalDeviceFormatProperties(physical_devices_[physical_device_], deviceInfo_.preferredDepthStencilFormat, &preferredProps); if ((preferredProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) && (preferredProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) { deviceInfo_.canBlitToPreferredDepthStencilFormat = true; } // This is as good a place as any to do this. vkGetPhysicalDeviceMemoryProperties(physical_devices_[physical_device_], &memory_properties_); INFO_LOG(G3D, "Memory Types (%d):", memory_properties_.memoryTypeCount); for (int i = 0; i < (int)memory_properties_.memoryTypeCount; i++) { // Don't bother printing dummy memory types. if (!memory_properties_.memoryTypes[i].propertyFlags) continue; INFO_LOG(G3D, " %d: Heap %d; Flags: %s%s%s%s ", i, memory_properties_.memoryTypes[i].heapIndex, (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ? "DEVICE_LOCAL " : "", (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ? "HOST_VISIBLE " : "", (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) ? "HOST_CACHED " : "", (memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) ? "HOST_COHERENT " : ""); } // Optional features if (extensionsLookup_.KHR_get_physical_device_properties2) { VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR}; // Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs. VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES }; features2.pNext = &multiViewFeatures; vkGetPhysicalDeviceFeatures2KHR(physical_devices_[physical_device_], &features2); deviceFeatures_.available.standard = features2.features; deviceFeatures_.available.multiview = multiViewFeatures; } else { vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard); deviceFeatures_.available.multiview = {}; } deviceFeatures_.enabled = {}; // Enable a few safe ones if they are available. deviceFeatures_.enabled.standard.dualSrcBlend = deviceFeatures_.available.standard.dualSrcBlend; deviceFeatures_.enabled.standard.logicOp = deviceFeatures_.available.standard.logicOp; deviceFeatures_.enabled.standard.depthClamp = deviceFeatures_.available.standard.depthClamp; deviceFeatures_.enabled.standard.depthBounds = deviceFeatures_.available.standard.depthBounds; deviceFeatures_.enabled.standard.samplerAnisotropy = deviceFeatures_.available.standard.samplerAnisotropy; deviceFeatures_.enabled.standard.shaderClipDistance = deviceFeatures_.available.standard.shaderClipDistance; deviceFeatures_.enabled.standard.shaderCullDistance = deviceFeatures_.available.standard.shaderCullDistance; deviceFeatures_.enabled.standard.geometryShader = deviceFeatures_.available.standard.geometryShader; deviceFeatures_.enabled.standard.sampleRateShading = deviceFeatures_.available.standard.sampleRateShading; deviceFeatures_.enabled.multiview = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES }; deviceFeatures_.enabled.multiview.multiview = deviceFeatures_.available.multiview.multiview; // deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader; GetDeviceLayerExtensionList(nullptr, device_extension_properties_); device_extensions_enabled_.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); } bool VulkanContext::EnableDeviceExtension(const char *extension) { for (auto &iter : device_extension_properties_) { if (!strcmp(iter.extensionName, extension)) { device_extensions_enabled_.push_back(extension); return true; } } return false; } bool VulkanContext::EnableInstanceExtension(const char *extension) { for (auto &iter : instance_extension_properties_) { if (!strcmp(iter.extensionName, extension)) { instance_extensions_enabled_.push_back(extension); return true; } } return false; } VkResult VulkanContext::CreateDevice() { if (!init_error_.empty() || physical_device_ < 0) { ERROR_LOG(G3D, "Vulkan init failed: %s", init_error_.c_str()); return VK_ERROR_INITIALIZATION_FAILED; } VkDeviceQueueCreateInfo queue_info{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; float queue_priorities[1] = {1.0f}; queue_info.queueCount = 1; queue_info.pQueuePriorities = queue_priorities; bool found = false; for (int i = 0; i < (int)queue_count; i++) { if (queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { queue_info.queueFamilyIndex = i; found = true; break; } } _dbg_assert_(found); // TODO: A lot of these are on by default in later Vulkan versions, should check for that, technically. extensionsLookup_.KHR_maintenance1 = EnableDeviceExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME); extensionsLookup_.KHR_maintenance2 = EnableDeviceExtension(VK_KHR_MAINTENANCE2_EXTENSION_NAME); extensionsLookup_.KHR_maintenance3 = EnableDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME); extensionsLookup_.KHR_multiview = EnableDeviceExtension(VK_KHR_MULTIVIEW_EXTENSION_NAME); if (EnableDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME)) { extensionsLookup_.KHR_get_memory_requirements2 = true; extensionsLookup_.KHR_dedicated_allocation = EnableDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); } if (EnableDeviceExtension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME)) { if (EnableDeviceExtension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME)) { extensionsLookup_.EXT_external_memory_host = EnableDeviceExtension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); } } if (EnableDeviceExtension(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) { extensionsLookup_.KHR_create_renderpass2 = true; extensionsLookup_.KHR_depth_stencil_resolve = EnableDeviceExtension(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); } extensionsLookup_.EXT_shader_stencil_export = EnableDeviceExtension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME); extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME); VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; device_info.queueCreateInfoCount = 1; device_info.pQueueCreateInfos = &queue_info; device_info.enabledLayerCount = (uint32_t)device_layer_names_.size(); device_info.ppEnabledLayerNames = device_info.enabledLayerCount ? device_layer_names_.data() : nullptr; device_info.enabledExtensionCount = (uint32_t)device_extensions_enabled_.size(); device_info.ppEnabledExtensionNames = device_info.enabledExtensionCount ? device_extensions_enabled_.data() : nullptr; if (extensionsLookup_.KHR_get_physical_device_properties2) { device_info.pNext = &features2; features2.features = deviceFeatures_.enabled.standard; features2.pNext = &deviceFeatures_.enabled.multiview; } else { device_info.pEnabledFeatures = &deviceFeatures_.enabled.standard; } VkResult res = vkCreateDevice(physical_devices_[physical_device_], &device_info, nullptr, &device_); if (res != VK_SUCCESS) { init_error_ = "Unable to create Vulkan device"; ERROR_LOG(G3D, "Unable to create Vulkan device"); } else { VulkanLoadDeviceFunctions(device_, extensionsLookup_); } INFO_LOG(G3D, "Vulkan Device created"); VulkanSetAvailable(true); VmaAllocatorCreateInfo allocatorInfo = {}; allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0; allocatorInfo.physicalDevice = physical_devices_[physical_device_]; allocatorInfo.device = device_; allocatorInfo.instance = instance_; VkResult result = vmaCreateAllocator(&allocatorInfo, &allocator_); _assert_(result == VK_SUCCESS); _assert_(allocator_ != VK_NULL_HANDLE); // Examine the physical device to figure out super rough performance grade. // Basically all we want to do is to identify low performance mobile devices // so we can make decisions on things like texture scaling strategy. auto &props = physicalDeviceProperties_[physical_device_].properties; switch (props.vendorID) { case VULKAN_VENDOR_AMD: case VULKAN_VENDOR_NVIDIA: case VULKAN_VENDOR_INTEL: devicePerfClass_ = PerfClass::FAST; break; case VULKAN_VENDOR_ARM: devicePerfClass_ = PerfClass::SLOW; { // Parse the device name as an ultra rough heuristic. int maliG = 0; if (sscanf(props.deviceName, "Mali-G%d", &maliG) == 1) { if (maliG >= 72) { devicePerfClass_ = PerfClass::FAST; } } } break; case VULKAN_VENDOR_QUALCOMM: devicePerfClass_ = PerfClass::SLOW; #if PPSSPP_PLATFORM(ANDROID) if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) { devicePerfClass_ = PerfClass::FAST; } #endif break; case VULKAN_VENDOR_IMGTEC: default: devicePerfClass_ = PerfClass::SLOW; break; } return res; } VkResult VulkanContext::InitDebugUtilsCallback() { // We're intentionally skipping VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT and // VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, just too spammy. int bits = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; VkDebugUtilsMessengerCreateInfoEXT callback1{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT}; callback1.messageSeverity = bits; callback1.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; callback1.pfnUserCallback = &VulkanDebugUtilsCallback; callback1.pUserData = (void *)&g_LogOptions; VkDebugUtilsMessengerEXT messenger; VkResult res = vkCreateDebugUtilsMessengerEXT(instance_, &callback1, nullptr, &messenger); if (res != VK_SUCCESS) { ERROR_LOG(G3D, "Failed to register debug callback with vkCreateDebugUtilsMessengerEXT"); // Do error handling for VK_ERROR_OUT_OF_MEMORY } else { INFO_LOG(G3D, "Debug callback registered with vkCreateDebugUtilsMessengerEXT."); utils_callbacks.push_back(messenger); } return res; } void VulkanContext::SetDebugNameImpl(uint64_t handle, VkObjectType type, const char *name) { VkDebugUtilsObjectNameInfoEXT info{ VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT }; info.pObjectName = name; info.objectHandle = handle; info.objectType = type; vkSetDebugUtilsObjectNameEXT(device_, &info); } VkResult VulkanContext::InitSurface(WindowSystem winsys, void *data1, void *data2) { winsys_ = winsys; winsysData1_ = data1; winsysData2_ = data2; return ReinitSurface(); } VkResult VulkanContext::ReinitSurface() { if (surface_ != VK_NULL_HANDLE) { INFO_LOG(G3D, "Destroying Vulkan surface (%d, %d)", swapChainExtent_.width, swapChainExtent_.height); vkDestroySurfaceKHR(instance_, surface_, nullptr); surface_ = VK_NULL_HANDLE; } INFO_LOG(G3D, "Creating Vulkan surface for window (%p %p)", winsysData1_, winsysData2_); VkResult retval = VK_SUCCESS; switch (winsys_) { #ifdef _WIN32 case WINDOWSYSTEM_WIN32: { VkWin32SurfaceCreateInfoKHR win32{ VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR }; win32.flags = 0; win32.hwnd = (HWND)winsysData2_; win32.hinstance = (HINSTANCE)winsysData1_; retval = vkCreateWin32SurfaceKHR(instance_, &win32, nullptr, &surface_); break; } #endif #if defined(__ANDROID__) case WINDOWSYSTEM_ANDROID: { ANativeWindow *wnd = (ANativeWindow *)winsysData1_; VkAndroidSurfaceCreateInfoKHR android{ VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR }; android.flags = 0; android.window = wnd; retval = vkCreateAndroidSurfaceKHR(instance_, &android, nullptr, &surface_); break; } #endif #if defined(VK_USE_PLATFORM_METAL_EXT) case WINDOWSYSTEM_METAL_EXT: { VkMetalSurfaceCreateInfoEXT metal{ VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT }; metal.flags = 0; metal.pLayer = winsysData1_; metal.pNext = winsysData2_; retval = vkCreateMetalSurfaceEXT(instance_, &metal, nullptr, &surface_); break; } #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) case WINDOWSYSTEM_XLIB: { VkXlibSurfaceCreateInfoKHR xlib{ VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR }; xlib.flags = 0; xlib.dpy = (Display *)winsysData1_; xlib.window = (Window)winsysData2_; retval = vkCreateXlibSurfaceKHR(instance_, &xlib, nullptr, &surface_); break; } #endif #if defined(VK_USE_PLATFORM_XCB_KHR) case WINDOWSYSTEM_XCB: { VkXCBSurfaceCreateInfoKHR xcb{ VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR }; xcb.flags = 0; xcb.connection = (Connection *)winsysData1_; xcb.window = (Window)(uintptr_t)winsysData2_; retval = vkCreateXcbSurfaceKHR(instance_, &xcb, nullptr, &surface_); break; } #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) case WINDOWSYSTEM_WAYLAND: { VkWaylandSurfaceCreateInfoKHR wayland{ VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR }; wayland.flags = 0; wayland.display = (wl_display *)winsysData1_; wayland.surface = (wl_surface *)winsysData2_; retval = vkCreateWaylandSurfaceKHR(instance_, &wayland, nullptr, &surface_); break; } #endif #if defined(VK_USE_PLATFORM_DISPLAY_KHR) case WINDOWSYSTEM_DISPLAY: { VkDisplaySurfaceCreateInfoKHR display{ VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR }; #if !defined(__LIBRETRO__) /* And when not to use libretro need VkDisplaySurfaceCreateInfoKHR this extension, then you need to use dlopen to read vulkan loader in VulkanLoader.cpp. huangzihan China */ if(!vkGetPhysicalDeviceDisplayPropertiesKHR || !vkGetPhysicalDeviceDisplayPlanePropertiesKHR || !vkGetDisplayModePropertiesKHR || !vkGetDisplayPlaneSupportedDisplaysKHR || !vkGetDisplayPlaneCapabilitiesKHR ) { _assert_msg_(false, "DISPLAY Vulkan cannot find any vulkan function symbols."); return VK_ERROR_INITIALIZATION_FAILED; } //The following code is for reference: // https://github.com/vanfanel/ppsspp // When using the VK_KHR_display extension and not using LIBRETRO, a complete // VkDisplaySurfaceCreateInfoKHR is needed. uint32_t display_count; uint32_t plane_count; VkDisplayPropertiesKHR *display_props = NULL; VkDisplayPlanePropertiesKHR *plane_props = NULL; VkDisplayModePropertiesKHR* mode_props = NULL; VkExtent2D image_size; // This is the chosen physical_device, it has been chosen elsewhere. VkPhysicalDevice phys_device = physical_devices_[physical_device_]; VkDisplayModeKHR display_mode = VK_NULL_HANDLE; VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; uint32_t plane = UINT32_MAX; // For now, use the first available (connected) display. int display_index = 0; VkResult result; bool ret = false; bool mode_found = false; int i, j; // 1 physical device can have N displays connected. // Vulkan only counts the connected displays. // Get a list of displays on the physical device. display_count = 0; vkGetPhysicalDeviceDisplayPropertiesKHR(phys_device, &display_count, NULL); if (display_count == 0) { _assert_msg_(false, "DISPLAY Vulkan couldn't find any displays."); return VK_ERROR_INITIALIZATION_FAILED; } display_props = new VkDisplayPropertiesKHR[display_count]; vkGetPhysicalDeviceDisplayPropertiesKHR(phys_device, &display_count, display_props); // Get a list of display planes on the physical device. plane_count = 0; vkGetPhysicalDeviceDisplayPlanePropertiesKHR(phys_device, &plane_count, NULL); if (plane_count == 0) { _assert_msg_(false, "DISPLAY Vulkan couldn't find any planes on the physical device"); return VK_ERROR_INITIALIZATION_FAILED; } plane_props = new VkDisplayPlanePropertiesKHR[plane_count]; vkGetPhysicalDeviceDisplayPlanePropertiesKHR(phys_device, &plane_count, plane_props); // Get the Vulkan display we are going to use. VkDisplayKHR myDisplay = display_props[display_index].display; // Get the list of display modes of the display uint32_t mode_count = 0; vkGetDisplayModePropertiesKHR(phys_device, myDisplay, &mode_count, NULL); if (mode_count == 0) { _assert_msg_(false, "DISPLAY Vulkan couldn't find any video modes on the display"); return VK_ERROR_INITIALIZATION_FAILED; } mode_props = new VkDisplayModePropertiesKHR[mode_count]; vkGetDisplayModePropertiesKHR(phys_device, myDisplay, &mode_count, mode_props); // See if there's an appropiate mode available on the display display_mode = VK_NULL_HANDLE; for (i = 0; i < mode_count; ++i) { const VkDisplayModePropertiesKHR* mode = &mode_props[i]; if (mode->parameters.visibleRegion.width == pixel_xres && mode->parameters.visibleRegion.height == pixel_yres) { display_mode = mode->displayMode; mode_found = true; break; } } // Free the mode list now. delete [] mode_props; // If there are no useable modes found on the display, error out if (display_mode == VK_NULL_HANDLE) { _assert_msg_(false, "DISPLAY Vulkan couldn't find any video modes on the display"); return VK_ERROR_INITIALIZATION_FAILED; } /* Iterate on the list of planes of the physical device to find a plane that matches these criteria: -It must be compatible with the chosen display + mode. -It isn't currently bound to another display. -It supports per-pixel alpha, if possible. */ for (i = 0; i < plane_count; i++) { uint32_t supported_displays_count = 0; VkDisplayKHR* supported_displays; VkDisplayPlaneCapabilitiesKHR plane_caps; /* See if the plane is compatible with the current display. */ vkGetDisplayPlaneSupportedDisplaysKHR(phys_device, i, &supported_displays_count, NULL); if (supported_displays_count == 0) { /* This plane doesn't support any displays. Continue to the next plane. */ continue; } /* Get the list of displays supported by this plane. */ supported_displays = new VkDisplayKHR[supported_displays_count]; vkGetDisplayPlaneSupportedDisplaysKHR(phys_device, i, &supported_displays_count, supported_displays); /* The plane must be bound to the chosen display, or not in use. If none of these is true, iterate to another plane. */ if ( !( (plane_props[i].currentDisplay == myDisplay) || (plane_props[i].currentDisplay == VK_NULL_HANDLE))) continue; /* Iterate the list of displays supported by this plane in order to find out if the chosen display is among them. */ bool plane_supports_display = false; for (j = 0; j < supported_displays_count; j++) { if (supported_displays[j] == myDisplay) { plane_supports_display = true; break; } } /* Free the list of displays supported by this plane. */ delete [] supported_displays; /* If the display is not supported by this plane, iterate to the next plane. */ if (!plane_supports_display) continue; /* Want a plane that supports the alpha mode we have chosen. */ vkGetDisplayPlaneCapabilitiesKHR(phys_device, display_mode, i, &plane_caps); if (plane_caps.supportedAlpha & alpha_mode) { /* Yep, this plane is alright. */ plane = i; break; } } /* If we couldn't find an appropiate plane, error out. */ if (plane == UINT32_MAX) { _assert_msg_(false, "DISPLAY Vulkan couldn't find an appropiate plane"); return VK_ERROR_INITIALIZATION_FAILED; } // Finally, create the vulkan surface. image_size.width = pixel_xres; image_size.height = pixel_yres; display.displayMode = display_mode; display.imageExtent = image_size; display.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; display.alphaMode = alpha_mode; display.globalAlpha = 1.0f; display.planeIndex = plane; display.planeStackIndex = plane_props[plane].currentStackIndex; display.pNext = nullptr; delete [] display_props; delete [] plane_props; #endif display.flags = 0; retval = vkCreateDisplayPlaneSurfaceKHR(instance_, &display, nullptr, &surface_); break; } #endif default: _assert_msg_(false, "Vulkan support for chosen window system not implemented"); return VK_ERROR_INITIALIZATION_FAILED; } if (retval != VK_SUCCESS) { return retval; } if (!ChooseQueue()) { return VK_ERROR_INITIALIZATION_FAILED; } for (int i = 0; i < ARRAY_SIZE(frame_); i++) { frame_[i].profiler.Init(this); } return VK_SUCCESS; } bool VulkanContext::ChooseQueue() { // Iterate over each queue to learn whether it supports presenting: VkBool32 *supportsPresent = new VkBool32[queue_count]; for (uint32_t i = 0; i < queue_count; i++) { vkGetPhysicalDeviceSurfaceSupportKHR(physical_devices_[physical_device_], i, surface_, &supportsPresent[i]); } // Search for a graphics queue and a present queue in the array of queue // families, try to find one that supports both uint32_t graphicsQueueNodeIndex = UINT32_MAX; uint32_t presentQueueNodeIndex = UINT32_MAX; for (uint32_t i = 0; i < queue_count; i++) { if ((queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { if (graphicsQueueNodeIndex == UINT32_MAX) { graphicsQueueNodeIndex = i; } if (supportsPresent[i] == VK_TRUE) { graphicsQueueNodeIndex = i; presentQueueNodeIndex = i; break; } } } if (presentQueueNodeIndex == UINT32_MAX) { // If didn't find a queue that supports both graphics and present, then // find a separate present queue. NOTE: We don't actually currently support this arrangement! for (uint32_t i = 0; i < queue_count; ++i) { if (supportsPresent[i] == VK_TRUE) { presentQueueNodeIndex = i; break; } } } delete[] supportsPresent; // Generate error if could not find both a graphics and a present queue if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX) { ERROR_LOG(G3D, "Could not find a graphics and a present queue"); return false; } graphics_queue_family_index_ = graphicsQueueNodeIndex; // Get the list of VkFormats that are supported: uint32_t formatCount = 0; VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, nullptr); _assert_msg_(res == VK_SUCCESS, "Failed to get formats for device %d: %d", physical_device_, (int)res); if (res != VK_SUCCESS) { return false; } surfFormats_.resize(formatCount); res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, surfFormats_.data()); _dbg_assert_(res == VK_SUCCESS); if (res != VK_SUCCESS) { return false; } // If the format list includes just one entry of VK_FORMAT_UNDEFINED, // the surface has no preferred format. Otherwise, at least one // supported format will be returned. if (formatCount == 0 || (formatCount == 1 && surfFormats_[0].format == VK_FORMAT_UNDEFINED)) { INFO_LOG(G3D, "swapchain_format: Falling back to B8G8R8A8_UNORM"); swapchainFormat_ = VK_FORMAT_B8G8R8A8_UNORM; } else { swapchainFormat_ = VK_FORMAT_UNDEFINED; for (uint32_t i = 0; i < formatCount; ++i) { if (surfFormats_[i].colorSpace != VK_COLORSPACE_SRGB_NONLINEAR_KHR) { continue; } if (surfFormats_[i].format == VK_FORMAT_B8G8R8A8_UNORM || surfFormats_[i].format == VK_FORMAT_R8G8B8A8_UNORM) { swapchainFormat_ = surfFormats_[i].format; break; } } if (swapchainFormat_ == VK_FORMAT_UNDEFINED) { // Okay, take the first one then. swapchainFormat_ = surfFormats_[0].format; } INFO_LOG(G3D, "swapchain_format: %d (/%d)", swapchainFormat_, formatCount); } vkGetDeviceQueue(device_, graphics_queue_family_index_, 0, &gfx_queue_); return true; } int clamp(int x, int a, int b) { if (x < a) return a; if (x > b) return b; return x; } static std::string surface_transforms_to_string(VkSurfaceTransformFlagsKHR transformFlags) { std::string str; if (transformFlags & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) str += "IDENTITY "; if (transformFlags & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) str += "ROTATE_90 "; if (transformFlags & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) str += "ROTATE_180 "; if (transformFlags & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) str += "ROTATE_270 "; if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR) str += "HMIRROR "; if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR) str += "HMIRROR_90 "; if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR) str += "HMIRROR_180 "; if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR) str += "HMIRROR_270 "; if (transformFlags & VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR) str += "INHERIT "; return str; } bool VulkanContext::InitSwapchain() { VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_devices_[physical_device_], surface_, &surfCapabilities_); if (res == VK_ERROR_SURFACE_LOST_KHR) { // Not much to do. ERROR_LOG(G3D, "VK: Surface lost in InitSwapchain"); return false; } _dbg_assert_(res == VK_SUCCESS); uint32_t presentModeCount; res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_devices_[physical_device_], surface_, &presentModeCount, nullptr); _dbg_assert_(res == VK_SUCCESS); VkPresentModeKHR *presentModes = new VkPresentModeKHR[presentModeCount]; _dbg_assert_(presentModes); res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_devices_[physical_device_], surface_, &presentModeCount, presentModes); _dbg_assert_(res == VK_SUCCESS); VkExtent2D currentExtent { surfCapabilities_.currentExtent }; // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSurfaceCapabilitiesKHR.html // currentExtent is the current width and height of the surface, or the special value (0xFFFFFFFF, 0xFFFFFFFF) indicating that the surface size will be determined by the extent of a swapchain targeting the surface. if (currentExtent.width == 0xFFFFFFFFu || currentExtent.height == 0xFFFFFFFFu) { _dbg_assert_((bool)cbGetDrawSize_) if (cbGetDrawSize_) { currentExtent = cbGetDrawSize_(); } } swapChainExtent_.width = clamp(currentExtent.width, surfCapabilities_.minImageExtent.width, surfCapabilities_.maxImageExtent.width); swapChainExtent_.height = clamp(currentExtent.height, surfCapabilities_.minImageExtent.height, surfCapabilities_.maxImageExtent.height); INFO_LOG(G3D, "surfCapabilities_.current: %dx%d min: %dx%d max: %dx%d computed: %dx%d", currentExtent.width, currentExtent.height, surfCapabilities_.minImageExtent.width, surfCapabilities_.minImageExtent.height, surfCapabilities_.maxImageExtent.width, surfCapabilities_.maxImageExtent.height, swapChainExtent_.width, swapChainExtent_.height); // TODO: Find a better way to specify the prioritized present mode while being able // to fall back in a sensible way. VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; std::string modes = ""; for (size_t i = 0; i < presentModeCount; i++) { modes += PresentModeString(presentModes[i]); if (i != presentModeCount - 1) { modes += ", "; } } INFO_LOG(G3D, "Supported present modes: %s", modes.c_str()); for (size_t i = 0; i < presentModeCount; i++) { bool match = false; match = match || ((flags_ & VULKAN_FLAG_PRESENT_MAILBOX) && presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR); match = match || ((flags_ & VULKAN_FLAG_PRESENT_FIFO_RELAXED) && presentModes[i] == VK_PRESENT_MODE_FIFO_RELAXED_KHR); match = match || ((flags_ & VULKAN_FLAG_PRESENT_FIFO) && presentModes[i] == VK_PRESENT_MODE_FIFO_KHR); match = match || ((flags_ & VULKAN_FLAG_PRESENT_IMMEDIATE) && presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR); // Default to the first present mode from the list. if (match || swapchainPresentMode == VK_PRESENT_MODE_MAX_ENUM_KHR) { swapchainPresentMode = presentModes[i]; } if (match) { break; } } #ifdef __ANDROID__ // HACK swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; #endif delete[] presentModes; // Determine the number of VkImage's to use in the swap chain (we desire to // own only 1 image at a time, besides the images being displayed and // queued for display): uint32_t desiredNumberOfSwapChainImages = surfCapabilities_.minImageCount + 1; if ((surfCapabilities_.maxImageCount > 0) && (desiredNumberOfSwapChainImages > surfCapabilities_.maxImageCount)) { // Application must settle for fewer images than desired: desiredNumberOfSwapChainImages = surfCapabilities_.maxImageCount; } INFO_LOG(G3D, "Chosen present mode: %d (%s). numSwapChainImages: %d/%d", swapchainPresentMode, PresentModeString(swapchainPresentMode), desiredNumberOfSwapChainImages, surfCapabilities_.maxImageCount); // We mostly follow the practices from // https://arm-software.github.io/vulkan_best_practice_for_mobile_developers/samples/surface_rotation/surface_rotation_tutorial.html // VkSurfaceTransformFlagBitsKHR preTransform; std::string supportedTransforms = surface_transforms_to_string(surfCapabilities_.supportedTransforms); std::string currentTransform = surface_transforms_to_string(surfCapabilities_.currentTransform); g_display_rotation = DisplayRotation::ROTATE_0; g_display_rot_matrix.setIdentity(); uint32_t allowedRotations = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; // Hack: Don't allow 270 degrees pretransform (inverse landscape), it creates bizarre issues on some devices (see #15773). allowedRotations &= ~VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; if (surfCapabilities_.currentTransform & (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR | VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR)) { preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; } else if (surfCapabilities_.currentTransform & allowedRotations) { // Normal, sensible rotations. Let's handle it. preTransform = surfCapabilities_.currentTransform; g_display_rot_matrix.setIdentity(); switch (surfCapabilities_.currentTransform) { case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: g_display_rotation = DisplayRotation::ROTATE_90; g_display_rot_matrix.setRotationZ90(); std::swap(swapChainExtent_.width, swapChainExtent_.height); break; case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: g_display_rotation = DisplayRotation::ROTATE_180; g_display_rot_matrix.setRotationZ180(); break; case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: g_display_rotation = DisplayRotation::ROTATE_270; g_display_rot_matrix.setRotationZ270(); std::swap(swapChainExtent_.width, swapChainExtent_.height); break; default: _dbg_assert_(false); } } else { // Let the OS rotate the image (potentially slower on many Android devices) preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; } std::string preTransformStr = surface_transforms_to_string(preTransform); INFO_LOG(G3D, "Transform supported: %s current: %s chosen: %s", supportedTransforms.c_str(), currentTransform.c_str(), preTransformStr.c_str()); if (physicalDeviceProperties_[physical_device_].properties.vendorID == VULKAN_VENDOR_IMGTEC) { u32 driverVersion = physicalDeviceProperties_[physical_device_].properties.driverVersion; // Cutoff the hack at driver version 1.386.1368 (0x00582558, see issue #15773). if (driverVersion < 0x00582558) { INFO_LOG(G3D, "Applying PowerVR hack (rounding off the width!) driverVersion=%08x", driverVersion); // Swap chain width hack to avoid issue #11743 (PowerVR driver bug). // To keep the size consistent even with pretransform, do this after the swap. Should be fine. // This is fixed in newer PowerVR drivers but I don't know the cutoff. swapChainExtent_.width &= ~31; // TODO: Also modify display_xres/display_yres appropriately for scissors to match. // This will get a bit messy. Ideally we should remove that logic from app-android.cpp // and move it here, but the OpenGL code still needs it. } else { INFO_LOG(G3D, "PowerVR driver version new enough (%08x), not applying swapchain width hack", driverVersion); } } VkSwapchainCreateInfoKHR swap_chain_info{ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; swap_chain_info.surface = surface_; swap_chain_info.minImageCount = desiredNumberOfSwapChainImages; swap_chain_info.imageFormat = swapchainFormat_; swap_chain_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; swap_chain_info.imageExtent.width = swapChainExtent_.width; swap_chain_info.imageExtent.height = swapChainExtent_.height; swap_chain_info.preTransform = preTransform; swap_chain_info.imageArrayLayers = 1; swap_chain_info.presentMode = swapchainPresentMode; swap_chain_info.oldSwapchain = VK_NULL_HANDLE; swap_chain_info.clipped = true; swap_chain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // Don't ask for TRANSFER_DST for the swapchain image, we don't use that. // if (surfCapabilities_.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) // swap_chain_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; #ifndef ANDROID // We don't support screenshots on Android // Add more usage flags if they're supported. if (surfCapabilities_.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) swap_chain_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; #endif swap_chain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swap_chain_info.queueFamilyIndexCount = 0; swap_chain_info.pQueueFamilyIndices = NULL; // OPAQUE is not supported everywhere. if (surfCapabilities_.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) { swap_chain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; } else { // This should be supported anywhere, and is the only thing supported on the SHIELD TV, for example. swap_chain_info.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; } res = vkCreateSwapchainKHR(device_, &swap_chain_info, NULL, &swapchain_); if (res != VK_SUCCESS) { ERROR_LOG(G3D, "vkCreateSwapchainKHR failed!"); return false; } INFO_LOG(G3D, "Created swapchain: %dx%d", swap_chain_info.imageExtent.width, swap_chain_info.imageExtent.height); return true; } void VulkanContext::SetCbGetDrawSize(std::function cb) { cbGetDrawSize_ = cb; } VkFence VulkanContext::CreateFence(bool presignalled) { VkFence fence; VkFenceCreateInfo fenceInfo{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; fenceInfo.flags = presignalled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; vkCreateFence(device_, &fenceInfo, NULL, &fence); return fence; } void VulkanContext::PerformPendingDeletes() { for (int i = 0; i < ARRAY_SIZE(frame_); i++) { frame_[i].deleteList.PerformDeletes(this, allocator_); } Delete().PerformDeletes(this, allocator_); } void VulkanContext::DestroyDevice() { if (swapchain_) { ERROR_LOG(G3D, "DestroyDevice: Swapchain should have been destroyed."); } if (surface_) { ERROR_LOG(G3D, "DestroyDevice: Surface should have been destroyed."); } for (int i = 0; i < ARRAY_SIZE(frame_); i++) { frame_[i].profiler.Shutdown(); } INFO_LOG(G3D, "VulkanContext::DestroyDevice (performing deletes)"); PerformPendingDeletes(); vmaDestroyAllocator(allocator_); allocator_ = VK_NULL_HANDLE; vkDestroyDevice(device_, nullptr); device_ = nullptr; } bool VulkanContext::CreateShaderModule(const std::vector &spirv, VkShaderModule *shaderModule, const char *tag) { VkShaderModuleCreateInfo sm{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; sm.pCode = spirv.data(); sm.codeSize = spirv.size() * sizeof(uint32_t); sm.flags = 0; VkResult result = vkCreateShaderModule(device_, &sm, nullptr, shaderModule); if (tag) { SetDebugName(*shaderModule, VK_OBJECT_TYPE_SHADER_MODULE, tag); } if (result != VK_SUCCESS) { return false; } else { return true; } } void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int numMipLevels, int numLayers, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask) { VkImageMemoryBarrier image_memory_barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; image_memory_barrier.srcAccessMask = srcAccessMask; image_memory_barrier.dstAccessMask = dstAccessMask; image_memory_barrier.oldLayout = oldImageLayout; image_memory_barrier.newLayout = newImageLayout; image_memory_barrier.image = image; image_memory_barrier.subresourceRange.aspectMask = aspectMask; image_memory_barrier.subresourceRange.baseMipLevel = baseMip; image_memory_barrier.subresourceRange.levelCount = numMipLevels; image_memory_barrier.subresourceRange.layerCount = numLayers; // We never use more than one layer, and old Mali drivers have problems with VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS. image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; vkCmdPipelineBarrier(cmd, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); } EShLanguage FindLanguage(const VkShaderStageFlagBits shader_type) { switch (shader_type) { case VK_SHADER_STAGE_VERTEX_BIT: return EShLangVertex; case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: return EShLangTessControl; case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: return EShLangTessEvaluation; case VK_SHADER_STAGE_GEOMETRY_BIT: return EShLangGeometry; case VK_SHADER_STAGE_FRAGMENT_BIT: return EShLangFragment; case VK_SHADER_STAGE_COMPUTE_BIT: return EShLangCompute; default: return EShLangVertex; } } // Compile a given string containing GLSL into SPV for use by VK // Return value of false means an error was encountered. bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, std::vector &spirv, std::string *errorMessage) { glslang::TProgram program; const char *shaderStrings[1]; TBuiltInResource Resources{}; InitShaderResources(Resources); int defaultVersion = 0; EShMessages messages; EProfile profile; switch (variant) { case GLSLVariant::VULKAN: // Enable SPIR-V and Vulkan rules when parsing GLSL messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); defaultVersion = 450; profile = ECoreProfile; break; case GLSLVariant::GL140: messages = (EShMessages)(EShMsgDefault); defaultVersion = 140; profile = ECompatibilityProfile; break; case GLSLVariant::GLES300: messages = (EShMessages)(EShMsgDefault); defaultVersion = 300; profile = EEsProfile; break; default: return false; } EShLanguage stage = FindLanguage(shader_type); glslang::TShader shader(stage); shaderStrings[0] = sourceCode; shader.setStrings(shaderStrings, 1); if (!shader.parse(&Resources, defaultVersion, profile, false, true, messages)) { puts(shader.getInfoLog()); puts(shader.getInfoDebugLog()); if (errorMessage) { *errorMessage = shader.getInfoLog(); (*errorMessage) += shader.getInfoDebugLog(); } return false; // something didn't work } // TODO: Propagate warnings into errorMessages even if we succeeded here. // Note that program does not take ownership of &shader, so this is fine. program.addShader(&shader); if (!program.link(messages)) { puts(shader.getInfoLog()); puts(shader.getInfoDebugLog()); if (errorMessage) { *errorMessage = shader.getInfoLog(); (*errorMessage) += shader.getInfoDebugLog(); } return false; } // Can't fail, parsing worked, "linking" worked. glslang::SpvOptions options; options.disableOptimizer = false; options.optimizeSize = false; options.generateDebugInfo = false; glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &options); return true; } void init_glslang() { glslang::InitializeProcess(); } void finalize_glslang() { glslang::FinalizeProcess(); } void VulkanDeleteList::Take(VulkanDeleteList &del) { _dbg_assert_(cmdPools_.empty()); _dbg_assert_(descPools_.empty()); _dbg_assert_(modules_.empty()); _dbg_assert_(buffers_.empty()); _dbg_assert_(bufferViews_.empty()); _dbg_assert_(buffersWithAllocs_.empty()); _dbg_assert_(imageViews_.empty()); _dbg_assert_(imagesWithAllocs_.empty()); _dbg_assert_(deviceMemory_.empty()); _dbg_assert_(samplers_.empty()); _dbg_assert_(pipelines_.empty()); _dbg_assert_(pipelineCaches_.empty()); _dbg_assert_(renderPasses_.empty()); _dbg_assert_(framebuffers_.empty()); _dbg_assert_(pipelineLayouts_.empty()); _dbg_assert_(descSetLayouts_.empty()); _dbg_assert_(callbacks_.empty()); cmdPools_ = std::move(del.cmdPools_); descPools_ = std::move(del.descPools_); modules_ = std::move(del.modules_); buffers_ = std::move(del.buffers_); buffersWithAllocs_ = std::move(del.buffersWithAllocs_); bufferViews_ = std::move(del.bufferViews_); imageViews_ = std::move(del.imageViews_); imagesWithAllocs_ = std::move(del.imagesWithAllocs_); deviceMemory_ = std::move(del.deviceMemory_); samplers_ = std::move(del.samplers_); pipelines_ = std::move(del.pipelines_); pipelineCaches_ = std::move(del.pipelineCaches_); renderPasses_ = std::move(del.renderPasses_); framebuffers_ = std::move(del.framebuffers_); pipelineLayouts_ = std::move(del.pipelineLayouts_); descSetLayouts_ = std::move(del.descSetLayouts_); callbacks_ = std::move(del.callbacks_); del.cmdPools_.clear(); del.descPools_.clear(); del.modules_.clear(); del.buffers_.clear(); del.buffersWithAllocs_.clear(); del.imageViews_.clear(); del.imagesWithAllocs_.clear(); del.deviceMemory_.clear(); del.samplers_.clear(); del.pipelines_.clear(); del.pipelineCaches_.clear(); del.renderPasses_.clear(); del.framebuffers_.clear(); del.pipelineLayouts_.clear(); del.descSetLayouts_.clear(); del.callbacks_.clear(); } void VulkanDeleteList::PerformDeletes(VulkanContext *vulkan, VmaAllocator allocator) { for (auto &callback : callbacks_) { callback.func(vulkan, callback.userdata); } callbacks_.clear(); VkDevice device = vulkan->GetDevice(); for (auto &cmdPool : cmdPools_) { vkDestroyCommandPool(device, cmdPool, nullptr); } cmdPools_.clear(); for (auto &descPool : descPools_) { vkDestroyDescriptorPool(device, descPool, nullptr); } descPools_.clear(); for (auto &module : modules_) { vkDestroyShaderModule(device, module, nullptr); } modules_.clear(); for (auto &buf : buffers_) { vkDestroyBuffer(device, buf, nullptr); } buffers_.clear(); for (auto &buf : buffersWithAllocs_) { vmaDestroyBuffer(allocator, buf.buffer, buf.alloc); } buffersWithAllocs_.clear(); for (auto &bufView : bufferViews_) { vkDestroyBufferView(device, bufView, nullptr); } bufferViews_.clear(); for (auto &imageWithAlloc : imagesWithAllocs_) { vmaDestroyImage(allocator, imageWithAlloc.image, imageWithAlloc.alloc); } imagesWithAllocs_.clear(); for (auto &imageView : imageViews_) { vkDestroyImageView(device, imageView, nullptr); } imageViews_.clear(); for (auto &mem : deviceMemory_) { vkFreeMemory(device, mem, nullptr); } deviceMemory_.clear(); for (auto &sampler : samplers_) { vkDestroySampler(device, sampler, nullptr); } samplers_.clear(); for (auto &pipeline : pipelines_) { vkDestroyPipeline(device, pipeline, nullptr); } pipelines_.clear(); for (auto &pcache : pipelineCaches_) { vkDestroyPipelineCache(device, pcache, nullptr); } pipelineCaches_.clear(); for (auto &renderPass : renderPasses_) { vkDestroyRenderPass(device, renderPass, nullptr); } renderPasses_.clear(); for (auto &framebuffer : framebuffers_) { vkDestroyFramebuffer(device, framebuffer, nullptr); } framebuffers_.clear(); for (auto &pipeLayout : pipelineLayouts_) { vkDestroyPipelineLayout(device, pipeLayout, nullptr); } pipelineLayouts_.clear(); for (auto &descSetLayout : descSetLayouts_) { vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr); } descSetLayouts_.clear(); for (auto &queryPool : queryPools_) { vkDestroyQueryPool(device, queryPool, nullptr); } queryPools_.clear(); } void VulkanContext::GetImageMemoryRequirements(VkImage image, VkMemoryRequirements *mem_reqs, bool *dedicatedAllocation) { if (Extensions().KHR_dedicated_allocation) { VkImageMemoryRequirementsInfo2KHR memReqInfo2{VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR}; memReqInfo2.image = image; VkMemoryRequirements2KHR memReq2 = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR}; VkMemoryDedicatedRequirementsKHR memDedicatedReq{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR}; memReq2.pNext = &memDedicatedReq; vkGetImageMemoryRequirements2KHR(GetDevice(), &memReqInfo2, &memReq2); *mem_reqs = memReq2.memoryRequirements; *dedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE) || (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else { vkGetImageMemoryRequirements(GetDevice(), image, mem_reqs); *dedicatedAllocation = false; } } bool IsHashMaliDriverVersion(const VkPhysicalDeviceProperties &props) { // ARM used to put a hash in place of the driver version. // Now they only use major versions. We'll just make a bad heuristic. uint32_t major = VK_VERSION_MAJOR(props.driverVersion); uint32_t branch = VK_VERSION_PATCH(props.driverVersion); if (branch > 0) return true; if (branch > 100 || major > 100) return true; return false; } // From Sascha's code std::string FormatDriverVersion(const VkPhysicalDeviceProperties &props) { if (props.vendorID == VULKAN_VENDOR_NVIDIA) { // For whatever reason, NVIDIA has their own scheme. // 10 bits = major version (up to r1023) // 8 bits = minor version (up to 255) // 8 bits = secondary branch version/build version (up to 255) // 6 bits = tertiary branch/build version (up to 63) uint32_t major = (props.driverVersion >> 22) & 0x3ff; uint32_t minor = (props.driverVersion >> 14) & 0x0ff; uint32_t secondaryBranch = (props.driverVersion >> 6) & 0x0ff; uint32_t tertiaryBranch = (props.driverVersion) & 0x003f; return StringFromFormat("%d.%d.%d.%d", major, minor, secondaryBranch, tertiaryBranch); } else if (props.vendorID == VULKAN_VENDOR_ARM) { // ARM used to just put a hash here. No point in splitting it up. if (IsHashMaliDriverVersion(props)) { return StringFromFormat("(hash) %08x", props.driverVersion); } } // Qualcomm has an inscrutable versioning scheme. Let's just display it as normal. // Standard scheme, use the standard macros. uint32_t major = VK_VERSION_MAJOR(props.driverVersion); uint32_t minor = VK_VERSION_MINOR(props.driverVersion); uint32_t branch = VK_VERSION_PATCH(props.driverVersion); return StringFromFormat("%d.%d.%d (%08x)", major, minor, branch, props.driverVersion); } // Mainly just the formats seen on gpuinfo.org for swapchains, as this function is only used for listing // those in the UI. Also depth buffers that we used in one place. // Might add more in the future if we find more uses for this. const char *VulkanFormatToString(VkFormat format) { switch (format) { case VK_FORMAT_A1R5G5B5_UNORM_PACK16: return "A1R5G5B5_UNORM_PACK16"; case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return "A2B10G10R10_UNORM_PACK32"; case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return "A2R10G10B10_UNORM_PACK32"; case VK_FORMAT_A8B8G8R8_SNORM_PACK32: return "A8B8G8R8_SNORM_PACK32"; case VK_FORMAT_A8B8G8R8_SRGB_PACK32: return "A8B8G8R8_SRGB_PACK32"; case VK_FORMAT_A8B8G8R8_UNORM_PACK32: return "A8B8G8R8_UNORM_PACK32"; case VK_FORMAT_B10G11R11_UFLOAT_PACK32: return "B10G11R11_UFLOAT_PACK32"; case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return "B4G4R4A4_UNORM_PACK16"; case VK_FORMAT_B5G5R5A1_UNORM_PACK16: return "B5G5R5A1_UNORM_PACK16"; case VK_FORMAT_B5G6R5_UNORM_PACK16: return "B5G6R5_UNORM_PACK16"; case VK_FORMAT_B8G8R8A8_SNORM: return "B8G8R8A8_SNORM"; case VK_FORMAT_B8G8R8A8_SRGB: return "B8G8R8A8_SRGB"; case VK_FORMAT_B8G8R8A8_UNORM: return "B8G8R8A8_UNORM"; case VK_FORMAT_R16G16B16A16_SFLOAT: return "R16G16B16A16_SFLOAT"; case VK_FORMAT_R16G16B16A16_SNORM: return "R16G16B16A16_SNORM"; case VK_FORMAT_R16G16B16A16_UNORM: return "R16G16B16A16_UNORM"; case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return "R4G4B4A4_UNORM_PACK16"; case VK_FORMAT_R5G5B5A1_UNORM_PACK16: return "R5G5B5A1_UNORM_PACK16"; case VK_FORMAT_R5G6B5_UNORM_PACK16: return "R5G6B5_UNORM_PACK16"; case VK_FORMAT_R8G8B8A8_SNORM: return "R8G8B8A8_SNORM"; case VK_FORMAT_R8G8B8A8_SRGB: return "R8G8B8A8_SRGB"; case VK_FORMAT_R8G8B8A8_UNORM: return "R8G8B8A8_UNORM"; case VK_FORMAT_D24_UNORM_S8_UINT: return "D24S8"; case VK_FORMAT_D16_UNORM: return "D16"; case VK_FORMAT_D16_UNORM_S8_UINT: return "D16S8"; case VK_FORMAT_D32_SFLOAT: return "D32f"; case VK_FORMAT_D32_SFLOAT_S8_UINT: return "D32fS8"; case VK_FORMAT_S8_UINT: return "S8"; case VK_FORMAT_UNDEFINED: return "UNDEFINED (BAD!)"; default: return "(format not added to string list)"; } } // I miss Rust where this is automatic :( const char *VulkanColorSpaceToString(VkColorSpaceKHR colorSpace) { switch (colorSpace) { case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: return "SRGB_NONLINEAR"; case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT: return "DISPLAY_P3_NONLINEAR"; case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: return "EXTENDED_SRGB_LINEAR"; case VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT: return "DISPLAY_P3_LINEAR"; case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT: return "DCI_P3_NONLINEAR"; case VK_COLOR_SPACE_BT709_LINEAR_EXT: return "BT709_LINEAR"; case VK_COLOR_SPACE_BT709_NONLINEAR_EXT: return "BT709_NONLINEAR"; case VK_COLOR_SPACE_BT2020_LINEAR_EXT: return "BT2020_LINEAR"; case VK_COLOR_SPACE_HDR10_ST2084_EXT: return "HDR10_ST2084"; case VK_COLOR_SPACE_DOLBYVISION_EXT: return "DOLBYVISION"; case VK_COLOR_SPACE_HDR10_HLG_EXT: return "HDR10_HLG"; case VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT: return "ADOBERGB_LINEAR"; case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT: return "ADOBERGB_NONLINEAR"; case VK_COLOR_SPACE_PASS_THROUGH_EXT: return "PASS_THROUGH"; case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT: return "EXTENDED_SRGB_NONLINEAR"; case VK_COLOR_SPACE_DISPLAY_NATIVE_AMD: return "DISPLAY_NATIVE_AMD"; default: return "(unknown)"; } }