From 69458bd90b20e5f1d5bfa1d28114ea541d6fec10 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 23 Dec 2025 15:44:50 +1000 Subject: [PATCH] VulkanDevice: Persist instance throughout launches Testing with AMD on Windows and LLVMPipe on Linux, creating and destroying a Vulkan instance appears to leak around 20-30MB of memory. Just keep the thing around for the whole time. Reduces startup time too, so everyone wins. Unless you're switching renderers all the time, then you lose a bit of memory. --- src/core/fullscreenui_settings.cpp | 28 +- src/core/gpu_thread.h | 4 + src/core/settings.cpp | 8 +- src/core/settings.h | 2 +- src/duckstation-mini/mini_host.cpp | 6 + src/duckstation-qt/graphicssettingswidget.cpp | 17 +- src/duckstation-qt/qthost.cpp | 6 + src/duckstation-qt/qtwindowinfo.cpp | 28 + src/duckstation-qt/qtwindowinfo.h | 3 + src/duckstation-regtest/regtest_host.cpp | 5 + src/util/CMakeLists.txt | 2 +- src/util/d3d_common.cpp | 23 +- src/util/d3d_common.h | 2 +- src/util/gpu_device.cpp | 15 +- src/util/gpu_device.h | 4 +- src/util/util.vcxproj | 2 +- src/util/util.vcxproj.filters | 2 +- src/util/vulkan_builders.cpp | 29 + src/util/vulkan_builders.h | 4 +- src/util/vulkan_device.cpp | 627 +------------- src/util/vulkan_device.h | 49 +- src/util/vulkan_entry_points.h | 20 - src/util/vulkan_headers.h | 73 ++ src/util/vulkan_loader.cpp | 818 ++++++++++++++++-- src/util/vulkan_loader.h | 105 +-- src/util/vulkan_pipeline.h | 2 +- src/util/vulkan_stream_buffer.h | 2 +- src/util/vulkan_swap_chain.cpp | 29 +- src/util/vulkan_swap_chain.h | 4 +- src/util/vulkan_texture.h | 2 +- 30 files changed, 1088 insertions(+), 833 deletions(-) delete mode 100644 src/util/vulkan_entry_points.h create mode 100644 src/util/vulkan_headers.h diff --git a/src/core/fullscreenui_settings.cpp b/src/core/fullscreenui_settings.cpp index baba2974e..25d6ad480 100644 --- a/src/core/fullscreenui_settings.cpp +++ b/src/core/fullscreenui_settings.cpp @@ -1660,15 +1660,29 @@ void FullscreenUI::SwitchToGameSettings(const GameList::Entry* entry, SettingsPa void FullscreenUI::PopulateGraphicsAdapterList() { + GPURenderer renderer; const auto lock = Core::GetSettingsLock(); - const GPURenderer renderer = - Settings::ParseRendererName(GetEffectiveTinyStringSetting(GetEditingSettingsInterface(false), "GPU", "Renderer", - Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)) - .c_str()) - .value_or(Settings::DEFAULT_GPU_RENDERER); + { + renderer = Settings::ParseRendererName( + GetEffectiveTinyStringSetting(GetEditingSettingsInterface(false), "GPU", "Renderer").c_str()) + .value_or(Settings::DEFAULT_GPU_RENDERER); + } - s_settings_locals.graphics_adapter_list_cache = - GPUDevice::GetAdapterListForAPI(Settings::GetRenderAPIForRenderer(renderer)); + Error error; + std::optional adapter_list = GPUDevice::GetAdapterListForAPI( + Settings::GetRenderAPIForRenderer(renderer), + g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWindowInfo().type : + WindowInfoType::Surfaceless, + &error); + if (adapter_list.has_value()) + { + s_settings_locals.graphics_adapter_list_cache = std::move(adapter_list.value()); + } + else + { + ERROR_LOG("Failed to enumerate graphics adapters: {}", error.GetDescription()); + s_settings_locals.graphics_adapter_list_cache = {}; + } } void FullscreenUI::PopulateGameListDirectoryCache(const SettingsInterface& si) diff --git a/src/core/gpu_thread.h b/src/core/gpu_thread.h index eeccda27b..2847904ea 100644 --- a/src/core/gpu_thread.h +++ b/src/core/gpu_thread.h @@ -18,6 +18,7 @@ class ThreadHandle; enum class RenderAPI : u8; enum class GPUVSyncMode : u8; +enum class WindowInfoType : u8; enum class GPURenderer : u8; enum class GPUBackendCommandType : u8; @@ -117,6 +118,9 @@ namespace Host { std::optional AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, Error* error); +/// Returns the window type for the host. +WindowInfoType GetRenderWindowInfoType(); + /// Called when the core is finished with a render window. void ReleaseRenderWindow(); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index b6f1fc493..c94c287d2 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,6 +5,7 @@ #include "achievements.h" #include "controller.h" #include "core.h" +#include "gpu_thread.h" #include "gte_types.h" #include "imgui_overlays.h" #include "system.h" @@ -1585,7 +1586,7 @@ RenderAPI Settings::GetRenderAPIForRenderer(GPURenderer renderer) case GPURenderer::Software: case GPURenderer::Automatic: default: - return GPUDevice::GetPreferredAPI(); + return GPUDevice::GetPreferredAPI(Host::GetRenderWindowInfoType()); } } @@ -1622,11 +1623,6 @@ GPURenderer Settings::GetRendererForRenderAPI(RenderAPI api) } } -GPURenderer Settings::GetAutomaticRenderer() -{ - return GetRendererForRenderAPI(GPUDevice::GetPreferredAPI()); -} - static constexpr const std::array s_texture_filter_names = { "Nearest", "Bilinear", "BilinearBinAlpha", "JINC2", "JINC2BinAlpha", "xBR", "xBRBinAlpha", "Scale2x", "Scale3x", "MMPX", "MMPXEnhanced", diff --git a/src/core/settings.h b/src/core/settings.h index e264f4029..6e3e300a0 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -22,6 +22,7 @@ enum class Level : u32; } enum class RenderAPI : u8; +enum class WindowInfoType : u8; enum class MediaCaptureBackend : u8; enum class OSDMessageType : u8; @@ -476,7 +477,6 @@ struct Settings : public GPUSettings static const char* GetRendererDisplayName(GPURenderer renderer); static RenderAPI GetRenderAPIForRenderer(GPURenderer renderer); static GPURenderer GetRendererForRenderAPI(RenderAPI api); - static GPURenderer GetAutomaticRenderer(); static std::optional ParseTextureFilterName(const char* str); static const char* GetTextureFilterName(GPUTextureFilter filter); diff --git a/src/duckstation-mini/mini_host.cpp b/src/duckstation-mini/mini_host.cpp index 148c872d8..b094773cf 100644 --- a/src/duckstation-mini/mini_host.cpp +++ b/src/duckstation-mini/mini_host.cpp @@ -650,6 +650,12 @@ std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool f return wi; } +WindowInfoType Host::GetRenderWindowInfoType() +{ + // Assume SDL for GL/Vulkan. + return WindowInfoType::SDL; +} + void Host::ReleaseRenderWindow() { using namespace MiniHost; diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index 0245247f8..ba0b5aeff 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -4,6 +4,7 @@ #include "graphicssettingswidget.h" #include "qthost.h" #include "qtutils.h" +#include "qtwindowinfo.h" #include "settingswindow.h" #include "settingwidgetbinder.h" #include "ui_texturereplacementsettingsdialog.h" @@ -19,6 +20,7 @@ #include "util/media_capture.h" #include "common/error.h" +#include "common/log.h" #include #include @@ -28,6 +30,8 @@ #include "moc_graphicssettingswidget.cpp" +LOG_CHANNEL(Host); + static QVariant GetMSAAModeValue(uint multisamples, bool ssaa) { const uint userdata = (multisamples & 0x7FFFFFFFu) | (static_cast(ssaa) << 31); @@ -836,8 +840,17 @@ void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_ m_adapters_render_api = render_api; QtAsyncTask::create(this, [this, render_api]() { - GPUDevice::AdapterInfoList adapters = GPUDevice::GetAdapterListForAPI(render_api); - return [this, adapters = std::move(adapters), render_api]() mutable { + Error error; + std::optional adapters = + GPUDevice::GetAdapterListForAPI(render_api, QtUtils::GetWindowInfoType(), &error); + if (!adapters.has_value()) + { + ERROR_LOG("Failed to get adapter list for {} API: {}", GPUDevice::RenderAPIToString(render_api), + error.GetDescription()); + adapters.emplace(); + } + + return [this, adapters = std::move(adapters.value()), render_api]() mutable { if (m_adapters_render_api != render_api) return; diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index e1a791ef4..a8fa269cd 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -8,6 +8,7 @@ #include "mainwindow.h" #include "qtprogresscallback.h" #include "qtutils.h" +#include "qtwindowinfo.h" #include "settingswindow.h" #include "setupwizarddialog.h" @@ -2872,6 +2873,11 @@ std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool f return g_core_thread->acquireRenderWindow(render_api, fullscreen, exclusive_fullscreen, error); } +WindowInfoType Host::GetRenderWindowInfoType() +{ + return QtUtils::GetWindowInfoType(); +} + void Host::ReleaseRenderWindow() { g_core_thread->releaseRenderWindow(); diff --git a/src/duckstation-qt/qtwindowinfo.cpp b/src/duckstation-qt/qtwindowinfo.cpp index cad87f155..c2a88c09b 100644 --- a/src/duckstation-qt/qtwindowinfo.cpp +++ b/src/duckstation-qt/qtwindowinfo.cpp @@ -4,6 +4,8 @@ #include "qtwindowinfo.h" #include "qtutils.h" +#include "core/core.h" + #include "util/gpu_device.h" #include "common/error.h" @@ -61,6 +63,32 @@ std::pair QtUtils::GetPixelSizeForWidget(const QWidget* widget) return std::make_pair(ApplyDevicePixelRatioToSize(widget->size(), device_pixel_ratio), device_pixel_ratio); } +WindowInfoType QtUtils::GetWindowInfoType() +{ + // Windows and Apple are easy here since there's no display connection. +#if defined(_WIN32) + return WindowInfoType::Win32; +#elif defined(__APPLE__) + return WindowInfoType::MacOS; +#else + const QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("xcb")) + { + // This is only used for determining the automatic Vulkan renderer, therefore XCB/XLib doesn't matter here. + // See the comment below for information about this bullshit. + return WindowInfoType::XCB; + } + else if (platform_name == QStringLiteral("wayland")) + { + return WindowInfoType::Wayland; + } + else + { + return WindowInfoType::Surfaceless; + } +#endif +} + std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error) { WindowInfo wi; diff --git a/src/duckstation-qt/qtwindowinfo.h b/src/duckstation-qt/qtwindowinfo.h index 0b847bd3c..00baa38b2 100644 --- a/src/duckstation-qt/qtwindowinfo.h +++ b/src/duckstation-qt/qtwindowinfo.h @@ -17,6 +17,9 @@ namespace QtUtils { /// Also returns the "real" DPR scale for the widget, ignoring any operating-system level downsampling. std::pair GetPixelSizeForWidget(const QWidget* widget); +/// Returns the window type for the current Qt platform. +WindowInfoType GetWindowInfoType(); + /// Returns the common window info structure for a Qt widget. std::optional GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error = nullptr); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index e7c6230c8..e9ee211eb 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -437,6 +437,11 @@ std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool f return WindowInfo(); } +WindowInfoType Host::GetRenderWindowInfoType() +{ + return WindowInfoType::Surfaceless; +} + void Host::ReleaseRenderWindow() { // diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index b382b3936..b9d8960be 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -173,8 +173,8 @@ if(ENABLE_VULKAN) vulkan_builders.h vulkan_device.cpp vulkan_device.h - vulkan_entry_points.h vulkan_entry_points.inl + vulkan_headers.h vulkan_loader.cpp vulkan_loader.h vulkan_pipeline.cpp diff --git a/src/util/d3d_common.cpp b/src/util/d3d_common.cpp index 240b39f91..ac3dc9562 100644 --- a/src/util/d3d_common.cpp +++ b/src/util/d3d_common.cpp @@ -301,13 +301,13 @@ static std::string FixupDuplicateAdapterNames(const GPUDevice::AdapterInfoList& return adapter_name; } -GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList() +std::optional D3DCommon::GetAdapterInfoList(Error* error) { - GPUDevice::AdapterInfoList adapters; + std::optional ret; - Microsoft::WRL::ComPtr factory = CreateFactory(false, nullptr); + Microsoft::WRL::ComPtr factory = CreateFactory(false, error); if (!factory) - return adapters; + return ret; Microsoft::WRL::ComPtr adapter; for (u32 index = 0;; index++) @@ -322,10 +322,13 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList() continue; } + if (!ret.has_value()) + ret.emplace(); + // Unfortunately we can't get any properties such as feature level without creating the device. // So just assume a max of the D3D11 max across the board. GPUDevice::AdapterInfo ai; - ai.name = FixupDuplicateAdapterNames(adapters, GetAdapterName(adapter.Get(), &ai.driver_type)); + ai.name = FixupDuplicateAdapterNames(ret.value(), GetAdapterName(adapter.Get(), &ai.driver_type)); ai.max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; ai.max_multisamples = 8; ai.supports_sample_shading = true; @@ -365,10 +368,16 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList() ERROR_LOG("EnumOutputs() failed: {:08X}", static_cast(hr)); } - adapters.push_back(std::move(ai)); + ret->push_back(std::move(ai)); } - return adapters; + if (!ret.has_value()) + { + Error::SetStringView(error, "No DXGI adapters found."); + return ret; + } + + return ret; } std::optional diff --git a/src/util/d3d_common.h b/src/util/d3d_common.h index 21c03144f..01ba8e021 100644 --- a/src/util/d3d_common.h +++ b/src/util/d3d_common.h @@ -60,7 +60,7 @@ bool CreateD3D12Device(IDXGIAdapter* adapter, D3D_FEATURE_LEVEL feature_level, Microsoft::WRL::ComPtr SerializeRootSignature(const D3D12_ROOT_SIGNATURE_DESC* desc, Error* error); // returns a list of all adapter names -GPUDevice::AdapterInfoList GetAdapterInfoList(); +std::optional GetAdapterInfoList(Error* error); // returns the fullscreen mode to use for the specified dimensions std::optional diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 65df728d0..484f525fe 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -41,6 +41,7 @@ LOG_CHANNEL(GPUDevice); #ifdef ENABLE_VULKAN #include "vulkan_device.h" +#include "vulkan_loader.h" #endif std::unique_ptr g_gpu_device; @@ -328,7 +329,7 @@ GPUDevice::GPUDevice() GPUDevice::~GPUDevice() = default; -RenderAPI GPUDevice::GetPreferredAPI() +RenderAPI GPUDevice::GetPreferredAPI(WindowInfoType window_type) { static RenderAPI preferred_renderer = RenderAPI::None; if (preferred_renderer == RenderAPI::None) [[unlikely]] @@ -343,7 +344,7 @@ RenderAPI GPUDevice::GetPreferredAPI() preferred_renderer = RenderAPI::Metal; #elif defined(ENABLE_OPENGL) && defined(ENABLE_VULKAN) // On Linux, if we have both GL and Vulkan, prefer VK if the driver isn't software. - preferred_renderer = VulkanDevice::IsSuitableDefaultRenderer() ? RenderAPI::Vulkan : RenderAPI::OpenGL; + preferred_renderer = VulkanLoader::IsSuitableDefaultRenderer(window_type) ? RenderAPI::Vulkan : RenderAPI::OpenGL; #elif defined(ENABLE_OPENGL) preferred_renderer = RenderAPI::OpenGL; #elif defined(ENABLE_VULKAN) @@ -413,15 +414,16 @@ bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs) (rhs == RenderAPI::OpenGL || rhs == RenderAPI::OpenGLES))); } -GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api) +std::optional GPUDevice::GetAdapterListForAPI(RenderAPI api, WindowInfoType window_type, + Error* error) { - AdapterInfoList ret; + std::optional ret; switch (api) { #ifdef ENABLE_VULKAN case RenderAPI::Vulkan: - ret = VulkanDevice::GetAdapterList(); + ret = VulkanLoader::GetAdapterList(window_type, error); break; #endif @@ -429,13 +431,14 @@ GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api) case RenderAPI::OpenGL: case RenderAPI::OpenGLES: // No way of querying. + ret = AdapterInfoList(); break; #endif #ifdef _WIN32 case RenderAPI::D3D11: case RenderAPI::D3D12: - ret = D3DCommon::GetAdapterInfoList(); + ret = D3DCommon::GetAdapterInfoList(error); break; #endif diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 026d91f50..506e6fead 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -695,7 +695,7 @@ public: virtual ~GPUDevice(); /// Returns the default/preferred API for the system. - static RenderAPI GetPreferredAPI(); + static RenderAPI GetPreferredAPI(WindowInfoType window_type); /// Returns a string representing the specified API. static const char* RenderAPIToString(RenderAPI api); @@ -713,7 +713,7 @@ public: static bool IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs); /// Returns a list of adapters for the given API. - static AdapterInfoList GetAdapterListForAPI(RenderAPI api); + static std::optional GetAdapterListForAPI(RenderAPI api, WindowInfoType window_type, Error* error); /// Dumps out a shader that failed compilation. static void DumpBadShader(std::string_view code, std::string_view errors); diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index b427b2f17..d3803a449 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -101,7 +101,7 @@ - + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 6550a42ca..6d8e67a49 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -30,7 +30,6 @@ - @@ -81,6 +80,7 @@ + diff --git a/src/util/vulkan_builders.cpp b/src/util/vulkan_builders.cpp index b9d84d428..77026fbb8 100644 --- a/src/util/vulkan_builders.cpp +++ b/src/util/vulkan_builders.cpp @@ -118,6 +118,35 @@ void Vulkan::SetErrorObject(Error* errptr, std::string_view prefix, VkResult res Error::SetStringFmt(errptr, "{} (0x{:08X}: {})", prefix, static_cast(res), VkResultToString(res)); } +u32 Vulkan::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkPhysicalDeviceProperties& properties) +{ + VkImageFormatProperties color_properties = {}; + vkGetPhysicalDeviceImageFormatProperties(physical_device, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 0, + &color_properties); + VkImageFormatProperties depth_properties = {}; + vkGetPhysicalDeviceImageFormatProperties(physical_device, VK_FORMAT_D32_SFLOAT, VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 0, + &depth_properties); + const VkSampleCountFlags combined_properties = properties.limits.framebufferColorSampleCounts & + properties.limits.framebufferDepthSampleCounts & + color_properties.sampleCounts & depth_properties.sampleCounts; + if (combined_properties & VK_SAMPLE_COUNT_64_BIT) + return 64; + else if (combined_properties & VK_SAMPLE_COUNT_32_BIT) + return 32; + else if (combined_properties & VK_SAMPLE_COUNT_16_BIT) + return 16; + else if (combined_properties & VK_SAMPLE_COUNT_8_BIT) + return 8; + else if (combined_properties & VK_SAMPLE_COUNT_4_BIT) + return 4; + else if (combined_properties & VK_SAMPLE_COUNT_2_BIT) + return 2; + else + return 1; +} + Vulkan::DescriptorSetLayoutBuilder::DescriptorSetLayoutBuilder() { Clear(); diff --git a/src/util/vulkan_builders.h b/src/util/vulkan_builders.h index 40165d2a1..5bfdd4a81 100644 --- a/src/util/vulkan_builders.h +++ b/src/util/vulkan_builders.h @@ -4,7 +4,7 @@ #pragma once #include "gpu_device.h" -#include "vulkan_loader.h" +#include "vulkan_headers.h" #include "common/small_string.h" #include "common/string_util.h" @@ -28,6 +28,8 @@ const char* VkResultToString(VkResult res); void LogVulkanResult(const char* func_name, VkResult res, std::string_view msg); void SetErrorObject(Error* errptr, std::string_view prefix, VkResult res); +u32 GetMaxMultisamples(VkPhysicalDevice physical_device, const VkPhysicalDeviceProperties& properties); + class DescriptorSetLayoutBuilder { public: diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index b4cca2fe4..f731bb7df 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -3,6 +3,7 @@ #include "vulkan_device.h" #include "vulkan_builders.h" +#include "vulkan_loader.h" #include "vulkan_pipeline.h" #include "vulkan_stream_buffer.h" #include "vulkan_swap_chain.h" @@ -24,11 +25,6 @@ #include "fmt/format.h" #include "xxhash.h" -#ifdef ENABLE_SDL -#include -#include -#endif - #include #include #include @@ -117,9 +113,6 @@ static const VkRenderPass DYNAMIC_RENDERING_RENDER_PASS = ((VkRenderPass) static static u32 s_debug_scope_depth = 0; #endif -// We need to synchronize instance creation because of adapter enumeration from the UI thread. -static std::mutex s_instance_mutex; - VulkanDevice::VulkanDevice() { m_render_api = RenderAPI::Vulkan; @@ -145,302 +138,6 @@ GPUTextureFormat VulkanDevice::GetFormatForVkFormat(VkFormat format) return GPUTextureFormat::Unknown; } -VkInstance VulkanDevice::CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils, - bool enable_validation_layer) -{ - ExtensionList enabled_extensions; - if (!SelectInstanceExtensions(&enabled_extensions, wi, oe, enable_debug_utils)) - return VK_NULL_HANDLE; - - u32 maxApiVersion = VK_API_VERSION_1_0; - if (vkEnumerateInstanceVersion) - { - VkResult res = vkEnumerateInstanceVersion(&maxApiVersion); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkEnumerateInstanceVersion() failed: "); - maxApiVersion = VK_API_VERSION_1_0; - } - } - else - { - WARNING_LOG("Driver does not provide vkEnumerateInstanceVersion()."); - } - - // Cap out at 1.1 for consistency. - const u32 apiVersion = std::min(maxApiVersion, VK_API_VERSION_1_1); - INFO_LOG("Supported instance version: {}.{}.{}, requesting version {}.{}.{}", VK_API_VERSION_MAJOR(maxApiVersion), - VK_API_VERSION_MINOR(maxApiVersion), VK_API_VERSION_PATCH(maxApiVersion), VK_API_VERSION_MAJOR(apiVersion), - VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion)); - - // Remember to manually update this every release. We don't pull in svnrev.h here, because - // it's only the major/minor version, and rebuilding the file every time something else changes - // is unnecessary. - VkApplicationInfo app_info = {}; - app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - app_info.pNext = nullptr; - app_info.pApplicationName = "DuckStation"; - app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0); - app_info.pEngineName = "DuckStation"; - app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0); - app_info.apiVersion = apiVersion; - - VkInstanceCreateInfo instance_create_info = {}; - instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instance_create_info.pNext = nullptr; - instance_create_info.flags = 0; - instance_create_info.pApplicationInfo = &app_info; - instance_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); - instance_create_info.ppEnabledExtensionNames = enabled_extensions.data(); - instance_create_info.enabledLayerCount = 0; - instance_create_info.ppEnabledLayerNames = nullptr; - - // Enable debug layer on debug builds - if (enable_validation_layer) - { - static const char* layer_names[] = {"VK_LAYER_KHRONOS_validation"}; - instance_create_info.enabledLayerCount = 1; - instance_create_info.ppEnabledLayerNames = layer_names; - } - - VkInstance instance; - VkResult res = vkCreateInstance(&instance_create_info, nullptr, &instance); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateInstance failed: "); - return nullptr; - } - - return instance; -} - -bool VulkanDevice::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, OptionalExtensions* oe, - bool enable_debug_utils) -{ - u32 extension_count = 0; - VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); - return false; - } - - if (extension_count == 0) - { - ERROR_LOG("Vulkan: No extensions supported by instance."); - return false; - } - - std::vector available_extension_list(extension_count); - res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, available_extension_list.data()); - DebugAssert(res == VK_SUCCESS); - - auto SupportsExtension = [&](const char* name, bool required) { - if (std::find_if(available_extension_list.begin(), available_extension_list.end(), - [&](const VkExtensionProperties& properties) { - return !strcmp(name, properties.extensionName); - }) != available_extension_list.end()) - { - DEV_LOG("Enabling extension: {}", name); - extension_list->push_back(name); - return true; - } - - if (required) - ERROR_LOG("Vulkan: Missing required extension {}.", name); - - return false; - }; - -#if defined(VK_USE_PLATFORM_WIN32_KHR) - if (wi.type == WindowInfoType::Win32 && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || - !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))) - return false; -#endif -#if defined(VK_USE_PLATFORM_XCB_KHR) - if (wi.type == WindowInfoType::XCB && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || - !SupportsExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, true))) - return false; -#endif -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - if (wi.type == WindowInfoType::Wayland && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || - !SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))) - return false; -#endif -#if defined(VK_USE_PLATFORM_METAL_EXT) - if (wi.type == WindowInfoType::MacOS && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || - !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))) - { - return false; - } -#endif -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - if (wi.type == WindowInfoType::Android && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || - !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))) - { - return false; - } -#endif - -#if defined(ENABLE_SDL) - if (wi.type == WindowInfoType::SDL) - { - Uint32 sdl_extension_count = 0; - const char* const* sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extension_count); - if (!sdl_extensions) - { - ERROR_LOG("SDL_Vulkan_GetInstanceExtensions() failed: {}", SDL_GetError()); - return false; - } - - for (unsigned int i = 0; i < sdl_extension_count; i++) - { - if (!SupportsExtension(sdl_extensions[i], true)) - return false; - } - } -#endif - - // VK_EXT_debug_utils - if (enable_debug_utils && !SupportsExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false)) - WARNING_LOG("Vulkan: Debug report requested, but extension is not available."); - - // Needed for exclusive fullscreen control. - SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false); - - oe->vk_khr_get_surface_capabilities2 = (wi.type != WindowInfoType::Surfaceless && - SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false)); - oe->vk_ext_surface_maintenance1 = - (wi.type != WindowInfoType::Surfaceless && SupportsExtension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, false)); - oe->vk_ext_swapchain_maintenance1 = - (wi.type != WindowInfoType::Surfaceless && SupportsExtension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, false)); - oe->vk_khr_get_physical_device_properties2 = - SupportsExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false); - - return true; -} - -VulkanDevice::GPUList VulkanDevice::EnumerateGPUs(VkInstance instance) -{ - GPUList gpus; - - u32 gpu_count = 0; - VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr); - if ((res != VK_SUCCESS && res != VK_INCOMPLETE) || gpu_count == 0) - { - LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices (1) failed: "); - return gpus; - } - - std::vector physical_devices(gpu_count); - res = vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices.data()); - if (res == VK_INCOMPLETE) - { - WARNING_LOG("First vkEnumeratePhysicalDevices() call returned {} devices, but second returned {}", - physical_devices.size(), gpu_count); - } - else if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices (2) failed: "); - return gpus; - } - - // Maybe we lost a GPU? - if (gpu_count < physical_devices.size()) - physical_devices.resize(gpu_count); - - gpus.reserve(physical_devices.size()); - for (VkPhysicalDevice device : physical_devices) - { - VkPhysicalDeviceProperties2 props = {}; - VkPhysicalDeviceDriverProperties driver_props = {}; - - if (vkGetPhysicalDeviceProperties2) - { - props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; - driver_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; - Vulkan::AddPointerToChain(&props, &driver_props); - vkGetPhysicalDeviceProperties2(device, &props); - } - - // just in case the chained version fails - vkGetPhysicalDeviceProperties(device, &props.properties); - - VkPhysicalDeviceFeatures available_features = {}; - vkGetPhysicalDeviceFeatures(device, &available_features); - - AdapterInfo ai; - ai.name = props.properties.deviceName; - ai.max_texture_size = - std::min(props.properties.limits.maxFramebufferWidth, props.properties.limits.maxImageDimension2D); - ai.max_multisamples = GetMaxMultisamples(device, props.properties); - ai.driver_type = GuessDriverType(props.properties, driver_props); - ai.supports_sample_shading = available_features.sampleRateShading; - - // handle duplicate adapter names - if (std::any_of(gpus.begin(), gpus.end(), [&ai](const auto& other) { return (ai.name == other.second.name); })) - { - std::string original_adapter_name = std::move(ai.name); - - u32 current_extra = 2; - do - { - ai.name = fmt::format("{} ({})", original_adapter_name, current_extra); - current_extra++; - } while ( - std::any_of(gpus.begin(), gpus.end(), [&ai](const auto& other) { return (ai.name == other.second.name); })); - } - - gpus.emplace_back(device, std::move(ai)); - } - - return gpus; -} - -VulkanDevice::GPUList VulkanDevice::EnumerateGPUs() -{ - GPUList ret; - std::unique_lock lock(s_instance_mutex); - - // Device shouldn't be torn down since we have the lock. - if (g_gpu_device && g_gpu_device->GetRenderAPI() == RenderAPI::Vulkan && Vulkan::IsVulkanLibraryLoaded()) - { - ret = EnumerateGPUs(VulkanDevice::GetInstance().m_instance); - } - else - { - if (Vulkan::LoadVulkanLibrary(nullptr)) - { - OptionalExtensions oe = {}; - const VkInstance instance = CreateVulkanInstance(WindowInfo(), &oe, false, false); - if (instance != VK_NULL_HANDLE) - { - if (Vulkan::LoadVulkanInstanceFunctions(instance)) - ret = EnumerateGPUs(instance); - - if (vkDestroyInstance) - vkDestroyInstance(instance, nullptr); - else - ERROR_LOG("Vulkan instance was leaked because vkDestroyInstance() could not be loaded."); - } - - Vulkan::UnloadVulkanLibrary(); - } - } - - return ret; -} - -GPUDevice::AdapterInfoList VulkanDevice::GetAdapterList() -{ - AdapterInfoList ret; - GPUList gpus = EnumerateGPUs(); - ret.reserve(gpus.size()); - for (auto& [physical_device, adapter_info] : gpus) - ret.push_back(std::move(adapter_info)); - return ret; -} - bool VulkanDevice::EnableOptionalDeviceExtensions(VkPhysicalDevice physical_device, std::span available_extensions, ExtensionList& enabled_extensions, @@ -544,26 +241,6 @@ bool VulkanDevice::EnableOptionalDeviceExtensions(VkPhysicalDevice physical_devi } } - // we might not have VK_KHR_get_physical_device_properties2... - if (!vkGetPhysicalDeviceFeatures2 || !vkGetPhysicalDeviceProperties2 || !vkGetPhysicalDeviceMemoryProperties2) - { - if (!vkGetPhysicalDeviceFeatures2KHR || !vkGetPhysicalDeviceProperties2KHR || - !vkGetPhysicalDeviceMemoryProperties2KHR) - { - ERROR_LOG("One or more functions from VK_KHR_get_physical_device_properties2 is missing, disabling extension."); - m_optional_extensions.vk_khr_get_physical_device_properties2 = false; - vkGetPhysicalDeviceFeatures2 = nullptr; - vkGetPhysicalDeviceProperties2 = nullptr; - vkGetPhysicalDeviceMemoryProperties2 = nullptr; - } - else - { - vkGetPhysicalDeviceFeatures2 = vkGetPhysicalDeviceFeatures2KHR; - vkGetPhysicalDeviceProperties2 = vkGetPhysicalDeviceProperties2KHR; - vkGetPhysicalDeviceMemoryProperties2 = vkGetPhysicalDeviceMemoryProperties2KHR; - } - } - // don't bother querying if we're not actually looking at any features if (vkGetPhysicalDeviceFeatures2 && features2.pNext) vkGetPhysicalDeviceFeatures2(physical_device, &features2); @@ -613,7 +290,7 @@ bool VulkanDevice::EnableOptionalDeviceExtensions(VkPhysicalDevice physical_devi vkGetPhysicalDeviceProperties2(physical_device, &properties2); // set driver type - SetDriverType(GuessDriverType(m_device_properties, m_device_driver_properties)); + SetDriverType(VulkanLoader::GuessDriverType(m_device_properties, m_device_driver_properties)); // check we actually support enough m_optional_extensions.vk_khr_push_descriptor &= (push_descriptor_properties.maxPushDescriptors >= 1); @@ -728,13 +405,10 @@ bool VulkanDevice::EnableOptionalDeviceExtensions(VkPhysicalDevice physical_devi LOG_EXT("VK_EXT_fragment_shader_interlock", vk_ext_fragment_shader_interlock); LOG_EXT("VK_EXT_memory_budget", vk_ext_memory_budget); LOG_EXT("VK_EXT_rasterization_order_attachment_access", vk_ext_rasterization_order_attachment_access); - LOG_EXT("VK_EXT_surface_maintenance1", vk_ext_surface_maintenance1); LOG_EXT("VK_EXT_swapchain_maintenance1", vk_ext_swapchain_maintenance1); - LOG_EXT("VK_KHR_get_physical_device_properties2", vk_khr_get_physical_device_properties2); LOG_EXT("VK_KHR_driver_properties", vk_khr_driver_properties); LOG_EXT("VK_KHR_dynamic_rendering", vk_khr_dynamic_rendering); LOG_EXT("VK_KHR_dynamic_rendering_local_read", vk_khr_dynamic_rendering_local_read); - LOG_EXT("VK_KHR_get_surface_capabilities2", vk_khr_get_surface_capabilities2); LOG_EXT("VK_KHR_maintenance4", vk_khr_maintenance4); LOG_EXT("VK_KHR_maintenance5", vk_khr_maintenance5); LOG_EXT("VK_KHR_push_descriptor", vk_khr_push_descriptor); @@ -750,8 +424,8 @@ bool VulkanDevice::EnableOptionalDeviceExtensions(VkPhysicalDevice physical_devi return true; } -bool VulkanDevice::CreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, bool enable_validation_layer, - CreateFlags create_flags, Error* error) +bool VulkanDevice::CreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, CreateFlags create_flags, + Error* error) { u32 queue_family_count; vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); @@ -911,7 +585,8 @@ bool VulkanDevice::CreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR s Vulkan::AddPointerToChain(&device_info, &maintenance5_features); } - res = vkCreateDevice(physical_device, &device_info, nullptr, &m_device); + VkDevice device; + res = vkCreateDevice(physical_device, &device_info, nullptr, &device); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateDevice failed: "); @@ -921,10 +596,19 @@ bool VulkanDevice::CreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR s // With the device created, we can fill the remaining entry points. m_physical_device = physical_device; - if (!Vulkan::LoadVulkanDeviceFunctions(m_device)) + if (!VulkanLoader::LoadDeviceFunctions(device, error)) + { + if (vkDestroyDevice) + vkDestroyDevice(device, nullptr); + else + ERROR_LOG("Vulkan device leaked because vkDestroyDevice() function pointer was null."); + + VulkanLoader::ResetDeviceFunctions(); return false; + } // Grab the graphics and present queues. + m_device = device; vkGetDeviceQueue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue); if (surface) vkGetDeviceQueue(m_device, m_present_queue_family_index, 0, &m_present_queue); @@ -955,7 +639,7 @@ bool VulkanDevice::CreateAllocator() ci.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; ci.physicalDevice = m_physical_device; ci.device = m_device; - ci.instance = m_instance; + ci.instance = VulkanLoader::GetVulkanInstance(); if (m_optional_extensions.vk_ext_memory_budget) { @@ -1707,78 +1391,6 @@ void VulkanDevice::DeferSamplerDestruction(VkSampler object) [this, object]() { vkDestroySampler(m_device, object, nullptr); }); } -VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, - VkDebugUtilsMessageTypeFlagsEXT messageType, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) -{ - if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) - { - ERROR_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", - pCallbackData->pMessage); - } - else if (severity & (VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)) - { - WARNING_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", - pCallbackData->pMessage); - } - else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) - { - INFO_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", - pCallbackData->pMessage); - } - else - { - DEV_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", - pCallbackData->pMessage); - } - - return VK_FALSE; -} - -bool VulkanDevice::EnableDebugUtils() -{ - // Already enabled? - if (m_debug_messenger_callback != VK_NULL_HANDLE) - return true; - - // Check for presence of the functions before calling - if (!vkCreateDebugUtilsMessengerEXT || !vkDestroyDebugUtilsMessengerEXT || !vkSubmitDebugUtilsMessageEXT) - { - return false; - } - - VkDebugUtilsMessengerCreateInfoEXT messenger_info = { - VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - nullptr, - 0, - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, - VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, - DebugMessengerCallback, - nullptr}; - - const VkResult res = - vkCreateDebugUtilsMessengerEXT(m_instance, &messenger_info, nullptr, &m_debug_messenger_callback); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateDebugUtilsMessengerEXT failed: "); - return false; - } - - return true; -} - -void VulkanDevice::DisableDebugUtils() -{ - if (m_debug_messenger_callback != VK_NULL_HANDLE) - { - vkDestroyDebugUtilsMessengerEXT(m_instance, m_debug_messenger_callback, nullptr); - m_debug_messenger_callback = VK_NULL_HANDLE; - } -} - VkRenderPass VulkanDevice::CreateCachedRenderPass(RenderPassCacheKey key) { std::array color_references; @@ -1929,115 +1541,23 @@ void VulkanDevice::DestroyFramebuffer(VkFramebuffer fbo) VulkanDevice::GetInstance().DeferFramebufferDestruction(fbo); } -bool VulkanDevice::IsSuitableDefaultRenderer() -{ -#ifdef __ANDROID__ - // No way in hell. - return false; -#else - GPUList gpus = EnumerateGPUs(); - if (gpus.empty()) - { - // No adapters, not gonna be able to use VK. - return false; - } - - // Check the first GPU, should be enough. - const AdapterInfo& ainfo = gpus.front().second; - INFO_LOG("Using Vulkan GPU '{}' for automatic renderer check.", ainfo.name); - - // Any software rendering (LLVMpipe, SwiftShader). - if ((ainfo.driver_type & GPUDriverType::SoftwareFlag) == GPUDriverType::SoftwareFlag) - { - INFO_LOG("Not using Vulkan for software renderer."); - return false; - } - -#ifdef __linux__ - // Intel Ivy Bridge/Haswell/Broadwell drivers are incomplete. - if (ainfo.driver_type == GPUDriverType::IntelMesa && - (ainfo.name.find("Ivy Bridge") != std::string::npos || ainfo.name.find("Haswell") != std::string::npos || - ainfo.name.find("Broadwell") != std::string::npos || ainfo.name.find("(IVB") != std::string::npos || - ainfo.name.find("(HSW") != std::string::npos || ainfo.name.find("(BDW") != std::string::npos)) - { - INFO_LOG("Not using Vulkan for Intel GPU with incomplete driver."); - return false; - } -#endif - -#if defined(__linux__) || defined(__ANDROID__) - // V3D is buggy, image copies with larger textures are broken. - if (ainfo.driver_type == GPUDriverType::BroadcomMesa) - { - INFO_LOG("Not using Vulkan for V3D GPU with buggy driver."); - return false; - } -#endif - - INFO_LOG("Allowing Vulkan as default renderer."); - return true; -#endif -} - bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, CreateFlags create_flags, const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, const ExclusiveFullscreenMode* exclusive_fullscreen_mode, std::optional exclusive_fullscreen_control, Error* error) { - std::unique_lock lock(s_instance_mutex); - bool enable_debug_utils = m_debug_device; - bool enable_validation_layer = m_debug_device; - -#ifdef ENABLE_SDL - const bool library_loaded = - (wi.type == WindowInfoType::SDL) ? Vulkan::LoadVulkanLibraryFromSDL(error) : Vulkan::LoadVulkanLibrary(error); -#else - const bool library_loaded = Vulkan::LoadVulkanLibrary(error); -#endif - if (!library_loaded) + if (!VulkanLoader::CreateVulkanInstance(wi.type, &m_debug_device, error)) { - Error::AddPrefix(error, - "Failed to load Vulkan library. Does your GPU and/or driver support Vulkan?\nThe error was:"); + Error::AddPrefix(error, "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?\n"); return false; } - m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer); - if (m_instance == VK_NULL_HANDLE) - { - if (enable_debug_utils || enable_validation_layer) - { - // Try again without the validation layer. - enable_debug_utils = false; - enable_validation_layer = false; - m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer); - if (m_instance == VK_NULL_HANDLE) - { - Error::SetStringView(error, "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?"); - return false; - } - - ERROR_LOG("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device."); - } - } - - if (!Vulkan::LoadVulkanInstanceFunctions(m_instance)) - { - ERROR_LOG("Failed to load Vulkan instance functions"); - Error::SetStringView(error, "Failed to load Vulkan instance functions"); - - if (vkDestroyInstance) - vkDestroyInstance(std::exchange(m_instance, nullptr), nullptr); - else - ERROR_LOG("Vulkan instance was leaked because vkDestroyInstance() could not be loaded."); - - return false; - } - - GPUList gpus = EnumerateGPUs(m_instance); + const VulkanLoader::GPUList gpus = VulkanLoader::EnumerateGPUs(error); if (gpus.empty()) { - Error::SetStringView(error, "No physical devices found. Does your GPU and/or driver support Vulkan?"); + Error::AddPrefix(error, "No physical devices found. Does your GPU and/or driver support Vulkan?\n"); + VulkanLoader::ReleaseVulkanInstance(); return false; } @@ -2067,25 +1587,23 @@ bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, Create physical_device = gpus[0].first; } - if (enable_debug_utils) - EnableDebugUtils(); - std::unique_ptr swap_chain; if (!wi.IsSurfaceless()) { swap_chain = std::make_unique(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control); - if (!swap_chain->CreateSurface(m_instance, physical_device, error)) + if (!swap_chain->CreateSurface(physical_device, error)) { swap_chain->Destroy(*this, false); + VulkanLoader::ReleaseVulkanInstance(); return false; } } // Attempt to create the device. - if (!CreateDevice(physical_device, swap_chain ? swap_chain->GetSurface() : VK_NULL_HANDLE, enable_validation_layer, - create_flags, error)) + if (!CreateDevice(physical_device, swap_chain ? swap_chain->GetSurface() : VK_NULL_HANDLE, create_flags, error)) { + VulkanLoader::ReleaseVulkanInstance(); return false; } @@ -2119,8 +1637,6 @@ bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, Create void VulkanDevice::DestroyDevice() { - std::unique_lock lock(s_instance_mutex); - if (InRenderPass()) EndRenderPass(); @@ -2160,18 +1676,9 @@ void VulkanDevice::DestroyDevice() { vkDestroyDevice(m_device, nullptr); m_device = VK_NULL_HANDLE; + VulkanLoader::ResetDeviceFunctions(); + VulkanLoader::ReleaseVulkanInstance(); } - - if (m_debug_messenger_callback != VK_NULL_HANDLE) - DisableDebugUtils(); - - if (m_instance != VK_NULL_HANDLE) - { - vkDestroyInstance(m_instance, nullptr); - m_instance = VK_NULL_HANDLE; - } - - Vulkan::UnloadVulkanLibrary(); } bool VulkanDevice::ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header, Error* error) @@ -2292,7 +1799,7 @@ std::unique_ptr VulkanDevice::CreateSwapChain(const WindowInfo& wi { std::unique_ptr swap_chain = std::make_unique(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control); - if (swap_chain->CreateSurface(m_instance, m_physical_device, error) && swap_chain->CreateSwapChain(*this, error) && + if (swap_chain->CreateSurface(m_physical_device, error) && swap_chain->CreateSwapChain(*this, error) && swap_chain->CreateSwapChainImages(*this, error)) { if (InRenderPass()) @@ -2457,35 +1964,6 @@ void VulkanDevice::InsertDebugMessage(const char* msg) #endif -u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkPhysicalDeviceProperties& properties) -{ - VkImageFormatProperties color_properties = {}; - vkGetPhysicalDeviceImageFormatProperties(physical_device, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D, - VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 0, - &color_properties); - VkImageFormatProperties depth_properties = {}; - vkGetPhysicalDeviceImageFormatProperties(physical_device, VK_FORMAT_D32_SFLOAT, VK_IMAGE_TYPE_2D, - VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 0, - &depth_properties); - const VkSampleCountFlags combined_properties = properties.limits.framebufferColorSampleCounts & - properties.limits.framebufferDepthSampleCounts & - color_properties.sampleCounts & depth_properties.sampleCounts; - if (combined_properties & VK_SAMPLE_COUNT_64_BIT) - return 64; - else if (combined_properties & VK_SAMPLE_COUNT_32_BIT) - return 32; - else if (combined_properties & VK_SAMPLE_COUNT_16_BIT) - return 16; - else if (combined_properties & VK_SAMPLE_COUNT_8_BIT) - return 8; - else if (combined_properties & VK_SAMPLE_COUNT_4_BIT) - return 4; - else if (combined_properties & VK_SAMPLE_COUNT_2_BIT) - return 2; - else - return 1; -} - void VulkanDevice::SetFeatures(CreateFlags create_flags, VkPhysicalDevice physical_device, const VkPhysicalDeviceFeatures& vk_features) { @@ -2494,7 +1972,7 @@ void VulkanDevice::SetFeatures(CreateFlags create_flags, VkPhysicalDevice physic (VK_API_VERSION_MINOR(store_api_version) * 10u) + (VK_API_VERSION_PATCH(store_api_version)); m_max_texture_size = std::min(m_device_properties.limits.maxImageDimension2D, m_device_properties.limits.maxFramebufferWidth); - m_max_multisamples = static_cast(GetMaxMultisamples(physical_device, m_device_properties)); + m_max_multisamples = static_cast(Vulkan::GetMaxMultisamples(physical_device, m_device_properties)); m_features.dual_source_blend = !HasCreateFlag(create_flags, CreateFlags::DisableDualSourceBlend) && vk_features.dualSrcBlend; @@ -2547,51 +2025,6 @@ void VulkanDevice::SetFeatures(CreateFlags create_flags, VkPhysicalDevice physic (!HasCreateFlag(create_flags, CreateFlags::DisableCompressedTextures) && vk_features.textureCompressionBC); } -GPUDriverType VulkanDevice::GuessDriverType(const VkPhysicalDeviceProperties& device_properties, - const VkPhysicalDeviceDriverProperties& driver_properties) -{ - static constexpr const std::pair table[] = { - {VK_DRIVER_ID_NVIDIA_PROPRIETARY, GPUDriverType::NVIDIAProprietary}, - {VK_DRIVER_ID_AMD_PROPRIETARY, GPUDriverType::AMDProprietary}, - {VK_DRIVER_ID_AMD_OPEN_SOURCE, GPUDriverType::AMDProprietary}, - {VK_DRIVER_ID_MESA_RADV, GPUDriverType::AMDMesa}, - {VK_DRIVER_ID_NVIDIA_PROPRIETARY, GPUDriverType::NVIDIAProprietary}, - {VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, GPUDriverType::IntelProprietary}, - {VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, GPUDriverType::IntelMesa}, - {VK_DRIVER_ID_IMAGINATION_PROPRIETARY, GPUDriverType::ImaginationProprietary}, - {VK_DRIVER_ID_QUALCOMM_PROPRIETARY, GPUDriverType::QualcommProprietary}, - {VK_DRIVER_ID_ARM_PROPRIETARY, GPUDriverType::ARMProprietary}, - {VK_DRIVER_ID_GOOGLE_SWIFTSHADER, GPUDriverType::SwiftShader}, - {VK_DRIVER_ID_GGP_PROPRIETARY, GPUDriverType::Unknown}, - {VK_DRIVER_ID_BROADCOM_PROPRIETARY, GPUDriverType::BroadcomProprietary}, - {VK_DRIVER_ID_MESA_LLVMPIPE, GPUDriverType::LLVMPipe}, - {VK_DRIVER_ID_MOLTENVK, GPUDriverType::AppleProprietary}, - {VK_DRIVER_ID_COREAVI_PROPRIETARY, GPUDriverType::Unknown}, - {VK_DRIVER_ID_JUICE_PROPRIETARY, GPUDriverType::Unknown}, - {VK_DRIVER_ID_VERISILICON_PROPRIETARY, GPUDriverType::Unknown}, - {VK_DRIVER_ID_MESA_TURNIP, GPUDriverType::QualcommMesa}, - {VK_DRIVER_ID_MESA_V3DV, GPUDriverType::BroadcomMesa}, - {VK_DRIVER_ID_MESA_PANVK, GPUDriverType::ARMMesa}, - {VK_DRIVER_ID_SAMSUNG_PROPRIETARY, GPUDriverType::AMDProprietary}, - {VK_DRIVER_ID_MESA_VENUS, GPUDriverType::Unknown}, - {VK_DRIVER_ID_MESA_DOZEN, GPUDriverType::DozenMesa}, - {VK_DRIVER_ID_MESA_NVK, GPUDriverType::NVIDIAMesa}, - {VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA, GPUDriverType::ImaginationMesa}, - {VK_DRIVER_ID_MESA_AGXV, GPUDriverType::AppleMesa}, - }; - - const auto iter = std::find_if(std::begin(table), std::end(table), [&driver_properties](const auto& it) { - return (driver_properties.driverID == it.first); - }); - if (iter != std::end(table)) - return iter->second; - - return GPUDevice::GuessDriverType( - device_properties.vendorID, {}, - std::string_view(device_properties.deviceName, - StringUtil::Strnlen(device_properties.deviceName, std::size(device_properties.deviceName)))); -} - void VulkanDevice::CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src, u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 width, u32 height) diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index 527088a10..1f692aa31 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -6,7 +6,7 @@ #include "gpu_device.h" #include "gpu_framebuffer_manager.h" #include "gpu_texture.h" -#include "vulkan_loader.h" +#include "vulkan_headers.h" #include "vulkan_stream_buffer.h" #include "common/dimensional_array.h" @@ -40,21 +40,14 @@ public: NUM_COMMAND_BUFFERS = 3, }; - struct OptionalInstanceExtensions - { - bool vk_ext_surface_maintenance1 : 1; - bool vk_ext_swapchain_maintenance1 : 1; - bool vk_khr_get_surface_capabilities2 : 1; - bool vk_khr_get_physical_device_properties2 : 1; - }; - - struct OptionalExtensions : OptionalInstanceExtensions + struct OptionalExtensions { bool vk_ext_external_memory_host : 1; bool vk_ext_fragment_shader_interlock : 1; bool vk_ext_full_screen_exclusive : 1; bool vk_ext_memory_budget : 1; bool vk_ext_rasterization_order_attachment_access : 1; + bool vk_ext_swapchain_maintenance1 : 1; bool vk_khr_driver_properties : 1; bool vk_khr_dynamic_rendering : 1; bool vk_khr_dynamic_rendering_local_read : 1; @@ -64,6 +57,8 @@ public: bool vk_khr_shader_non_semantic_info : 1; }; + using ExtensionList = std::vector; + static GPUTextureFormat GetFormatForVkFormat(VkFormat format); static const std::array(GPUTextureFormat::MaxCount)> TEXTURE_FORMAT_MAPPING; @@ -72,12 +67,6 @@ public: VulkanDevice(); ~VulkanDevice() override; - // Returns a list of Vulkan-compatible GPUs. - using GPUList = std::vector>; - static GPUList EnumerateGPUs(VkInstance instance); - static GPUList EnumerateGPUs(); - static AdapterInfoList GetAdapterList(); - std::string GetDriverInfo() const override; void FlushCommands() override; @@ -163,7 +152,6 @@ public: // Global state accessors ALWAYS_INLINE static VulkanDevice& GetInstance() { return *static_cast(g_gpu_device.get()); } - ALWAYS_INLINE VkInstance GetVulkanInstance() const { return m_instance; } ALWAYS_INLINE VkDevice GetVulkanDevice() const { return m_device; } ALWAYS_INLINE VmaAllocator GetAllocator() const { return m_allocator; } ALWAYS_INLINE VkPhysicalDevice GetVulkanPhysicalDevice() const { return m_physical_device; } @@ -171,9 +159,6 @@ public: ALWAYS_INLINE u32 GetPresentQueueFamilyIndex() const { return m_present_queue_family_index; } ALWAYS_INLINE const OptionalExtensions& GetOptionalExtensions() const { return m_optional_extensions; } - /// Returns true if Vulkan is suitable as a default for the devices in the system. - static bool IsSuitableDefaultRenderer(); - // Helpers for getting constants ALWAYS_INLINE u32 GetBufferCopyOffsetAlignment() const { @@ -326,22 +311,10 @@ private: using CleanupObjectFunction = void (*)(VulkanDevice& dev, void* obj); - // Helper method to create a Vulkan instance. - static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils, - bool enable_validation_layer); - bool ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header, Error* error); void FillPipelineCacheHeader(VK_PIPELINE_CACHE_HEADER* header); - // Enable/disable debug message runtime. - bool EnableDebugUtils(); - void DisableDebugUtils(); - - using ExtensionList = std::vector; - static bool SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, OptionalExtensions* oe, - bool enable_debug_utils); - bool CreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, bool enable_validation_layer, - CreateFlags create_flags, Error* error); + bool CreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, CreateFlags create_flags, Error* error); bool EnableOptionalDeviceExtensions(VkPhysicalDevice physical_device, std::span available_extensions, ExtensionList& enabled_extensions, VkPhysicalDeviceFeatures& enabled_features, @@ -349,10 +322,6 @@ private: void SetFeatures(CreateFlags create_flags, VkPhysicalDevice physical_device, const VkPhysicalDeviceFeatures& vk_features); - static GPUDriverType GuessDriverType(const VkPhysicalDeviceProperties& device_properties, - const VkPhysicalDeviceDriverProperties& driver_properties); - static u32 GetMaxMultisamples(VkPhysicalDevice physical_device, const VkPhysicalDeviceProperties& properties); - bool CreateAllocator(); void DestroyAllocator(); bool CreateCommandBuffers(); @@ -403,8 +372,6 @@ private: void EndAndSubmitCommandBuffer(VulkanSwapChain* present_swap_chain, bool explicit_present); void QueuePresent(VulkanSwapChain* present_swap_chain); - VkInstance m_instance = VK_NULL_HANDLE; - VkPhysicalDevice m_physical_device = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE; VmaAllocator m_allocator = VK_NULL_HANDLE; @@ -475,7 +442,7 @@ private: m_pipeline_layouts = {}; // Cold variables. + VkPhysicalDevice m_physical_device = VK_NULL_HANDLE; VkPhysicalDeviceProperties m_device_properties = {}; VkPhysicalDeviceDriverProperties m_device_driver_properties = {}; - VkDebugUtilsMessengerEXT m_debug_messenger_callback = VK_NULL_HANDLE; }; diff --git a/src/util/vulkan_entry_points.h b/src/util/vulkan_entry_points.h deleted file mode 100644 index 5f45e3947..000000000 --- a/src/util/vulkan_entry_points.h +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define VULKAN_MODULE_ENTRY_POINT(name, required) extern PFN_##name name; -#define VULKAN_INSTANCE_ENTRY_POINT(name, required) extern PFN_##name name; -#define VULKAN_DEVICE_ENTRY_POINT(name, required) extern PFN_##name name; -#include "vulkan_entry_points.inl" -#undef VULKAN_DEVICE_ENTRY_POINT -#undef VULKAN_INSTANCE_ENTRY_POINT -#undef VULKAN_MODULE_ENTRY_POINT - -#ifdef __cplusplus -} -#endif diff --git a/src/util/vulkan_headers.h b/src/util/vulkan_headers.h new file mode 100644 index 000000000..aaeaae7e2 --- /dev/null +++ b/src/util/vulkan_headers.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +class Error; + +#define VK_NO_PROTOTYPES + +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR + +// vulkan.h pulls in windows.h on Windows, so we need to include our replacement header first +#include "common/windows_headers.h" +#elif defined(__APPLE__) +#define VK_USE_PLATFORM_METAL_EXT +#elif defined(__ANDROID__) +#define VK_USE_PLATFORM_ANDROID_KHR +#else +#ifdef ENABLE_X11 +#define VK_USE_PLATFORM_XCB_KHR +#endif + +#ifdef ENABLE_WAYLAND +#define VK_USE_PLATFORM_WAYLAND_KHR +#endif +#endif + +#include "vulkan/vulkan.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VULKAN_MODULE_ENTRY_POINT(name, required) extern PFN_##name name; +#define VULKAN_INSTANCE_ENTRY_POINT(name, required) extern PFN_##name name; +#define VULKAN_DEVICE_ENTRY_POINT(name, required) extern PFN_##name name; +#include "vulkan_entry_points.inl" +#undef VULKAN_DEVICE_ENTRY_POINT +#undef VULKAN_INSTANCE_ENTRY_POINT +#undef VULKAN_MODULE_ENTRY_POINT + +#ifdef __cplusplus +} +#endif + +// We include vk_mem_alloc globally, so we don't accidentally include it before the vulkan header somewhere. +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-completeness" +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#pragma clang diagnostic ignored "-Wunused-function" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +#pragma warning(push, 0) +#endif + +#define VMA_STATIC_VULKAN_FUNCTIONS 1 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#define VMA_STATS_STRING_ENABLED 0 +#include "vulkan/vk_mem_alloc.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/src/util/vulkan_loader.cpp b/src/util/vulkan_loader.cpp index b717fa72d..e813ccddc 100644 --- a/src/util/vulkan_loader.cpp +++ b/src/util/vulkan_loader.cpp @@ -5,6 +5,10 @@ #define VMA_IMPLEMENTATION #include "vulkan_loader.h" +#include "vulkan_builders.h" +#include "vulkan_device.h" + +#include "core/settings.h" #include "common/assert.h" #include "common/dynamic_library.h" @@ -34,80 +38,162 @@ extern "C" { #undef VULKAN_MODULE_ENTRY_POINT } -void Vulkan::ResetVulkanLibraryFunctionPointers() -{ -#define VULKAN_MODULE_ENTRY_POINT(name, required) name = nullptr; -#define VULKAN_INSTANCE_ENTRY_POINT(name, required) name = nullptr; -#define VULKAN_DEVICE_ENTRY_POINT(name, required) name = nullptr; -#include "vulkan_entry_points.inl" -#undef VULKAN_DEVICE_ENTRY_POINT -#undef VULKAN_INSTANCE_ENTRY_POINT -#undef VULKAN_MODULE_ENTRY_POINT -} +namespace VulkanLoader { -static DynamicLibrary s_vulkan_library; +static bool LoadVulkanLibrary(WindowInfoType wtype, Error* error); +static void ResetModuleFunctions(); +static bool LoadInstanceFunctions(VkInstance instance, Error* error); +static void ResetInstanceFunctions(); +static void UnloadVulkanLibrary(); #ifdef ENABLE_SDL -static bool s_vulkan_library_loaded_from_sdl = false; +static bool LoadVulkanLibraryFromSDL(Error* error); +static void UnloadVulkanLibraryFromSDL(); #endif -bool Vulkan::IsVulkanLibraryLoaded() +static bool LockedCreateVulkanInstance(WindowInfoType wtype, bool* request_debug_instance, Error* error); +static void LockedReleaseVulkanInstance(); +static void LockedDestroyVulkanInstance(); + +static bool SelectInstanceExtensions(VulkanDevice::ExtensionList* extension_list, WindowInfoType wtype, + bool debug_instance, Error* error); + +VKAPI_ATTR static VkBool32 VKAPI_CALL DebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); + +namespace { +struct Locals { + ~Locals(); + + DynamicLibrary library; + VkInstance instance = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT debug_messenger_callback = VK_NULL_HANDLE; + u32 reference_count = 0; + OptionalExtensions optional_extensions{}; + WindowInfoType window_type = WindowInfoType::Surfaceless; + bool is_debug_instance = false; #ifdef ENABLE_SDL - return (s_vulkan_library.IsOpen() || s_vulkan_library_loaded_from_sdl); -#else - return s_vulkan_library.IsOpen(); + bool library_loaded_from_sdl = false; #endif + + std::mutex mutex; +}; + +} // namespace + +ALIGN_TO_CACHE_LINE static Locals s_locals; + +} // namespace VulkanLoader + +VulkanLoader::Locals::~Locals() +{ + // Called at process shutdown. + if (instance) + LockedDestroyVulkanInstance(); + +#ifdef ENABLE_SDL + if (library_loaded_from_sdl) + SDL_Vulkan_UnloadLibrary(); +#endif + + library.Close(); } -bool Vulkan::LoadVulkanLibrary(Error* error) +bool VulkanLoader::LoadVulkanLibrary(WindowInfoType wtype, Error* error) { - AssertMsg(!s_vulkan_library.IsOpen(), "Vulkan module is not loaded."); +#ifdef ENABLE_SDL + // Switching to/from SDL? + if (wtype == WindowInfoType::SDL) + { + if (s_locals.library_loaded_from_sdl) + return true; + + UnloadVulkanLibrary(); + if (!LoadVulkanLibraryFromSDL(error)) + return false; + } + else + { + // Unload from SDL if we were previously using it.. unlikely. + if (s_locals.library_loaded_from_sdl) + UnloadVulkanLibraryFromSDL(); + } +#endif + + if (s_locals.library.IsOpen()) + return true; #ifdef __APPLE__ // Check if a path to a specific Vulkan library has been specified. char* libvulkan_env = getenv("LIBVULKAN_PATH"); if (libvulkan_env) - s_vulkan_library.Open(libvulkan_env, error); - if (!s_vulkan_library.IsOpen() && - !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("MoltenVK").c_str(), error)) + s_locals.library.Open(libvulkan_env, error); + if (!s_locals.library.IsOpen() && + !s_locals.library.Open(DynamicLibrary::GetVersionedFilename("MoltenVK").c_str(), error)) { return false; } #else // try versioned first, then unversioned. - if (!s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan", 1).c_str(), error) && - !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan").c_str(), error)) + if (!s_locals.library.Open(DynamicLibrary::GetVersionedFilename("vulkan", 1).c_str(), error) && + !s_locals.library.Open(DynamicLibrary::GetVersionedFilename("vulkan").c_str(), error)) { return false; } #endif bool required_functions_missing = false; + const auto load_function = [&error, &required_functions_missing](PFN_vkVoidFunction* func_ptr, const char* name, + bool is_required) { + if (!s_locals.library.GetSymbol(name, func_ptr) && is_required && !required_functions_missing) + { + Error::SetStringFmt(error, "Failed to load required module function {}", name); + required_functions_missing = true; + } + }; #define VULKAN_MODULE_ENTRY_POINT(name, required) \ - if (!s_vulkan_library.GetSymbol(#name, &name) && required) \ - { \ - ERROR_LOG("Vulkan: Failed to load required module function {}", #name); \ - required_functions_missing = true; \ - } + load_function(reinterpret_cast(&name), #name, required); #include "vulkan_entry_points.inl" #undef VULKAN_MODULE_ENTRY_POINT if (required_functions_missing) { - Error::SetStringView(error, "One or more required functions are missing. The log contains more information."); - ResetVulkanLibraryFunctionPointers(); - s_vulkan_library.Close(); + ResetModuleFunctions(); + s_locals.library.Close(); return false; } return true; } +void VulkanLoader::UnloadVulkanLibrary() +{ +#ifdef ENABLE_SDL + if (s_locals.library_loaded_from_sdl) + { + UnloadVulkanLibraryFromSDL(); + return; + } +#endif + + ResetModuleFunctions(); + s_locals.library.Close(); +} + +void VulkanLoader::ResetModuleFunctions() +{ +#define VULKAN_MODULE_ENTRY_POINT(name, required) name = nullptr; +#include "vulkan_entry_points.inl" +#undef VULKAN_MODULE_ENTRY_POINT +} + #ifdef ENABLE_SDL -bool Vulkan::LoadVulkanLibraryFromSDL(Error* error) +bool VulkanLoader::LoadVulkanLibraryFromSDL(Error* error) { if (!SDL_Vulkan_LoadLibrary(nullptr)) { @@ -124,84 +210,684 @@ bool Vulkan::LoadVulkanLibraryFromSDL(Error* error) } bool required_functions_missing = false; + const auto load_function = [&error, &required_functions_missing](PFN_vkVoidFunction* func_ptr, const char* name, + bool is_required) { + // vkGetInstanceProcAddr() can't resolve itself until Vulkan 1.2. + if (func_ptr == reinterpret_cast(&vkGetInstanceProcAddr)) + return; - // vkGetInstanceProcAddr() can't resolve itself until Vulkan 1.2. + *func_ptr = vkGetInstanceProcAddr(nullptr, name); + if (!(*func_ptr) && is_required && !required_functions_missing) + { + Error::SetStringFmt(error, "Failed to load required module function {}", name); + required_functions_missing = true; + } + }; #define VULKAN_MODULE_ENTRY_POINT(name, required) \ - if ((reinterpret_cast(&name) != reinterpret_cast(&vkGetInstanceProcAddr)) && \ - !(name = reinterpret_cast(vkGetInstanceProcAddr(nullptr, #name))) && required) \ - { \ - ERROR_LOG("Vulkan: Failed to load required module function {}", #name); \ - required_functions_missing = true; \ - } + load_function(reinterpret_cast(&name), #name, required); #include "vulkan_entry_points.inl" #undef VULKAN_MODULE_ENTRY_POINT if (required_functions_missing) { - Error::SetStringView(error, "One or more required functions are missing. The log contains more information."); - ResetVulkanLibraryFunctionPointers(); + ResetModuleFunctions(); SDL_Vulkan_UnloadLibrary(); return false; } - s_vulkan_library_loaded_from_sdl = true; + s_locals.library_loaded_from_sdl = true; return true; } -#endif - -void Vulkan::UnloadVulkanLibrary() +void VulkanLoader::UnloadVulkanLibraryFromSDL() { - ResetVulkanLibraryFunctionPointers(); - - s_vulkan_library.Close(); - -#ifdef ENABLE_SDL - if (s_vulkan_library_loaded_from_sdl) - { - s_vulkan_library_loaded_from_sdl = false; - SDL_Vulkan_UnloadLibrary(); - } -#endif + ResetModuleFunctions(); + s_locals.library_loaded_from_sdl = false; + SDL_Vulkan_UnloadLibrary(); } -bool Vulkan::LoadVulkanInstanceFunctions(VkInstance instance) +#endif // ENABLE_SDL + +bool VulkanLoader::LoadInstanceFunctions(VkInstance instance, Error* error) { bool required_functions_missing = false; - auto LoadFunction = [&](PFN_vkVoidFunction* func_ptr, const char* name, bool is_required) { + const auto load_function = [&instance, &error, &required_functions_missing](PFN_vkVoidFunction* func_ptr, + const char* name, bool is_required) { *func_ptr = vkGetInstanceProcAddr(instance, name); - if (!(*func_ptr) && is_required) + if (!(*func_ptr) && is_required && !required_functions_missing) { - ERROR_LOG("Vulkan: Failed to load required instance function {}", name); + Error::SetStringFmt(error, "Failed to load required instance function {}", name); required_functions_missing = true; } }; #define VULKAN_INSTANCE_ENTRY_POINT(name, required) \ - LoadFunction(reinterpret_cast(&name), #name, required); + load_function(reinterpret_cast(&name), #name, required); #include "vulkan_entry_points.inl" #undef VULKAN_INSTANCE_ENTRY_POINT + // we might not have VK_KHR_get_physical_device_properties2... + if (!vkGetPhysicalDeviceFeatures2 || !vkGetPhysicalDeviceProperties2 || !vkGetPhysicalDeviceMemoryProperties2) + { + if (!vkGetPhysicalDeviceFeatures2KHR || !vkGetPhysicalDeviceProperties2KHR || + !vkGetPhysicalDeviceMemoryProperties2KHR) + { + ERROR_LOG("One or more functions from VK_KHR_get_physical_device_properties2 is missing, disabling extension."); + s_locals.optional_extensions.vk_khr_get_physical_device_properties2 = false; + vkGetPhysicalDeviceFeatures2 = nullptr; + vkGetPhysicalDeviceProperties2 = nullptr; + vkGetPhysicalDeviceMemoryProperties2 = nullptr; + } + else + { + vkGetPhysicalDeviceFeatures2 = vkGetPhysicalDeviceFeatures2KHR; + vkGetPhysicalDeviceProperties2 = vkGetPhysicalDeviceProperties2KHR; + vkGetPhysicalDeviceMemoryProperties2 = vkGetPhysicalDeviceMemoryProperties2KHR; + } + } + return !required_functions_missing; } -bool Vulkan::LoadVulkanDeviceFunctions(VkDevice device) +void VulkanLoader::ResetInstanceFunctions() +{ +#define VULKAN_INSTANCE_ENTRY_POINT(name, required) name = nullptr; +#include "vulkan_entry_points.inl" +#undef VULKAN_INSTANCE_ENTRY_POINT +} + +bool VulkanLoader::LoadDeviceFunctions(VkDevice device, Error* error) { bool required_functions_missing = false; - auto LoadFunction = [&](PFN_vkVoidFunction* func_ptr, const char* name, bool is_required) { + const auto load_function = [&device, &error, &required_functions_missing](PFN_vkVoidFunction* func_ptr, + const char* name, bool is_required) { *func_ptr = vkGetDeviceProcAddr(device, name); - if (!(*func_ptr) && is_required) + if (!(*func_ptr) && is_required && !required_functions_missing) { - ERROR_LOG("Vulkan: Failed to load required device function {}", name); + Error::SetStringFmt(error, "Failed to load required device function {}", name); required_functions_missing = true; } }; #define VULKAN_DEVICE_ENTRY_POINT(name, required) \ - LoadFunction(reinterpret_cast(&name), #name, required); + load_function(reinterpret_cast(&name), #name, required); #include "vulkan_entry_points.inl" #undef VULKAN_DEVICE_ENTRY_POINT return !required_functions_missing; } + +void VulkanLoader::ResetDeviceFunctions() +{ +#define VULKAN_DEVICE_ENTRY_POINT(name, required) name = nullptr; +#include "vulkan_entry_points.inl" +#undef VULKAN_DEVICE_ENTRY_POINT +} + +bool VulkanLoader::LockedCreateVulkanInstance(WindowInfoType wtype, bool* request_debug_instance, Error* error) +{ + if (s_locals.instance != VK_NULL_HANDLE && + ((request_debug_instance && *request_debug_instance != s_locals.is_debug_instance) || + s_locals.window_type != wtype)) + { + // Different debug setting, need to recreate the instance. + if (s_locals.reference_count > 0) + ERROR_LOG("Cannot change Vulkan instance window type/debug setting while in use."); + else + LockedDestroyVulkanInstance(); + } + + if (s_locals.instance != VK_NULL_HANDLE) + { + s_locals.reference_count++; + DEV_LOG("Using cached Vulkan instance, reference count {}", s_locals.reference_count); + return s_locals.instance; + } + + if (!LoadVulkanLibrary(wtype, error)) + return false; + + bool debug_instance = request_debug_instance ? *request_debug_instance : g_settings.gpu_use_debug_device; + INFO_LOG("Creating new Vulkan instance (debug {}, wtype {})...", debug_instance, static_cast(wtype)); + + VulkanDevice::ExtensionList enabled_extensions; + if (!SelectInstanceExtensions(&enabled_extensions, wtype, debug_instance, error)) + return false; + + u32 maxApiVersion = VK_API_VERSION_1_0; + if (vkEnumerateInstanceVersion) + { + VkResult res = vkEnumerateInstanceVersion(&maxApiVersion); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumerateInstanceVersion() failed: "); + maxApiVersion = VK_API_VERSION_1_0; + } + } + else + { + WARNING_LOG("Driver does not provide vkEnumerateInstanceVersion()."); + } + + // Cap out at 1.1 for consistency. + const u32 apiVersion = std::min(maxApiVersion, VK_API_VERSION_1_1); + INFO_LOG("Supported instance version: {}.{}.{}, requesting version {}.{}.{}", VK_API_VERSION_MAJOR(maxApiVersion), + VK_API_VERSION_MINOR(maxApiVersion), VK_API_VERSION_PATCH(maxApiVersion), VK_API_VERSION_MAJOR(apiVersion), + VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion)); + + // Remember to manually update this every release. We don't pull in svnrev.h here, because + // it's only the major/minor version, and rebuilding the file every time something else changes + // is unnecessary. + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pNext = nullptr; + app_info.pApplicationName = "DuckStation"; + app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0); + app_info.pEngineName = "DuckStation"; + app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0); + app_info.apiVersion = apiVersion; + + VkInstanceCreateInfo instance_create_info = {}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = nullptr; + instance_create_info.flags = 0; + instance_create_info.pApplicationInfo = &app_info; + instance_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + instance_create_info.ppEnabledExtensionNames = enabled_extensions.data(); + instance_create_info.enabledLayerCount = 0; + instance_create_info.ppEnabledLayerNames = nullptr; + + // Enable debug layer on debug builds + if (debug_instance) + { + static const char* layer_names[] = {"VK_LAYER_KHRONOS_validation"}; + instance_create_info.enabledLayerCount = 1; + instance_create_info.ppEnabledLayerNames = layer_names; + } + + DebugAssert(s_locals.instance == VK_NULL_HANDLE && s_locals.reference_count == 0); + VkResult res = vkCreateInstance(&instance_create_info, nullptr, &s_locals.instance); + if (res != VK_SUCCESS) + { + // If creation failed, try without the debug flag. + if (debug_instance) + { + LOG_VULKAN_ERROR(res, "vkCreateInstance() failed, trying without debug layers: "); + debug_instance = false; + if (SelectInstanceExtensions(&enabled_extensions, wtype, false, error)) + { + instance_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + instance_create_info.ppEnabledExtensionNames = enabled_extensions.data(); + instance_create_info.enabledLayerCount = 0; + instance_create_info.ppEnabledLayerNames = nullptr; + res = vkCreateInstance(&instance_create_info, nullptr, &s_locals.instance); + } + } + + if (res != VK_SUCCESS) + { + Vulkan::SetErrorObject(error, "vkCreateInstance() failed: ", res); + return false; + } + } + + if (!LoadInstanceFunctions(s_locals.instance, error)) + { + LockedDestroyVulkanInstance(); + return false; + } + + DEV_LOG("Created new Vulkan instance."); + s_locals.reference_count = 1; + s_locals.window_type = wtype; + s_locals.is_debug_instance = debug_instance; + if (request_debug_instance) + *request_debug_instance = debug_instance; + + // Check for presence of the functions before calling + if (debug_instance) + { + if (vkCreateDebugUtilsMessengerEXT && vkDestroyDebugUtilsMessengerEXT && vkSubmitDebugUtilsMessageEXT) + { + const VkDebugUtilsMessengerCreateInfoEXT messenger_info = { + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + nullptr, + 0, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + DebugMessengerCallback, + nullptr}; + + res = + vkCreateDebugUtilsMessengerEXT(s_locals.instance, &messenger_info, nullptr, &s_locals.debug_messenger_callback); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkCreateDebugUtilsMessengerEXT failed: "); + } + else + { + WARNING_LOG("Vulkan: Debug messenger requested, but functions are not available."); + } + } + + return true; +} + +bool VulkanLoader::SelectInstanceExtensions(VulkanDevice::ExtensionList* extension_list, WindowInfoType wtype, + bool debug_instance, Error* error) +{ + u32 extension_count = 0; + VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); + return false; + } + + if (extension_count == 0) + { + ERROR_LOG("Vulkan: No extensions supported by instance."); + return false; + } + + std::vector available_extension_list(extension_count); + res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, available_extension_list.data()); + DebugAssert(res == VK_SUCCESS); + + const auto SupportsExtension = [&available_extension_list, &extension_list](const char* name, bool required) { + if (std::find_if(available_extension_list.begin(), available_extension_list.end(), + [&](const VkExtensionProperties& properties) { + return (std::strcmp(name, properties.extensionName) == 0); + }) != available_extension_list.end()) + { + DEV_LOG("Enabling extension: {}", name); + extension_list->push_back(name); + return true; + } + + if (required) + ERROR_LOG("Vulkan: Missing required extension {}.", name); + + return false; + }; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (wtype == WindowInfoType::Win32 && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || + !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))) + { + return false; + } +#endif +#if defined(VK_USE_PLATFORM_XCB_KHR) + if (wtype == WindowInfoType::XCB && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || + !SupportsExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, true))) + { + return false; + } +#endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (wtype == WindowInfoType::Wayland && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || + !SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))) + { + return false; + } +#endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (wtype == WindowInfoType::MacOS && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || + !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))) + { + return false; + } +#endif +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + if (wtype == WindowInfoType::Android && (!SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true) || + !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))) + { + return false; + } +#endif + +#if defined(ENABLE_SDL) + if (wtype == WindowInfoType::SDL) + { + Uint32 sdl_extension_count = 0; + const char* const* sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extension_count); + if (!sdl_extensions) + { + ERROR_LOG("SDL_Vulkan_GetInstanceExtensions() failed: {}", SDL_GetError()); + return false; + } + + for (unsigned int i = 0; i < sdl_extension_count; i++) + { + if (!SupportsExtension(sdl_extensions[i], true)) + return false; + } + } +#endif + + // VK_EXT_debug_utils + if (debug_instance && !SupportsExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false)) + WARNING_LOG("Vulkan: Debug report requested, but extension is not available."); + + // Needed for exclusive fullscreen control. + SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false); + + s_locals.optional_extensions.vk_khr_get_surface_capabilities2 = + (wtype != WindowInfoType::Surfaceless && + SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false)); + s_locals.optional_extensions.vk_ext_surface_maintenance1 = + (wtype != WindowInfoType::Surfaceless && SupportsExtension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, false)); + s_locals.optional_extensions.vk_khr_get_physical_device_properties2 = + SupportsExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false); + +#define LOG_EXT(name, field) \ + GENERIC_LOG(___LogChannel___, Log::Level::Info, \ + s_locals.optional_extensions.field ? Log::Color::StrongGreen : Log::Color::StrongOrange, name " is {}", \ + s_locals.optional_extensions.field ? "supported" : "NOT supported") + + LOG_EXT("VK_EXT_surface_maintenance1", vk_ext_surface_maintenance1); + LOG_EXT("VK_KHR_get_physical_device_properties2", vk_khr_get_physical_device_properties2); + LOG_EXT("VK_KHR_get_surface_capabilities2", vk_khr_get_surface_capabilities2); + +#undef LOG_EXT + + return true; +} + +void VulkanLoader::LockedReleaseVulkanInstance() +{ + Assert(s_locals.reference_count > 0); + s_locals.reference_count--; + + // We specifically keep the instance around even after releasing it. + // Both AMD on Windows and Mesa leak a few tens of megabytes for every instance... + DEV_LOG("Released Vulkan instance, reference count {}", s_locals.reference_count); +} + +void VulkanLoader::LockedDestroyVulkanInstance() +{ + DebugAssert(s_locals.reference_count == 0); + DebugAssert(s_locals.instance != VK_NULL_HANDLE); + + if (s_locals.debug_messenger_callback != VK_NULL_HANDLE) + { + vkDestroyDebugUtilsMessengerEXT(s_locals.instance, s_locals.debug_messenger_callback, nullptr); + s_locals.debug_messenger_callback = VK_NULL_HANDLE; + } + + if (vkDestroyInstance) + vkDestroyInstance(s_locals.instance, nullptr); + else + ERROR_LOG("Vulkan instance was leaked because vkDestroyInstance() could not be loaded."); + + s_locals.optional_extensions = {}; + s_locals.instance = VK_NULL_HANDLE; + ResetInstanceFunctions(); +} + +bool VulkanLoader::CreateVulkanInstance(WindowInfoType window_type, bool* request_debug_instance, Error* error) +{ + const std::lock_guard lock(s_locals.mutex); + return LockedCreateVulkanInstance(window_type, request_debug_instance, error); +} + +VkInstance VulkanLoader::GetVulkanInstance() +{ + // Doesn't need to be locked, but should have an instance. + DebugAssert(s_locals.instance != VK_NULL_HANDLE); + return s_locals.instance; +} + +void VulkanLoader::ReleaseVulkanInstance() +{ + const std::lock_guard lock(s_locals.mutex); + LockedReleaseVulkanInstance(); +} + +const VulkanLoader::OptionalExtensions& VulkanLoader::GetOptionalExtensions() +{ + return s_locals.optional_extensions; +} + +VulkanLoader::GPUList VulkanLoader::EnumerateGPUs(Error* error) +{ + GPUList gpus; + + u32 gpu_count = 0; + VkResult res = vkEnumeratePhysicalDevices(s_locals.instance, &gpu_count, nullptr); + if ((res != VK_SUCCESS && res != VK_INCOMPLETE) || gpu_count == 0) + { + Vulkan::SetErrorObject(error, "vkEnumeratePhysicalDevices (1) failed: ", res); + return gpus; + } + + std::vector physical_devices(gpu_count); + res = vkEnumeratePhysicalDevices(s_locals.instance, &gpu_count, physical_devices.data()); + if (res == VK_INCOMPLETE) + { + WARNING_LOG("First vkEnumeratePhysicalDevices() call returned {} devices, but second returned {}", + physical_devices.size(), gpu_count); + } + else if (res != VK_SUCCESS) + { + Vulkan::SetErrorObject(error, "vkEnumeratePhysicalDevices (2) failed: ", res); + return gpus; + } + + if (gpu_count == 0) + { + Error::SetStringView(error, "No Vulkan physical devices available."); + return gpus; + } + + // Maybe we lost a GPU? + if (gpu_count < physical_devices.size()) + physical_devices.resize(gpu_count); + + gpus.reserve(physical_devices.size()); + for (VkPhysicalDevice device : physical_devices) + { + VkPhysicalDeviceProperties2 props = {}; + VkPhysicalDeviceDriverProperties driver_props = {}; + + if (vkGetPhysicalDeviceProperties2) + { + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + driver_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + Vulkan::AddPointerToChain(&props, &driver_props); + vkGetPhysicalDeviceProperties2(device, &props); + } + + // just in case the chained version fails + vkGetPhysicalDeviceProperties(device, &props.properties); + + VkPhysicalDeviceFeatures available_features = {}; + vkGetPhysicalDeviceFeatures(device, &available_features); + + GPUDevice::AdapterInfo ai; + ai.name = props.properties.deviceName; + ai.max_texture_size = + std::min(props.properties.limits.maxFramebufferWidth, props.properties.limits.maxImageDimension2D); + ai.max_multisamples = Vulkan::GetMaxMultisamples(device, props.properties); + ai.driver_type = GuessDriverType(props.properties, driver_props); + ai.supports_sample_shading = available_features.sampleRateShading; + + // handle duplicate adapter names + if (std::any_of(gpus.begin(), gpus.end(), [&ai](const auto& other) { return (ai.name == other.second.name); })) + { + std::string original_adapter_name = std::move(ai.name); + + u32 current_extra = 2; + do + { + ai.name = fmt::format("{} ({})", original_adapter_name, current_extra); + current_extra++; + } while ( + std::any_of(gpus.begin(), gpus.end(), [&ai](const auto& other) { return (ai.name == other.second.name); })); + } + + gpus.emplace_back(device, std::move(ai)); + } + + return gpus; +} + +bool VulkanLoader::IsSuitableDefaultRenderer(WindowInfoType window_type) +{ +#ifdef __ANDROID__ + // No way in hell. + return false; +#else + const std::optional adapter_list = GetAdapterList(window_type, nullptr); + if (!adapter_list.has_value() || adapter_list->empty()) + { + // No adapters, not gonna be able to use VK. + return false; + } + + // Check the first GPU, should be enough. + const GPUDevice::AdapterInfo& ainfo = adapter_list->front(); + INFO_LOG("Using Vulkan GPU '{}' for automatic renderer check.", ainfo.name); + + // Any software rendering (LLVMpipe, SwiftShader). + if ((ainfo.driver_type & GPUDriverType::SoftwareFlag) == GPUDriverType::SoftwareFlag) + { + INFO_LOG("Not using Vulkan for software renderer."); + return false; + } + +#ifdef __linux__ + // Intel Ivy Bridge/Haswell/Broadwell drivers are incomplete. + if (ainfo.driver_type == GPUDriverType::IntelMesa && + (ainfo.name.find("Ivy Bridge") != std::string::npos || ainfo.name.find("Haswell") != std::string::npos || + ainfo.name.find("Broadwell") != std::string::npos || ainfo.name.find("(IVB") != std::string::npos || + ainfo.name.find("(HSW") != std::string::npos || ainfo.name.find("(BDW") != std::string::npos)) + { + INFO_LOG("Not using Vulkan for Intel GPU with incomplete driver."); + return false; + } +#endif + +#if defined(__linux__) || defined(__ANDROID__) + // V3D is buggy, image copies with larger textures are broken. + if (ainfo.driver_type == GPUDriverType::BroadcomMesa) + { + INFO_LOG("Not using Vulkan for V3D GPU with buggy driver."); + return false; + } +#endif + + INFO_LOG("Allowing Vulkan as default renderer."); + return true; +#endif +} + +GPUDriverType VulkanLoader::GuessDriverType(const VkPhysicalDeviceProperties& device_properties, + const VkPhysicalDeviceDriverProperties& driver_properties) +{ + static constexpr const std::pair table[] = { + {VK_DRIVER_ID_NVIDIA_PROPRIETARY, GPUDriverType::NVIDIAProprietary}, + {VK_DRIVER_ID_AMD_PROPRIETARY, GPUDriverType::AMDProprietary}, + {VK_DRIVER_ID_AMD_OPEN_SOURCE, GPUDriverType::AMDProprietary}, + {VK_DRIVER_ID_MESA_RADV, GPUDriverType::AMDMesa}, + {VK_DRIVER_ID_NVIDIA_PROPRIETARY, GPUDriverType::NVIDIAProprietary}, + {VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, GPUDriverType::IntelProprietary}, + {VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, GPUDriverType::IntelMesa}, + {VK_DRIVER_ID_IMAGINATION_PROPRIETARY, GPUDriverType::ImaginationProprietary}, + {VK_DRIVER_ID_QUALCOMM_PROPRIETARY, GPUDriverType::QualcommProprietary}, + {VK_DRIVER_ID_ARM_PROPRIETARY, GPUDriverType::ARMProprietary}, + {VK_DRIVER_ID_GOOGLE_SWIFTSHADER, GPUDriverType::SwiftShader}, + {VK_DRIVER_ID_GGP_PROPRIETARY, GPUDriverType::Unknown}, + {VK_DRIVER_ID_BROADCOM_PROPRIETARY, GPUDriverType::BroadcomProprietary}, + {VK_DRIVER_ID_MESA_LLVMPIPE, GPUDriverType::LLVMPipe}, + {VK_DRIVER_ID_MOLTENVK, GPUDriverType::AppleProprietary}, + {VK_DRIVER_ID_COREAVI_PROPRIETARY, GPUDriverType::Unknown}, + {VK_DRIVER_ID_JUICE_PROPRIETARY, GPUDriverType::Unknown}, + {VK_DRIVER_ID_VERISILICON_PROPRIETARY, GPUDriverType::Unknown}, + {VK_DRIVER_ID_MESA_TURNIP, GPUDriverType::QualcommMesa}, + {VK_DRIVER_ID_MESA_V3DV, GPUDriverType::BroadcomMesa}, + {VK_DRIVER_ID_MESA_PANVK, GPUDriverType::ARMMesa}, + {VK_DRIVER_ID_SAMSUNG_PROPRIETARY, GPUDriverType::AMDProprietary}, + {VK_DRIVER_ID_MESA_VENUS, GPUDriverType::Unknown}, + {VK_DRIVER_ID_MESA_DOZEN, GPUDriverType::DozenMesa}, + {VK_DRIVER_ID_MESA_NVK, GPUDriverType::NVIDIAMesa}, + {VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA, GPUDriverType::ImaginationMesa}, + {VK_DRIVER_ID_MESA_AGXV, GPUDriverType::AppleMesa}, + }; + + const auto iter = std::find_if(std::begin(table), std::end(table), [&driver_properties](const auto& it) { + return (driver_properties.driverID == it.first); + }); + if (iter != std::end(table)) + return iter->second; + + return GPUDevice::GuessDriverType( + device_properties.vendorID, {}, + std::string_view(device_properties.deviceName, + StringUtil::Strnlen(device_properties.deviceName, std::size(device_properties.deviceName)))); +} + +std::optional VulkanLoader::GetAdapterList(WindowInfoType window_type, Error* error) +{ + std::optional ret; + GPUList gpus; + { + const std::lock_guard lock(s_locals.mutex); + + // Prefer re-using the instance if we can to avoid expensive loading. + if (s_locals.instance != VK_NULL_HANDLE) + { + gpus = EnumerateGPUs(error); + } + else + { + // Otherwise we need to create a temporary instance. + // Hold the lock for both creation and querying, otherwise the UI thread could race creation. + if (!LockedCreateVulkanInstance(window_type, nullptr, error)) + return ret; + + gpus = EnumerateGPUs(error); + + LockedReleaseVulkanInstance(); + } + } + + ret.emplace(); + ret->reserve(gpus.size()); + for (auto& [physical_device, adapter_info] : gpus) + ret->push_back(std::move(adapter_info)); + + return ret; +} + +VkBool32 VulkanLoader::DebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) +{ + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + { + ERROR_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", + pCallbackData->pMessage); + } + else if (severity & (VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)) + { + WARNING_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", + pCallbackData->pMessage); + } + else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + { + INFO_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", + pCallbackData->pMessage); + } + else + { + DEV_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "", + pCallbackData->pMessage); + } + + return VK_FALSE; +} diff --git a/src/util/vulkan_loader.h b/src/util/vulkan_loader.h index 2497fa375..a1a5bae2e 100644 --- a/src/util/vulkan_loader.h +++ b/src/util/vulkan_loader.h @@ -1,74 +1,65 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once -class Error; +#include "gpu_device.h" +#include "vulkan_headers.h" +#include "window_info.h" -#define VK_NO_PROTOTYPES +#include "common/types.h" -#ifdef _WIN32 -#define VK_USE_PLATFORM_WIN32_KHR +#include +#include +#include -// vulkan.h pulls in windows.h on Windows, so we need to include our replacement header first -#include "common/windows_headers.h" -#elif defined(__APPLE__) -#define VK_USE_PLATFORM_METAL_EXT -#elif defined(__ANDROID__) -#define VK_USE_PLATFORM_ANDROID_KHR -#else -#ifdef ENABLE_X11 -#define VK_USE_PLATFORM_XCB_KHR -#endif +namespace VulkanLoader { -#ifdef ENABLE_WAYLAND -#define VK_USE_PLATFORM_WAYLAND_KHR -#endif -#endif +/// @brief List of Vulkan-compatible GPUs and associated adapter information. +using GPUList = std::vector>; -#include "vulkan/vulkan.h" +/// @brief Optional extensions for an instance. +struct OptionalExtensions +{ + bool vk_ext_surface_maintenance1 : 1; + bool vk_khr_get_surface_capabilities2 : 1; + bool vk_khr_get_physical_device_properties2 : 1; +}; -#include "vulkan_entry_points.h" +/// Creates the shared Vulkan instance. If debug_instance is changed, the instance will be recreated. +/// @param window_type Window type for selecting required extensions. +/// @param request_debug_instance Set to true if a debug instance is requested. May be modified to reflect actual state. +/// @param error Error information if the instance could not be created. +/// @return The Vulkan instance, or VK_NULL_HANDLE on failure. +bool CreateVulkanInstance(WindowInfoType window_type, bool* request_debug_instance, Error* error); -// We include vk_mem_alloc globally, so we don't accidentally include it before the vulkan header somewhere. -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnullability-completeness" -#pragma clang diagnostic ignored "-Wunused-variable" -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#pragma clang diagnostic ignored "-Wunused-function" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wunused-function" -#elif defined(_MSC_VER) -#pragma warning(push, 0) -#endif +/// Returns the shared Vulkan instance. +VkInstance GetVulkanInstance(); -#define VMA_STATIC_VULKAN_FUNCTIONS 1 -#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 -#define VMA_STATS_STRING_ENABLED 0 -#include "vulkan/vk_mem_alloc.h" +/// Releases the shared Vulkan instance. +void ReleaseVulkanInstance(); -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#elif defined(_MSC_VER) -#pragma warning(pop) -#endif +/// Returns optional extensions for the current instance. +const OptionalExtensions& GetOptionalExtensions(); -namespace Vulkan { +/// Enumerates Vulkan devices. +GPUList EnumerateGPUs(Error* error); -bool IsVulkanLibraryLoaded(); -bool LoadVulkanLibrary(Error* error); -bool LoadVulkanInstanceFunctions(VkInstance instance); -bool LoadVulkanDeviceFunctions(VkDevice device); -void UnloadVulkanLibrary(); -void ResetVulkanLibraryFunctionPointers(); +/// Safely creates the instance and returns a list of adapters and associated information. +std::optional GetAdapterList(WindowInfoType window_type, Error* error); -#ifdef ENABLE_SDL -bool LoadVulkanLibraryFromSDL(Error* error); -#endif +/// Returns true if Vulkan is suitable as a default for the devices in the system. +bool IsSuitableDefaultRenderer(WindowInfoType window_type); -} // namespace Vulkan +/// Loads Vulkan device-level functions for the given device. +/// @param device The Vulkan device to load functions for. +bool LoadDeviceFunctions(VkDevice device, Error* error); + +/// Releases Vulkan device-level functions. +void ResetDeviceFunctions(); + +/// @brief Guesses the GPU driver type based on device and driver properties. +GPUDriverType GuessDriverType(const VkPhysicalDeviceProperties& device_properties, + const VkPhysicalDeviceDriverProperties& driver_properties); + +} // namespace VulkanLoader diff --git a/src/util/vulkan_pipeline.h b/src/util/vulkan_pipeline.h index 91be444d7..d9c9aaf9e 100644 --- a/src/util/vulkan_pipeline.h +++ b/src/util/vulkan_pipeline.h @@ -2,7 +2,7 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "gpu_device.h" -#include "vulkan_loader.h" +#include "vulkan_headers.h" class VulkanDevice; diff --git a/src/util/vulkan_stream_buffer.h b/src/util/vulkan_stream_buffer.h index 7e95f8a5a..c360acddf 100644 --- a/src/util/vulkan_stream_buffer.h +++ b/src/util/vulkan_stream_buffer.h @@ -3,7 +3,7 @@ #pragma once -#include "vulkan_loader.h" +#include "vulkan_headers.h" #include "common/types.h" diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index 1b18d28dc..e40050e9b 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -4,6 +4,7 @@ #include "vulkan_swap_chain.h" #include "vulkan_builders.h" #include "vulkan_device.h" +#include "vulkan_loader.h" #include "common/assert.h" #include "common/error.h" @@ -82,7 +83,7 @@ VulkanSwapChain::~VulkanSwapChain() Destroy(VulkanDevice::GetInstance(), true); } -bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error) +bool VulkanSwapChain::CreateSurface(VkPhysicalDevice physical_device, Error* error) { #if defined(VK_USE_PLATFORM_WIN32_KHR) if (m_window_info.type == WindowInfoType::Win32) @@ -92,7 +93,8 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic .flags = 0, .hinstance = NULL, .hwnd = static_cast(m_window_info.window_handle)}; - const VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); + const VkResult res = + vkCreateWin32SurfaceKHR(VulkanLoader::GetVulkanInstance(), &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { Vulkan::SetErrorObject(error, "vkCreateWin32SurfaceKHR() failed: ", res); @@ -114,7 +116,8 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic .pNext = nullptr, .flags = 0, .pLayer = static_cast(m_metal_layer)}; - const VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &m_surface); + const VkResult res = + vkCreateMetalSurfaceEXT(VulkanLoader::GetVulkanInstance(), &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { Vulkan::SetErrorObject(error, "vkCreateMetalSurfaceEXT failed: ", res); @@ -133,7 +136,8 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic .pNext = nullptr, .flags = 0, .window = static_cast(m_window_info.window_handle)}; - const VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); + const VkResult res = + vkCreateAndroidSurfaceKHR(VulkanLoader::GetVulkanInstance(), &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { Vulkan::SetErrorObject(error, "vkCreateAndroidSurfaceKHR failed: ", res); @@ -153,7 +157,8 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic .flags = 0, .connection = static_cast(m_window_info.display_connection), .window = static_cast(reinterpret_cast(m_window_info.window_handle))}; - const VkResult res = vkCreateXcbSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); + const VkResult res = + vkCreateXcbSurfaceKHR(VulkanLoader::GetVulkanInstance(), &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { Vulkan::SetErrorObject(error, "vkCreateXcbSurfaceKHR failed: ", res); @@ -173,7 +178,8 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic .flags = 0, .display = static_cast(m_window_info.display_connection), .surface = static_cast(m_window_info.window_handle)}; - VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); + VkResult res = + vkCreateWaylandSurfaceKHR(VulkanLoader::GetVulkanInstance(), &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { Vulkan::SetErrorObject(error, "vkCreateWaylandSurfaceEXT failed: ", res); @@ -187,7 +193,8 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic #if defined(ENABLE_SDL) if (m_window_info.type == WindowInfoType::SDL) { - if (!SDL_Vulkan_CreateSurface(static_cast(m_window_info.window_handle), instance, nullptr, &m_surface)) + if (!SDL_Vulkan_CreateSurface(static_cast(m_window_info.window_handle), + VulkanLoader::GetVulkanInstance(), nullptr, &m_surface)) { Error::SetStringFmt(error, "SDL_Vulkan_CreateSurface() failed: {}", SDL_GetError()); return false; @@ -205,7 +212,7 @@ void VulkanSwapChain::DestroySurface() { if (m_surface != VK_NULL_HANDLE) { - vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), m_surface, nullptr); + vkDestroySurfaceKHR(VulkanLoader::GetVulkanInstance(), m_surface, nullptr); m_surface = VK_NULL_HANDLE; } @@ -349,8 +356,8 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error) VkResult res = VK_NOT_READY; // The present mode can alter the number of images required. Use VK_KHR_get_surface_capabilities2 to confirm it. - if (dev.GetOptionalExtensions().vk_khr_get_surface_capabilities2 && - dev.GetOptionalExtensions().vk_ext_surface_maintenance1) + const VulkanLoader::OptionalExtensions& optional_extensions = VulkanLoader::GetOptionalExtensions(); + if (optional_extensions.vk_khr_get_surface_capabilities2 && optional_extensions.vk_ext_surface_maintenance1) { VkPhysicalDeviceSurfaceInfo2KHR dsi = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, .pNext = nullptr, .surface = m_surface}; @@ -858,7 +865,7 @@ bool VulkanSwapChain::RecreateSurface(VulkanDevice& dev, Error* error) DestroySurface(); // Re-create the surface with the new native handle - if (!CreateSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), error)) + if (!CreateSurface(dev.GetVulkanPhysicalDevice(), error)) return false; // The validation layers get angry at us if we don't call this before creating the swapchain. diff --git a/src/util/vulkan_swap_chain.h b/src/util/vulkan_swap_chain.h index abef44597..86d98cb1f 100644 --- a/src/util/vulkan_swap_chain.h +++ b/src/util/vulkan_swap_chain.h @@ -4,7 +4,7 @@ #pragma once #include "gpu_device.h" -#include "vulkan_loader.h" +#include "vulkan_headers.h" #include "vulkan_texture.h" #include "window_info.h" @@ -45,7 +45,7 @@ public: return &m_images[m_current_image].present_semaphore; } - bool CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error); + bool CreateSurface(VkPhysicalDevice physical_device, Error* error); bool CreateSwapChain(VulkanDevice& dev, Error* error); bool CreateSwapChainImages(VulkanDevice& dev, Error* error); void Destroy(VulkanDevice& dev, bool wait_for_idle); diff --git a/src/util/vulkan_texture.h b/src/util/vulkan_texture.h index 2489c9b8b..3e0b6f878 100644 --- a/src/util/vulkan_texture.h +++ b/src/util/vulkan_texture.h @@ -5,7 +5,7 @@ #include "gpu_device.h" #include "gpu_texture.h" -#include "vulkan_loader.h" +#include "vulkan_headers.h" #include "vulkan_stream_buffer.h" #include