X Tutup
Skip to content

Apple: Support output to EDR (HDR) displays#106814

Merged
akien-mga merged 1 commit intogodotengine:masterfrom
stuartcarnie:rendering/hdr-output
Mar 2, 2026
Merged

Apple: Support output to EDR (HDR) displays#106814
akien-mga merged 1 commit intogodotengine:masterfrom
stuartcarnie:rendering/hdr-output

Conversation

@stuartcarnie
Copy link
Contributor

@stuartcarnie stuartcarnie commented May 26, 2025

Add support for EDR displays to Apple platforms.

Depends on

Implements godotengine/godot-proposals#10817 for Apple platforms
Testing/Sample project: https://github.com/DarkKilauea/godot-hdr-output

  • 64-bit (RGBA 16-bit float)
  • Metal renderer and context updated
  • Vulkan HDR support disabled for Apple platforms
  • macOS display driver
  • macOS embedded display driver
  • Apple Embedded display driver (for iOS, visionOS)

Note

Apple's HDR Content page is also useful.

In particular, Understanding EDR Values:

On Macs that support EDR, a 1.0 value for a pixel corresponds to the maximum standard dynamic range (SDR) brightness level, but this brightness level doesn’t have to correspond to the maximum brightness of the display. If the display can produce higher brightness levels, additional range (headroom) may be available. The amount of headroom may vary, depending on the actual display characteristics:

  • If the UI brightness level is at 100 nits and the panel is capable of a maximum brightness of 400 nits, then enabling EDR allows values up to 4.0 to be displayed at 400 nits.
  • On the same panel, if the user adjusts the UI brightness level to 200 nits, then 1.0 values are displayed at 200 nits, and 2.0 values are at 400 nits.

Important

visionOS does not provide APIs to query the EDR headroom or maximum potential, so these are hard-coded to 100 nits and 200 nits maximum, which was learned from https://developer.apple.com/videos/play/wwdc2023/10089/?time=603

@ArchercatNEO
Copy link
Contributor

There have been some upstream API changes (like splitting screen_luminance into auto_adjust_reference_luminance and auto_adjust_maximum_luminance) which this PR has not been updated to cover. Do you plan on updating this PR once the upstream one has been merged? Asking because some things like high/low precision have different behaviour on wayland than it did on windows (on windows low precision is faster, on wayland high precision is faster) so mac might have even more different behaviour than either of them. Windows HDR on vulkan is broadly broken but it seems you've verified than on mac things work well?

The primary useful point of data would be which of high/low precision is faster on macos (and that they both work, on wayland they used to not work until changes were made to the godot vulkan driver).

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 with the MRP from #94496 (review), it works as expected (Metal and Vulkan).

Mac specifications
  • MacBook Pro 16 2024
  • SoC: M4 Max 16-core CPU, 40-core GPU
  • RAM: 48 GB
  • SSD: 1 TB
  • OS: macOS 15.5

FSR2 works on Metal and MoltenVK, and MetalFX Temporal works on Metal when HDR is enabled (with no unintended effects). However, MetalFX Spatial is broken on my end, but I also encountered this issue with HDR disabled and even on 4.4.1.stable, so it's not related to this PR:

Screenshot 2025-08-07 at 16 10 33

The Use Screen Luminance project setting seems to be working. When enabled, there's a perfect match for UI brightness between SDR and HDR (that's macOS advantage at play 😛). However, changing Max Luminance doesn't seem to do anything, even when Use Screen Luminance is unchecked and after restarting the editor.

When using Metal, Prefer High Precision impacts visuals significantly on bright highlights, especially red ones. Red highlights become orange-ish when the option is disabled, so having it enabled is more accurate to the tonemapped SDR presentation (except brighter). This shift in tone is not present when using MoltenVK (where both options look indistinguishable to the naked eye... with one exception I could spot below), even though there's still a performance difference in small window sizes.

Off On
Screenshot 2025-08-07 at 15 46 14 Screenshot 2025-08-07 at 15 46 21

