0
\$\begingroup\$

I have code set up that finds the native resolution of a monitor and uses that to determine the resolution for (DirectX exclusive) fullscreen mode, using SetupAPI:

HRESULT Window::Output::extractNativeResolution() {
    DISPLAY_DEVICEW dd = { 0 };
    CString DeviceID;
    HDEVINFO devInfo = NULL;
    HRESULT hr;
    DWORD err;
    BOOL bresult;

    static const GUID GUID_CLASS_MONITOR = {
        0x4d36e96e,
        0xe325,
        0x11ce,
        0xbf,
        0xc1,
        0x08,
        0x00,
        0x2b,
        0xe1,
        0x03,
        0x18
    };

    dd.cb = sizeof(dd);

    if (EnumDisplayDevicesW(desc.DeviceName, 0, &dd, 0) == FALSE) {
        goto failure;
    }

    DeviceID.Format(L"%s", dd.DeviceID);
    DeviceID = DeviceID.Mid(8, DeviceID.Find(L"\\", 9) - 8);

    devInfo = SetupDiGetClassDevsExW (
        &GUID_CLASS_MONITOR,                    //const GUID* ClassGuid
        NULL,                                   //PCWSTR Enumerator
        NULL,                                   //HWND hWndParent
        DIGCF_PRESENT,                          //DWORD Flags
        NULL,                                   //HDEVINFO DeviceInfoSet
        NULL,                                   //PCWSTR MachineName
        NULL                                    //PVOID Reserved
    );

    if (devInfo == NULL) {
        err = GetLastError();
        CHECK_ERR();
    }

    for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i) {
        SP_DEVINFO_DATA devInfoData;
        memset(&devInfoData, 0, sizeof(devInfoData));
        devInfoData.cbSize = sizeof(devInfoData);

        if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData)) {
            bool bRes = false;

            HKEY hDevRegKey = SetupDiOpenDevRegKey (
                devInfo,                        //HDEVINFO DeviceInfoSet
                &devInfoData,                   //PSP_DEVINFO_DATA DeviceInfoData
                DICS_FLAG_GLOBAL,               //DWORD Scope
                0,                              //DWORD HwProfile
                DIREG_DEV,                      //DWORD KeyType
                KEY_READ                        //REGSAM samDesired
            );

            if (!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE)) {
                continue;
            }

            {
                DWORD dwType, AcutalValueNameLength = 128;
                TCHAR valueName[128];

                BYTE EDIDdata[1024];
                DWORD edidsize = sizeof(EDIDdata);

                for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i) {
                    retValue = RegEnumValueW (
                        hDevRegKey,             //HKEY hKey
                        i,                      //DWORD dwIndex
                        &valueName[0],          //LPWSTR lpValueName
                        &AcutalValueNameLength, //LPDWORD lpcchValueName
                        NULL,                   //LPDWORD lpReserved
                        &dwType,                //LPDWORD lpType
                        EDIDdata,               //LPBYTE lpData
                        &edidsize               //LPDWORD lpcbData
                    );

                    if (retValue != ERROR_SUCCESS || 0 != wcscmp(valueName, L"EDID")) {
                        continue;
                    }

                    nativeWidth = ((EDIDdata[58] >> 4) << 8) | EDIDdata[56];
                    nativeHeight = ((EDIDdata[61] >> 4) << 8) | EDIDdata[59];

                    bRes = true;
                }
            }

            RegCloseKey(hDevRegKey);

            if (bRes) {
                break;
            }
        }
    }

    bresult = SetupDiDestroyDeviceInfoList(devInfo);
    CHECK_BRESULT();

    if (nativeWidth == 0 || nativeHeight == 0) {
        failure:

        nativeWidth = desktopMode.dmPelsWidth;
        nativeHeight = desktopMode.dmPelsHeight;
    }

    return S_OK;
}

(Adapted from here)

Basically, this code digs into the monitor's EDID information to find the primary resolution. Any time the user enters exclusive fullscreen, the back buffer will be set to this resolution, and an intermediate buffer will be used for cropping and scaling based on the user's graphics settings. I've decided to use this method instead of relying on DXGI because the implementation is much simpler (DXGI is... weird).

The problem with this code, though, is that it doesn't work on all monitors. Some don't follow the standard, or opt to provide resolution information in a different format. I'm thinking about improving this to work on all (or most) monitors, but is something like this really necessary for a game? Is it safe to assume that most users' desktop resolution is the same as their native resolution? If not, does it really matter?

