diff --git a/src/core/fullscreenui_settings.cpp b/src/core/fullscreenui_settings.cpp index bcdc8f41a..4f6b77b25 100644 --- a/src/core/fullscreenui_settings.cpp +++ b/src/core/fullscreenui_settings.cpp @@ -3816,6 +3816,9 @@ void FullscreenUI::DrawGraphicsSettingsPage() options.emplace_back(FSUI_STR("Default"), current_adapter.has_value() && current_adapter->empty()); for (const GPUDevice::AdapterInfo& adapter : s_settings_locals.graphics_adapter_list_cache) { + if (adapter.name.empty()) + continue; + const bool checked = (current_adapter.has_value() && current_adapter.value() == adapter.name); options.emplace_back(adapter.name, checked); } diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index 2b9def0dd..dd3caebfe 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -683,6 +683,9 @@ void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_ const std::string current_adapter_name = m_dialog->getEffectiveStringValue("GPU", "Adapter", ""); for (const GPUDevice::AdapterInfo& adapter : m_adapters) { + if (adapter.name.empty()) + continue; + const QString qadaptername = QString::fromStdString(adapter.name); m_ui.adapter->addItem(qadaptername, QVariant(qadaptername)); if (adapter.name == current_adapter_name) diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index cc3e3af2a..681457841 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -200,6 +200,7 @@ if(NOT ANDROID) sdl_audio_stream.cpp sdl_input_source.cpp sdl_input_source.h + sdl_video_helpers.h ) if(ENABLE_OPENGL) target_sources(util PRIVATE diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 91b7b9ab7..dbe5c7239 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -37,6 +37,7 @@ LOG_CHANNEL(GPUDevice); #endif #ifdef ENABLE_OPENGL +#include "opengl_context.h" #include "opengl_device.h" #endif @@ -394,8 +395,7 @@ std::optional GPUDevice::GetAdapterListForAPI(Render #ifdef ENABLE_OPENGL case RenderAPI::OpenGL: case RenderAPI::OpenGLES: - // No way of querying. - ret = AdapterInfoList(); + ret = OpenGLContext::GetAdapterList(window_type, error); break; #endif diff --git a/src/util/opengl_context.cpp b/src/util/opengl_context.cpp index 37712a415..838400575 100644 --- a/src/util/opengl_context.cpp +++ b/src/util/opengl_context.cpp @@ -210,3 +210,13 @@ std::unique_ptr OpenGLContext::Create(WindowInfo& wi, SurfaceHand return context; } + +GPUDevice::AdapterInfoList OpenGLContext::GetAdapterList(WindowInfoType window_type, Error* error) +{ +#ifdef ENABLE_SDL + if (window_type == WindowInfoType::SDL) + return OpenGLContextSDL::GetAdapterList(window_type, error); +#endif + + return {}; +} diff --git a/src/util/opengl_context.h b/src/util/opengl_context.h index 36d237281..c48b7aa02 100644 --- a/src/util/opengl_context.h +++ b/src/util/opengl_context.h @@ -3,6 +3,7 @@ #pragma once +#include "gpu_device.h" #include "window_info.h" #include "common/types.h" @@ -52,6 +53,8 @@ public: static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, bool prefer_gles_context, Error* error); + static GPUDevice::AdapterInfoList GetAdapterList(WindowInfoType window_type, Error* error); + protected: Version m_version = {}; }; diff --git a/src/util/opengl_context_sdl.cpp b/src/util/opengl_context_sdl.cpp index 16c680728..ed1d90473 100644 --- a/src/util/opengl_context_sdl.cpp +++ b/src/util/opengl_context_sdl.cpp @@ -1,16 +1,17 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "opengl_context_sdl.h" #include "gpu_texture.h" #include "opengl_loader.h" +#include "sdl_video_helpers.h" #include "common/assert.h" #include "common/error.h" #include "common/log.h" #include "common/scoped_guard.h" -#include "SDL3/SDL.h" +#include LOG_CHANNEL(GPUDevice); @@ -35,6 +36,26 @@ std::unique_ptr OpenGLContextSDL::Create(WindowInfo& wi, SurfaceH return context; } +GPUDevice::AdapterInfoList OpenGLContextSDL::GetAdapterList(WindowInfoType window_type, Error* error) +{ + std::vector fullscreen_modes = SDLVideoHelpers::GetFullscreenModeList(); + if (fullscreen_modes.empty()) + { + // no point adding anything if no modes + return {}; + } + + // Set some reasonable defaults, since we don't know this until we actually create the context. + GPUDevice::AdapterInfoList ret; + GPUDevice::AdapterInfo& ai = ret.emplace_back(); + ai.driver_type = GPUDriverType::Unknown; + ai.max_multisamples = 8; + ai.supports_sample_shading = true; + ai.max_texture_size = 16384; + ai.fullscreen_modes = std::move(fullscreen_modes); + return ret; +} + bool OpenGLContextSDL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, bool share_context, Error* error) { diff --git a/src/util/opengl_context_sdl.h b/src/util/opengl_context_sdl.h index 9da1e22fd..66458a37f 100644 --- a/src/util/opengl_context_sdl.h +++ b/src/util/opengl_context_sdl.h @@ -19,6 +19,7 @@ public: static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, Error* error); + static GPUDevice::AdapterInfoList GetAdapterList(WindowInfoType window_type, Error* error); void* GetProcAddress(const char* name) override; SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override; diff --git a/src/util/sdl_video_helpers.h b/src/util/sdl_video_helpers.h new file mode 100644 index 000000000..389fc2034 --- /dev/null +++ b/src/util/sdl_video_helpers.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "gpu_device.h" + +#include "common/error.h" +#include "common/log.h" + +#include + +#include + +namespace SDLVideoHelpers { + +inline std::vector GetFullscreenModeList() +{ + int display_count = 0; + const SDL_DisplayID* const displays = SDL_GetDisplays(&display_count); + if (display_count <= 0) + { + GENERIC_LOG(Log::Channel::SDL, Log::Level::Error, Log::Color::Default, "SDL_GetDisplays() returned no displays: {}", + SDL_GetError()); + return {}; + } + + std::vector modes; + for (int i = 0; i < display_count; i++) + { + int dm_count = 0; + const SDL_DisplayMode* const* const dms = SDL_GetFullscreenDisplayModes(displays[i], &dm_count); + if (dm_count <= 0) + { + GENERIC_LOG(Log::Channel::SDL, Log::Level::Error, Log::Color::Default, + "SDL_GetFullscreenDisplayModes() returned no modes for display {}: {}", displays[i], SDL_GetError()); + continue; + } + + modes.reserve(modes.size() + static_cast(dm_count)); + + for (int j = 0; j < dm_count; j++) + { + const SDL_DisplayMode* const dm = dms[j]; + const GPUDevice::ExclusiveFullscreenMode mode{static_cast(dm->w), static_cast(dm->h), dm->refresh_rate}; + if (std::ranges::find(modes, mode) == modes.end()) + modes.push_back(mode); + } + } + + return modes; +} + +} // namespace SDLVideoHelpers diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index 10d2569a3..c12504746 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -92,6 +92,7 @@ + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index c965de77e..1218ce015 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -82,6 +82,7 @@ + diff --git a/src/util/vulkan_loader.cpp b/src/util/vulkan_loader.cpp index 46168d543..951e4e77d 100644 --- a/src/util/vulkan_loader.cpp +++ b/src/util/vulkan_loader.cpp @@ -16,6 +16,7 @@ #include "common/log.h" #ifdef ENABLE_SDL +#include "sdl_video_helpers.h" #include #endif @@ -58,6 +59,8 @@ static void LockedDestroyVulkanInstance(); static bool SelectInstanceExtensions(VulkanDevice::ExtensionList* extension_list, WindowInfoType wtype, bool debug_instance, Error* error); +static std::vector EnumerateFullscreenModes(WindowInfoType wtype); + VKAPI_ATTR static VkBool32 VKAPI_CALL DebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, @@ -735,6 +738,16 @@ VulkanLoader::GPUList VulkanLoader::EnumerateGPUs(Error* error) return gpus; } +std::vector VulkanLoader::EnumerateFullscreenModes(WindowInfoType wtype) +{ +#ifdef ENABLE_SDL + if (wtype == WindowInfoType::SDL) + return SDLVideoHelpers::GetFullscreenModeList(); +#endif + + return {}; +} + bool VulkanLoader::IsSuitableDefaultRenderer(WindowInfoType window_type) { #ifdef __ANDROID__ @@ -834,6 +847,7 @@ GPUDriverType VulkanLoader::GuessDriverType(const VkPhysicalDeviceProperties& de std::optional VulkanLoader::GetAdapterList(WindowInfoType window_type, Error* error) { std::optional ret; + std::vector fullscreen_modes; GPUList gpus; { const std::lock_guard lock(s_locals.mutex); @@ -842,6 +856,7 @@ std::optional VulkanLoader::GetAdapterList(WindowInf if (s_locals.instance != VK_NULL_HANDLE) { gpus = EnumerateGPUs(error); + fullscreen_modes = EnumerateFullscreenModes(window_type); } else { @@ -851,6 +866,7 @@ std::optional VulkanLoader::GetAdapterList(WindowInf return ret; gpus = EnumerateGPUs(error); + fullscreen_modes = EnumerateFullscreenModes(window_type); LockedReleaseVulkanInstance(); } @@ -858,8 +874,18 @@ std::optional VulkanLoader::GetAdapterList(WindowInf ret.emplace(); ret->reserve(gpus.size()); - for (auto& [physical_device, adapter_info] : gpus) - ret->push_back(std::move(adapter_info)); + for (size_t i = 0; i < gpus.size(); i++) + { + GPUDevice::AdapterInfo& ai = gpus[i].second; + + // splat fullscreen modes across gpus + if (i == (gpus.size() - 1)) + ai.fullscreen_modes = std::move(fullscreen_modes); + else + ai.fullscreen_modes = fullscreen_modes; + + ret->push_back(std::move(ai)); + } return ret; }