From 9a6551aa771501ad6b55dd15ff23c4d0884135a4 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 12 Jul 2024 19:28:09 -0700 Subject: [PATCH] Use DXGI to get precise display mode refresh rate values Fixes https://github.com/libsdl-org/SDL/issues/10185 --- src/video/windows/SDL_windowsmodes.c | 87 ++++++++++++++++++++++++++-- src/video/windows/SDL_windowsvideo.c | 78 ++++++++++--------------- src/video/windows/SDL_windowsvideo.h | 11 ++++ 3 files changed, 122 insertions(+), 54 deletions(-) diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c index a221779e9696c..7d7921010d9e9 100644 --- a/src/video/windows/SDL_windowsmodes.c +++ b/src/video/windows/SDL_windowsmodes.c @@ -111,6 +111,53 @@ static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DW } } +static void *WIN_GetDXGIOutput(SDL_VideoDevice *_this, const WCHAR *DeviceName) +{ + void *retval = NULL; + +#ifdef HAVE_DXGI_H + const SDL_VideoData *videodata = (const SDL_VideoData *)_this->driverdata; + int nAdapter, nOutput; + IDXGIAdapter *pDXGIAdapter; + IDXGIOutput *pDXGIOutput; + + if (!videodata->pDXGIFactory) { + return NULL; + } + + nAdapter = 0; + while (!retval && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) { + nOutput = 0; + while (!retval && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) { + DXGI_OUTPUT_DESC outputDesc; + if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) { + if (SDL_wcscmp(outputDesc.DeviceName, DeviceName) == 0) { + retval = pDXGIOutput; + } + } + if (pDXGIOutput != retval) { + IDXGIOutput_Release(pDXGIOutput); + } + nOutput++; + } + IDXGIAdapter_Release(pDXGIAdapter); + nAdapter++; + } +#endif + return retval; +} + +static void WIN_ReleaseDXGIOutput(void *dxgi_output) +{ +#ifdef HAVE_DXGI_H + IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output; + + if (pDXGIOutput) { + IDXGIOutput_Release(pDXGIOutput); + } +#endif +} + static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode) { int width = mode->dmPelsWidth; @@ -161,7 +208,7 @@ static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode) } } -static void WIN_GetRefreshRate(DEVMODE *mode, int *numerator, int *denominator) +static void WIN_GetRefreshRate(void *dxgi_output, DEVMODE *mode, int *numerator, int *denominator) { /* We're not currently using DXGI to query display modes, so fake NTSC timings */ switch (mode->dmDisplayFrequency) { @@ -176,6 +223,26 @@ static void WIN_GetRefreshRate(DEVMODE *mode, int *numerator, int *denominator) *denominator = 1; break; } + +#ifdef HAVE_DXGI_H + if (dxgi_output) { + IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output; + DXGI_MODE_DESC modeToMatch; + DXGI_MODE_DESC closestMatch; + + SDL_zero(modeToMatch); + modeToMatch.Width = mode->dmPelsWidth; + modeToMatch.Height = mode->dmPelsHeight; + modeToMatch.RefreshRate.Numerator = *numerator; + modeToMatch.RefreshRate.Denominator = *denominator; + modeToMatch.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + + if (SUCCEEDED(IDXGIOutput_FindClosestMatchingMode(pDXGIOutput, &modeToMatch, &closestMatch, NULL))) { + *numerator = closestMatch.RefreshRate.Numerator; + *denominator = closestMatch.RefreshRate.Denominator; + } + } +#endif // HAVE_DXGI_H } static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor) @@ -204,7 +271,7 @@ static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor) return dpi / (float)USER_DEFAULT_SCREEN_DPI; } -static SDL_bool WIN_GetDisplayMode(SDL_VideoDevice *_this, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation) +static SDL_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) { SDL_DisplayModeData *data; DEVMODE devmode; @@ -227,7 +294,7 @@ static SDL_bool WIN_GetDisplayMode(SDL_VideoDevice *_this, HMONITOR hMonitor, LP mode->format = SDL_PIXELFORMAT_UNKNOWN; mode->w = data->DeviceMode.dmPelsWidth; mode->h = data->DeviceMode.dmPelsHeight; - WIN_GetRefreshRate(&data->DeviceMode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator); + WIN_GetRefreshRate(dxgi_output, &data->DeviceMode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator); /* Fill in the mode information */ WIN_UpdateDisplayMode(_this, deviceName, index, mode); @@ -486,6 +553,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI int i, index = *display_index; SDL_VideoDisplay display; SDL_DisplayData *displaydata; + void *dxgi_output = NULL; SDL_DisplayMode mode; SDL_DisplayOrientation natural_orientation; SDL_DisplayOrientation current_orientation; @@ -495,7 +563,10 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI SDL_Log("Display: %s\n", WIN_StringToUTF8W(info->szDevice)); #endif - if (!WIN_GetDisplayMode(_this, hMonitor, info->szDevice, ENUM_CURRENT_SETTINGS, &mode, &natural_orientation, ¤t_orientation)) { + dxgi_output = WIN_GetDXGIOutput(_this, info->szDevice); + SDL_bool found = WIN_GetDisplayMode(_this, dxgi_output, hMonitor, info->szDevice, ENUM_CURRENT_SETTINGS, &mode, &natural_orientation, ¤t_orientation); + WIN_ReleaseDXGIOutput(dxgi_output); + if (!found) { return; } @@ -692,11 +763,14 @@ int WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display int WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) { SDL_DisplayData *data = display->driverdata; + void *dxgi_output; DWORD i; SDL_DisplayMode mode; + dxgi_output = WIN_GetDXGIOutput(_this, data->DeviceName); + for (i = 0;; ++i) { - if (!WIN_GetDisplayMode(_this, data->MonitorHandle, data->DeviceName, i, &mode, NULL, NULL)) { + if (!WIN_GetDisplayMode(_this, dxgi_output, data->MonitorHandle, data->DeviceName, i, &mode, NULL, NULL)) { break; } if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) { @@ -712,6 +786,9 @@ int WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) SDL_free(mode.driverdata); } } + + WIN_ReleaseDXGIOutput(dxgi_output); + return 0; } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 757a3f6bd2b4a..1074d9b629930 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -103,6 +103,14 @@ static void WIN_DeleteDevice(SDL_VideoDevice *device) if (data->shcoreDLL) { SDL_UnloadObject(data->shcoreDLL); } +#endif +#ifndef HAVE_DXGI_H + if (data->pDXGIFactory) { + IDXGIFactory_Release(pDXGIFactory); + } + if (data->dxgiDLL) { + SDL_UnloadObject(pDXGIDLL); + } #endif if (device->wakeup_lock) { SDL_DestroyMutex(device->wakeup_lock); @@ -170,6 +178,22 @@ static SDL_VideoDevice *WIN_CreateDevice(void) } #endif /* #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) */ +#ifdef HAVE_DXGI_H + data->dxgiDLL = SDL_LoadObject("DXGI.DLL"); + if (data->dxgiDLL) { + /* *INDENT-OFF* */ /* clang-format off */ + typedef HRESULT (WINAPI *CreateDXGI_t)(REFIID riid, void **ppFactory); + /* *INDENT-ON* */ /* clang-format on */ + CreateDXGI_t CreateDXGI; + + CreateDXGI = (CreateDXGI_t)SDL_LoadFunction(data->dxgiDLL, "CreateDXGIFactory"); + if (CreateDXGI) { + GUID dxgiGUID = { 0x7b7166ec, 0x21c7, 0x44ae, { 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 } }; + CreateDXGI(&dxgiGUID, (void **)&data->pDXGIFactory); + } + } +#endif + /* Set the function pointers */ device->VideoInit = WIN_VideoInit; device->VideoQuit = WIN_VideoQuit; @@ -605,41 +629,6 @@ int SDL_Direct3D9GetAdapterIndex(SDL_DisplayID displayID) } #endif /* !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) */ -#ifdef HAVE_DXGI_H -#define CINTERFACE -#define COBJMACROS -#include - -static SDL_bool DXGI_LoadDLL(void **pDXGIDLL, IDXGIFactory **pDXGIFactory) -{ - *pDXGIDLL = SDL_LoadObject("DXGI.DLL"); - if (*pDXGIDLL) { - /* *INDENT-OFF* */ /* clang-format off */ - typedef HRESULT (WINAPI *CreateDXGI_t)(REFIID riid, void **ppFactory); - /* *INDENT-ON* */ /* clang-format on */ - CreateDXGI_t CreateDXGI; - - CreateDXGI = (CreateDXGI_t)SDL_LoadFunction(*pDXGIDLL, "CreateDXGIFactory"); - if (CreateDXGI) { - GUID dxgiGUID = { 0x7b7166ec, 0x21c7, 0x44ae, { 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 } }; - if (!SUCCEEDED(CreateDXGI(&dxgiGUID, (void **)pDXGIFactory))) { - *pDXGIFactory = NULL; - } - } - if (!*pDXGIFactory) { - SDL_UnloadObject(*pDXGIDLL); - *pDXGIDLL = NULL; - return SDL_FALSE; - } - - return SDL_TRUE; - } else { - *pDXGIFactory = NULL; - return SDL_FALSE; - } -} -#endif - SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int *outputIndex) { #ifndef HAVE_DXGI_H @@ -652,11 +641,10 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int * SDL_SetError("SDL was compiled without DXGI support due to missing dxgi.h header"); return SDL_FALSE; #else + const SDL_VideoDevice *videodevice = SDL_GetVideoDevice(); + const SDL_VideoData *videodata = videodevice ? videodevice->driverdata : NULL; SDL_DisplayData *pData = SDL_GetDisplayDriverData(displayID); - void *pDXGIDLL; - char *displayName; int nAdapter, nOutput; - IDXGIFactory *pDXGIFactory = NULL; IDXGIAdapter *pDXGIAdapter; IDXGIOutput *pDXGIOutput; @@ -678,24 +666,21 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int * return SDL_FALSE; } - if (!DXGI_LoadDLL(&pDXGIDLL, &pDXGIFactory)) { + if (!videodata || !videodata->pDXGIFactory) { SDL_SetError("Unable to create DXGI interface"); return SDL_FALSE; } - displayName = WIN_StringToUTF8W(pData->DeviceName); nAdapter = 0; - while (*adapterIndex == -1 && SUCCEEDED(IDXGIFactory_EnumAdapters(pDXGIFactory, nAdapter, &pDXGIAdapter))) { + while (*adapterIndex == -1 && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) { nOutput = 0; while (*adapterIndex == -1 && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) { DXGI_OUTPUT_DESC outputDesc; if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) { - char *outputName = WIN_StringToUTF8W(outputDesc.DeviceName); - if (SDL_strcmp(outputName, displayName) == 0) { + if (SDL_wcscmp(outputDesc.DeviceName, pData->DeviceName) == 0) { *adapterIndex = nAdapter; *outputIndex = nOutput; } - SDL_free(outputName); } IDXGIOutput_Release(pDXGIOutput); nOutput++; @@ -703,11 +688,6 @@ SDL_bool SDL_DXGIGetOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int * IDXGIAdapter_Release(pDXGIAdapter); nAdapter++; } - SDL_free(displayName); - - /* free up the DXGI factory */ - IDXGIFactory_Release(pDXGIFactory); - SDL_UnloadObject(pDXGIDLL); if (*adapterIndex == -1) { return SDL_FALSE; diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 44b3050ca48e6..c4ceca66af1f0 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -27,6 +27,12 @@ #include "../SDL_sysvideo.h" +#ifdef HAVE_DXGI_H +#define CINTERFACE +#define COBJMACROS +#include +#endif + #if defined(_MSC_VER) && (_MSC_VER >= 1500) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) #include #else @@ -406,6 +412,11 @@ struct SDL_VideoData /* *INDENT-ON* */ /* clang-format on */ #endif /*!defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)*/ +#ifdef HAVE_DXGI_H + void *dxgiDLL; + IDXGIFactory *pDXGIFactory; +#endif + SDL_bool cleared; BYTE *rawinput;