Skip to content

[LinuxBSD] Add support for HDR output (Wayland)#102987

Open
ArchercatNEO wants to merge 1 commit intogodotengine:masterfrom
ArchercatNEO:wayland-hdr
Open

[LinuxBSD] Add support for HDR output (Wayland)#102987
ArchercatNEO wants to merge 1 commit intogodotengine:masterfrom
ArchercatNEO:wayland-hdr

Conversation

@ArchercatNEO
Copy link
Contributor

@ArchercatNEO ArchercatNEO commented Feb 18, 2025

Description updated for 2026/02/23.

Based on #94496 (merged to master)
Implements godotengine/godot-proposals#10817 for Wayland
Testing/Sample project: https://github.com/DarkKilauea/godot-hdr-output

This PR implements the luminance based features of the wp-color-management protocol as well as the minimum amount of colorspace + transfer function features necessary for HDR output.

Due to the unreliability of HDR applying glare compensation or not, SDR over HDR is assumed to be performed against a monitor which does not apply glare compensation which differs from the assumptions made in the Windows and Mac implementations for HDR. The Wayland protocol was made with this assumption so we will not deviate from it despite having the possibility to do so.

Since this feature is exclusive to Wayland, the display server must be set to Wayland in both project settings and editor settings (not setting both to Wayland leads to inconsistencies in display server selection).

For editor: run/platforms/linuxbsd/prefer_wayland must be enabled.
For project: display/display_server/driver.linuxbsd set to "wayland" (X11 is still set chosen when "deafault" is selected).

@DarkKilauea
Copy link
Contributor

This looks like a good start, I had no idea HDR support in Wayland was this far along.

Unfortunately I can't speak to the correctness of the Wayland code as I don't have experience with it. You may want to look at the latest commit for Windows support and gradually implement the remaining methods and features.

Generally I'd expect a full implementation to support:

  1. Declaring its support for HDR (already done).
  2. Getting HDR support and luminance data from the display (you mostly have this).
  3. Telling the RenderDevice to use HDR and passing the parameters for precision and luminance. (you have most of the methods implemented).
  4. Automatically responding to changes in HDR support and movement of windows between displays.
  5. Automatically mapping the window to the display's luminance capabilities if use_screen_luminance is enabled.
  6. Preventing common errors like:
    • Trying to change luminance on a window if use_screen_luminance is enabled
    • Trying to enable HDR on a screen that does not support it, or if the RenderDevice does not support it.
    • Remembering if the user tried to enable HDR and automatically enabling it if the screen starts supporting it (or the window moves to an HDR capable screen).
    • Preventing lag by not setting hdr_output_enabled or hdr_output_prefer_high_precision if they are already match the requested value.

@ArchercatNEO ArchercatNEO force-pushed the wayland-hdr branch 2 times, most recently from e522785 to b67a8c6 Compare February 19, 2025 17:52
@ArchercatNEO
Copy link
Contributor Author

Update on what I have understood each bit of the protocol to mean and what I'm trying to work with:

Does the DisplayServer support HDR?

  • If the Compositor implements color-management-v1 then yes.
  • If the Compositor doesn't implement color-management then vulkan APIs may still be able to handle it but unless it's far easier than I think to get information from Vulkan it doesn't seem practical.
  • Related APIs: color-management-v1

Does the Screen (Wayland output, PC monitor) support HDR? And if so, what is the luminance/primaries/tf/etc?

  • First obtain a wp_color_management_output. This (after a few layers of indirection) will give the screen's ICC profile
  • From this profile if the primaries_named and tf_named combination is a known HDR profile that godot supports, yes.
  • If the profile is an SDR profile there is no HDR. If it is an unsupported HDR profile probably raise a warning.
  • The luminance of the screen itself I am assuming to be from the luminaries event.
  • The target_* events I assume to refer to window values so they will be used for use_screen_luminance later
  • The rest of the events may be helpful for a full wayland ICC implementation but again that is outside the scope
  • Related APIs: wp_color_manager_v1::get_output, wp_image_description_info

How to manipulate HDR values of this Window (Wayland surface)?

  • This seems largely a Vulkan (or RenderingDriver) API which was already implemented so just defer to RenderingDriver.

What is the preferred screen luminance (for use_screen_luminance)?

