Browse Source

Use DXGI to get precise display mode refresh rate values

Fixes https://github.com/libsdl-org/SDL/issues/10185
Sam Lantinga 9 months ago
parent
commit
00ab330207

+ 82 - 5
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, &current_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, &current_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;
 }
 

+ 29 - 49
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 <dxgi.h>
-
-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;

+ 11 - 0
src/video/windows/SDL_windowsvideo.h

@@ -27,6 +27,12 @@
 
 #include "../SDL_sysvideo.h"
 
+#ifdef HAVE_DXGI_H
+#define CINTERFACE
+#define COBJMACROS
+#include <dxgi.h>
+#endif
+
 #if defined(_MSC_VER) && (_MSC_VER >= 1500) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 #include <msctf.h>
 #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;