HostDisplay: Implement exclusive fullscreen for D3D11 renderer

This commit is contained in:
Connor McLaughlin 2020-11-02 00:38:54 +10:00
parent 858f39827d
commit 414bc30f24
19 changed files with 355 additions and 50 deletions

View file

@ -66,6 +66,8 @@ public:
virtual void DestroyRenderDevice() = 0; virtual void DestroyRenderDevice() = 0;
virtual void DestroyRenderSurface() = 0; virtual void DestroyRenderSurface() = 0;
virtual bool ChangeRenderWindow(const WindowInfo& wi) = 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 bool CreateResources() = 0;
virtual void DestroyResources() = 0; virtual void DestroyResources() = 0;

View file

@ -5,6 +5,7 @@
#include "host_interface.h" #include "host_interface.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cctype>
#include <numeric> #include <numeric>
Settings g_settings; Settings g_settings;

View file

@ -156,6 +156,16 @@ void LibretroHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_windo
m_window_info.surface_height = new_window_height; 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) bool LibretroHostDisplay::SetPostProcessingChain(const std::string_view& config)
{ {
return false; return false;

View file

@ -23,6 +23,8 @@ public:
bool ChangeRenderWindow(const WindowInfo& wi) override; bool ChangeRenderWindow(const WindowInfo& wi) override;
void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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; void DestroyRenderSurface() override;
bool SetPostProcessingChain(const std::string_view& config) override; bool SetPostProcessingChain(const std::string_view& config) override;

View file

@ -42,10 +42,12 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
false); false);
connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&DisplaySettingsWidget::populateGPUAdapters); &DisplaySettingsWidget::populateGPUAdaptersAndResolutions);
connect(m_ui.adapter, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(m_ui.adapter, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&DisplaySettingsWidget::onGPUAdapterIndexChanged); &DisplaySettingsWidget::onGPUAdapterIndexChanged);
populateGPUAdapters(); connect(m_ui.fullscreenMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&DisplaySettingsWidget::onGPUFullscreenModeIndexChanged);
populateGPUAdaptersAndResolutions();
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.renderer, tr("Renderer"), m_ui.renderer, tr("Renderer"),
@ -118,15 +120,20 @@ void DisplaySettingsWidget::setupAdditionalUi()
} }
} }
void DisplaySettingsWidget::populateGPUAdapters() void DisplaySettingsWidget::populateGPUAdaptersAndResolutions()
{ {
std::vector<std::string> adapter_names; std::vector<std::string> adapter_names;
std::vector<std::string> fullscreen_modes;
switch (static_cast<GPURenderer>(m_ui.renderer->currentIndex())) switch (static_cast<GPURenderer>(m_ui.renderer->currentIndex()))
{ {
#ifdef WIN32 #ifdef WIN32
case GPURenderer::HardwareD3D11: 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 #endif
case GPURenderer::HardwareVulkan: case GPURenderer::HardwareVulkan:
@ -137,26 +144,46 @@ void DisplaySettingsWidget::populateGPUAdapters()
break; 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)); const std::string current_adapter(m_host_interface->GetStringSettingValue("GPU", "Adapter"));
m_ui.adapter->addItem(qadapter_name); QSignalBlocker blocker(m_ui.adapter);
if (qadapter_name == current_value) // add the default entry - we'll fall back to this if the GPU no longer exists, or there's no options
m_ui.adapter->setCurrentIndex(m_ui.adapter->count() - 1); 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() void DisplaySettingsWidget::onGPUAdapterIndexChanged()
@ -170,3 +197,16 @@ void DisplaySettingsWidget::onGPUAdapterIndexChanged()
m_host_interface->SetStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData()); 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());
}

View file

@ -17,8 +17,9 @@ public:
~DisplaySettingsWidget(); ~DisplaySettingsWidget();
private Q_SLOTS: private Q_SLOTS:
void populateGPUAdapters(); void populateGPUAdaptersAndResolutions();
void onGPUAdapterIndexChanged(); void onGPUAdapterIndexChanged();
void onGPUFullscreenModeIndexChanged();
private: private:
void setupAdditionalUi(); void setupAdditionalUi();

View file

@ -52,7 +52,17 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="adapter"/> <widget class="QComboBox" name="adapter"/>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Fullscreen Mode:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="fullscreenMode"/>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="vsync"> <widget class="QCheckBox" name="vsync">
<property name="text"> <property name="text">
<string>VSync</string> <string>VSync</string>

View file

