Rendering HDR content is well-understood in games and has existed since at least 2005 with Half-Life 2: Lost Coast created by Valve specifically to showcase HDR rendering.
HDR displays, however, have only started reaching the consumer market quite recently so HDR output is still not straightforward and, in some cases, requires using multiple OS-specific and vendor-specific APIs in the same codebase.
This post will cover the different methods of presenting HDR content to the user on desktop platforms.
HDR formats
Unlike films, game titles have to render HDR content locally and in real-time. Color grading and tonemapping have to be applied to all frames in the scene and cannot be done per-frame, with the exception of cutscenes.
However, rendering locally means that the tonemapping can be tailored to the user’s display and OS resulting in more control over the final image.
The recommended pipeline for rendering HDR content is to use compositor-specific formats. These will ensure that the final image is displayed correctly and will prevent the compositor from having to perform additional conversions.
On all platforms, the recommended backbuffer format is 16-bit floating point (not UNORM which limits all channels to 0-1).
Windows
Desktop HDR
Windows 10 Creators Update (version 1703) introduced support for rendering the entire desktop using HDR. This is mostly useful for DirectX 12 titles that use flip model swap chains and do not use exclusive fullscreen mode.
DWM (the compositor) uses scRGB in HDR mode. This is a linear format with the same color primaries as sRGB / Rec. 709 but a linear brightness scale.
In scRGB, 80 nits of brightness are mapped to 1.0 (for example, 1000 nits of brightness will be produced from the value of 12.5).
Detecting HDR support
To detect whether the display supports HDR content, the title has to:
- Obtain the
IDXGIOutput6
interface - Use
IDXGIOutput6::GetDesc1
to obtain theDXGI_OUTPUT_DESC1
structure
The DXGI_OUTPUT_DESC1
’s ColorSpace
member will be:
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020
if the display supports HDR and HDR mode is enabled in SettingsDXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
if the display does not support HDR or HDR mode is not enabled in Settings
Sample code:
IDXGIOutput6 *output6 = nullptr;
output->QueryInterface(IID_PPV_ARGS(&output6));
DXGI_OUTPUT_DESC1 oDesc;
output6->GetDesc1(&oDesc);
bool supportsHDR = (oDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
When support is detected, the title can present the HDR option to the user and proceed with outputting HDR content.
Obtaining display capabilities
DXGI_OUTPUT_DESC1
contains several fields that are relevant for tonemapping:
RedPrimary
,GreenPrimary
,BluePrimary
arefloat[2]
that contain the X and Y coordinates of color primariesWhitePoint
is afloat[2]
that contains the X and Y coordinates of the white pointMinLuminance
andMaxLuminance
are the minimum and maximum luminance of the display in nitsMaxFullFrameLuminance
is the maximum luminance of the display that is valid for the entire surface
These values should be used for tonemapping the image. Remember to properly map the luminance values to component values (as mentioned above, 80 nits of brightness are mapped to 1.0 in scRGB).
To detect any display changes, the title should call IDXGIFactory1::IsCurrent
every frame and re-enumerate the outputs if the result is FALSE
.
Presenting HDR content
To present HDR content, the title should:
- use
DXGI_FORMAT_R16G16B16A16_FLOAT
as the backbuffer format - use a flip model swap chain (created with
DXGI_SWAP_EFFECT_FLIP_DISCARD
orDXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
) - obtain
IDXGISwapChain3
- verify support for the color space using
IDXGISwapChain3::CheckColorSpaceSupport
- set the color space to
DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709
usingIDXGISwapChain3::SetColorSpace1
Exclusive fullscreen with AMD GPUs
Use desktop HDR if possible, it is easier to implement and provides better user experience.
If the title runs in exclusive fullscreen mode or the Windows version is older than 1703, the title has to use AMD-specific APIs to output HDR content. AMD GPUs support HDR with FreeSync HDR-compatible displays and titles that link and use AMD GPU Services (AGS).
To check whether GPU is manufactured by AMD, the title can obtain the DXGI_ADAPTER_DESC
or any of its newer versions and check the VendorId
member. AMD’s vendor IDs are 0x1002
and 0x1022
.
AMD provides detailed instructions and code samples for using AGS to output HDR content here: https://gpuopen.com/learn/using-amd-freesync-premium-pro-hdr-code-samples/
Exclusive fullscreen with NVIDIA GPUs
Use desktop HDR if possible, it is easier to implement and provides better user experience.
If the title runs in exclusive fullscreen mode or the Windows version is older than 1703, the title has to use NVIDIA-specific APIs to output HDR content. NVIDIA GPUs support HDR with HDR10-compatible displays and titles that link and use NVAPI.
To check whether GPU is manufactured by NVIDIA, the title can obtain the DXGI_ADAPTER_DESC
or any of its newer versions and check the VendorId
member. NVIDIA’s vendor ID is 0x10DE
.
NVIDIA provides detailed instructions and code samples for using NVAPI to output HDR content here: https://developer.nvidia.com/displaying-hdr-nuts-and-bolts
macOS
macOS supports HDR output natively in any application from version 10.15 (Catalina) as long as the display supports HDR and the preset applied to the display has “Enable HDR content” checked.
macOS’s Quartz Compositor supports linear extended sRGB for high-performance direct rendering. This is a linear format with the same color primaries as sRGB / Rec. 709 but a linear brightness scale. 100 nits of brightness are mapped to 1.0 in this format (for example, 1000 nits of brightness will be produced from the value of 10.0).
Detecting HDR support
CAMetalLayer
always outputs in the display’s native color space. To detect whether the display supports HDR content, the title has to:
- Obtain the
NSScreen
object that represents the target display - Check the value of
maximumPotentialExtendedDynamicRangeColorComponentValue
is greater than 1.0. If it is, the display supports HDR content
Sample code:
NSScreen *screen = [NSScreen mainScreen];
CGFloat maxEDR = [screen maximumPotentialExtendedDynamicRangeColorComponentValue];
bool supportsHDR = maxEDR > 1.0;
When support is detected, the title can present the HDR option to the user and proceed with outputting HDR content.
Obtaining display capabilities
NSScreen
contains maximumExtendedDynamicRangeColorComponentValue
that is the maximum luminance of the display in nits. This value should be used for tonemapping the image.
To detect any display changes, the title should subscribe to NSApplicationDidChangeScreenParametersNotification
and re-enumerate the screens if the notification is received.
Presenting HDR content
To present HDR content, the title should:
- use
MTLPixelFormatRGBA16Float
as the backbuffer format - set
wantsExtendedDynamicRangeContent
toYES
on theCAMetalLayer
object - set the color space to linear extended sRGB on the
CAMetalLayer
object
Sample code:
CAMetalLayer *metalLayer = [CAMetalLayer new];
metalLayer.wantsExtendedDynamicRangeContent = YES;
metalLayer.pixelFormat = MTLPixelFormatRGBA16Float;
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
metalLayer.colorspace = colorspace;
CGColorSpaceRelease(colorspace);
Further reading
https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
https://developer.nvidia.com/downloads/hdr-gdc-2018
https://www.asawicki.info/news_1703_programming_hdr_monitor_support_in_direct3d