Direct3D12で解像度とリフレッシュレートを設定する

ウィンドウモードの場合は解像度はウィンドウサイズを変更することで設定できます。(もちろん、RTVは作り直します。) しかし、ウィンドウ単位でリフレッシュレートは設定できません。

リフレッシュレートを変更するためには、フルスクリーンで動いている必要があります。 前回の記事で、フルスクリーンへの切り替え方法を書きましたので、今回はその続きになります。

まず、ChatGPT先生に、リフレッシュレートを変更する方法を聞いたところ、ResizeTargetというAPIを教えてくれました。

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

しかし、この方法は、実際のディスプレイの設定を変更するものではなく、SwapChainの表示モードを変更するだけのようです。 Windowの設定なども変更されませんので、imguiの設定も反映されず、うまく動きませんでした。

そのため、別の方法を調べたところ、実際のディスプレイの設定を変更する方法として、ChangeDisplaySettingsExというAPIがあるようです。

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

これは、ディスプレイの設定のプリセットが決まっていて、そのうちの一つを選んで設定するというものです。 解像度とリフレッシュレートだけでなく、中央にドットバイドットで表示するとか、画面いっぱいに拡大して表示するとか、細かい設定も含まれています。

処理の流れを以下に示します。

まず、マルチディスプレイ対応として、ウィンドウが表示されているディスプレイ名を取得します。

    std::string GetMonitorDeviceNameFromWindow(HWND hWnd) {
        // ウィンドウが表示されているモニターのハンドルを取得
        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;
    }

次に、ディスプレイの表示可能なプリセットを取得します。

    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);
        }
    }

最後に、プリセットの中から、指定したインデックスのものをディスプレイに設定します。 このAPIはフルスクリーンで動いているときのみ使うことができます。

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

            DEVMODE& d = modeVec[mode_index];

            // ウィンドウのサイズを変更
            SetWindowLong(hWindow, GWL_STYLE, WS_POPUP | WS_VISIBLE);
            SetWindowPos(hWindow, HWND_TOP, 0, 0, d.dmPelsWidth, d.dmPelsHeight, SWP_NOMOVE | SWP_FRAMECHANGED);

            // ディスプレイの設定を変更
            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;
    }

まだ、バグが残っている可能性もありますが、処理の流れはこんな感じです。