@ -87,13 +87,20 @@ QtDisplayWidget* MainWindow::createDisplay(QThread* worker_thread, const QString
Assert(!m_host_display && !m_display_widget); Assert(!m_host_display && !m_display_widget);
Assert(!fullscreen || !render_to_main); 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 = new QtDisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr);
m_display_widget->setWindowTitle(windowTitle()); m_display_widget->setWindowTitle(windowTitle());
m_display_widget->setWindowIcon(windowIcon()); m_display_widget->setWindowIcon(windowIcon());
if (fullscreen) 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); m_display_widget->setCursor(Qt::BlankCursor);
} }
else if (!render_to_main) else if (!render_to_main)
@ -126,6 +133,9 @@ QtDisplayWidget* MainWindow::createDisplay(QThread* worker_thread, const QString
return nullptr; return nullptr;
} }
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
m_host_display->DoneRenderContextCurrent(); m_host_display->DoneRenderContextCurrent();
return m_display_widget; 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_fullscreen = m_display_widget->isFullScreen();
const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); 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) if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main)
return m_display_widget; return m_display_widget;
@ -146,7 +158,10 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre
if (fullscreen) 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); m_display_widget->setCursor(Qt::BlankCursor);
} }
else if (!render_to_main) else if (!render_to_main)
@ -174,6 +189,9 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre
if (!m_host_display->ChangeRenderWindow(wi.value())) if (!m_host_display->ChangeRenderWindow(wi.value()))
Panic("Failed to recreate surface on new widget."); Panic("Failed to recreate surface on new widget.");
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
m_display_widget->setFocus(); m_display_widget->setFocus();
QSignalBlocker blocker(m_ui.actionFullscreen); QSignalBlocker blocker(m_ui.actionFullscreen);
@ -181,6 +199,23 @@ QtDisplayWidget* MainWindow::updateDisplay(QThread* worker_thread, bool fullscre
return m_display_widget; 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() void MainWindow::destroyDisplay()
{ {
DebugAssert(m_host_display && m_display_widget); DebugAssert(m_host_display && m_display_widget);

View file

@ -105,6 +105,7 @@ private:
void saveDisplayWindowGeometryToConfig(); void saveDisplayWindowGeometryToConfig();
void restoreDisplayWindowGeometryFromConfig(); void restoreDisplayWindowGeometryFromConfig();
void destroyDisplayWidget(); void destroyDisplayWidget();
void setDisplayFullscreen(const std::string& fullscreen_mode);
SettingsDialog* getSettingsDialog(); SettingsDialog* getSettingsDialog();
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count); void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
void updateDebugMenuCPUExecutionMode(); void updateDebugMenuCPUExecutionMode();

View file

@ -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 // re-render the display, since otherwise it will be out of date and stretched if paused
if (!System::IsShutdown()) 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(); g_gpu->UpdateResolutionScale();
renderDisplay(); renderDisplay();
} }
@ -495,6 +504,7 @@ bool QtHostInterface::AcquireHostDisplay()
} }
connectDisplaySignals(display_widget); connectDisplaySignals(display_widget);
m_is_exclusive_fullscreen = m_display->IsFullscreen();
ImGui::NewFrame(); ImGui::NewFrame();
return true; return true;
} }
@ -551,6 +561,7 @@ void QtHostInterface::updateDisplayState()
Panic("Failed to make device context current after updating"); Panic("Failed to make device context current after updating");
connectDisplaySignals(display_widget); connectDisplaySignals(display_widget);
m_is_exclusive_fullscreen = m_display->IsFullscreen();
if (!System::IsShutdown()) if (!System::IsShutdown())
{ {

View file

@ -264,4 +264,5 @@ private:
bool m_is_rendering_to_main = false; bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false; bool m_is_fullscreen = false;
bool m_is_exclusive_fullscreen = false;
}; };

View file