On the concrete impl side of things

  • Screen luminance and descriptor_params are still unimplemented. I believe using the supports_* flags will be neccesary to coordinate what profile godot will actually use
  • Most of the relevant APIs are events which should make automatically responding more transparent
  • Object destruction needs to happen to prevent memory leaks
  • Docs/Warns/Errors will likely be handled last after I get more advice on where this state should really go

If I got the relation between wayland API and godot hdr rendering stuff correct then it seems like what's left is largely state management in wayland. If you don't mind @Riteo I'd also like to get your feedback on this specially with where to put the more stateful stuff like the compositors prefered icc profile.

@deralmas
Copy link
Contributor

deralmas commented Feb 19, 2025

If you don't mind @Riteo I'd also like to get your feedback on this specially with where to put the more stateful stuff like the compositors prefered icc profile.

Yea sure! :D

I'm currently very busy with a thing I'm working on but I could not resist taking a very quick look at the protocol.

I can't really comment on the feature itself as I have very little experience with HDR. That said, the logic you're describing seems great! My only concern is how much the godot-side API depends on the screen since 99% of Wayland stuff revolves around windows, with the screen being a way smaller piece of context compared to traditional servers.

Regarding the data layout, the general rule of thumb is to put all state for an object in an aptly-named *State class, which you'll need to bind anyways to the object so that it can be accessed by the handler. Even the "global" data you're talking about is still bound to an object, so it becomes quite easy to see where I'm going.

Actually, you don't even need to make a "global" variable in the thread class; a common approach I've chosen is to consider the objects themselves... objects, and just malloc their state in the user_data. See the *_get_*_state classes and the way their objects are handled.

So you could just do an:

	ColorManagerState *state = wp_color_manager_get_state(the_actual_color_manager_proxy);
	if (state) {
		// Stuff!
	}

And always have a valid state reference. If you implement it like the other methods, it will also automatically check for whatever to not be null and whether it's properly "tagged" (a concept we use to signal that our objects are, well, ours and with our data attached to them) thanks to wl_proxy_tag_godot.

Crap, this was supposed to be a quick message. There's a lot of similar things I have to document somewhere. I hope that was not too much. If you need some further clarifications feel free to tell me.

@Zamundaaa
Copy link

If the Compositor implements color-management-v1 then yes

It also needs to support parametric image descriptions, and the primaries and transfer function you want to use. Ofc if you use Vulkan to set the colorspace and HDR metadata, the Vulkan driver also has to support them.

If the Compositor doesn't implement color-management then vulkan APIs may still be able to handle it but unless it's far easier than I think to get information from Vulkan it doesn't seem practical.

You can output HDR content with Vulkan, but it doesn't (yet) actually have any API to figure out what the compositor is asking for / what the display is capable of.

Does the Screen (Wayland output, PC monitor) support HDR? First obtain a wp_color_management_output.

Please don't do that. Outside of special cases, the color management output should not be used.

HDR support should only be determined by whether or not the compositor supports it, the display isn't really relevant beyond the luminance ranges (which you can get from the preferred image description). If you output scRGB or BT2020PQ, the compositor will do the needed transformations to make it work on any display.

KWin for example does proper HDR on SDR laptops (as proper as the displays can do at least); while the primary use case for that is HDR videos, it would be cool if that would work with games too.

If you want to ensure that you don't waste power presenting HDR content when it's not necessary, you can instead check if the preferred image description has a maximum target luminance that's higher than the reference luminance. If it is higher, displaying HDR makes sense, if it isn't, then it's probably not useful.

The target_* events I assume to refer to window values so they will be used for use_screen_luminance later

The image description just describes what you get it from, so they're about the screen if you get it from the color management output, or about the window if you get the preferred image description from the surface feedback.

In all cases use wp_image_description_creator_params to tell the compositor what profile we have chosen so that it may perform it's job better

Note that while you can have multiple wp_color_management_surface_feedback_v1, you can only have one wp_color_management_surface_v1 per Wayland surface at a time. If Godot already uses Vulkan to set a colorspace and HDR metadata on the window, then you must not create a color management surface in addition, as the driver creates one too!

If you want to use the protocol directly, you can do that if you set VK_COLOR_SPACE_PASS_THROUGH_EXT as the colorspace with Vulkan, which tells the driver to not use the protocol.

You can use https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32038 for testing either approach.


There's one more important topic I want to touch on: Reference luminance / paper white. Does Godot already have a centralized setting for it?