Notice the blue hue in the reflection being more present when Prefer High Precision is enabled. This difference is present in both Metal and MoltenVK. (Sorry for the tonemapped screenshots, but I couldn't figure out how to take a proper HDR screenshot on macOS.)

Lastly, I get this error on every startup:

ERROR: Not implemented
   at: window_is_hdr_output_using_screen_luminance (platform/macos/display_server_macos.mm:3218)

I'm using Reinhard tonemapping with a whitepoint of 4.0 for all tests here, as it seems to provide comparable visuals to Reinhard tonemapping with a whitepoint of 1.0 when HDR is off (except with bright highlights actually being brighter on screen this time around).

Benchmark

Running the test project. I've tested each run at least twice as many of the results are unexpected, namely HDR off being slower than HDR on (with high precision disabled).

Also, HDR on with high precision is basically just as fast in this test project, especially at lower resolutions. In fullscreen, HDR on with high precision is faster than HDR on without high precision. This contrasts with the usual wisdom of "HDR output has a 5-10% performance cost on the GPU", which I've seen and experienced in many (Windows) games.

1152x648 window

Backend HDR off HDR on HDR on (prefer high precision)
Metal 681 FPS (1.47 mspf) 1443 FPS (0.69 mspf) 688 FPS (1.45 mspf)
MoltenVK 694 FPS (1.44 mspf) 1439 FPS (0.69 mspf) 705 FPS (1.42 mspf)

Fullscreen

Backend HDR off HDR on HDR on (prefer high precision)
Metal 385 FPS (2.60 mspf) 360 FPS (2.78 mspf) 374 FPS (2.67 mspf)
MoltenVK 347 FPS (2.88 mspf) 326 FPS (3.07 mspf) 343 FPS (2.92 mspf)

@ArchercatNEO
Copy link
Contributor

@Calinou Thanks for testing this on Mac. There's a chance that some of this is from old bugs that have since been since been fixed (this hasn't been rebased in a while) but most likely there's some very important performance considerations around high/low precision on mac that would be worth bringing up with allenwp.

As for the hue shifts hopefully it's just a bug in the metal implemtation (the colorspaces chosen in metal say something about P3 which seems strange to me) but if there's also a bug in the vulkan implementation it's good to have caught it here.

@stuartcarnie
Copy link
Contributor Author

Do you plan on updating this PR once the upstream one has been merged?

@ArchercatNEO absolutely! Just waiting for the upstream PR to stabilise and merge and I'll rework this PR. I'm very excited to add EDR support for Apple.

@stuartcarnie
Copy link
Contributor Author

@Calinou identified a bug in how the MetalFX spatial shader is configured, that has been fixed in #109406

@stuartcarnie
Copy link
Contributor Author

stuartcarnie commented Aug 7, 2025

Running the test project. I've tested each run at least twice as many of the results are unexpected, namely HDR off being slower than HDR on (with high precision disabled).

I wonder if you would observe the same if you target a non-HDR display? Pure speculation, but is it possible that Apple's rendering / compositing pipeline on the MacBook Pro's built-in display is always HDR, so SDR content must be converted?

Generally, I've seen all Metal literature indicate the default pixel format it BGRA8 for views.

@MaddTheSane
Copy link

I think you can set the colorspace of the display to be SDR-only.

@oscarbg
Copy link

oscarbg commented Aug 24, 2025

any way to get a precompiled build of this PR (a github actions CI link with artifact?) to do some testing on a mac Mini M4 with HDR monitor?

I have tested succesfully godot HDR Windows and Linux HDR support using artifacts.. but seems the current build has not Mac build artifact:
https://github.com/godotengine/godot/pull/106814/checks?check_run_id=42881854888
I'm ok?

@allenwp
Copy link
Contributor

allenwp commented Sep 26, 2025

any way to get a precompiled build of this PR (a github actions CI link with artifact?) to do some testing on a mac Mini M4 with HDR monitor?

I have tested succesfully godot HDR Windows and Linux HDR support using artifacts.. but seems the current build has not Mac build artifact: https://github.com/godotengine/godot/pull/106814/checks?check_run_id=42881854888 I'm ok?

I just looked into doing a rebase and pushing that, which would generate a new build artifact, but I'm having issues getting this PR working on my Mac when running a game project (before or after my rebase). We'll need to wait a bit longer to really test out Mac OS HDR output support.

@allenwp allenwp force-pushed the rendering/hdr-output branch from ec3f97b to ca58450 Compare September 26, 2025 18:38
@allenwp
Copy link
Contributor

