Skip to content

Commit 6ea352d

Browse files
committed
Qt: remove duplicate entries on resolutions menu
When querying display information, entries could be duplicated because DXGI returns one entry per combination of scanline ordering and scaling for the same resolution and refresh rate. For example, 1920x1080 @ 60Hz may appear multiple times with combinations such as progressive + unspecified scaling, unspecified + centered scaling, and unspecified + stretched scaling. Since only the resolution and refresh rate are used, duplicates are now excluded. Also added per-monitor grouping to the fullscreen mode list, so users can select a resolution and refresh rate for any connected display, not just their primary.
1 parent 06616ec commit 6ea352d

File tree

3 files changed

+109
-18
lines changed

3 files changed

+109
-18
lines changed

pcsx2-qt/Settings/GraphicsSettingsWidget.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "QtUtils.h"
66
#include "SettingWidgetBinder.h"
77
#include "SettingsWindow.h"
8+
#include <QtGui/QStandardItemModel>
89
#include <QtWidgets/QMessageBox>
910

1011
#include "pcsx2/Host.h"
@@ -1104,11 +1105,24 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
11041105

11051106
if (current_adapter_info)
11061107
{
1107-
for (const std::string& fs_mode : current_adapter_info->fullscreen_modes)
1108+
for (const GSAdapterInfo::MonitorInfo& monitor : current_adapter_info->monitors)
11081109
{
1109-
m_display.fullscreenModes->addItem(QString::fromStdString(fs_mode));
1110-
if (current_mode == fs_mode)
1111-
m_display.fullscreenModes->setCurrentIndex(m_display.fullscreenModes->count() - 1);
1110+
// Add a disabled separator item as a group header for this monitor.
1111+
const int separator_index = m_display.fullscreenModes->count();
1112+
m_display.fullscreenModes->addItem(QString::fromStdString(monitor.name));
1113+
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(m_display.fullscreenModes->model());
1114+
if (model)
1115+
{
1116+
QStandardItem* separator = model->item(separator_index);
1117+
separator->setFlags(separator->flags() & ~Qt::ItemIsEnabled & ~Qt::ItemIsSelectable);
1118+
}
1119+
1120+
for (const std::string& fs_mode : monitor.fullscreen_modes)
1121+
{
1122+
m_display.fullscreenModes->addItem(QString::fromStdString(fs_mode));
1123+
if (current_mode == fs_mode)
1124+
m_display.fullscreenModes->setCurrentIndex(m_display.fullscreenModes->count() - 1);
1125+
}
11121126
}
11131127
}
11141128
}

pcsx2/GS/GS.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ enum class GSDisplayAlignment
4343

4444
struct GSAdapterInfo
4545
{
46+
struct MonitorInfo
47+
{
48+
std::string name;
49+
std::vector<std::string> fullscreen_modes;
50+
};
51+
4652
std::string name;
47-
std::vector<std::string> fullscreen_modes;
53+
std::vector<MonitorInfo> monitors;
4854
u32 max_texture_size;
4955
u32 max_upscale_multiplier;
5056
};

pcsx2/GS/Renderers/DX11/D3D.cpp

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,81 @@ std::vector<GSAdapterInfo> D3D::GetAdapterInfo(IDXGIFactory5* factory)
8585
ai.max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
8686
ai.max_upscale_multiplier = GSGetMaxUpscaleMultiplier(ai.max_texture_size);
8787