@ -2478,6 +2478,53 @@ void CommonHostInterface::ReloadPostProcessingShaders()
AddOSDMessage(TranslateStdString("OSDMessage", "Post-processing shaders reloaded."), 10.0f); 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<u32> owidth = StringUtil::FromChars<u32>(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<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1));
sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2]))
sep2++;
if (oheight.has_value() && sep2 < mode.length())
{
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(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 #ifdef WITH_DISCORD_PRESENCE
void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled) void CommonHostInterface::SetDiscordPresenceEnabled(bool enabled)

View file

@ -177,6 +177,9 @@ public:
/// Reloads post processing shaders with the current configuration. /// Reloads post processing shaders with the current configuration.
void ReloadPostProcessingShaders(); 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: protected:
enum : u32 enum : u32
{ {

View file

@ -196,16 +196,16 @@ bool D3D11HostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view
u32 adapter_index; u32 adapter_index;
if (!adapter_name.empty()) if (!adapter_name.empty())
{ {
std::vector<std::string> adapter_names = EnumerateAdapterNames(temp_dxgi_factory.Get()); AdapterInfo adapter_info = GetAdapterInfo(temp_dxgi_factory.Get());
for (adapter_index = 0; adapter_index < static_cast<u32>(adapter_names.size()); adapter_index++) for (adapter_index = 0; adapter_index < static_cast<u32>(adapter_info.adapter_names.size()); adapter_index++)
{ {
if (adapter_name == adapter_names[adapter_index]) if (adapter_name == adapter_info.adapter_names[adapter_index])
break; break;
} }
if (adapter_index == static_cast<u32>(adapter_names.size())) if (adapter_index == static_cast<u32>(adapter_info.adapter_names.size()))
{ {
Log_WarningPrintf("Could not find adapter '%s', using first (%s)", std::string(adapter_name).c_str(), 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; adapter_index = 0;
} }
} }
@ -293,7 +293,7 @@ bool D3D11HostDisplay::InitializeRenderDevice(std::string_view shader_cache_dire
{ {
#ifndef LIBRETRO #ifndef LIBRETRO
if (m_window_info.type != WindowInfo::Type::Surfaceless && m_window_info.type != WindowInfo::Type::Libretro && if (m_window_info.type != WindowInfo::Type::Surfaceless && m_window_info.type != WindowInfo::Type::Libretro &&
!CreateSwapChain()) !CreateSwapChain(nullptr))
{ {
return false; return false;
} }
@ -335,7 +335,7 @@ bool D3D11HostDisplay::DoneRenderContextCurrent()
#ifndef LIBRETRO #ifndef LIBRETRO
bool D3D11HostDisplay::CreateSwapChain() bool D3D11HostDisplay::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
{ {
if (m_window_info.type != WindowInfo::Type::Win32) if (m_window_info.type != WindowInfo::Type::Win32)
return false; return false;
@ -359,12 +359,19 @@ bool D3D11HostDisplay::CreateSwapChain()
swap_chain_desc.Windowed = TRUE; swap_chain_desc.Windowed = TRUE;
swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; 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) 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, if (fullscreen_mode)
m_using_flip_model_swap_chain ? "flip-discard" : "discard", {
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"); swap_chain_desc.Windowed ? "windowed" : "full-screen");
HRESULT hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf()); 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(); DestroyRenderSurface();
m_window_info = new_wi; m_window_info = new_wi;
return CreateSwapChain(); return CreateSwapChain(nullptr);
#else #else
m_window_info = new_wi; m_window_info = new_wi;
return true; return true;
@ -443,6 +450,9 @@ bool D3D11HostDisplay::ChangeRenderWindow(const WindowInfo& new_wi)
void D3D11HostDisplay::DestroyRenderSurface() void D3D11HostDisplay::DestroyRenderSurface()
{ {
#ifndef LIBRETRO #ifndef LIBRETRO
if (IsFullscreen())
SetFullscreen(false, 0, 0, 0.0f);
m_swap_chain_rtv.Reset(); m_swap_chain_rtv.Reset();
m_swap_chain.Reset(); m_swap_chain.Reset();
#endif #endif
@ -466,6 +476,82 @@ void D3D11HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_h
#endif #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(&current_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<UINT>(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() bool D3D11HostDisplay::CreateResources()
{ {
HRESULT hr; HRESULT hr;
@ -689,22 +775,22 @@ void D3D11HostDisplay::RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 he
#ifndef LIBRETRO #ifndef LIBRETRO
std::vector<std::string> D3D11HostDisplay::EnumerateAdapterNames() D3D11HostDisplay::AdapterInfo D3D11HostDisplay::GetAdapterInfo()
{ {
ComPtr<IDXGIFactory> dxgi_factory; ComPtr<IDXGIFactory> dxgi_factory;
HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(dxgi_factory.GetAddressOf())); HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(dxgi_factory.GetAddressOf()));
if (FAILED(hr)) if (FAILED(hr))
return {}; return {};
return EnumerateAdapterNames(dxgi_factory.Get()); return GetAdapterInfo(dxgi_factory.Get());
} }
std::vector<std::string> D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* dxgi_factory) D3D11HostDisplay::AdapterInfo D3D11HostDisplay::GetAdapterInfo(IDXGIFactory* dxgi_factory)
{ {
std::vector<std::string> adapter_names; AdapterInfo adapter_info;
ComPtr<IDXGIAdapter> current_adapter; ComPtr<IDXGIAdapter> current_adapter;
while (SUCCEEDED( while (SUCCEEDED(dxgi_factory->EnumAdapters(static_cast<UINT>(adapter_info.adapter_names.size()),
dxgi_factory->EnumAdapters(static_cast<UINT>(adapter_names.size()), current_adapter.ReleaseAndGetAddressOf()))) current_adapter.ReleaseAndGetAddressOf())))
{ {
DXGI_ADAPTER_DESC adapter_desc; DXGI_ADAPTER_DESC adapter_desc;
std::string adapter_name; std::string adapter_name;
@ -724,8 +810,30 @@ std::vector<std::string> D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* d
adapter_name.assign("(Unknown)"); adapter_name.assign("(Unknown)");
} }
if (adapter_info.fullscreen_modes.empty())
{
ComPtr<IDXGIOutput> 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<DXGI_MODE_DESC> 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<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
}
}
}
}
}
// handle duplicate adapter names // 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); })) [&adapter_name](const std::string& other) { return (adapter_name == other); }))
{ {
std::string original_adapter_name = std::move(adapter_name); std::string original_adapter_name = std::move(adapter_name);
@ -735,14 +843,14 @@ std::vector<std::string> D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* d
{ {
adapter_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra); adapter_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra);
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_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) bool D3D11HostDisplay::SetPostProcessingChain(const std::string_view& config)

View file

@ -44,6 +44,8 @@ public:
virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override; virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override;
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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 void DestroyRenderSurface() override;
virtual bool SetPostProcessingChain(const std::string_view& config) override; virtual bool SetPostProcessingChain(const std::string_view& config) override;
@ -60,13 +62,20 @@ public:
virtual bool Render() override; virtual bool Render() override;
#ifndef LIBRETRO #ifndef LIBRETRO
static std::vector<std::string> EnumerateAdapterNames(); struct AdapterInfo
{
std::vector<std::string> adapter_names;
std::vector<std::string> fullscreen_modes;
};
static AdapterInfo GetAdapterInfo();
#endif #endif
protected: protected:
static constexpr u32 DISPLAY_UNIFORM_BUFFER_SIZE = 16; static constexpr u32 DISPLAY_UNIFORM_BUFFER_SIZE = 16;
static std::vector<std::string> EnumerateAdapterNames(IDXGIFactory* dxgi_factory); #ifndef LIBRETRO
static AdapterInfo GetAdapterInfo(IDXGIFactory* dxgi_factory);
#endif
virtual bool CreateResources() override; virtual bool CreateResources() override;
virtual void DestroyResources() override; virtual void DestroyResources() override;
@ -75,7 +84,7 @@ protected:
virtual void DestroyImGuiContext(); virtual void DestroyImGuiContext();
#ifndef LIBRETRO #ifndef LIBRETRO
bool CreateSwapChain(); bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChainRTV(); bool CreateSwapChainRTV();
#endif #endif

View file

@ -315,6 +315,16 @@ void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_
#endif #endif
} }
bool OpenGLHostDisplay::IsFullscreen()
{
return false;
}
bool OpenGLHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
{
return false;
}
void OpenGLHostDisplay::DestroyRenderSurface() void OpenGLHostDisplay::DestroyRenderSurface()
{ {
if (!m_gl_context) if (!m_gl_context)

View file

@ -44,6 +44,8 @@ public:
virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override; virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override;
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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 void DestroyRenderSurface() override;
virtual bool SetPostProcessingChain(const std::string_view& config) override; virtual bool SetPostProcessingChain(const std::string_view& config) override;

View file

@ -174,6 +174,16 @@ void VulkanHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_
#endif #endif
} }
bool VulkanHostDisplay::IsFullscreen()
{
return false;
}
bool VulkanHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
{
return false;
}
void VulkanHostDisplay::DestroyRenderSurface() void VulkanHostDisplay::DestroyRenderSurface()
{ {
m_window_info = {}; m_window_info = {};

View file

@ -41,6 +41,8 @@ public:
virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override; virtual bool ChangeRenderWindow(const WindowInfo& new_wi) override;
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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 void DestroyRenderSurface() override;
virtual bool SetPostProcessingChain(const std::string_view& config) override; virtual bool SetPostProcessingChain(const std::string_view& config) override;