allenwp commented Sep 26, 2025

Alright, it turns out my issues were just that I needed to run the game without the "Embed on next play" option selected in my Game workspace. Once I did this, it works fine.

I pushed the rebase and updates to fix colour space issues, but this PR now fails checks due to 'maximumPotentialExtendedDynamicRangeColorComponentValue' is only available on macOS 10.15 or newer [-Werror,-Wunguarded-availability-new]. It builds fine if you build it locally!

@allenwp allenwp force-pushed the rendering/hdr-output branch from a396ee6 to 187a9db Compare October 2, 2025 19:05
@stuartcarnie
Copy link
Contributor Author

but this PR now fails checks due to 'maximumPotentialExtendedDynamicRangeColorComponentValue' is only available on macOS 10.15 or newer [-Werror,-Wunguarded-availability-new]

I'll have to add some availability checks to guard against running on older OSs

@allenwp allenwp mentioned this pull request Oct 14, 2025
@allenwp allenwp force-pushed the rendering/hdr-output branch from 9dc1f3f to 232a6dc Compare October 17, 2025 18:10
@allenwp
Copy link
Contributor

allenwp commented Oct 24, 2025

@stuartcarnie I've made changes to this PR to correct the way that Max/Reference luminance is handled. The rebase is going to be substantial, so please take note of the current state of the PR before the rebase; put simply, max luminance needs to be calculated as follows:

// Note: this absolute nits value is incorrect when reference luminance is below 100 nits.
// But that's OK because it will still be correct *relative* to reference luminance.
float max_luminance = screen.maximumPotentialExtendedDynamicRangeColorComponentValue * 100.0f;

And reference luminance can be calculated based on that and the current max value:

maximum_value = screen.maximumExtendedDynamicRangeColorComponentValue;

// Note: this absolute nits value is incorrect when reference luminance is below 100 nits.
// But that's OK because it will still be correct *relative* to max luminance.
float reference_luminance = max_luminance / maximum_value;

@stuartcarnie
Copy link
Contributor Author

I've made changes to this PR to correct the way that Max/Reference luminance is handled. The rebase is going to be substantial, so please take note of the current state of the PR before the rebase;

@allenwp sounds good! Fortunately, GitHub makes it easy to compare between commits / rebases

Copy link
Contributor

@allenwp allenwp left a comment

Choose a reason for hiding this comment

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

Thanks, Stuart, I think we're very close to finishing this!

In addition to my other suggestions, <constant name="FEATURE_HDR_OUTPUT" value="35" enum="Feature"> of doc/classes/DisplayServer.xml needs to be updated with supported platforms.

I've finished looking over the PR now; I expect I won't have any further review comments or suggestions.

@stuartcarnie stuartcarnie force-pushed the rendering/hdr-output branch 2 times, most recently from 4933a14 to 6207597 Compare February 26, 2026 01:30
@stuartcarnie stuartcarnie requested a review from allenwp February 26, 2026 01:43
@stuartcarnie
Copy link
Contributor Author

@allenwp

  • ✅ hardware reference luminance normalised; 100.0f for macOS, 100.0f for visionOS and 203.0f for iOS
  • ✅ DisplayServer.xml updated
  • ✅ Updated error to "Apple embedded devices"

@stuartcarnie
Copy link
Contributor Author

@allenwp I re-synced and pushed the display_server_macos_base.mm file, so it should be good now.

@stuartcarnie stuartcarnie requested a review from allenwp February 26, 2026 18:24
Copy link
Contributor

@allenwp allenwp left a comment

Choose a reason for hiding this comment

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

To me, this PR appears to be working well and is ready for merging! Thanks, Stuart!

Some notes worth mentioning:

  • I have not tested visionOS changes so I cannot verify if they are implemented correctly.
  • Vulkan support should probably be added as a followup PR, since we'll need to maintain Vulkan HDR output behaviour for Wayland and Android anyway. The implementation should be trivial (it might be just a matter of setting hdr_linear_luminance_scale to 100.0 on macOS and visionOS and... maybe 203 on iOS??)
  • I made a comment about _THREAD_SAFE_METHOD_ in DisplayServerAppleEmbedded::window... functions. I know basically nothing about threading in Godot, so I leave that to someone else to verify.