88-
wil::com_ptr_nothrow<IDXGIOutput> output;
89-
// Only check the first output, which would be the primary display (if any is connected)
90-
if (SUCCEEDED(hr = adapter->EnumOutputs(0, &output)))
88+
for (u32 output_index = 0;; output_index++)
9189
{
90+
wil::com_ptr_nothrow<IDXGIOutput> output;
91+
hr = adapter->EnumOutputs(output_index, output.put());
92+
if (hr == DXGI_ERROR_NOT_FOUND)
93+
break;
94+
if (FAILED(hr))
95+
{
96+
ERROR_LOG("EnumOutputs() failed: {:08X}", static_cast<unsigned>(hr));
97+
break;
98+
}
99+
100+
DXGI_OUTPUT_DESC output_desc;
101+
if (FAILED(output->GetDesc(&output_desc)) || !output_desc.AttachedToDesktop)
102+
continue;
103+
104+
// Build a friendly monitor name using QueryDisplayConfig, with a fallback to the device name.
105+
MONITORINFOEX monitor_info = {};
106+
monitor_info.cbSize = sizeof(monitor_info);
107+
const bool got_monitor_info = GetMonitorInfo(output_desc.Monitor, &monitor_info);
108+
const bool is_primary = got_monitor_info && (monitor_info.dwFlags & MONITORINFOF_PRIMARY);
109+
110+
std::string monitor_name;
111+
if (got_monitor_info)
112+
{
113+
UINT32 num_paths = 0, num_modes = 0;
114+
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &num_paths, &num_modes) == ERROR_SUCCESS)
115+
{
116+
std::vector<DISPLAYCONFIG_PATH_INFO> paths(num_paths);
117+
std::vector<DISPLAYCONFIG_MODE_INFO> modes(num_modes);
118+
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &num_paths, paths.data(), &num_modes, modes.data(), nullptr) == ERROR_SUCCESS)
119+
{
120+
for (const DISPLAYCONFIG_PATH_INFO& path : paths)
121+
{
122+
DISPLAYCONFIG_SOURCE_DEVICE_NAME source = {};
123+
source.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
124+
source.header.size = sizeof(source);
125+
source.header.adapterId = path.sourceInfo.adapterId;
126+
source.header.id = path.sourceInfo.id;
127+
if (DisplayConfigGetDeviceInfo(&source.header) != ERROR_SUCCESS)
128+
continue;
129+
if (wcscmp(source.viewGdiDeviceName, monitor_info.szDevice) != 0)
130+
continue;
131+
132+
DISPLAYCONFIG_TARGET_DEVICE_NAME target = {};
133+
target.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
134+
target.header.size = sizeof(target);
135+
target.header.adapterId = path.targetInfo.adapterId;
136+
target.header.id = path.targetInfo.id;
137+
if (DisplayConfigGetDeviceInfo(&target.header) != ERROR_SUCCESS)
138+
continue;
139+
140+
if (target.flags.friendlyNameFromEdid)
141+
monitor_name = StringUtil::WideStringToUTF8String(target.monitorFriendlyDeviceName);
142+
break;
143+
}
144+
}
145+
}
146+
}
147+
148+
// Format: "Primary: <name>", "Display N: <name>", or "Display N" if no friendly name.
149+
std::string label;
150+
if (is_primary)
151+
label = "Primary";
152+
else
153+
label = fmt::format("Display {}", output_index + 1);
154+
155+
if (!monitor_name.empty())
156+
label += ": " + monitor_name;
157+
158+
monitor_name = std::move(label);
159+
160+
GSAdapterInfo::MonitorInfo mi;
161+
mi.name = std::move(monitor_name);
162+
92163
UINT num_modes = 0;
93164
if (SUCCEEDED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr)))
94165
{
@@ -97,8 +168,10 @@ std::vector<GSAdapterInfo> D3D::GetAdapterInfo(IDXGIFactory5* factory)
97168
{
98169
for (const DXGI_MODE_DESC& mode : dmodes)
99170
{
100-
ai.fullscreen_modes.push_back(GSDevice::GetFullscreenModeString(mode.Width, mode.Height,
101-
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
171+
std::string fs_mode = GSDevice::GetFullscreenModeString(mode.Width, mode.Height,
172+
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator));
173+
if (std::find(mi.fullscreen_modes.begin(), mi.fullscreen_modes.end(), fs_mode) == mi.fullscreen_modes.end())
174+
mi.fullscreen_modes.push_back(std::move(fs_mode));
102175
}
103176
}
104177
else
@@ -110,10 +183,8 @@ std::vector<GSAdapterInfo> D3D::GetAdapterInfo(IDXGIFactory5* factory)
110183
{
111184
ERROR_LOG("GetDisplayModeList() failed: {:08X}", static_cast<unsigned>(hr));
112185
}
113-
}
114-
else if (hr != DXGI_ERROR_NOT_FOUND)
115-
{
116-
ERROR_LOG("EnumOutputs() failed: {:08X}", static_cast<unsigned>(hr));
186+
187+
ai.monitors.push_back(std::move(mi));
117188
}
118189

119190
adapters.push_back(std::move(ai));
@@ -385,10 +456,10 @@ GSRendererType D3D::GetPreferredRenderer()
385456
if (!GSDeviceVK::EnumerateGPUs().empty())
386457
return true;
387458

388-
Host::AddIconOSDMessage("VKDriverUnsupported", ICON_FA_TV, TRANSLATE_STR("GS",
389-
"The Vulkan graphics API was automatically selected, but no compatible devices were found.\n"
390-
" You should update all graphics drivers in your system, including any integrated GPUs\n"
391-
" to use the Vulkan renderer."), Host::OSD_WARNING_DURATION);
459+
Host::AddIconOSDMessage("VKDriverUnsupported", ICON_FA_TV, TRANSLATE_STR("GS", "The Vulkan graphics API was automatically selected, but no compatible devices were found.\n"
460+
" You should update all graphics drivers in your system, including any integrated GPUs\n"
461+
" to use the Vulkan renderer."),
462+
Host::OSD_WARNING_DURATION);
392463
return false;
393464
};
394465
#else

0 commit comments

Comments
 (0)