diff --git a/src/core/host_display.h b/src/core/host_display.h index 8c1e17886..f1c84230a 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -66,6 +66,8 @@ public: virtual void DestroyRenderDevice() = 0; virtual void DestroyRenderSurface() = 0; virtual bool ChangeRenderWindow(const WindowInfo& wi) = 0; + virtual bool IsFullscreen() = 0; + virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0; virtual bool CreateResources() = 0; virtual void DestroyResources() = 0; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index ffa1dc155..626b2a078 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -5,6 +5,7 @@ #include "host_interface.h" #include #include +#include #include Settings g_settings; diff --git a/src/duckstation-libretro/libretro_host_display.cpp b/src/duckstation-libretro/libretro_host_display.cpp index 88ca8e746..de285d0d4 100644 --- a/src/duckstation-libretro/libretro_host_display.cpp +++ b/src/duckstation-libretro/libretro_host_display.cpp @@ -156,6 +156,16 @@ void LibretroHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_windo m_window_info.surface_height = new_window_height; } +bool LibretroHostDisplay::IsFullscreen() +{ + return false; +} + +bool LibretroHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + return false; +} + bool LibretroHostDisplay::SetPostProcessingChain(const std::string_view& config) { return false; diff --git a/src/duckstation-libretro/libretro_host_display.h b/src/duckstation-libretro/libretro_host_display.h index af49a7dda..ab922f4ad 100644 --- a/src/duckstation-libretro/libretro_host_display.h +++ b/src/duckstation-libretro/libretro_host_display.h @@ -23,6 +23,8 @@ public: bool ChangeRenderWindow(const WindowInfo& wi) override; void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + bool IsFullscreen() override; + bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; void DestroyRenderSurface() override; bool SetPostProcessingChain(const std::string_view& config) override; diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp index b0b32cf0d..92d9f563d 100644 --- a/src/duckstation-qt/displaysettingswidget.cpp +++ b/src/duckstation-qt/displaysettingswidget.cpp @@ -42,10 +42,12 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW false); connect(m_ui.renderer, QOverload::of(&QComboBox::currentIndexChanged), this, - &DisplaySettingsWidget::populateGPUAdapters); + &DisplaySettingsWidget::populateGPUAdaptersAndResolutions); connect(m_ui.adapter, QOverload::of(&QComboBox::currentIndexChanged), this, &DisplaySettingsWidget::onGPUAdapterIndexChanged); - populateGPUAdapters(); + connect(m_ui.fullscreenMode, QOverload::of(&QComboBox::currentIndexChanged), this, + &DisplaySettingsWidget::onGPUFullscreenModeIndexChanged); + populateGPUAdaptersAndResolutions(); dialog->registerWidgetHelp( m_ui.renderer, tr("Renderer"), @@ -118,15 +120,20 @@ void DisplaySettingsWidget::setupAdditionalUi() } } -void DisplaySettingsWidget::populateGPUAdapters() +void DisplaySettingsWidget::populateGPUAdaptersAndResolutions() { std::vector adapter_names; + std::vector fullscreen_modes; switch (static_cast(m_ui.renderer->currentIndex())) { #ifdef WIN32 case GPURenderer::HardwareD3D11: - adapter_names = FrontendCommon::D3D11HostDisplay::EnumerateAdapterNames(); - break; + { + FrontendCommon::D3D11HostDisplay::AdapterInfo adapter_info = FrontendCommon::D3D11HostDisplay::GetAdapterInfo(); + adapter_names = std::move(adapter_info.adapter_names); + fullscreen_modes = std::move(adapter_info.fullscreen_modes); + } + break; #endif case GPURenderer::HardwareVulkan: @@ -137,26 +144,46 @@ void DisplaySettingsWidget::populateGPUAdapters() break; } - QString current_value = QString::fromStdString(m_host_interface->GetStringSettingValue("GPU", "Adapter")); - - QSignalBlocker blocker(m_ui.adapter); - - // add the default entry - we'll fall back to this if the GPU no longer exists, or there's no options - m_ui.adapter->clear(); - m_ui.adapter->addItem(tr("(Default)")); - - // add the other adapters - for (const std::string& adapter_name : adapter_names) { - QString qadapter_name(QString::fromStdString(adapter_name)); - m_ui.adapter->addItem(qadapter_name); + const std::string current_adapter(m_host_interface->GetStringSettingValue("GPU", "Adapter")); + QSignalBlocker blocker(m_ui.adapter); - if (qadapter_name == current_value) - m_ui.adapter->setCurrentIndex(m_ui.adapter->count() - 1); + // add the default entry - we'll fall back to this if the GPU no longer exists, or there's no options + m_ui.adapter->clear(); + m_ui.adapter->addItem(tr("(Default)")); + + // add the other adapters + for (const std::string& adapter_name : adapter_names) + { + m_ui.adapter->addItem(QString::fromStdString(adapter_name)); + + if (adapter_name == current_adapter) + m_ui.adapter->setCurrentIndex(m_ui.adapter->count() - 1); + } + + // disable it if we don't have a choice + m_ui.adapter->setEnabled(!adapter_names.empty()); } - // disable it if we don't have a choice - m_ui.adapter->setEnabled(!adapter_names.empty()); + { + const std::string current_mode(m_host_interface->GetStringSettingValue("GPU", "FullscreenMode", "")); + QSignalBlocker blocker(m_ui.fullscreenMode); + + m_ui.fullscreenMode->clear(); + m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen")); + m_ui.fullscreenMode->setCurrentIndex(0); + + for (const std::string& mode_name : fullscreen_modes) + { + m_ui.fullscreenMode->addItem(QString::fromStdString(mode_name)); + + if (mode_name == current_mode) + m_ui.fullscreenMode->setCurrentIndex(m_ui.fullscreenMode->count() - 1); + } + + // disable it if we don't have a choice + m_ui.fullscreenMode->setEnabled(!fullscreen_modes.empty()); + } } void DisplaySettingsWidget::onGPUAdapterIndexChanged() @@ -170,3 +197,16 @@ void DisplaySettingsWidget::onGPUAdapterIndexChanged() m_host_interface->SetStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData()); } + +void DisplaySettingsWidget::onGPUFullscreenModeIndexChanged() +{ + if (m_ui.fullscreenMode->currentIndex() == 0) + { + // default + m_host_interface->RemoveSettingValue("GPU", "FullscreenMode"); + return; + } + + m_host_interface->SetStringSettingValue("GPU", "FullscreenMode", + m_ui.fullscreenMode->currentText().toUtf8().constData()); +} \ No newline at end of file diff --git a/src/duckstation-qt/displaysettingswidget.h b/src/duckstation-qt/displaysettingswidget.h index a940b0486..5a98f2c88 100644 --- a/src/duckstation-qt/displaysettingswidget.h +++ b/src/duckstation-qt/displaysettingswidget.h @@ -17,8 +17,9 @@ public: ~DisplaySettingsWidget(); private Q_SLOTS: - void populateGPUAdapters(); + void populateGPUAdaptersAndResolutions(); void onGPUAdapterIndexChanged(); + void onGPUFullscreenModeIndexChanged(); private: void setupAdditionalUi(); diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui index 8deae5e13..5ae78f14e 100644 --- a/src/duckstation-qt/displaysettingswidget.ui +++ b/src/duckstation-qt/displaysettingswidget.ui @@ -52,7 +52,17 @@ - + + + + Fullscreen Mode: + + + + + + + VSync diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index b0355bcbf..2ca667f4a 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -87,13 +87,20 @@ QtDisplayWidget* MainWindow::createDisplay(QThread* worker_thread, const QString Assert(!m_host_display && !m_display_widget); Assert(!fullscreen || !render_to_main); + const std::string fullscreen_mode = m_host_interface->GetStringSettingValue("GPU", "FullscreenMode", ""); + const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty()); + m_display_widget = new QtDisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); m_display_widget->setWindowTitle(windowTitle()); m_display_widget->setWindowIcon(windowIcon()); if (fullscreen) { - m_display_widget->showFullScreen(); + if (!is_exclusive_fullscreen) + m_display_widget->showFullScreen(); + else + m_display_widget->showNormal(); + m_display_widget->setCursor(Qt::BlankCursor); } else if (!render_to_main) @@ -126,6 +133,9 @@ QtDisplayWidget* MainWindow::createDisplay(QThread* worker_thread, const QString return nullptr; } + if (is_exclusive_fullscreen) + setDisplayFullscreen(fullscreen_mode); + m_host_display->DoneRenderContextCurrent(); return m_display_widget; } @@ -134,6 +144,8 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre { const bool is_fullscreen = m_display_widget->isFullScreen(); const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); + const std::string fullscreen_mode = m_host_interface->GetStringSettingValue("GPU", "FullscreenMode", ""); + const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty()); if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) return m_display_widget; @@ -146,7 +158,10 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre if (fullscreen) { - m_display_widget->showFullScreen(); + if (!is_exclusive_fullscreen) + m_display_widget->showFullScreen(); + else + m_display_widget->showNormal(); m_display_widget->setCursor(Qt::BlankCursor); } else if (!render_to_main) @@ -174,6 +189,9 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre if (!m_host_display->ChangeRenderWindow(wi.value())) Panic("Failed to recreate surface on new widget."); + if (is_exclusive_fullscreen) + setDisplayFullscreen(fullscreen_mode); + m_display_widget->setFocus(); QSignalBlocker blocker(m_ui.actionFullscreen); @@ -181,6 +199,23 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre return m_display_widget; } +void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode) +{ + u32 width, height; + float refresh_rate; + bool result = false; + + if (CommonHostInterface::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate)) + { + result = m_host_display->SetFullscreen(true, width, height, refresh_rate); + if (!result) + { + m_host_interface->AddOSDMessage( + m_host_interface->TranslateStdString("OSDMessage", "Failed to acquire exclusive fullscreen."), 20.0f); + } + } +} + void MainWindow::destroyDisplay() { DebugAssert(m_host_display && m_display_widget); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index ca2f4bde2..575102ba9 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -105,6 +105,7 @@ private: void saveDisplayWindowGeometryToConfig(); void restoreDisplayWindowGeometryFromConfig(); void destroyDisplayWidget(); + void setDisplayFullscreen(const std::string& fullscreen_mode); SettingsDialog* getSettingsDialog(); void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count); void updateDebugMenuCPUExecutionMode(); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 780d866b8..44481a597 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -435,6 +435,15 @@ void QtHostInterface::onHostDisplayWindowResized(int width, int height) // re-render the display, since otherwise it will be out of date and stretched if paused if (!System::IsShutdown()) { + if (m_is_exclusive_fullscreen && !m_display->IsFullscreen()) + { + // we lost exclusive fullscreen + AddOSDMessage(TranslateStdString("OSDMessage", "Lost exclusive fullscreen."), 20.0f); + m_is_exclusive_fullscreen = false; + m_is_fullscreen = false; + updateDisplayState(); + } + g_gpu->UpdateResolutionScale(); renderDisplay(); } @@ -495,6 +504,7 @@ bool QtHostInterface::AcquireHostDisplay() } connectDisplaySignals(display_widget); + m_is_exclusive_fullscreen = m_display->IsFullscreen(); ImGui::NewFrame(); return true; } @@ -551,6 +561,7 @@ void QtHostInterface::updateDisplayState() Panic("Failed to make device context current after updating"); connectDisplaySignals(display_widget); + m_is_exclusive_fullscreen = m_display->IsFullscreen(); if (!System::IsShutdown()) { diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 13d57dd7b..05e27fb9e 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -264,4 +264,5 @@ private: bool m_is_rendering_to_main = false; bool m_is_fullscreen = false; + bool m_is_exclusive_fullscreen = false; }; diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index b3d66b69f..1a0c0e87c 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -2478,6 +2478,53 @@ void CommonHostInterface::ReloadPostProcessingShaders() AddOSDMessage(TranslateStdString("OSDMessage", "Post-processing shaders reloaded."), 10.0f); } +bool CommonHostInterface::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, + float* refresh_rate) +{ + if (!mode.empty()) + { + std::string_view::size_type sep1 = mode.find('x'); + if (sep1 != std::string_view::npos) + { + std::optional owidth = StringUtil::FromChars(mode.substr(0, sep1)); + sep1++; + + while (sep1 < mode.length() && std::isspace(mode[sep1])) + sep1++; + + if (owidth.has_value() && sep1 < mode.length()) + { + std::string_view::size_type sep2 = mode.find('@', sep1); + if (sep2 != std::string_view::npos) + { + std::optional oheight = StringUtil::FromChars(mode.substr(sep1, sep2 - sep1)); + sep2++; + + while (sep2 < mode.length() && std::isspace(mode[sep2])) + sep2++; + + if (oheight.has_value() && sep2 < mode.length()) + { + std::optional orefresh_rate = StringUtil::FromChars(mode.substr(sep2)); + if (orefresh_rate.has_value()) + { + *width = owidth.value(); + *height = oheight.value(); + *refresh_rate = orefresh_rate.value(); + return true; + } + } + } + } + } + } + + *width = 0; + *height = 0; + *refresh_rate = 0; + return false; +} + #ifdef WITH_DISCORD_PRESENCE void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled) diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index a48b13d3c..3396aa689 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -177,6 +177,9 @@ public: /// Reloads post processing shaders with the current configuration. void ReloadPostProcessingShaders(); + /// Parses a fullscreen mode into its components (width * height @ refresh hz) + static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate); + protected: enum : u32 { diff --git a/src/frontend-common/d3d11_host_display.cpp b/src/frontend-common/d3d11_host_display.cpp index a9be30010..967f4bc78 100644 --- a/src/frontend-common/d3d11_host_display.cpp +++ b/src/frontend-common/d3d11_host_display.cpp @@ -196,16 +196,16 @@ bool D3D11HostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view u32 adapter_index; if (!adapter_name.empty()) { - std::vector adapter_names = EnumerateAdapterNames(temp_dxgi_factory.Get()); - for (adapter_index = 0; adapter_index < static_cast(adapter_names.size()); adapter_index++) + AdapterInfo adapter_info = GetAdapterInfo(temp_dxgi_factory.Get()); + for (adapter_index = 0; adapter_index < static_cast(adapter_info.adapter_names.size()); adapter_index++) { - if (adapter_name == adapter_names[adapter_index]) + if (adapter_name == adapter_info.adapter_names[adapter_index]) break; } - if (adapter_index == static_cast(adapter_names.size())) + if (adapter_index == static_cast(adapter_info.adapter_names.size())) { Log_WarningPrintf("Could not find adapter '%s', using first (%s)", std::string(adapter_name).c_str(), - adapter_names[0].c_str()); + adapter_info.adapter_names[0].c_str()); adapter_index = 0; } } @@ -293,7 +293,7 @@ bool D3D11HostDisplay::InitializeRenderDevice(std::string_view shader_cache_dire { #ifndef LIBRETRO if (m_window_info.type != WindowInfo::Type::Surfaceless && m_window_info.type != WindowInfo::Type::Libretro && - !CreateSwapChain()) + !CreateSwapChain(nullptr)) { return false; } @@ -335,7 +335,7 @@ bool D3D11HostDisplay::DoneRenderContextCurrent() #ifndef LIBRETRO -bool D3D11HostDisplay::CreateSwapChain() +bool D3D11HostDisplay::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) { if (m_window_info.type != WindowInfo::Type::Win32) return false; @@ -359,12 +359,19 @@ bool D3D11HostDisplay::CreateSwapChain() swap_chain_desc.Windowed = TRUE; swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; - m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain); + m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !fullscreen_mode); if (m_using_allow_tearing) - swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - Log_InfoPrintf("Creating a %dx%d %s %s swap chain", width, height, - m_using_flip_model_swap_chain ? "flip-discard" : "discard", + if (fullscreen_mode) + { + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + swap_chain_desc.Windowed = FALSE; + swap_chain_desc.BufferDesc = *fullscreen_mode; + } + + Log_InfoPrintf("Creating a %dx%d %s %s swap chain", swap_chain_desc.BufferDesc.Width, + swap_chain_desc.BufferDesc.Height, m_using_flip_model_swap_chain ? "flip-discard" : "discard", swap_chain_desc.Windowed ? "windowed" : "full-screen"); HRESULT hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf()); @@ -433,7 +440,7 @@ bool D3D11HostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) DestroyRenderSurface(); m_window_info = new_wi; - return CreateSwapChain(); + return CreateSwapChain(nullptr); #else m_window_info = new_wi; return true; @@ -443,6 +450,9 @@ bool D3D11HostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) void D3D11HostDisplay::DestroyRenderSurface() { #ifndef LIBRETRO + if (IsFullscreen()) + SetFullscreen(false, 0, 0, 0.0f); + m_swap_chain_rtv.Reset(); m_swap_chain.Reset(); #endif @@ -466,6 +476,82 @@ void D3D11HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_h #endif } +bool D3D11HostDisplay::IsFullscreen() +{ +#ifndef LIBRETRO + BOOL is_fullscreen = FALSE; + return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen); +#else + return false; +#endif +} + +bool D3D11HostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ +#ifndef LIBRETRO + if (!m_swap_chain) + return false; + + BOOL is_fullscreen = FALSE; + HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr); + if (!fullscreen) + { + // leaving fullscreen + if (is_fullscreen) + return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr)); + else + return true; + } + + IDXGIOutput* output; + if (FAILED(hr = m_swap_chain->GetContainingOutput(&output))) + return false; + + DXGI_SWAP_CHAIN_DESC current_desc; + hr = m_swap_chain->GetDesc(¤t_desc); + if (FAILED(hr)) + return false; + + DXGI_MODE_DESC new_mode = current_desc.BufferDesc; + new_mode.Width = width; + new_mode.Height = height; + new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); + new_mode.RefreshRate.Denominator = 1000u; + + DXGI_MODE_DESC closest_mode; + if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) || + new_mode.Format != current_desc.BufferDesc.Format) + { + Log_ErrorPrintf("Failed to find closest matching mode, hr=%08X", hr); + return false; + } + + if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Width && + new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator && + new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator) + { + Log_InfoPrintf("Fullscreen mode already set"); + return true; + } + + m_swap_chain_rtv.Reset(); + m_swap_chain.Reset(); + + if (!CreateSwapChain(&closest_mode)) + { + Log_ErrorPrintf("Failed to create a fullscreen swap chain"); + if (!CreateSwapChain(nullptr)) + Panic("Failed to recreate windowed swap chain"); + + return false; + } + + return true; +#else + return false; +#endif +} + bool D3D11HostDisplay::CreateResources() { HRESULT hr; @@ -689,22 +775,22 @@ void D3D11HostDisplay::RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 he #ifndef LIBRETRO -std::vector D3D11HostDisplay::EnumerateAdapterNames() +D3D11HostDisplay::AdapterInfo D3D11HostDisplay::GetAdapterInfo() { ComPtr dxgi_factory; HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(dxgi_factory.GetAddressOf())); if (FAILED(hr)) return {}; - return EnumerateAdapterNames(dxgi_factory.Get()); + return GetAdapterInfo(dxgi_factory.Get()); } -std::vector D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* dxgi_factory) +D3D11HostDisplay::AdapterInfo D3D11HostDisplay::GetAdapterInfo(IDXGIFactory* dxgi_factory) { - std::vector adapter_names; + AdapterInfo adapter_info; ComPtr current_adapter; - while (SUCCEEDED( - dxgi_factory->EnumAdapters(static_cast(adapter_names.size()), current_adapter.ReleaseAndGetAddressOf()))) + while (SUCCEEDED(dxgi_factory->EnumAdapters(static_cast(adapter_info.adapter_names.size()), + current_adapter.ReleaseAndGetAddressOf()))) { DXGI_ADAPTER_DESC adapter_desc; std::string adapter_name; @@ -724,8 +810,30 @@ std::vector D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* d adapter_name.assign("(Unknown)"); } + if (adapter_info.fullscreen_modes.empty()) + { + ComPtr output; + if (SUCCEEDED(current_adapter->EnumOutputs(0, &output))) + { + UINT num_modes = 0; + if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr))) + { + std::vector modes(num_modes); + if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, modes.data()))) + { + for (const DXGI_MODE_DESC& mode : modes) + { + adapter_info.fullscreen_modes.push_back(StringUtil::StdStringFromFormat( + "%u x %u @ %f hz", mode.Width, mode.Height, + static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator))); + } + } + } + } + } + // handle duplicate adapter names - if (std::any_of(adapter_names.begin(), adapter_names.end(), + if (std::any_of(adapter_info.adapter_names.begin(), adapter_info.adapter_names.end(), [&adapter_name](const std::string& other) { return (adapter_name == other); })) { std::string original_adapter_name = std::move(adapter_name); @@ -735,14 +843,14 @@ std::vector D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* d { adapter_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra); current_extra++; - } while (std::any_of(adapter_names.begin(), adapter_names.end(), + } while (std::any_of(adapter_info.adapter_names.begin(), adapter_info.adapter_names.end(), [&adapter_name](const std::string& other) { return (adapter_name == other); })); } - adapter_names.push_back(std::move(adapter_name)); + adapter_info.adapter_names.push_back(std::move(adapter_name)); } - return adapter_names; + return adapter_info; } bool D3D11HostDisplay::SetPostProcessingChain(const std::string_view& config) diff --git a/src/frontend-common/d3d11_host_display.h b/src/frontend-common/d3d11_host_display.h index 67731bb7d..c4d525855 100644 --- a/src/frontend-common/d3d11_host_display.h +++ b/src/frontend-common/d3d11_host_display.h @@ -44,6 +44,8 @@ public: virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + virtual bool IsFullscreen() override; + virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; virtual void DestroyRenderSurface() override; virtual bool SetPostProcessingChain(const std::string_view& config) override; @@ -60,13 +62,20 @@ public: virtual bool Render() override; #ifndef LIBRETRO - static std::vector EnumerateAdapterNames(); + struct AdapterInfo + { + std::vector adapter_names; + std::vector fullscreen_modes; + }; + static AdapterInfo GetAdapterInfo(); #endif protected: static constexpr u32 DISPLAY_UNIFORM_BUFFER_SIZE = 16; - static std::vector EnumerateAdapterNames(IDXGIFactory* dxgi_factory); +#ifndef LIBRETRO + static AdapterInfo GetAdapterInfo(IDXGIFactory* dxgi_factory); +#endif virtual bool CreateResources() override; virtual void DestroyResources() override; @@ -75,7 +84,7 @@ protected: virtual void DestroyImGuiContext(); #ifndef LIBRETRO - bool CreateSwapChain(); + bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); bool CreateSwapChainRTV(); #endif diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index 9a92c10c2..1597a5dc6 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -315,6 +315,16 @@ void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_ #endif } +bool OpenGLHostDisplay::IsFullscreen() +{ + return false; +} + +bool OpenGLHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + return false; +} + void OpenGLHostDisplay::DestroyRenderSurface() { if (!m_gl_context) diff --git a/src/frontend-common/opengl_host_display.h b/src/frontend-common/opengl_host_display.h index 7f01307bc..db04712dc 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -44,6 +44,8 @@ public: virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + virtual bool IsFullscreen() override; + virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; virtual void DestroyRenderSurface() override; virtual bool SetPostProcessingChain(const std::string_view& config) override; diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index 5623053b8..926361993 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -174,6 +174,16 @@ void VulkanHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_ #endif } +bool VulkanHostDisplay::IsFullscreen() +{ + return false; +} + +bool VulkanHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + return false; +} + void VulkanHostDisplay::DestroyRenderSurface() { m_window_info = {}; diff --git a/src/frontend-common/vulkan_host_display.h b/src/frontend-common/vulkan_host_display.h index 429670b4e..776ea1512 100644 --- a/src/frontend-common/vulkan_host_display.h +++ b/src/frontend-common/vulkan_host_display.h @@ -41,6 +41,8 @@ public: virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + virtual bool IsFullscreen() override; + virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; virtual void DestroyRenderSurface() override; virtual bool SetPostProcessingChain(const std::string_view& config) override;