AMD just showed Oasis demo, presenting usage of its FreeSync 2 HDR technology. If you wonder how could you implement same features in your Windows DirectX program or game (it doesn’t matter if you use D3D11 or D3D12), here is an article for you.
But first, a disclaimer: Although I already put it on my “About” page, I’d like to stress that this is my personal blog, so all opinions presented here are my own and do not reflect that of my employer.
Radeon FreeSync (its new, official web page is here: Radeon™ FreeSync™ Technology | FreeSync™ 2 HDR Games) is an AMD technology that covers two different things, which may cause some confusion. First is variable refresh rate, second is HDR. Both of them need to be supported by a monitor. The database of FreeSync compatible monitors and their parameters is: Freesync Monitors.
Traditionally, presenting a freshly rendered frame can happen in two modes. With vertical synchronization (V-sync) off, it happens immediately. Game then renders new frames as fast as it can, but flip to a new frame may happen in the middle of scanline process, which causes unpleasant effect called “tearing”. On the other hand, when V-sync is on, graphics card has to wait with presenting the new frame until a new monitor refresh (vertical synchronization) happens, which occurs at a constant pace equal to the monitor refresh rate (typically 60 Hz). Game performance, as measured in frames per second (FPS) is then limited to that frequency. (Please note that FPS is the same unit - a frequency in Hz = 1/second). The game then works smoothly, with no tearing, as long as GPU is able to render frames on time. When it doesn’t, it needs to wait until next V-sync, which causes visible stuttering.
Variable refresh rate, in form of VESA Adaptive-Sync (as implemented in FreeSync) is an addition to DisplayPort and HDMI protocol that allows GPU to inform the monitor when a new frame is ready. Screen refresh can happen in irregular intervals, which combines benefits from both methods described above - no tearing and no stuttering, in a range of supported refresh rates (in case of my monitor: LG 32GK850F, it’s 48-144 Hz).
In order to enable it:
SyncInterval= 1, not 0. Otherwise my monitor reports variable refresh rate still working, but tearing is visible, so it doesn’t make much sense.
What happens is that:
I tried many different display settings. None of them disabled variable refresh rate.
IDXGISwapChain::SetFullscreenState(TRUE, NULL), but also tried not to do it, just create a borderless window covering whole screen.
mode = AGSDisplaySettings::Mode_Freesync2_scRGB, but also skipped doing this.
DXGI_MODE_DESC::ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIEDor
DXGI_SWAPCHAIN_DESC::SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
Another thing is displaying colors in high dynamic range (HDR) of brightness. There are various HDR-supporting monitors and TVs on the market and they can be controlled from various GPUs. I researched the topic of Programming HDR monitor support in my previous blog post, where I made experiments on AMD, Nvidia, and Intel.
Now as I know a bit more about it, I’d like to describe steps recommended to activate HDR as supported by FreeSync 2. With this technology, you can query the monitor for its capabilities (maximum luminance in nits, XY primaries of red/green/blue color etc.) and adjust the tone mapping in the postprocessing pass of your game accordingly. This way you have more information and more control over the process, and over the final result! To do that:
agsInitto initialize the library.
agsInit. Find the GPU and then the display of your interest. Remember its describing
AGSDisplayInfostructure. Let’s call it
(dispInfo.displayFlags & AGS_DISPLAYFLAG_FREESYNC_2) != 0.
AGSDisplaySettingsstructure with following parameters and submit it using
AGSDisplaySettings settings; settings.mode = AGSDisplaySettings::Mode_Freesync2_scRGB; settings.chromaticityRedX = dispInfo.chromaticityRedX; // Just copy them all. settings.chromaticityRedY = dispInfo.chromaticityRedY; settings.chromaticityGreenX = dispInfo.chromaticityGreenX; settings.chromaticityGreenY = dispInfo.chromaticityGreenY; settings.chromaticityBlueX = dispInfo.chromaticityBlueX; settings.chromaticityBlueY = dispInfo.chromaticityBlueY; settings.chromaticityWhitePointX = dispInfo.chromaticityWhitePointX; settings.chromaticityWhitePointY = dispInfo.chromaticityWhitePointY; settings.minLuminance = dispInfo.minLuminance; settings.maxLuminance = dispInfo.maxLuminance; settings.maxContentLightLevel = dispInfo.maxLuminance; // !! settings.maxFrameAverageLightLevel = dispInfo.maxLuminance * 0.5; // !! settings.flags = 0; agsSetDisplayMode(agsContext, deviceIndex, displayIndex, &settings);
By using these specific values for the filled structure, you make sure that your colors are displayed as-is, with no additional tone mapping or other postprocessing taking place in the monitor, which can improve quality and reduce latency. In practice I can’t see any difference comparing to just enabling HDR in a simple way, as I described in my previous post, but that’s the theory.
Just like in the previous post, I focus solely on high dynamic range of brightness here, not on color gamut. That’s because most content pipelines today still use traditional SDR sRGB color space for textures and all the input graphics. Authoring them in wide color gamut or remapping the image to the color gamut reported by the monitor is another, complex topic, which will become more important in the future.
Finally, let me repeat that again: Everything I described here is just my personal knowledge and anecdotal evidence of what worked in my case, not an official guide. But as I couldn’t find any other publicly available information about this technology for developers, I guess it’s better than nothing :)
Platform used in my tests: OS = Windows 10 64-bit version 1809 (OD Build 17763.316), GPU = Radeon RX Vega 56, driver = 19.2.3, monitor = LG 32GK850F, cable = DisplayPort.