With Vulkan, the assumption on Wayland is that the paper white is 203 cd/m² with PQ, and 80cd/m² with scRGB, and the compositor will map that to the system wide user setting - so using anything else as the paper white will cause undesired outcomes. With the protocol directly, you can set the reference luminance with set_luminances, but that's an optional feature and not every compositor currently supports it.

Ideally, games would either always use the reference luminance for the transfer function they use, or use the same reference luminance the compositor would like to have, and the preference for this would be completely hidden on Wayland. Same with maximum luminance, it should just be read from the compositor, so that it can automatically adjust to brightness settings changes and to different displays without the user having to change the settings of each game.

I'm not sure how you handle that on Windows - technically it also has such settings, but I know the very vast majority of games just ignore them completely. This has already caused some user confusion on Plasma, as the Windows game settings don't necessarily directly translate to the brightness values of the screen, so it would be really great if we could avoid that mess and always have them configure things system wide instead.


Sorry for the wall of text, after working on it so long I'm really exited for some great HDR experiences on Linux :)

@ArchercatNEO
Copy link
Contributor Author

@DarkKilauea should the screen_* APIs be implemented against the real profile of the wl_output or the reccomended profile of wl_surface_feedback? I am partial to @Zamundaaa since the intention of using the screen profile I expect is for performance/efficiency/power but the feedback is the one that gives the most optimal profile not the output.

Just doing ctrl + shift + f gives no places where these methods are called so I can't tell what the expected use is. In efficiency scenarios feedback seems more fitting but in the (I think quite esoteric) case of enumerating screens to see which one is most fitting for custom HDR output. Next update will likely be using feedback and just dropping output entirely but clarification on this would be helpful.

@DarkKilauea
Copy link
Contributor

@ArchercatNEO The screen APIs should return the characteristics of the display that Godot should be tonemapping to. I would bias towards returning the actual capabilities of the display instead of asking the compositor to perform some of the mapping itself.

The general recommendation for games is to avoid the display or compositor performing a conversion to the final values, due to poor results on many displays (if the display/compositor even bothers). That is the primary reason my PR does not provide HDR10 metadata to the system. We should avoid doing the same for Wayland.

@ArchercatNEO
Copy link
Contributor Author

The surface feedback is simply the profile the compositor would like us to use according to docs. There is no reason why if the compositor is not able to transform values itself it shouldn’t return the actual profile of the screen and perform no transformation on the incoming data.

My fear with not using the feedback and using the screen format is that a compositor may then transform it into its preferred format to do processing and then back into the screens native format when we could have used the compositors format from the beginning.

The way I see it there are 3 ways we could do this.

  1. Use output for everything and ignore feedback. I hesitate on this one because it assumes the compositor a bad implementation of the protocol and cannot be trusted.
  2. Use output for screen APIs and feedback for screen_luminance. My greatest hesitation on this is just the inconsistency. Since I haven’t seen useage of the screen APIs I’m not aware if the inconsistency is significant
  3. Use feedback for everything. This assumes the compositor behaves well (which could include doing no transformation at all) which seems like a reasonable assumption. It means we can’t actually enumerate the properties of screens the main window is not on but this doesn’t seem too large an issue for me. It also maintains consistency between screen and screen_luminance APIs

As of now I have no reason to believe compositors would implement a buggy transformation instead of performing no transformation at all which makes me partial to 3. If enumerating screens is a desirable property I would go with 2 because assuming the compositor is buggy and will not perform transformations on its preferred format correctly (as 1 assumes) does not seem that reasonable.

For people with more experience with wayland: when a compositor implements a protocol, is assuming a correct implementation reasonable? I don’t have much experience with the reliability of different compositors beyond knowing mutter doesn’t support SSD so knowing if they are so unreliable we should not rely on feedback would make 1 my preferred option.

@ArchercatNEO
Copy link
Contributor Author

Finally got round to a screen_luminance implementation.

In terms of what I think has to be done before this stops being a draft I think the most important is making sure that if the preferred profile changes (whether that be from the screen itself or from the compositor) and screen_luminance is enabled we set the window luminances. We should also destroy all the wayland objects that we aren't already destroying.

After that I think just cleaning up error messages and state management (not setting values that are already equal, remembering whether we tried to enable hdr, etc) are the main things for a mergable PR.