I'm really excited to see this out in the wild because macOS and iOS have the best HDR output implementations with the highest colour consistency and accuracy of all major platforms. Great work, Stuart!

@blueskythlikesclouds
Copy link
Contributor

I don't seem to be able to compile this PR on my MBP M4 Pro for some reason:

platform/macos/display_server_macos_base.mm:167:20: error: no visible @interface for 'NSScreen' declares the selector 'CGDirectDisplayID'
  167 |                 return [p_screen CGDirectDisplayID];

Any idea why?

@iuymatiao
Copy link

iuymatiao commented Feb 27, 2026

@blueskythlikesclouds One time, I also had trouble compiling for Mac using scons, but the issue went away after restarting the OS. Not sure why this helps, though.

Edit: I should clarify that this OS restart was due to applying a security patch for macOS Sequoia. But the end result was still that Mac builds started working again after the restart.

@hsvfan-jan
Copy link

hsvfan-jan commented Feb 27, 2026

No issue with basic scons platform=macos arch=arm64 in the terminal on MBP M2 Pro, macOS 26.3. Maybe specific to the newer M-series chips.

@bruvzg
Copy link
Member

bruvzg commented Feb 27, 2026

I don't seem to be able to compile this PR on my MBP M4 Pro for some reason

I do not get this error on M4 Pro, with Xcode 26.3 (17C529).

This property is documented as macOS 26+, but it's in @available(macOS 26.0, *) so it should not be an issue with older Xcode versions.

https://developer.apple.com/documentation/appkit/nsscreen/cgdirectdisplayid-7uvhw?language=objc

@blueskythlikesclouds
Copy link
Contributor

blueskythlikesclouds commented Feb 27, 2026

Hmm, odd. It doesn't compile from VSCode or the terminal but compiling within XCode works.

For reference, my macOS version is Sequoia 15.7.3

@bruvzg
Copy link
Member

bruvzg commented Feb 27, 2026

Hmm, odd. It doesn't compile from VSCode or the terminal but compiling within XCode works.

It might be a mismatch of Xcode and command line tools, which sometimes happens:

  • Check the output of xcode-select -p command, if it's not /Applications/Xcode.app/Contents/Developer, run sudo xcode-select -s /Applications/Xcode.app/Contents/Developer.
  • If it won't help, delete /Library/Developer/CommandLineTools and run xcode-select --install to reinstall command line tools.

@iuymatiao
Copy link

iuymatiao commented Feb 27, 2026

@blueskythlikesclouds I was on a similar version of Sequoia when I ran into compilation issues. Then I upgraded to 15.7.4 and the compile succeeded.

See if that fixes the issue for you.

@Repiteo Repiteo requested review from blueskythlikesclouds and removed request for DarkKilauea February 27, 2026 21:26
@blueskythlikesclouds
Copy link
Contributor

Check the output of xcode-select -p command, if it's not /Applications/Xcode.app/Contents/Developer, run sudo xcode-select -s /Applications/Xcode.app/Contents/Developer.

This works.

@akien-mga akien-mga merged commit f1ce0e6 into godotengine:master Mar 2, 2026
20 checks passed
@akien-mga
Copy link
Member

akien-mga commented Mar 2, 2026

Thanks! Amazing work from everyone involved! 🎉

@shakesoda
Copy link
Contributor

getting a build failure on this post-merge:

platform/macos/display_server_macos_base.mm:167:20: error: no visible @interface for 'NSScreen' declares the selector 'CGDirectDisplayID'
  167 |                 return [p_screen CGDirectDisplayID];

on m4 mac mini running 15.7.4 w/xcode 16.4, macos 15.5 sdk

@blueskythlikesclouds
Copy link
Contributor

blueskythlikesclouds commented Mar 2, 2026

See the discussion above, I had the same problem. #106814 (comment)

Make sure your Xcode is updated.

@shakesoda
Copy link
Contributor

See the discussion above, I had the same problem. #106814 (comment)

i should've scrolled up, thanks!

@stuartcarnie stuartcarnie deleted the rendering/hdr-output branch March 4, 2026 03:50
stuartcarnie added a commit to stuartcarnie/godot that referenced this pull request Mar 7, 2026
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.

X Tutup