summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c927
1 files changed, 927 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c
new file mode 100644
index 0000000..77ebab2
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c
@@ -0,0 +1,927 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26#include "../../events/SDL_displayevents_c.h"
27
28#ifdef HAVE_DXGI1_6_H
29#define COBJMACROS
30#include <dxgi1_6.h>
31#endif
32
33// Windows CE compatibility
34#ifndef CDS_FULLSCREEN
35#define CDS_FULLSCREEN 0
36#endif
37
38// #define DEBUG_MODES
39// #define HIGHDPI_DEBUG_VERBOSE
40
41static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode)
42{
43 SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal;
44 HDC hdc;
45
46 data->DeviceMode.dmFields = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS);
47
48 // NOLINTNEXTLINE(bugprone-assignment-in-if-condition): No simple way to extract the assignment
49 if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) {
50 char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
51 LPBITMAPINFO bmi;
52 HBITMAP hbm;
53
54 SDL_zeroa(bmi_data);
55 bmi = (LPBITMAPINFO)bmi_data;
56 bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
57
58 hbm = CreateCompatibleBitmap(hdc, 1, 1);
59 GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
60 GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
61 DeleteObject(hbm);
62 DeleteDC(hdc);
63 if (bmi->bmiHeader.biCompression == BI_BITFIELDS) {
64 switch (*(Uint32 *)bmi->bmiColors) {
65 case 0x00FF0000:
66 mode->format = SDL_PIXELFORMAT_XRGB8888;
67 break;
68 case 0x000000FF:
69 mode->format = SDL_PIXELFORMAT_XBGR8888;
70 break;
71 case 0xF800:
72 mode->format = SDL_PIXELFORMAT_RGB565;
73 break;
74 case 0x7C00:
75 mode->format = SDL_PIXELFORMAT_XRGB1555;
76 break;
77 }
78 } else if (bmi->bmiHeader.biCompression == BI_RGB) {
79 if (bmi->bmiHeader.biBitCount == 24) {
80 mode->format = SDL_PIXELFORMAT_RGB24;
81 } else if (bmi->bmiHeader.biBitCount == 8) {
82 mode->format = SDL_PIXELFORMAT_INDEX8;
83 } else if (bmi->bmiHeader.biBitCount == 4) {
84 mode->format = SDL_PIXELFORMAT_INDEX4LSB;
85 }
86 }
87 } else if (mode->format == SDL_PIXELFORMAT_UNKNOWN) {
88 // FIXME: Can we tell what this will be?
89 if ((data->DeviceMode.dmFields & DM_BITSPERPEL) == DM_BITSPERPEL) {
90 switch (data->DeviceMode.dmBitsPerPel) {
91 case 32:
92 mode->format = SDL_PIXELFORMAT_XRGB8888;
93 break;
94 case 24:
95 mode->format = SDL_PIXELFORMAT_RGB24;
96 break;
97 case 16:
98 mode->format = SDL_PIXELFORMAT_RGB565;
99 break;
100 case 15:
101 mode->format = SDL_PIXELFORMAT_XRGB1555;
102 break;
103 case 8:
104 mode->format = SDL_PIXELFORMAT_INDEX8;
105 break;
106 case 4:
107 mode->format = SDL_PIXELFORMAT_INDEX4LSB;
108 break;
109 }
110 }
111 }
112}
113
114static void *WIN_GetDXGIOutput(SDL_VideoDevice *_this, const WCHAR *DeviceName)
115{
116 void *result = NULL;
117
118#ifdef HAVE_DXGI_H
119 const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal;
120 int nAdapter, nOutput;
121 IDXGIAdapter *pDXGIAdapter;
122 IDXGIOutput *pDXGIOutput;
123
124 if (!videodata->pDXGIFactory) {
125 return NULL;
126 }
127
128 nAdapter = 0;
129 while (!result && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) {
130 nOutput = 0;
131 while (!result && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) {
132 DXGI_OUTPUT_DESC outputDesc;
133 if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) {
134 if (SDL_wcscmp(outputDesc.DeviceName, DeviceName) == 0) {
135 result = pDXGIOutput;
136 }
137 }
138 if (pDXGIOutput != result) {
139 IDXGIOutput_Release(pDXGIOutput);
140 }
141 nOutput++;
142 }
143 IDXGIAdapter_Release(pDXGIAdapter);
144 nAdapter++;
145 }
146#endif
147 return result;
148}
149
150static void WIN_ReleaseDXGIOutput(void *dxgi_output)
151{
152#ifdef HAVE_DXGI_H
153 IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output;
154
155 if (pDXGIOutput) {
156 IDXGIOutput_Release(pDXGIOutput);
157 }
158#endif
159}
160
161static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode)
162{
163 int width = mode->dmPelsWidth;
164 int height = mode->dmPelsHeight;
165
166 // Use unrotated width/height to guess orientation
167 if (mode->dmDisplayOrientation == DMDO_90 || mode->dmDisplayOrientation == DMDO_270) {
168 int temp = width;
169 width = height;
170 height = temp;
171 }
172
173 if (width >= height) {
174 return SDL_ORIENTATION_LANDSCAPE;
175 } else {
176 return SDL_ORIENTATION_PORTRAIT;
177 }
178}
179
180static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode)
181{
182 if (WIN_GetNaturalOrientation(mode) == SDL_ORIENTATION_LANDSCAPE) {
183 switch (mode->dmDisplayOrientation) {
184 case DMDO_DEFAULT:
185 return SDL_ORIENTATION_LANDSCAPE;
186 case DMDO_90:
187 return SDL_ORIENTATION_PORTRAIT;
188 case DMDO_180:
189 return SDL_ORIENTATION_LANDSCAPE_FLIPPED;
190 case DMDO_270:
191 return SDL_ORIENTATION_PORTRAIT_FLIPPED;
192 default:
193 return SDL_ORIENTATION_UNKNOWN;
194 }
195 } else {
196 switch (mode->dmDisplayOrientation) {
197 case DMDO_DEFAULT:
198 return SDL_ORIENTATION_PORTRAIT;
199 case DMDO_90:
200 return SDL_ORIENTATION_LANDSCAPE_FLIPPED;
201 case DMDO_180:
202 return SDL_ORIENTATION_PORTRAIT_FLIPPED;
203 case DMDO_270:
204 return SDL_ORIENTATION_LANDSCAPE;
205 default:
206 return SDL_ORIENTATION_UNKNOWN;
207 }
208 }
209}
210
211static void WIN_GetRefreshRate(void *dxgi_output, DEVMODE *mode, int *numerator, int *denominator)
212{
213 // We're not currently using DXGI to query display modes, so fake NTSC timings
214 switch (mode->dmDisplayFrequency) {
215 case 119:
216 case 59:
217 case 29:
218 *numerator = (mode->dmDisplayFrequency + 1) * 1000;
219 *denominator = 1001;
220 break;
221 default:
222 *numerator = mode->dmDisplayFrequency;
223 *denominator = 1;
224 break;
225 }
226
227#ifdef HAVE_DXGI_H
228 if (dxgi_output) {
229 IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output;
230 DXGI_MODE_DESC modeToMatch;
231 DXGI_MODE_DESC closestMatch;
232
233 SDL_zero(modeToMatch);
234 modeToMatch.Width = mode->dmPelsWidth;
235 modeToMatch.Height = mode->dmPelsHeight;
236 modeToMatch.RefreshRate.Numerator = *numerator;
237 modeToMatch.RefreshRate.Denominator = *denominator;
238 modeToMatch.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
239
240 if (SUCCEEDED(IDXGIOutput_FindClosestMatchingMode(pDXGIOutput, &modeToMatch, &closestMatch, NULL))) {
241 *numerator = closestMatch.RefreshRate.Numerator;
242 *denominator = closestMatch.RefreshRate.Denominator;
243 }
244 }
245#endif // HAVE_DXGI_H
246}
247
248static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor)
249{
250 const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal;
251 int dpi = 0;
252
253 if (videodata->GetDpiForMonitor) {
254 UINT hdpi_uint, vdpi_uint;
255 if (videodata->GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) {
256 dpi = (int)hdpi_uint;
257 }
258 }
259 if (dpi == 0) {
260 // Window 8.0 and below: same DPI for all monitors
261 HDC hdc = GetDC(NULL);
262 if (hdc) {
263 dpi = GetDeviceCaps(hdc, LOGPIXELSX);
264 ReleaseDC(NULL, hdc);
265 }
266 }
267 if (dpi == 0) {
268 // Safe default
269 dpi = USER_DEFAULT_SCREEN_DPI;
270 }
271 return dpi / (float)USER_DEFAULT_SCREEN_DPI;
272}
273
274static bool WIN_GetDisplayMode(SDL_VideoDevice *_this, void *dxgi_output, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation)
275{
276 SDL_DisplayModeData *data;
277 DEVMODE devmode;
278
279 devmode.dmSize = sizeof(devmode);
280 devmode.dmDriverExtra = 0;
281 if (!EnumDisplaySettingsW(deviceName, index, &devmode)) {
282 return false;
283 }
284
285 data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data));
286 if (!data) {
287 return false;
288 }
289
290 SDL_zerop(mode);
291 mode->internal = data;
292 data->DeviceMode = devmode;
293
294 mode->format = SDL_PIXELFORMAT_UNKNOWN;
295 mode->w = data->DeviceMode.dmPelsWidth;
296 mode->h = data->DeviceMode.dmPelsHeight;
297 WIN_GetRefreshRate(dxgi_output, &data->DeviceMode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator);
298
299 // Fill in the mode information
300 WIN_UpdateDisplayMode(_this, deviceName, index, mode);
301
302 if (natural_orientation) {
303 *natural_orientation = WIN_GetNaturalOrientation(&devmode);
304 }
305 if (current_orientation) {
306 *current_orientation = WIN_GetDisplayOrientation(&devmode);
307 }
308
309 return true;
310}
311
312static char *WIN_GetDisplayNameVista(SDL_VideoData *videodata, const WCHAR *deviceName)
313{
314 DISPLAYCONFIG_PATH_INFO *paths = NULL;
315 DISPLAYCONFIG_MODE_INFO *modes = NULL;
316 char *result = NULL;
317 UINT32 pathCount = 0;
318 UINT32 modeCount = 0;
319 UINT32 i;
320 LONG rc;
321
322 if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) {
323 return NULL;
324 }
325
326 do {
327 rc = videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount);
328 if (rc != ERROR_SUCCESS) {
329 goto WIN_GetDisplayNameVista_failed;
330 }
331
332 SDL_free(paths);
333 SDL_free(modes);
334
335 paths = (DISPLAYCONFIG_PATH_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_PATH_INFO) * pathCount);
336 modes = (DISPLAYCONFIG_MODE_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_MODE_INFO) * modeCount);
337 if ((!paths) || (!modes)) {
338 goto WIN_GetDisplayNameVista_failed;
339 }
340
341 rc = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths, &modeCount, modes, 0);
342 } while (rc == ERROR_INSUFFICIENT_BUFFER);
343
344 if (rc == ERROR_SUCCESS) {
345 for (i = 0; i < pathCount; i++) {
346 DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
347 DISPLAYCONFIG_TARGET_DEVICE_NAME targetName;
348
349 SDL_zero(sourceName);
350 sourceName.header.adapterId = paths[i].targetInfo.adapterId;
351 sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
352 sourceName.header.size = sizeof(sourceName);
353 sourceName.header.id = paths[i].sourceInfo.id;
354 rc = videodata->DisplayConfigGetDeviceInfo(&sourceName.header);
355 if (rc != ERROR_SUCCESS) {
356 break;
357 } else if (SDL_wcscmp(deviceName, sourceName.viewGdiDeviceName) != 0) {
358 continue;
359 }
360
361 SDL_zero(targetName);
362 targetName.header.adapterId = paths[i].targetInfo.adapterId;
363 targetName.header.id = paths[i].targetInfo.id;
364 targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
365 targetName.header.size = sizeof(targetName);
366 rc = videodata->DisplayConfigGetDeviceInfo(&targetName.header);
367 if (rc == ERROR_SUCCESS) {
368 result = WIN_StringToUTF8W(targetName.monitorFriendlyDeviceName);
369 /* if we got an empty string, treat it as failure so we'll fallback
370 to getting the generic name. */
371 if (result && (*result == '\0')) {
372 SDL_free(result);
373 result = NULL;
374 }
375 }
376 break;
377 }
378 }
379
380 SDL_free(paths);
381 SDL_free(modes);
382 return result;
383
384WIN_GetDisplayNameVista_failed:
385 SDL_free(result);
386 SDL_free(paths);
387 SDL_free(modes);
388 return NULL;
389}
390
391#ifdef HAVE_DXGI1_6_H
392static bool WIN_GetMonitorDESC1(HMONITOR hMonitor, DXGI_OUTPUT_DESC1 *desc)
393{
394 typedef HRESULT (WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory);
395 PFN_CREATE_DXGI_FACTORY CreateDXGIFactoryFunc = NULL;
396 SDL_SharedObject *hDXGIMod = NULL;
397 bool found = false;
398
399 hDXGIMod = SDL_LoadObject("dxgi.dll");
400 if (hDXGIMod) {
401 CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(hDXGIMod, "CreateDXGIFactory1");
402 }
403 if (CreateDXGIFactoryFunc) {
404 static const GUID SDL_IID_IDXGIFactory1 = { 0x770aae78, 0xf26f, 0x4dba, { 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87 } };
405 static const GUID SDL_IID_IDXGIOutput6 = { 0x068346e8, 0xaaec, 0x4b84, { 0xad, 0xd7, 0x13, 0x7f, 0x51, 0x3f, 0x77, 0xa1 } };
406 IDXGIFactory1 *dxgiFactory;
407
408 if (SUCCEEDED(CreateDXGIFactoryFunc(&SDL_IID_IDXGIFactory1, (void **)&dxgiFactory))) {
409 IDXGIAdapter1 *dxgiAdapter;
410 UINT adapter = 0;
411 while (!found && SUCCEEDED(IDXGIFactory1_EnumAdapters1(dxgiFactory, adapter, &dxgiAdapter))) {
412 IDXGIOutput *dxgiOutput;
413 UINT output = 0;
414 while (!found && SUCCEEDED(IDXGIAdapter1_EnumOutputs(dxgiAdapter, output, &dxgiOutput))) {
415 IDXGIOutput6 *dxgiOutput6;
416 if (SUCCEEDED(IDXGIOutput_QueryInterface(dxgiOutput, &SDL_IID_IDXGIOutput6, (void **)&dxgiOutput6))) {
417 if (SUCCEEDED(IDXGIOutput6_GetDesc1(dxgiOutput6, desc))) {
418 if (desc->Monitor == hMonitor) {
419 found = true;
420 }
421 }
422 IDXGIOutput6_Release(dxgiOutput6);
423 }
424 IDXGIOutput_Release(dxgiOutput);
425 ++output;
426 }
427 IDXGIAdapter1_Release(dxgiAdapter);
428 ++adapter;
429 }
430 IDXGIFactory2_Release(dxgiFactory);
431 }
432 }
433 if (hDXGIMod) {
434 SDL_UnloadObject(hDXGIMod);
435 }
436 return found;
437}
438
439static bool WIN_GetMonitorPathInfo(SDL_VideoData *videodata, HMONITOR hMonitor, DISPLAYCONFIG_PATH_INFO *path_info)
440{
441 LONG result;
442 MONITORINFOEXW view_info;
443 UINT32 i;
444 UINT32 num_path_array_elements = 0;
445 UINT32 num_mode_info_array_elements = 0;
446 DISPLAYCONFIG_PATH_INFO *path_infos = NULL, *new_path_infos;
447 DISPLAYCONFIG_MODE_INFO *mode_infos = NULL, *new_mode_infos;
448 bool found = false;
449
450 if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) {
451 return false;
452 }
453
454 SDL_zero(view_info);
455 view_info.cbSize = sizeof(view_info);
456 if (!GetMonitorInfoW(hMonitor, (MONITORINFO *)&view_info)) {
457 goto done;
458 }
459
460 do {
461 if (videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, &num_mode_info_array_elements) != ERROR_SUCCESS) {
462 SDL_free(path_infos);
463 SDL_free(mode_infos);
464 return false;
465 }
466
467 new_path_infos = (DISPLAYCONFIG_PATH_INFO *)SDL_realloc(path_infos, num_path_array_elements * sizeof(*path_infos));
468 if (!new_path_infos) {
469 goto done;
470 }
471 path_infos = new_path_infos;
472
473 new_mode_infos = (DISPLAYCONFIG_MODE_INFO *)SDL_realloc(mode_infos, num_mode_info_array_elements * sizeof(*mode_infos));
474 if (!new_mode_infos) {
475 goto done;
476 }
477 mode_infos = new_mode_infos;
478
479 result = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, path_infos, &num_mode_info_array_elements, mode_infos, NULL);
480
481 } while (result == ERROR_INSUFFICIENT_BUFFER);
482
483 if (result == ERROR_SUCCESS) {
484 for (i = 0; i < num_path_array_elements; ++i) {
485 DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name;
486
487 SDL_zero(device_name);
488 device_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
489 device_name.header.size = sizeof(device_name);
490 device_name.header.adapterId = path_infos[i].sourceInfo.adapterId;
491 device_name.header.id = path_infos[i].sourceInfo.id;
492 if (videodata->DisplayConfigGetDeviceInfo(&device_name.header) == ERROR_SUCCESS) {
493 if (SDL_wcscmp(view_info.szDevice, device_name.viewGdiDeviceName) == 0) {
494 SDL_copyp(path_info, &path_infos[i]);
495 found = true;
496 break;
497 }
498 }
499 }
500 }
501
502done:
503 SDL_free(path_infos);
504 SDL_free(mode_infos);
505
506 return found;
507}
508
509static float WIN_GetSDRWhitePoint(SDL_VideoDevice *_this, HMONITOR hMonitor)
510{
511 DISPLAYCONFIG_PATH_INFO path_info;
512 SDL_VideoData *videodata = _this->internal;
513 float SDR_white_level = 1.0f;
514
515 if (WIN_GetMonitorPathInfo(videodata, hMonitor, &path_info)) {
516 /* workarounds for https://github.com/libsdl-org/SDL/issues/11193 */
517 struct SDL_DISPLAYCONFIG_SDR_WHITE_LEVEL {
518 DISPLAYCONFIG_DEVICE_INFO_HEADER header;
519 ULONG SDRWhiteLevel;
520 } white_level;
521 #define DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL 11
522
523 SDL_zero(white_level);
524 white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
525 white_level.header.size = sizeof(white_level);
526 white_level.header.adapterId = path_info.targetInfo.adapterId;
527 white_level.header.id = path_info.targetInfo.id;
528 // WIN_GetMonitorPathInfo() succeeded: DisplayConfigGetDeviceInfo is not NULL
529 if (videodata->DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS &&
530 white_level.SDRWhiteLevel > 0) {
531 SDR_white_level = (white_level.SDRWhiteLevel / 1000.0f);
532 }
533 }
534 return SDR_white_level;
535}
536
537static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_HDROutputProperties *HDR)
538{
539 DXGI_OUTPUT_DESC1 desc;
540
541 SDL_zerop(HDR);
542
543 if (WIN_GetMonitorDESC1(hMonitor, &desc)) {
544 if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
545 HDR->SDR_white_level = WIN_GetSDRWhitePoint(_this, hMonitor);
546 HDR->HDR_headroom = (desc.MaxLuminance / 80.0f) / HDR->SDR_white_level;
547 }
548 }
549}
550#endif // HAVE_DXGI1_6_H
551
552static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONITORINFOEXW *info, int *display_index)
553{
554 int i, index = *display_index;
555 SDL_VideoDisplay display;
556 SDL_DisplayData *displaydata;
557 void *dxgi_output = NULL;
558 SDL_DisplayMode mode;
559 SDL_DisplayOrientation natural_orientation;
560 SDL_DisplayOrientation current_orientation;
561 float content_scale = WIN_GetContentScale(_this, hMonitor);
562
563#ifdef DEBUG_MODES
564 SDL_Log("Display: %s", WIN_StringToUTF8W(info->szDevice));
565#endif
566
567 dxgi_output = WIN_GetDXGIOutput(_this, info->szDevice);
568 bool found = WIN_GetDisplayMode(_this, dxgi_output, hMonitor, info->szDevice, ENUM_CURRENT_SETTINGS, &mode, &natural_orientation, &current_orientation);
569 WIN_ReleaseDXGIOutput(dxgi_output);
570 if (!found) {
571 return;
572 }
573
574 // Prevent adding duplicate displays. Do this after we know the display is
575 // ready to be added to allow any displays that we can't fully query to be
576 // removed
577 for (i = 0; i < _this->num_displays; ++i) {
578 SDL_DisplayData *internal = _this->displays[i]->internal;
579 if (SDL_wcscmp(internal->DeviceName, info->szDevice) == 0) {
580 bool moved = (index != i);
581 bool changed_bounds = false;
582
583 if (internal->state != DisplayRemoved) {
584 // We've already enumerated this display, don't move it
585 return;
586 }
587
588 if (index >= _this->num_displays) {
589 // This should never happen due to the check above, but just in case...
590 return;
591 }
592
593 if (moved) {
594 SDL_VideoDisplay *tmp;
595
596 tmp = _this->displays[index];
597 _this->displays[index] = _this->displays[i];
598 _this->displays[i] = tmp;
599 i = index;
600 }
601
602 internal->MonitorHandle = hMonitor;
603 internal->state = DisplayUnchanged;
604
605 if (!_this->setting_display_mode) {
606 SDL_VideoDisplay *existing_display = _this->displays[i];
607 SDL_Rect bounds;
608
609 SDL_ResetFullscreenDisplayModes(existing_display);
610 SDL_SetDesktopDisplayMode(existing_display, &mode);
611 if (WIN_GetDisplayBounds(_this, existing_display, &bounds) &&
612 SDL_memcmp(&internal->bounds, &bounds, sizeof(bounds)) != 0) {
613 changed_bounds = true;
614 SDL_copyp(&internal->bounds, &bounds);
615 }
616 if (moved || changed_bounds) {
617 SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
618 }
619 SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_ORIENTATION, current_orientation, 0);
620 SDL_SetDisplayContentScale(existing_display, content_scale);
621#ifdef HAVE_DXGI1_6_H
622 SDL_HDROutputProperties HDR;
623 WIN_GetHDRProperties(_this, hMonitor, &HDR);
624 SDL_SetDisplayHDRProperties(existing_display, &HDR);
625#endif
626 }
627 goto done;
628 }
629 }
630
631 displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
632 if (!displaydata) {
633 return;
634 }
635 SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName));
636 displaydata->MonitorHandle = hMonitor;
637 displaydata->state = DisplayAdded;
638
639 SDL_zero(display);
640 display.name = WIN_GetDisplayNameVista(_this->internal, info->szDevice);
641 if (!display.name) {
642 DISPLAY_DEVICEW device;
643 SDL_zero(device);
644 device.cb = sizeof(device);
645 if (EnumDisplayDevicesW(info->szDevice, 0, &device, 0)) {
646 display.name = WIN_StringToUTF8W(device.DeviceString);
647 }
648 }
649
650 display.desktop_mode = mode;
651 display.natural_orientation = natural_orientation;
652 display.current_orientation = current_orientation;
653 display.content_scale = content_scale;
654 display.device = _this;
655 display.internal = displaydata;
656 WIN_GetDisplayBounds(_this, &display, &displaydata->bounds);
657#ifdef HAVE_DXGI1_6_H
658 WIN_GetHDRProperties(_this, hMonitor, &display.HDR);
659#endif
660 SDL_AddVideoDisplay(&display, false);
661 SDL_free(display.name);
662
663done:
664 *display_index += 1;
665}
666
667typedef struct _WIN_AddDisplaysData
668{
669 SDL_VideoDevice *video_device;
670 int display_index;
671 bool want_primary;
672} WIN_AddDisplaysData;
673
674static BOOL CALLBACK WIN_AddDisplaysCallback(HMONITOR hMonitor,
675 HDC hdcMonitor,
676 LPRECT lprcMonitor,
677 LPARAM dwData)
678{
679 WIN_AddDisplaysData *data = (WIN_AddDisplaysData *)dwData;
680 MONITORINFOEXW info;
681
682 SDL_zero(info);
683 info.cbSize = sizeof(info);
684
685 if (GetMonitorInfoW(hMonitor, (LPMONITORINFO)&info) != 0) {
686 const bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY);
687
688 if (is_primary == data->want_primary) {
689 WIN_AddDisplay(data->video_device, hMonitor, &info, &data->display_index);
690 }
691 }
692
693 // continue enumeration
694 return TRUE;
695}
696
697static void WIN_AddDisplays(SDL_VideoDevice *_this)
698{
699 WIN_AddDisplaysData callback_data;
700 callback_data.video_device = _this;
701 callback_data.display_index = 0;
702
703 callback_data.want_primary = true;
704 EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
705
706 callback_data.want_primary = false;
707 EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
708}
709
710bool WIN_InitModes(SDL_VideoDevice *_this)
711{
712 WIN_AddDisplays(_this);
713
714 if (_this->num_displays == 0) {
715 return SDL_SetError("No displays available");
716 }
717 return true;
718}
719
720bool WIN_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
721{
722 const SDL_DisplayData *data = display->internal;
723 MONITORINFO minfo;
724 BOOL rc;
725
726 SDL_zero(minfo);
727 minfo.cbSize = sizeof(MONITORINFO);
728 rc = GetMonitorInfo(data->MonitorHandle, &minfo);
729
730 if (!rc) {
731 return SDL_SetError("Couldn't find monitor data");
732 }
733
734 rect->x = minfo.rcMonitor.left;
735 rect->y = minfo.rcMonitor.top;
736 rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left;
737 rect->h = minfo.rcMonitor.bottom - minfo.rcMonitor.top;
738
739 return true;
740}
741
742bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
743{
744 const SDL_DisplayData *data = display->internal;
745 MONITORINFO minfo;
746 BOOL rc;
747
748 SDL_zero(minfo);
749 minfo.cbSize = sizeof(MONITORINFO);
750 rc = GetMonitorInfo(data->MonitorHandle, &minfo);
751
752 if (!rc) {
753 return SDL_SetError("Couldn't find monitor data");
754 }
755
756 rect->x = minfo.rcWork.left;
757 rect->y = minfo.rcWork.top;
758 rect->w = minfo.rcWork.right - minfo.rcWork.left;
759 rect->h = minfo.rcWork.bottom - minfo.rcWork.top;
760
761 return true;
762}
763
764bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
765{
766 SDL_DisplayData *data = display->internal;
767 void *dxgi_output;
768 DWORD i;
769 SDL_DisplayMode mode;
770
771 dxgi_output = WIN_GetDXGIOutput(_this, data->DeviceName);
772
773 for (i = 0;; ++i) {
774 if (!WIN_GetDisplayMode(_this, dxgi_output, data->MonitorHandle, data->DeviceName, i, &mode, NULL, NULL)) {
775 break;
776 }
777 if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) {
778 // We don't support palettized modes now
779 SDL_free(mode.internal);
780 continue;
781 }
782 if (mode.format != SDL_PIXELFORMAT_UNKNOWN) {
783 if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
784 SDL_free(mode.internal);
785 }
786 } else {
787 SDL_free(mode.internal);
788 }
789 }
790
791 WIN_ReleaseDXGIOutput(dxgi_output);
792
793 return true;
794}
795
796#ifdef DEBUG_MODES
797static void WIN_LogMonitor(SDL_VideoDevice *_this, HMONITOR mon)
798{
799 const SDL_VideoData *vid_data = (const SDL_VideoData *)_this->internal;
800 MONITORINFOEX minfo;
801 UINT xdpi = 0, ydpi = 0;
802 char *name_utf8;
803
804 if (vid_data->GetDpiForMonitor) {
805 vid_data->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);
806 }
807
808 SDL_zero(minfo);
809 minfo.cbSize = sizeof(minfo);
810 GetMonitorInfo(mon, (LPMONITORINFO)&minfo);
811
812 name_utf8 = WIN_StringToUTF8(minfo.szDevice);
813
814 SDL_Log("WIN_LogMonitor: monitor \"%s\": dpi: %d windows screen coordinates: %d, %d, %dx%d",
815 name_utf8,
816 xdpi,
817 minfo.rcMonitor.left,
818 minfo.rcMonitor.top,
819 minfo.rcMonitor.right - minfo.rcMonitor.left,
820 minfo.rcMonitor.bottom - minfo.rcMonitor.top);
821
822 SDL_free(name_utf8);
823}
824#endif
825
826bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
827{
828 SDL_DisplayData *displaydata = display->internal;
829 SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal;
830 LONG status;
831
832#ifdef DEBUG_MODES
833 SDL_Log("WIN_SetDisplayMode: monitor state before mode change:");
834 WIN_LogMonitor(_this, displaydata->MonitorHandle);
835#endif
836
837 /* High-DPI notes:
838
839 - ChangeDisplaySettingsEx always takes pixels.
840 - e.g. if the display is set to 2880x1800 with 200% scaling in Display Settings
841 - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height other than 2880x1800 will
842 change the monitor DPI to 96. (100% scaling)
843 - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height of 2880x1800 (or a NULL DEVMODE*) will
844 reset the monitor DPI to 192. (200% scaling)
845
846 NOTE: these are temporary changes in DPI, not modifications to the Control Panel setting. */
847 if (mode->internal == display->desktop_mode.internal) {
848#ifdef DEBUG_MODES
849 SDL_Log("WIN_SetDisplayMode: resetting to original resolution");
850#endif
851 status = ChangeDisplaySettingsExW(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL);
852 } else {
853#ifdef DEBUG_MODES
854 SDL_Log("WIN_SetDisplayMode: changing to %dx%d pixels", data->DeviceMode.dmPelsWidth, data->DeviceMode.dmPelsHeight);
855#endif
856 status = ChangeDisplaySettingsExW(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL);
857 }
858 if (status != DISP_CHANGE_SUCCESSFUL) {
859 const char *reason = "Unknown reason";
860 switch (status) {
861 case DISP_CHANGE_BADFLAGS:
862 reason = "DISP_CHANGE_BADFLAGS";
863 break;
864 case DISP_CHANGE_BADMODE:
865 reason = "DISP_CHANGE_BADMODE";
866 break;
867 case DISP_CHANGE_BADPARAM:
868 reason = "DISP_CHANGE_BADPARAM";
869 break;
870 case DISP_CHANGE_FAILED:
871 reason = "DISP_CHANGE_FAILED";
872 break;
873 }
874 return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason);
875 }
876
877#ifdef DEBUG_MODES
878 SDL_Log("WIN_SetDisplayMode: monitor state after mode change:");
879 WIN_LogMonitor(_this, displaydata->MonitorHandle);
880#endif
881
882 EnumDisplaySettingsW(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode);
883 WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode);
884 return true;
885}
886
887void WIN_RefreshDisplays(SDL_VideoDevice *_this)
888{
889 int i;
890
891 // Mark all displays as potentially invalid to detect
892 // entries that have actually been removed
893 for (i = 0; i < _this->num_displays; ++i) {
894 SDL_DisplayData *internal = _this->displays[i]->internal;
895 internal->state = DisplayRemoved;
896 }
897
898 // Enumerate displays to add any new ones and mark still
899 // connected entries as valid
900 WIN_AddDisplays(_this);
901
902 // Delete any entries still marked as invalid, iterate
903 // in reverse as each delete takes effect immediately
904 for (i = _this->num_displays - 1; i >= 0; --i) {
905 SDL_VideoDisplay *display = _this->displays[i];
906 SDL_DisplayData *internal = display->internal;
907 if (internal->state == DisplayRemoved) {
908 SDL_DelVideoDisplay(display->id, true);
909 }
910 }
911
912 // Send events for any newly added displays
913 for (i = 0; i < _this->num_displays; ++i) {
914 SDL_VideoDisplay *display = _this->displays[i];
915 SDL_DisplayData *internal = display->internal;
916 if (internal->state == DisplayAdded) {
917 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ADDED, 0, 0);
918 }
919 }
920}
921
922void WIN_QuitModes(SDL_VideoDevice *_this)
923{
924 // All fullscreen windows should have restored modes by now
925}
926
927#endif // SDL_VIDEO_DRIVER_WINDOWS