I went with feedback for everything because trusting the compositor felt right (and I checked the mesa PR, there you can see that the vulkan impl does actually use this protocol too so the system does get metadata) and consistency between screen and screen_luminance felt important. If we decide to go in another direction it probably won't be too hard to just add an IccProfile to ScreenData and do something similar to what we already do except instead of using the window's intrinsic feedback profile we just look for which screen the window is on and use its profile

@Zamundaaa
Copy link

The general recommendation for games is to avoid the display or compositor performing a conversion to the final values, due to poor results on many displays

That's definitely sensible, you don't want to stack multiple tone mapping processes on top of each other if it can be avoided. Even if the compositor does really good tone mapping, it may have a performance cost, so if you do tone mapping either way, you should do all of it in one go.

That does not mean you should use the output image description though, as that could require additional conversions. The preferred image description is the most optimal one for the window.

That is the primary reason my PR does not provide HDR10 metadata to the system. We should avoid doing the same for Wayland.

That won't work. Wayland is not like Windows, where HDR metadata at best does nothing and at worst causes issues. If you don't provide HDR metadata, the compositor may (and KWin always does) clip the image to SDR range.

My fear with not using the feedback and using the screen format is that a compositor may then transform it into its preferred format to do processing and then back into the screens native format when we could have used the compositors format from the beginning.

Indeed, that is a likely scenario. If a compositor does blending in linear, if the game renders in linear, converts to BT2020PQ and sends that to the compositor, it'll convert back to linear, then do its compositing, and then convert to BT2020PQ for the display. The first two conversions could be optimized out by providing the compositor with scRGB instead of BT2020PQ for example.

For people with more experience with wayland: when a compositor implements a protocol, is assuming a correct implementation reasonable?

Yes. If you can't trust a compositor far enough to provide an optimal image description for the window, why would you trust it with the output image description?

The minimal implementation for the protocol just returns the image description of the output the window is on anyways.


