Monitor Display Changes and HDR with DXGI

This article provides an overview of how Windows applications use the DirectX Graphics Infrastructure (DXGI) to detect display connectivity changes and query High Dynamic Range (HDR) capabilities. It covers the detection of display topology shifts through Windows messaging and DXGI factory status checks, the retrieval of color space metadata using updated DXGI interfaces, and the necessary adjustments required to update swap chains for seamless HDR rendering.

Detecting Display Connectivity and Topology Changes

DXGI does not natively provide a standalone, push-based event system specifically for display hot-plugging. Instead, applications monitor display connectivity changes by combining the Win32 window messaging loop with DXGI factory validation.

  1. Listen for System Notifications: The application's window procedure should intercept the WM_DISPLAYCHANGE and WM_SETTINGCHANGE messages. The operating system broadcasts WM_DISPLAYCHANGE whenever the display resolution, color depth, or multi-monitor topology changes.
  2. Verify DXGI Factory State: An existing IDXGIFactory instance can become stale after a display event or GPU driver restart. Calling IDXGIFactory::IsCurrent() returns FALSE if the underlying display topology has changed.
  3. Re-enumerate Outputs: If IsCurrent() returns FALSE or a WM_DISPLAYCHANGE message is received, release the outdated IDXGIFactory instance and create a new one using CreateDXGIFactory2. Next, iterate through adapters using IDXGIFactory::EnumAdapters and their corresponding displays using IDXGIAdapter::EnumOutputs to rebuild the list of active display outputs.

Querying HDR Capabilities

HDR metadata and capabilities are associated with specific display outputs rather than the GPU adapter as a whole. To inspect HDR characteristics, the application queries modern DXGI output interfaces.

  1. Obtain IDXGIOutput6: Acquire an IDXGIOutput interface for the target display (either from adapter enumeration or via IDXGISwapChain::GetContainingOutput) and call QueryInterface to retrieve an IDXGIOutput6 pointer.
  2. Retrieve DXGI_OUTPUT_DESC1: Call IDXGIOutput6::GetDesc1 to fill the DXGI_OUTPUT_DESC1 structure. This structure extends legacy display information with vital colorimetry and luminance data:
    • ColorSpace: A DXGI_COLOR_SPACE_TYPE value indicating the current color space of the display. If the display is in HDR mode, this will typically report DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 (HDR10).
    • Luminance Parameters: MinLuminance, MaxLuminance, and MaxFullFrameLuminance define the minimum, peak, and sustained nit values supported by the panel.
    • Gamut Coordinates: RedPrimary, GreenPrimary, BluePrimary, and WhitePoint define the chromaticity coordinates for tone mapping.
  3. Evaluate HDR Active State: A monitor may physically support HDR, but the user may have disabled Windows HD Color in system settings. HDR is actively enabled on the target display only when DXGI_OUTPUT_DESC1::ColorSpace matches DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 and MaxLuminance exceeds standard SDR thresholds (typically 80 to 100 nits).

Updating the Pipeline and Swap Chain

When a display change or HDR toggle is detected, the rendering pipeline must dynamically adapt to prevent visual clipping or incorrect color mapping.

  1. Select the Back Buffer Format:
    • For HDR10, configure the swap chain with DXGI_FORMAT_R10G10B10A2_UNORM.
    • For scRGB, configure the swap chain with DXGI_FORMAT_R16G16B16A16_FLOAT, which allows rendering values outside the standard [0.0, 1.0] range into linear space.
  2. Apply the Color Space: Call IDXGISwapChain3::SetColorSpace1 on the swap chain to match the desired color output:
    • Use DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 for HDR10/PQ.
    • Use DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 for scRGB linear.
    • Revert to DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 if the display operates in standard SDR.
  3. Handle Window Repositioning: When a window transitions across monitors in a multi-display setup, listen for WM_MOVE. Retrieve the new containing output via IDXGISwapChain::GetContainingOutput, re-run the IDXGIOutput6::GetDesc1 query, and invoke SetColorSpace1 to match the configuration of the current target monitor.