\$\endgroup\$
3
\$\begingroup\$

Is it safe to assume that most users' desktop resolution is the same as their native resolution

You're asking the wrong question. Is it a "safe" assumption? Yes.

The real question: is it safe to actually try to use the "native" resolution without the user explicitly telling you to do so? Absolutely not!

If not, does it really matter?

What matters is making a game that works the first time the user opens it.

The desktop resolution is the one that the user has selected which we know works. The "native" resolution may in fact not work at all for any number of reasons (perhaps the monitor misbehaves at native resolution, or the user might play primarily from an attached mirror-mode projector that can't support the monitor's full resolution, or their GPU may have trouble driving their monitor's native resolution at a sensible refresh rate, or so on).

Always default your fullscreen mode to the user's desktop resolution. Always. Yes, this also means you shouldn't default to a smaller resolution like so many other games do, as that can also break in many of the same ways outlined above. If you want to be even safer, default to windowed mode (and make sure the window actually fits on the user's desktop) and not fullscreen.

Default to the option that is most certainly going to work, not the most flashy. If the user is for some reason running the desktop at a lower resolution than maximum, they have a reason. Respect the user, their system, their needs, and their choice.

\$\endgroup\$
  • \$\begingroup\$ This is kind of what I figured. I guess I won't even bother with EDID, then. \$\endgroup\$ – NmdMystery May 31 '15 at 3:04
  • 1
    \$\begingroup\$ This is indeed recommended best practice. See TR 1.5. \$\endgroup\$ – Chuck Walbourn Jun 1 '15 at 0:22
2
\$\begingroup\$

EDIT: The DPI issue was my fault for not reading all of the documentation. Calling SetProcessDPIAware() before anything else renders this point moot.

EDIT2: EnumDisplaySettings() can get the screen resolution regardless of whether or not the application is registered as "DPI aware".

While it probably is safe to assume that the users set resolution is the same as their desktop resolution, this doesn't mean that the values returned from those functions are necessarily accurate. Case in point, using the standard Windows API, I get 1280x720 for these calls, whereas the actual resolution of my screen is 1920x1080. Due to DPI scaling, however, Windows returns the modified resolution. This can be fixed with a call to SetProcessDPIAware(), or by using EnumDisplaySettings() to get the current screen's resolution.

#include "stdafx.h"
#include "wtypes.h"

#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{
    int w, h;

    // Works regardless of DPI setting
    DEVMODE dev;
    EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &dev);
    w = dev.dmPelsWidth;
    h = dev.dmPelsHeight;
    std::cout << w << ", " << h << std::endl;

    // Fix DPI issue
    SetProcessDPIAware();

    RECT desktop;
    const HWND hDesktop = GetDesktopWindow();
    GetWindowRect(hDesktop, &desktop);
    w = desktop.right;
    h = desktop.bottom;

    std::cout << w << ", " << h << std::endl;

    w = GetSystemMetrics(SM_CXFULLSCREEN);
    h = GetSystemMetrics(SM_CYFULLSCREEN);

    std::cout << w << ", " << h << std::endl;

    w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    h = GetSystemMetrics(SM_CYVIRTUALSCREEN);

    std::cout << w << ", " << h << std::endl;

    system("pause");
    return 0;
}

Not sure if this helps.

\$\endgroup\$
  • \$\begingroup\$ I think this raises even more questions. What happens if you query via DEVMODE/ENUM_CURRENT_SETTINGS? \$\endgroup\$ – NmdMystery May 31 '15 at 0:44
  • \$\begingroup\$ So the DPI issue can be fixed by calling SetProcessDPIAware(), I edited my response above. \$\endgroup\$ – Clayton May 31 '15 at 1:04
  • \$\begingroup\$ EnumDisplaySettings() with ENUM_CURRENT_SETTINGS appears to work without the DPI call. \$\endgroup\$ – Clayton May 31 '15 at 1:05
  • \$\begingroup\$ Well, given that, do you share the opinion that the desktop resolution is adequate? Do users often set their desktop resolution to anything other than native? If they do, should I assume they have an identical preference when gaming? \$\endgroup\$ – NmdMystery May 31 '15 at 1:22
  • \$\begingroup\$ I do. If anything, a user would prefer to reduce resolution in-game for performance reasons. \$\endgroup\$ – Clayton May 31 '15 at 3:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.