void WaylandThread::_wp_color_manager_on_supported_primaries_named(void *data, struct wp_color_manager_v1 *wp_color_manager_v1, uint32_t primaries) {
IccManagementState *ims = (IccManagementState *)data;
ims->supported_primaries = ims->supported_primaries | (1 << primaries);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

primaries, like transfer functions, features and intents are not bit fields but just normal enum values. You need to store them in a list

@DarkKilauea
Copy link
Contributor

That is the primary reason my PR does not provide HDR10 metadata to the system. We should avoid doing the same for Wayland.

That won't work. Wayland is not like Windows, where HDR metadata at best does nothing and at worst causes issues. If you don't provide HDR metadata, the compositor may (and KWin always does) clip the image to SDR range.

That's a little concerning and would suggest a project setting is needed to control whether Godot outputs HDR10 metadata to the system. Neither macOS nor Windows seems to require this, so it is odd that Wayland does. It is a new protocol though, so this may change as compositors run into programs that don't provide the required metadata.

I wonder if gamescope works the same way?

@ArchercatNEO
Copy link
Contributor Author

The mesa drivers for vulkan make use of this protocol. Unless we explicitly tell the vulkan driver to not give the compositor the information it needs (in which case we’d have to do so ourselves) then the compositor will receive the metadata.

I read through some of the “what this protocol is and what it isn’t”

  • Applications that do not supply metadata (most likely just apps with old libraries seeing as how mesa drivers themselves implement this protocol) are assumed to be using sRGB and the compositor should transform it as such
  • Applications that supply metadata (maybe because it’s the only one they can render in) are to be transformed by the compositor in whichever way it sees fit to render on the monitor
  • Applications that can render in many color spaces (like us) should render in the preferred profile as given by surface_feedback. It should also tell the compositor it is doing so (which is handled by vulkan)

In all situations it is better to supply metadata (or just let vulkan do it for us) because otherwise every compositor that implements this protocol will assume we are using sRGB and attempt to fix our rendering.

https://gitlab.freedesktop.org/pq/color-and-hdr/-/blob/main/doc/design_goals.md

@Zamundaaa
Copy link

That's a little concerning and would suggest a project setting is needed to control whether Godot outputs HDR10 metadata to the system. Neither macOS nor Windows seems to require this, so it is odd that Wayland does. It is a new protocol though, so this may change as compositors run into programs that don't provide the required metadata.

It won't change. Proper HDR metadata is fundamental to good HDR support, and without it you will always get a suboptimal experience.

KWin does have a fallback for existing but obviously crazy HDR metadata (many Windows games claim to have 10 million nits max_cll, because some engine has really broken HDR support) - but that fallback makes assumptions that may cause tone mapping to be applied when the game wouldn't need it. You do not want that to be applied to Godot.

MacOS does need HDR metadata too afaik. It might not clip in all situations, but for example their equivalent of HDR on SDR laptop displays can't really work without it.

I wonder if gamescope works the same way?

Gamescope does Windows behavior as far as it can. Afaik it completely ignores HDR metadata and doesn't do anything in regard to reference luminance.

@ArchercatNEO
Copy link
Contributor Author

We seem to be talking past each other a lot.

You seem very persistent is asking whether or not the screen our window is on supports HDR or not. As far as I can tell this question does not make sense under Wayland (even if it may make sense in other systems). According to the above link KWin (and I suspect other compositors too) are able to render HDR content on SDR displays and do so in a way that somewhat preserves the HDR nature of the content.

The question of SDR/HDR then needs to move on from does the screen support HDR.

If we are outputting standard SDR content on an HDR monitor we should default to use_hdr = true and use_screen_luminance = true as far as I can tell. Since we are ignoring the physical capabilities of the screen this means that if the compositor is asking for an HDR format in surface_feedback then likely this is an HDR monitor. If this is the case then internally we should collaborate with the compositor to render in an HDR format and this should be transparent to the user.

If we are outputting HDR content on an SDR screen then things may be more complex. Since on wayland the capabilities of the physical screen do not seem relevant we will not use them. If use_hdr has been enabled then we are rendering in an HDR format. If use_screen_luminance is also enabled perhaps what would make more sense is to disable hdr if the compositor prefers an srgb colorspace and then perform whatever transformation we require to clip our HDR content. If use_hdr is enabled and use_screen_luminance is disabled, then we will render output HDR content on a potentially SDR display.

So then to avoid outputting HDR content on a potentially SDR monitor we should make use_screen_luminance be independent from use_hdr. If it is enabled then we will internally decide all colorspace values including whether we use an SDR or HDR format. On wayland this will mean adopting surface_feedback to the best of our ability. On other systems it may mean adopting the physical screen profile to the best of our ability. If use_screen_luminance is disabled then it is up to the user to decide upon SDR/HDR with use_hdr and their values which may or may not be efficient.

I believe we should aim for as much transparency as possible when implementing this feature and this implementation of use_screen_luminace seems to me quite transparent as ideally it should require no configuration on the part of the developer or the end user, it can simply be enabled to allow for efficient transformation of SDR content on HDR screens or graceful clipping of HDR content on SDR screens. This could be by either godot internally (if use_screen_luminance is enabled) or the os/compositor/screen (if both use_screen_luminance is disabled and use_hdr is enabled). On systems where external clipping is known to be faulty (e.g. windows, mac) this can be restricted however you best see fit (e.g. complete failure, warnings, errors, etc.) however since I know of no such faults on wayland I see no reason to disallow outputting HDR content on SDR screens as long as both use_screen_luminance is disabled and use_hdr is enabled.

@ArchercatNEO
Copy link
Contributor Author

Without a change to how the display/window/hdr/enabled works we can't really implement "implicit" hdr and using the preferred profile with screen_luminance consistently. If we say screen_supports_hdr is "unconditionally" true (depends on compositor supports) but then check for max_luminance > sdr_white to actually enable it we're going to get bug reports. If the project setting didn't just enable hdr on all windows it's a simple fix.

bool enable_hdr = (GLOBAL_GET("display/window/hdr/enabled") && max_luminance > sdr_white) || window->hdr_enabled)

But there's a bit more I could do without that so that's this. I'd like it if there was more consistency but I think just pushing developers towards screen_luminance in docs will have to suffice. If they implement an hdr settings menu they're already not doing the preferred profile so that's really beyond attempting to optimise engine side (as far as this pr is concerned).

@Zamundaaa
Copy link

Without a change to how the display/window/hdr/enabled works we can't really implement "implicit" hdr and using the preferred profile with screen_luminance consistently.

I thought there was a preferred vs. supported distinction?

But if not, just setting supported only to true if the image description has max luminance > reference luminance would be totally fine. Like I wrote in #94496, letting the compositor tonemap in that situation is a really niche thing that you don't have to put any effort in for.

