Search code examples
c++winapidirectxdirect3d12

Getting display's refresh rate on D3D12


I am porting my code to D3D12 from D3D11 and I'm trying to obtain display's refresh rate on D3D12. I use the refresh rate for precise animation timing (this is a hard requirement). This code works on D3D11:

HRESULT GetRefreshRate(IUnknown* device, IDXGISwapChain* swapChain, double* outRefreshRate)
{
    Microsoft::WRL::ComPtr<IDXGIOutput> dxgiOutput;
    HRESULT hr = swapChain->GetContainingOutput(&dxgiOutput);
    if (FAILED(hr))
        return hr;

    Microsoft::WRL::ComPtr<IDXGIOutput1> dxgiOutput1;
    hr = dxgiOutput.As(&dxgiOutput1);
    if (FAILED(hr))
        return hr;

    DXGI_MODE_DESC1 emptyMode = {};
    DXGI_MODE_DESC1 modeDescription;
    hr = dxgiOutput1->FindClosestMatchingMode1(&emptyMode, &modeDescription, device);

    if (SUCCEEDED(hr))
        *outRefreshRate = (double)modeDescription.RefreshRate.Numerator / (double)modeDescription.RefreshRate.Denominator;

    return hr;
}

Unfortunately, ID3D12Device does not implement IDXGIDevice interface, and FindClosestMatchingMode1 therefore fails with this error:

DXGI ERROR: IDXGIOutput::FindClosestMatchingMode: pConcernedDevice doesn't support the IDXGIDevice interface [ MISCELLANEOUS ERROR #69: ]

Is there a way to obtain IDXGIDevice when using D3D12? Alternatively, how do I determine display's refresh rate on D3D12?

I know about EnumDisplaySettings however it returns an integer and therefore lacks precision, causing drift in animations. I also found DwmGetCompositionTimingInfo, however, it seems to only support getting info for the main monitor.

I also need a solution that would work on both traditional Win32 and UWP applications. I am open to having to use two code paths for different application models if needed.


Solution

  • We can get the refresh rate using CCD api, here is the code for your reference:

    HRESULT GetRefreshRate(IDXGISwapChain* swapChain, double* outRefreshRate)
    {
           ComPtr<IDXGIOutput> dxgiOutput;
           HRESULT hr = swapChain->GetContainingOutput(&dxgiOutput);
           // if swap chain get failed to get DXGIoutput then follow the below link get the details from remarks section
           //https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-getcontainingoutput
           if (SUCCEEDED(hr))
           {
    
              ComPtr<IDXGIOutput1> dxgiOutput1;
              hr = dxgiOutput.As(&dxgiOutput1);
              if (SUCCEEDED(hr))
              {
                     // get the descriptor for current output
                     // from which associated mornitor will be fetched
                     DXGI_OUTPUT_DESC outputDes{};
                     hr = dxgiOutput->GetDesc(&outputDes);
                     if (SUCCEEDED(hr))
                     {
    
                            MONITORINFOEXW info;
                            info.cbSize = sizeof(info);
                            // get the associated monitor info
                            if (GetMonitorInfoW(outputDes.Monitor, &info) != 0)
                            {
                                   // using the CCD get the associated path and display configuration
                                   UINT32 requiredPaths, requiredModes;
                                   if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, &requiredModes) == ERROR_SUCCESS)
                                   {
                                          std::vector<DISPLAYCONFIG_PATH_INFO> paths(requiredPaths);
                                          std::vector<DISPLAYCONFIG_MODE_INFO> modes2(requiredModes);
                                          if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, paths.data(), &requiredModes, modes2.data(), nullptr) == ERROR_SUCCESS)
                                          {
                                                 // iterate through all the paths until find the exact source to match
                                                 for (auto& p : paths) {
                                                        DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
                                                        sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
                                                        sourceName.header.size = sizeof(sourceName);
                                                        sourceName.header.adapterId = p.sourceInfo.adapterId;
                                                        sourceName.header.id = p.sourceInfo.id;
                                                        if (DisplayConfigGetDeviceInfo(&sourceName.header) == ERROR_SUCCESS)
                                                        {
                                                               // find the matched device which is associated with current device 
                                                               // there may be the possibility that display may be duplicated and windows may be one of them in such scenario
                                                               // there may be two callback because source is same target will be different
                                                               // as window is on both the display so either selecting either one is ok
                                                               if (wcscmp(info.szDevice, sourceName.viewGdiDeviceName) == 0) {
                                                                      // get the refresh rate
                                                                      UINT numerator = p.targetInfo.refreshRate.Numerator;
                                                                      UINT denominator = p.targetInfo.refreshRate.Denominator;
                                                                      double refrate = (double)numerator / (double)denominator;
                                                                      *outRefreshRate = refrate;
                                                                      break;
                                                               }
                                                        }
                                                 }
                                          }
                                          else
                                          {
                                                 hr = E_FAIL;
                                          }
                                   }
                                   else
                                   {
                                          hr = E_FAIL;
                                   }
                            }
                     }
              }
       }
       return hr;
    

    }

    More details about CCD API, you can refer the link below:

    https://learn.microsoft.com/en-us/windows-hardware/drivers/display/ccd-apis