SDL_GetDesktopDisplayMode resolution reported in Windows 10 when using app scaling

Hi all. Please bare with me here…

My monitor is set to 1920 x 1080. Fonts are too small to read. The only way I can find to enlarge everything so it’s big enough to see without my nose pressed against the display is to use Windows 10’s scale open and set it to 150%. It works as expected and I can now read text and see icons clearly.

Now here is a very weird thing. When this scale option is used, SDL_GetDesktopDisplayMode() is giving me 1280 x 720, which is a lie. I can see the display is set to native 1920 x 1080, and I can tell when it’s actually 1280 x 720 because everything’s more blurry. If I turn the scale down to 125%, then the reported desktop resolution is 1536 x 864. It seems as though Windows 10 is lying to apps to get them to scale fonts and everything. So, SDL is working correctly here. If I create a window of size 1280 x 720 when my display is set to 1920 x 1080, it is stretched to the size of the screen. This must have something to do with virtual desktop size.

My question is this: How can I bypass this behaviour and get the actual current screen mode, and not the Windows 10 desktop size after it’s lying to scale everything? The answer isn’t SDL_GetCurrentDisplayMode(), as that reports exactly the same.

Yeah, this is Windows’s “DPI Virtualization” feature. The idea is that you can run legacy applications on a high DPI monitor (say 200% scaled), and they’ll be blurry but the correct size instead of tiny and unreadable.

There are different ways to tell Windows that your app is DPI aware, which makes all of the Windows API’s take/return real pixels instead of the scaled coordinates. One is SetProcessDpiAwareness, and you can also add it to the application’s manifest. Here’s one article on msdn: https://msdn.microsoft.com/en-ca/magazine/dn574798.aspx

I’m working on a patch to support it properly inside SDL (it needs changes in SDL to handle multi-monitor setups with different DPI’s) that should be ready to share in a few days, hopefully.

Spot on Eric, thank-you.

Do you know what I need to include within SDL to get SetProcessDpiAwareness to work? I’m having trouble getting it working, do you know what I need to #include to make a quick #ifdef _WIN32 fix?. There’s also a SetProcessDPIAware which would work better?

Thanks.

I think you have to set WINVER to the Windows 8.1 constant to call SetProcessDpiAwareness directly, but then the app will be windows 8.1+ only.

Here’s a snippet to load them at runtime:

typedef enum PROCESS_DPI_AWARENESS {
    PROCESS_DPI_UNAWARE = 0,
    PROCESS_SYSTEM_DPI_AWARE = 1,
    PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;

void* userDLL;
BOOL(WINAPI *SetProcessDPIAware)(void); // Vista and later
void* shcoreDLL;
HRESULT(WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness); // Windows 8.1 and later

userDLL = SDL_LoadObject("USER32.DLL");
if (userDLL) {
    SetProcessDPIAware = (BOOL(WINAPI *)(void)) SDL_LoadFunction(userDLL, "SetProcessDPIAware");
}

shcoreDLL = SDL_LoadObject("SHCORE.DLL");
if (shcoreDLL) {
    SetProcessDpiAwareness = (HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS)) SDL_LoadFunction(shcoreDLL, "SetProcessDpiAwareness");
}

if (SetProcessDpiAwareness) {
    /* Try Windows 8.1+ version */
    HRESULT result = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
    SDL_Log("called SetProcessDpiAwareness: %d", (result == S_OK) ? 1 : 0);
}
else if (SetProcessDPIAware) {
    /* Try Vista - Windows 8 version.
    This has a constant scale factor for all monitors.
    */
    BOOL success = SetProcessDPIAware();
    SDL_Log("called SetProcessDPIAware: %d", (int)success);
}
2 Likes