@ArchercatNEO
Copy link
Contributor Author

As per a recommendation by riteo: now the color state of screens is in ScreenState instead of ScreenData. I did have to expose a new method to actually get ScreenState from the DisplayServer so I hope that's okay. Good news is with this change the wp_color_management_output_v1s actually get destroyed now so less memory leaks.

@ArchercatNEO
Copy link
Contributor Author

I have undone the changes relating to compound_2_4 and added a comment that explains a bit more why we unset the image description (relating to monitors possibly performing glare compensation or not). This was after a lengthy post by Allen which he will repost here himself at some point. That post explained what glare compensation and why it matters for this, I was going to propose changes to the protocol to make support for glare compensation better but before formally doing that I will allow Allen to explain in his own words what the problem is.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally on https://github.com/DarkKilauea/godot-hdr-output (rebased on top of master bf95b62), it works as expected. Code looks good to me.

This time, it's working well on NVIDIA with no special layers installed.

PC specifications
  • CPU: AMD Ryzen 9 9950X3D
  • GPU: NVIDIA GeForce RTX 5090 (driver 580.119.02)
  • RAM: 64 GB (2×32 GB DDR5-6000 CL30)
  • SSD: Solidigm P44 Pro 2 TB
  • OS: Linux (Fedora 43)
Operating System: Fedora Linux 43
KDE Plasma Version: 6.5.5
KDE Frameworks Version: 6.22.0
Qt Version: 6.10.1
Kernel Version: 6.18.8-200.fc43.x86_64 (64-bit)
Graphics Platform: Wayland

Some notes:

  • Custom reference and max luminance are not supported. Values are always read from the system settings.
