Set resolution and refresh rate in Direct3D12

In windowed mode, the resolution can be set by changing the window size. (Of course, the RTV will have to be rebuilt.) However, the refresh rate cannot be set on a per-window basis.

To change the refresh rate, you must be running in full screen. In my previous article, I wrote about how to switch to full screen, so this will be a continuation of that article.

First, I asked Dr. ChatGPT how to change the refresh rate, and he told me about an API called ResizeTarget.

https://learn.microsoft.com/ja-jp/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-resizetarget

However, this method does not change the actual display settings, but only the display mode of SwapChain. It does not change the Window settings, etc., so the imgui settings are not reflected, and it did not work.

Therefore, I looked for another method and found that there is an API called ChangeDisplaySettingsEx to change the actual display settings.

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-changedisplaysettingsexa

This has a set of presets of display settings, and you pick one of them to set. This includes not only the resolution and refresh rate, but also detailed settings such as centered dot-by-dot, or expanded to fill the screen.

The process flow is shown below.

First, the name of the display on which the window is displayed is obtained to support multiple displays.

    std::string GetMonitorDeviceNameFromWindow(HWND hWnd) {
        // Obtain the handle of the monitor on which the window is displayed
        HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);

        std::string ret;
        if (hMonitor != NULL) {
            MONITORINFOEX monitorInfo;
            monitorInfo.cbSize = sizeof(MONITORINFOEX);

            if (GetMonitorInfo(hMonitor, &monitorInfo)) {
                ret = monitorInfo.szDevice;

            }
        }
        return ret;
    }

Next, obtain the displayable presets for the display.

    void GetDisplayMode(std::string& name)
    {
        // DisplaySettings
        modeVec.clear();
        DEVMODE devMode = {};
        devMode.dmSize = sizeof(devMode);
        int modeNum = 0;
        const char* arg_name = name.length() > 0 ? name.c_str() : NULL;
        while (EnumDisplaySettingsEx(arg_name, modeNum++, &devMode, 0)) {
            modeVec.push_back(devMode);
            ZeroMemory(&devMode, sizeof(devMode));
            devMode.dmSize = sizeof(devMode);
        }
    }

Finally, the display is set to one of the presets with the specified index. This API can only be used when running in full screen.

    bool SetDisplay(int mode_index, std::string &name)
    {
        if (fullScreen)
        {
            WaitForFence();

            DEVMODE& d = modeVec[mode_index];

            // Resize window
            SetWindowLong(hWindow, GWL_STYLE, WS_POPUP | WS_VISIBLE);
            SetWindowPos(hWindow, HWND_TOP, 0, 0, d.dmPelsWidth, d.dmPelsHeight, SWP_NOMOVE | SWP_FRAMECHANGED);

            // Change display settings
            if (name.length() > 0)
            {
                LONG result = ChangeDisplaySettingsEx(name.c_str(), &d, NULL, CDS_FULLSCREEN, NULL);
                return (result == DISP_CHANGE_SUCCESSFUL);
            }
            else
            {
                LONG result = ChangeDisplaySettings(&d, CDS_FULLSCREEN);
                return (result == DISP_CHANGE_SUCCESSFUL);
            }
        }
        return false;
    }

There may still be some bugs, but the process flow looks like this.