[Windows] Support output to HDR monitors#94496
Conversation
|
I gave this a quick test locally (on Windows 11 23H2 + NVIDIA 560.80 + LG C2 42"), it works as expected. This is encouraging to see, I've been wanting this for a while 🙂 I'll need to look into building more extensive scenes and getting tonemapped screenshots/videos out of this. 2D HDR also needs to be tested thoroughly. Remember that JPEG XL or AVIF for images and AV1 for videos are a must for HDR, as other formats can only store SDR data. You may need to embed those in ZIP archives and ask users to preview them in a local media player, as GitHub doesn't allow uploading those formats and browsers often struggle displaying HDR correctly. I noticed some issues for now:
See the settings exposed by the Control HDR mod for an example of a best-in-class HDR implementation (related video): control_hdr_mod_settings.mp4Interesting, that UI seems to use the term "paperwhite" in a different way, and has a dedicated setting for the brightness of UI and HUD elements. |
88beb60 to
8df131d
Compare
Thanks for taking a look!
Odd that NVidia's RTX HDR doesn't detect the HDR color space and avoid messing with the final swap chain buffer. Auto-HDR in Windows 11 appears to avoid messing with Godot when HDR is enabled. Updating the NVidia Profile may be outside the scope of this PR and be best done with a more focused PR.
For the initial draft, yes, everything is mapped using the same tonemapper. However, we should map UI elements to a different brightness to avoid them being too bright. For now, that can be worked around with dimming the brightness of any UI elements via the theme, but I would like to fix that in this PR.
I haven't looked into configuring the editor to use HDR yet. Will do after I figure out how to properly tone map UI elements, if you enable HDR on the editor now, the UI is a little unpleasant.
Agreed, UI elements and other 2D elements should probably be mapped to a different brightness curve. I'll probably have to figure out where in the engine 3D and 2D elements are composited together and perform the tone mapping there.
That might be outside of the scope of this PR. I'm not sure how I would indicate that certain 3D elements need to be mapped using a different brightness curve once they are all combined into the same buffer. It would be similar to trying to avoid sRGB mapping certain rendered elements. For now, this can be worked around by decreasing the brightness of the color of these elements.
Baldur's Gate 3 and Cyberpunk 2077 also have really nice HDR settings menus. I've been basing some of this work off their approach, though modifying contrast and brightness I'm leaving up to Environment since those effects are already there. Thanks again for your comments! I'll add some TODO items to the description for tracking. |
b89985a to
e9742ba
Compare
e9742ba to
b2bd1a1
Compare
|
Can you use any Godot project to test this PR? Bistro-Demo-Tweaked and Crater-Province-Level both use physical light units, and use as close to reference values for luminosity on light sources. (i.e. the sun at noon is 100000 lux, the moon at midnight is 0.3 lux) I'd love to help test this PR but unfortunately I don't have HDR hardware |
|
I recently got a monitor that supports Anyway, adding HDR output to D3D12 should be trivial and I might give it a try. (No promises!) Shall we also consider implementing HDR display for the compatibility renderer? I am not sure if native OpenGL can do HDR, but it is very possible to implement on Windows with the help of ANGLE and some manual setting up. |
|
This needs a rebase on master, but I have a https://www.dell.com/en-ca/shop/alienware-34-curved-qd-oled-gaming-monitor-aw3423dw/apd/210-bcye/monitors-monitor-accessories HDR display. I can help test. |
You should be able to test with any scene, though keep in mind that the realistic light units will not map directly to the brightness of the display. Consumer desktop displays typically don't go much above 1000 nits on the high end, which is far too dim to simulate sunlight. Values from the scene will be mapped to a range fitting within the max luminosity set for the window. |
b2bd1a1 to
728912f
Compare
|
Here are the changes to get Rec. 2020 HDR output on D3D12: master...alvinhochun:godot:hdr-output-d3d12 |
|
The over-exposure in your screenshot is expected, but the colours are oversaturated because it is missing a colour space conversion. The colours need to be converted from BT.709 primaries to BT.2020 primaries. This is how it should look with the correct colours: The conversion may be done with something like this: diff --git a/servers/rendering/renderer_rd/shaders/color_space_inc.glsl b/servers/rendering/renderer_rd/shaders/color_space_inc.glsl
index 3583ee8365..76305a8a3c 100644
--- a/servers/rendering/renderer_rd/shaders/color_space_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/color_space_inc.glsl
@@ -19,6 +19,15 @@ vec3 linear_to_st2084(vec3 color, float max_luminance) {
// max_luminance is the display's peak luminance in nits
// we map it here to the native 10000 nits range of ST2084
float adjustment = max_luminance * (1.0f / 10000.0f);
+ color = color * adjustment;
+
+ // Color transformation matrix values taken from DirectXTK, may need verification.
+ const mat3 from709to2020 = mat3(
+ 0.6274040f, 0.0690970f, 0.0163916f,
+ 0.3292820f, 0.9195400f, 0.0880132f,
+ 0.0433136f, 0.0113612f, 0.8955950f
+ );
+ color = from709to2020 * color;
// Apply ST2084 curve
const float c1 = 0.8359375;
@@ -26,7 +35,7 @@ vec3 linear_to_st2084(vec3 color, float max_luminance) {
const float c3 = 18.6875;
const float m1 = 0.1593017578125;
const float m2 = 78.84375;
- vec3 cp = pow(abs(color.rgb * adjustment), vec3(m1));
+ vec3 cp = pow(abs(color.rgb), vec3(m1));
return pow((c1 + c2 * cp) / (1 + c3 * cp), vec3(m2));
}
|
728912f to
56d27a6
Compare
KoBeWi
left a comment
There was a problem hiding this comment.
The editor changes (the 3rd commit) look fine.
However, and this can be done in a later PR, updating the viewports can be better. There is repetitive code in a few places that waits for changing a setting and then updates the viewport (without checking if the relevant setting was modified). Maybe the viewports could be registered somewhere (probably EditorNode) and checking the setting could be done only once to update all relevant viewports.
|
Rebased and squashed down into a single commit. |
blueskythlikesclouds
left a comment
There was a problem hiding this comment.
The rendering changes look good to me now! I just have a minor nitpick.
|
I have force-pushed a docs change that swaps the word "display" to "screen" to better match existing Godot APIs, which primarily use the term "screen" to refer to the physical hardware. I noticed when reviewing these docs that I failed to update the glow blending docs in Additionally, I've updated the public HDR output API to not expose |
bruvzg
left a comment
There was a problem hiding this comment.
Windows platform changes looks good, and it seems to be working as expected on both Windows 11 and Wine (Game Porting Toolkit 3 on macOS).
There was a problem hiding this comment.
@DarkKilauea @blueskythlikesclouds It appears, to me, that the recent optimizations to retrieving HDR screen info (luminance values and SDRWhiteLevel) have broken the detection of changes from SDR to HDR:
- Create project with request HDR output enabled (or just use my HDR porting test project)
- Make sure Windows is running with HDR disabled
- Run the game, note that the Window correctly does not support HDR
- Press Win+Alt+B to enable HDR on Windows
- Note that Godot incorrectly believes that the Window does not support HDR, even though it should
Godot v4.7.dev (6ea844a69) - Windows 11 (build 26100) - Multi-window, 1 monitor - Direct3D 12 (Forward+) - dedicated NVIDIA GeForce RTX 5070 Ti (NVIDIA; 32.0.15.8088) - AMD Ryzen 9 9950X3D 16-Core Processor (32 threads) - 61.64 GiB memory
This issue does not occur with old builds of this PR. For your comparison, I have a snapshot saved of what this PR looked like before just before the breaking optimizations were applied: https://github.com/allenwp/godot/tree/rendering/hdr-output-src-jan-27
Other issues may have been introduced as a result of these changes; I have not done any further testing after discovering the above problem.
I cannot reproduce this, when I run my testing project (https://github.com/DarkKilauea/godot-hdr-output), both starting with HDR disabled and enabled, the System specs: |
|
Also works fine on my end. Both the shortcut and the Windows setting get detected by Godot, as well as the reference luminance setting. Godot v4.7.dev (a54d6d8) - Windows 11 (build 26220) - Multi-window, 2 monitors - Direct3D 12 (Forward+) - dedicated NVIDIA GeForce RTX 3070 Ti Laptop GPU (NVIDIA; 32.0.15.9144) - AMD Ryzen 7 6800H with Radeon Graphics (16 threads) - 31.26 GiB memory |
|
I updated my bug report comment with my system info. After some more trying, the bug doesn't happen 100% of the time for me. But thankfully, it still happens most of the time so I was able to catch when debugging. It seems that sometimes when the From looking at this new code it seems that there is probably another bug that exists: As I shared in a previous comment, changes to maximum luminance do not trigger the |
|
@allenwp I believe I've resolved the issues you discovered. I've reverted back to loading the entire HDR state on each frame. This does undo some of the performance improvements gained, but should guarantee HDR output matches the screen in all situations. insert gripe about Windows missing events for HDR display changes I tried a number of approaches to reduce the performance overhead:
My latest performance numbers are:
|
I guess that's acceptable. If Windows has no events, there isn't anything to do but eat the cost. |
allenwp
left a comment
There was a problem hiding this comment.
Thanks for looking into these options for optimization! I especially appreciate how easy it was to diff and compare with the previous version of the Windows DisplayServer; I had spent dozens of hours testing on many different configurations, so the easy diff helps give me confidence that no new regressions should be introduced by this latest attempt at optimizing.
Also, thank you for sharing your research and results! It does seem clear that WinRT events are the way that modern HDR should be implemented in Windows (to get AdvancedColorInfo events). Long term, maybe this will be an option for Godot. If someone else wants to take a stab at further optimizing this in a followup PR, I'm sure your notes will be very valuable!
Calinou
left a comment
There was a problem hiding this comment.
Tested locally (rebased on top of master #94496 (comment)) with https://github.com/DarkKilauea/godot-hdr-output, it works as expected. Code and documentation look good to me.
Toggling system HDR at runtime works correctly too.
Brightness in dark areas with HDR enabled/disabled seems to be a identical match, like on macOS (when HDR remains enabled at the system level).
Remember to disable RTX HDR when doing comparisons (if you've enabled it globally), as RTX HDR will automatically engage when HDR is disabled. Windows 11's own Auto HDR seems not to conflict though, as it needs to be specifically enabled on a per-game basis (and Godot isn't on this list by default).
I did not test this with dylanraga/win11hdr-srgb-to-gamma2.2-icm (which aims to make SDR-in-HDR behave closer to how it works on macOS), but someone could give it a try eventually.
PC specifications
- CPU: AMD Ryzen 9 9950X3D
- GPU: NVIDIA GeForce RTX 5090
- RAM: 64 GB (2×32 GB DDR5-6000 CL30)
- SSD: Solidigm P44 Pro 2 TB
- OS: Windows 11 24H2
Using an ASUS ROG PG32UCDP for tests.
For reference, here's how the reference brightness evolves according to the SDR Content Brightness setting in the Window slider:
sdr_content_brightness.mp4
clayjohn
left a comment
There was a problem hiding this comment.
Amazing work! This PR has come a long way and is looking extremely good at this point. We discussed this in the rendering meeting and agree that we are very happy with the exposed API and the implementation.
The only outstanding thing is that minor change from std140 vs. std430 and the need to mark a few of the functions as experimental. Once those are cleared up we can go ahead with merging (hopefully tomorrow)!
Co-authored-by: Alvin Wong <alvinhochun@gmail.com> Co-authored-by: Allen Pestaluky <allenpestaluky@gmail.com>
|
Made what are hopefully the last batch of fixes and rebased on the latest master branch. |
|
Thanks! Fantastic work getting this together! |
For the reference, I have checked it while working on #116349 (using WinRT for TTS), and getting |



Implements: godotengine/godot-proposals#10817 for Windows.
Overview
This PR enables the ability for Godot to output to HDR capable displays on Windows. This allows Godot to output brighter images than allowed in SDR mode and with more vibrant colors.
HDR output is implemented using Extended Dynamic Range (EDR), which is a technique that preserves the SDR range (of 0 to 1) while extending the upper range to include brighter values, up to the maximum brightness of the display. This approach preserves the shadows and darker areas of the scene while allowing highlights to reach much brighter values.
It also allows for 2D content take advantage of HDR displays by producing brighter colors that have a value over 1.0, without having to opt into tonemapping and the effects pipeline. 2D and 3D content can also be mixed in the same scene while keeping a consistent brightness between the two, based on a shared "reference" luminance.
Testing/Sample projects:
Examples (converted to SDR to work on all browsers):
Examples (HDR images, may not display correctly on all browsers):
Supported Platforms:
Supported Graphics APIs:
Supported HDR Formats:
Supported Tonemappers:
Features:
Limitations:
hdr_2dto output the additional dynamic range needed for HDR displays. Blending may look different whenhdr_2dis enabled.hdr_2dwill automatically be enabled for windows that turn on HDR output, which may result in a change in appearance of the scene. It's advised that applications planning to support HDR output create their content withhdr_2denabled, even when HDR output is disabled.Follow up work:
white,contrast, and future HDR support to the AgX tonemapper. #106940Trackers:
Getting Started
Project Settings
display/window/hdr/enabled(you may need to enable Advanced Settings first):rendering/viewport/hdr_2d:Runtime
Help Needed
Please give this a test, either with the linked sample project or with your own projects, and give feedback. Specifically I'm looking for input on how easy this feature was to use and if you encountered any issues with your particular display, OS, or driver configuration.
Remaining Work / Issues
SDRWhiteLevel: [Windows] Support output to HDR monitors #94496 (comment)