ERROR: DisplayServer does not support setting a manual reference luminance
   at: window_set_hdr_output_reference_luminance (platform/linuxbsd/wayland/display_server_wayland.cpp:1537)
   GDScript backtrace (most recent call first):
       [0] _on_custom_reference_luminance_toggled (res://hdr_controls.gd:85)
ERROR: DisplayServer does not support setting a manual maximum luminance
   at: window_set_hdr_output_max_luminance (platform/linuxbsd/wayland/display_server_wayland.cpp:1555)
   GDScript backtrace (most recent call first):
       [0] _on_custom_max_luminance_toggled (res://hdr_controls.gd:96)
  • This is printed when starting the HDR demo:
WARNING: HDR output requested but no HDR compatible format was found, falling back to SDR.
     at: _determine_swap_chain_format (drivers/vulkan/rendering_device_driver_vulkan.cpp:3506)

However, HDR output clearly appears to be functional (and looks correct, with only highlights becoming brighter and more detailed).

  • HDR output does not work correctly when using llvmpipe (--gpu-index 1). You can enable HDR, but everything will look washed out, almost as if you were looking at linear output in sRGB space. This is probably out of our control, but perhaps we should document it if it can be consistently reproduced across systems.

// If the surface requests HDR output, try to get an HDR format.
if (context_driver->get_colorspace_externally_managed()) {
// When the colorspace is externally managed, only the data format matters.
// TODO: determine if there are any drivers which do not support VK_FORMAT_R16G16B16A16_SFLOAT at all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be the issue with llvmpipe, I don't know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have investigated this now and have some findings.

First of all, we have indeed found a driver which doesn't support VK_FORMAT_R16G16B16A16_SFLOAT, but it does support VK_FORMAT_R16G16B16A16_UNORM. If I set the format to that we are able to get HDR but get a lot of banding.
llvmpipe_hdr_unorm

Secondly, the thing about it being washed out is actually our fault. Effectively when we enable HDR, the Wayland display server assumes that it actually worked and sets the color space to linear but since it failed due to missing format support, we render sRGB and decode with linear which is what happens here.

In theory we could query the rendering context to ask for the colorspace, but that's only updated the frame after we attempt to set HDR and I don't know if there's any loop in the display server which is synced with frames.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we could query the rendering context to ask for the colorspace, but that's only updated the frame after we attempt to set HDR and I don't know if there's any loop in the display server which is synced with frames.

process_events is a direct part of OS run loop, always called right before Main::iteration:

while (true) {
GodotProfileFrameMark;
GodotProfileZone("OS_LinuxBSD::run");
DisplayServer::get_singleton()->process_events(); // get rid of pending events
#ifdef SDL_ENABLED
if (joypad_sdl) {
joypad_sdl->process_events();
}
#endif
if (Main::iteration()) {
break;
}
}

Now, as far as I understand it this means that, as long as low consumption mode is not enabled, you will get a frame after process_events (and thus an eventual wl_surface.commit).

If low consumption mode is enabled you will need to check for RenderingServer::get_singleton()->has_changed(). Then you know that it will render a frame.

This is how I handled frame throttling ("vsync emulation"), see the relevant DisplayServerWayland code:

if (emulate_vsync) {
// Due to the way legacy suspension works, we have to treat low processor
// usage mode very differently than the regular one.
if (OS::get_singleton()->is_in_low_processor_usage_mode()) {
// NOTE: We must avoid committing a surface if we expect a new frame, as we
// might otherwise commit some inconsistent data (e.g. buffer scale). Note
// that if a new frame is expected it's going to be committed by the renderer
// soon anyways.
if (!RenderingServer::get_singleton()->has_changed()) {
// We _can't_ commit in a different thread (such as in the frame callback
// itself) because we would risk to step on the renderer's feet, which would
// cause subtle but severe issues, such as crashes on setups with explicit
// sync. This isn't normally a problem, as the renderer commits at every
// frame (which is what we need for atomic surface updates anyways), but in
// low processor usage mode that expectation is broken. When it's on, our
// frame rate stops being constant. This also reflects in the frame
// information we use for legacy suspension. In order to avoid issues, let's
// manually commit all surfaces, so that we can get fresh frame data.
wayland_thread.commit_surfaces();
try_suspend();
}
} else {
try_suspend();
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed this thread in general in 2 commits: the first adds support for checking if enabling HDR failed or not. It's a bit dirty because we are only able to check what colorspace the driver used on the frame after we enable HDR so there will be a frame where we claim the surface is using ext linear when it was actually rendered with nonlinear sRGB. I'm not sure if there's a way to make sure the colorspace we report is always correct with API modifications but at least HDR failing will fallback to SDR eventually.

The second simply adds supports for using VK_FORMAT_R16G16B16A16_UNORM so that llvmpipe can do HDR. If there's no major concerns about this we can add it, otherwise we can drop the commit (and I can edit the TODO to mention llvmpipe).

Comment on lines +971 to +977
static constexpr struct wp_color_manager_v1_listener wp_color_manager_listener = {
.supported_intent = _wp_color_manager_on_supported_intent,
.supported_feature = _wp_color_manager_on_supported_feature,
.supported_tf_named = _wp_color_manager_on_supported_tf_named,
.supported_primaries_named = _wp_color_manager_on_supported_primaries_named,
.done = _wp_color_manager_on_done,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Designated initializers are a C++20 feature, do we accept them on master yet? Or is this a special case where they could be used in C++17?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the convention for wayland protocol callbacks that was already here. @deralmas could probably explain why an exception was made here better than me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This convention happened because I originally had no idea it was a C only thing xD

Given that the rest of the code does it, I'd keep it like this and figure out if it's worth to get rid of this feature (by removing the initalizers and put the field names in a comment or something) or just wait for the bump to C++20 if it's near.

@ArchercatNEO
Copy link
Contributor Author

Thank you for the review! And good to know Nvidia HDR is working without any problems now.

I applied several of the suggested comment changes, hopefully got them right. As for the warning that gets printed, that should also be fixed now, it was an oversight where we were checking the vulkan colorspace (which may or not represent the actual colorspace we're targetting) instead of the RDD colorspace we will actually use.

As for llvmpipe, not sure what's happening there. I will try to test it at some point and see if there's anything I can do on our end but at least it should be a minor enough edgecase.

@allenwp
Copy link
Contributor

allenwp commented Feb 13, 2026

From a quick glance at the code, I don't see anything that stands out as being obviously incorrect, but I haven't done a thorough review because I don't currently have a setup that I can test with. I'm happy to see that Calinou has been able to test this now, thanks for that! 🎉

Especially with the Steam Machine, I we'll need to see how Steam OS handles reference and max luminance. If they provide very clear user-facing ways to adjust brightness (reference white luminance) and max luminance of external displays, the current state of this PR will be good. If they don't, then we'll need to add the ability to change these two parameters in Godot instead of this PR's current behaviour. This can obviously be done in a followup PR when the time comes, and only if necessary.

Thanks for your work on this, ArchercatNEO!

Copy link
Contributor

@deralmas deralmas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know basically nothing about HDR so I did a review of the Wayland-specific parts. Just a few nitpicks and notes before I approve, but overall this looks awesome!

(The Ref<T> comment was meant to be part of this review, sorry 😅 )

@ArchercatNEO ArchercatNEO force-pushed the wayland-hdr branch 2 times, most recently from b67c1ee to aefc2cd Compare February 15, 2026 11:16
Copy link
Contributor

@deralmas deralmas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Wayland side of things looks great!

Copy link
Member

@bruvzg bruvzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good. I was not able to test it, my Fedora install refuse to detect HDR monitor (which works fine on Windows on the same system).

Copy link
Contributor

@DarkKilauea DarkKilauea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with the current state of this PR.

We'll have to make some changes in the Android PR if this gets merged before that to address setting the RDD::Colorspace correctly. It might be worth having a follow up PR after all of the HDR PRs are merged to streamline the selection logic for the format and color space.

@allenwp
Copy link
Contributor

allenwp commented Feb 23, 2026

@ArchercatNEO Would you mind updating this PR's description with details about what is required for HDR output? Without considering Linux, this is what I have so far:

Steps to enable HDR output in your project:

  1. Ensure no Environment resources use SDR-only features:
    • Tonemap Mode: Filmic or ACES
    • Glow Blend Mode: Soft Light
    • Adjustments: Color Correction
  2. Configure the rendering/rendering_device/driver project setting to the following:
    • macOS: metal
    • iOS: metal
    • Windows: d3d12
  3. Turn on the rendering/viewport/hdr_2d project setting and enable use_hdr_2d for all Subviewports and Windows that should support HDR output.
  4. Turn on the display/window/hdr/request_hdr_output project setting and enable hdr_output_requested for all other Windows that should support HDR output.
  5. [Optional] Provide in-game settings for the player to adjust HDR brightness and maximum luminance by copying in_game_hdr_settings from this demo to your project.

Does there need to be something about the display/display_server/driver.linuxbsd project setting? (Does this need to be set to default or wayland?) Also, does the run/platforms/linuxbsd/prefer_wayland editor setting also need to set to On or can this be on or off to get HDR output in the editor?

@ArchercatNEO
Copy link
Contributor Author

For HDR output we need the Wayland display server, "default" for Linux is still X11 so we need to explicitly enable it.

For the project you need to set display/display_server/driver.linuxbsd to wayland (again because "default" is still X11). Projects don't look at editor settings so unless users are using command line arguments to run the games they will get X11/XWayland.

The editor uses a different method for choosing a display server (run/platforms/linuxbsd/prefer_wayland) and fully ignores the project setting. If the project is embedded in the editor it will inherit the display server that the editor uses instead of using the project setting which means it's possible to get weird behaviour where embedded and exported games behave differently.

I will update main PR description, though do I need to mention any of this in actual doc changes in this PR?

@allenwp
Copy link
Contributor

allenwp commented Feb 23, 2026

Thanks!

I will update main PR description, though do I need to mention any of this in actual doc changes in this PR?

I see you already have this in the changes to doc/classes/DisplayServer.xml

The editor uses a different method for choosing a display server (run/platforms/linuxbsd/prefer_wayland) and fully ignores the project setting. If the project is embedded in the editor it will inherit the display server that the editor uses instead of using the project setting which means it's possible to get weird behaviour where embedded and exported games behave differently.

Hmm. I'm curious why there is a separate editor setting at all, it sounds like a recipe for problems and also adds complexity to things like the documentation I'm writing for HDR output. Might be worth revisiting this in a separate proposal/PR and removing the editor setting entirely so the editor just follows the project settings if there isn't a good reason that still exists to have a separate editor setting.

@deralmas
Copy link
Contributor

Hmm. I'm curious why there is a separate editor setting at all, it sounds like a recipe for problems and also adds complexity to things like the documentation I'm writing for HDR output. Might be worth revisiting this in a separate proposal/PR and removing the editor setting entirely so the editor just follows the project settings if there isn't a good reason that still exists to have a separate editor setting.

The main rationale is that people might want to use the editor through Wayland but still export with the default display driver order (X11, Wayland) for stability. This was especially relevant when the Wayland backend was still single-window only, but I think that it's still useful to have this distinction until it becomes the default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.