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.
This commit is contained in:
Stenzek
2025-12-23 15:44:50 +10:00
parent e13b9a0c17
commit 69458bd90b
30 changed files with 1088 additions and 833 deletions

View File

@@ -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<GPUDevice::AdapterInfoList> 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)

View File

@@ -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<WindowInfo> 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();

View File

@@ -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",

View File

@@ -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<GPUTextureFilter> ParseTextureFilterName(const char* str);
static const char* GetTextureFilterName(GPUTextureFilter filter);

View File

@@ -650,6 +650,12 @@ std::optional<WindowInfo> 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;

View File

@@ -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 <QtCore/QDir>
#include <QtWidgets/QDialog>
@@ -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<uint>(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<GPUDevice::AdapterInfoList> 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;

View File

@@ -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<WindowInfo> 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();

View File

@@ -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<QSize, qreal> 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<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error)
{
WindowInfo wi;

View File

@@ -17,6 +17,9 @@ namespace QtUtils {
/// Also returns the "real" DPR scale for the widget, ignoring any operating-system level downsampling.
std::pair<QSize, qreal> 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<WindowInfo> GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error = nullptr);

View File

@@ -437,6 +437,11 @@ std::optional<WindowInfo> Host::AcquireRenderWindow(RenderAPI render_api, bool f
return WindowInfo();
}
WindowInfoType Host::GetRenderWindowInfoType()
{
return WindowInfoType::Surfaceless;
}
void Host::ReleaseRenderWindow()
{
//

View File

@@ -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

View File

@@ -301,13 +301,13 @@ static std::string FixupDuplicateAdapterNames(const GPUDevice::AdapterInfoList&
return adapter_name;
}
GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
std::optional<GPUDevice::AdapterInfoList> D3DCommon::GetAdapterInfoList(Error* error)
{
GPUDevice::AdapterInfoList adapters;
std::optional<GPUDevice::AdapterInfoList> ret;
Microsoft::WRL::ComPtr<IDXGIFactory5> factory = CreateFactory(false, nullptr);
Microsoft::WRL::ComPtr<IDXGIFactory5> factory = CreateFactory(false, error);
if (!factory)
return adapters;
return ret;
Microsoft::WRL::ComPtr<IDXGIAdapter1> 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<unsigned>(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<DXGI_MODE_DESC>

View File

@@ -60,7 +60,7 @@ bool CreateD3D12Device(IDXGIAdapter* adapter, D3D_FEATURE_LEVEL feature_level,
Microsoft::WRL::ComPtr<ID3DBlob> SerializeRootSignature(const D3D12_ROOT_SIGNATURE_DESC* desc, Error* error);
// returns a list of all adapter names
GPUDevice::AdapterInfoList GetAdapterInfoList();
std::optional<GPUDevice::AdapterInfoList> GetAdapterInfoList(Error* error);
// returns the fullscreen mode to use for the specified dimensions
std::optional<DXGI_MODE_DESC>

View File

@@ -41,6 +41,7 @@ LOG_CHANNEL(GPUDevice);
#ifdef ENABLE_VULKAN
#include "vulkan_device.h"
#include "vulkan_loader.h"
#endif
std::unique_ptr<GPUDevice> 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::AdapterInfoList> GPUDevice::GetAdapterListForAPI(RenderAPI api, WindowInfoType window_type,
Error* error)
{
AdapterInfoList ret;
std::optional<AdapterInfoList> 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

View File

@@ -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<AdapterInfoList> 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);

View File

@@ -101,7 +101,7 @@
<ClInclude Include="translation.h" />
<ClInclude Include="vulkan_builders.h" />
<ClInclude Include="vulkan_device.h" />
<ClInclude Include="vulkan_entry_points.h" />
<ClInclude Include="vulkan_headers.h" />
<ClInclude Include="vulkan_loader.h" />
<ClInclude Include="vulkan_pipeline.h" />
<ClInclude Include="vulkan_stream_buffer.h" />

View File

@@ -30,7 +30,6 @@
<ClInclude Include="opengl_texture.h" />
<ClInclude Include="vulkan_builders.h" />
<ClInclude Include="vulkan_device.h" />
<ClInclude Include="vulkan_entry_points.h" />
<ClInclude Include="vulkan_loader.h" />
<ClInclude Include="vulkan_pipeline.h" />
<ClInclude Include="vulkan_stream_buffer.h" />
@@ -81,6 +80,7 @@
<ClInclude Include="imgui_gsvector.h" />
<ClInclude Include="translation.h" />
<ClInclude Include="core_audio_stream.h" />
<ClInclude Include="vulkan_headers.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="state_wrapper.cpp" />

View File

@@ -118,6 +118,35 @@ void Vulkan::SetErrorObject(Error* errptr, std::string_view prefix, VkResult res
Error::SetStringFmt(errptr, "{} (0x{:08X}: {})", prefix, static_cast<unsigned>(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();

View File

@@ -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:

View File

@@ -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 <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#endif
#include <cstdlib>
#include <limits>
#include <mutex>
@@ -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<uint32_t>(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<VkExtensionProperties> 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<VkPhysicalDevice> 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<const VkExtensionProperties> 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<VkAttachmentReference, MAX_RENDER_TARGETS> 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<bool> 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<VulkanSwapChain> swap_chain;
if (!wi.IsSurfaceless())
{
swap_chain =
std::make_unique<VulkanSwapChain>(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<GPUSwapChain> VulkanDevice::CreateSwapChain(const WindowInfo& wi
{
std::unique_ptr<VulkanSwapChain> swap_chain =
std::make_unique<VulkanSwapChain>(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<u16>(GetMaxMultisamples(physical_device, m_device_properties));
m_max_multisamples = static_cast<u16>(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<VkDriverId, GPUDriverType> 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)

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// 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<const char*>;
static GPUTextureFormat GetFormatForVkFormat(VkFormat format);
static const std::array<VkFormat, static_cast<u32>(GPUTextureFormat::MaxCount)> TEXTURE_FORMAT_MAPPING;
@@ -72,12 +67,6 @@ public:
VulkanDevice();
~VulkanDevice() override;
// Returns a list of Vulkan-compatible GPUs.
using GPUList = std::vector<std::pair<VkPhysicalDevice, AdapterInfo>>;
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<VulkanDevice*>(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<const char*>;
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<const VkExtensionProperties> 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;
};

View File

@@ -1,20 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// 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

73
src/util/vulkan_headers.h Normal file
View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// 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

View File

@@ -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<PFN_vkVoidFunction*>(&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<PFN_vkVoidFunction*>(&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<const void*>(&name) != reinterpret_cast<const void*>(&vkGetInstanceProcAddr)) && \
!(name = reinterpret_cast<decltype(name)>(vkGetInstanceProcAddr(nullptr, #name))) && required) \
{ \
ERROR_LOG("Vulkan: Failed to load required module function {}", #name); \
required_functions_missing = true; \
}
load_function(reinterpret_cast<PFN_vkVoidFunction*>(&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<PFN_vkVoidFunction*>(&name), #name, required);
load_function(reinterpret_cast<PFN_vkVoidFunction*>(&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<PFN_vkVoidFunction*>(&name), #name, required);
load_function(reinterpret_cast<PFN_vkVoidFunction*>(&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<u32>(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<uint32_t>(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<uint32_t>(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<VkExtensionProperties> 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<VkPhysicalDevice> 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<GPUDevice::AdapterInfoList> 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<VkDriverId, GPUDriverType> 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<GPUDevice::AdapterInfoList> VulkanLoader::GetAdapterList(WindowInfoType window_type, Error* error)
{
std::optional<GPUDevice::AdapterInfoList> 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;
}

View File

@@ -1,74 +1,65 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// 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 <optional>
#include <utility>
#include <vector>
// 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<std::pair<VkPhysicalDevice, GPUDevice::AdapterInfo>>;
#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<GPUDevice::AdapterInfoList> 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

View File

@@ -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;

View File

@@ -3,7 +3,7 @@
#pragma once
#include "vulkan_loader.h"
#include "vulkan_headers.h"
#include "common/types.h"

View File

@@ -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<HWND>(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<const CAMetalLayer*>(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<ANativeWindow*>(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<xcb_connection_t*>(m_window_info.display_connection),
.window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(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<struct wl_display*>(m_window_info.display_connection),
.surface = static_cast<struct wl_surface*>(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<SDL_Window*>(m_window_info.window_handle), instance, nullptr, &m_surface))
if (!SDL_Vulkan_CreateSurface(static_cast<SDL_Window*>(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.

View File

@@ -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);

View File

@@ -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 <limits>