Inconsistent HDR video exposure/reference white on external HDR monitor vs MBP XDR internal display
mpv Information
andrewke@Andrews-MacBook-Air ~ % /Volumes/Macintosh\ HD/Applications/mpv.app/Contents/MacOS/mpv --version
Error parsing option target-colorspace-hint (option not found)
/Users/andrewke/.config/mpv/mpv.conf:26: setting option target-colorspace-hint='yes' failed.
Error parsing option gamut-mapping-mode (option not found)
/Users/andrewke/.config/mpv/mpv.conf:56: setting option gamut-mapping-mode='clip' failed.
mpv 0.34.1 Copyright © 2000-2021 mpv/MPlayer/mplayer2 projects
built on Sat Jan 8 16:50:23 CET 2022
FFmpeg library versions:
libavutil 56.70.100
libavcodec 58.134.100
libavformat 58.76.100
libswscale 5.9.100
libavfilter 7.110.100
Other Information
- macOS version:macOS 26.1 (25B5042k)
- Source of mpv: latest nightly build https://github.com/mpv-player/mpv/actions/runs/18327328279
- Latest known working version: NIL
- Issue started after the following happened:NIL
Reproduction Steps
- Use the MPV config below
- Attach an external HDR monitor (I am using the Samsung S95F OLED TV)
- Use the macOS settings brightness slider to set HDR monitor brightness to SDR~100 nits. Check using console (Display 2 swap brightness: 100.219, limit: nan, indicator brightness: nan, ambient lux: nan, low ambient strength: nan, high ambient strength: nan, contrast preservation: nan, contrast enhancer: nan)
- Play
100 203 nit.MOVusing MPV attached to this bug report (PQ HDR video containing 100 and 203 nits test pattern)
Config:
autofit-larger=100%x100% loop-playlist=inf
--hwdec=auto-safe
video-sync=display-resample vo=gpu-next
--target-colorspace-hint --target-prim=bt.2020 --target-trc=pq [HDR] target-peak=1000 --tone-mapping=clip
profile-cond = p["video-params/colormatrix"]~="dolbyvision" and p["video-params/sig-peak"]>1
--vf-clr
Expected Behavior
I expect the HDR video to have the same exposure on an external HDR monitor as compared to the MacBook Pro 16 in ST2084 reference mode. I expect that the HDR video’s 100 nit test pattern to match the brightness of the 100 nit UI SDR white.
Current behavior:
- On external HDR monitor, in MPV the 100 nits test pattern is darker than SDR UI white, 203 nits test pattern is the same brightness as SDR UI white. Effectively the video is at half the exposure
- On MBP 16 M1 Pro internal display in reference ST2084 mode, in MPV the 100 nits test pattern is the same brightness as SDR UI white
In summary, HDR videos are shown as half the exposure on an external HDR monitor
Desired behaviour: There is an option to set reference white. If the user chooses reference white as 100 nits, the following behavior occurs:
- On both internal and external HDR displays, in MPV the 100 nits test pattern is the same brightness as SDR UI white
In summary, I would like to request for an option to interpret PQ 100 nits as reference white (EDR 1.0) when MPV is used on an external monitor.
Actual Behavior
Current behaviour mentioned previously.
Log File
Sample Files
Sample video with test patterns at 100 and 203 nits. https://github.com/user-attachments/assets/e7b8edb6-48d6-47bc-9c49-65bb36c2b2db
Screenshots of MPV displaying the test patterns. The HDR screenshots are captured in PQ BT2020 space.
Note, to view the screenshots in the correct brightness, please use Davinci Resolve in a PQ BT2020 timeline, or use Lightroom in HDR mode and set exposure +1.02
macOS preview is able to show the screenshots, although it is slightly dark. Nonetheless, one can see that the 203 nit screenshot matches SDR white.
I carefully read all instruction and confirm that I did the following:
- [x] I tested with the latest mpv version to validate that the issue is not already fixed.
- [x] I provided all required information including system and mpv version.
- [x] I produced the log file with the exact same set of files, parameters, and conditions used in "Reproduction Steps", with the addition of
--log-file=output.txt. - [x] I produced the log file while the behaviors described in "Actual Behavior" were actively observed.
- [x] I attached the full, untruncated log file.
- [x] I attached the backtrace in the case of a crash.
In SDR output mode, you can control your reference white level with --target-peak, for example --target-peak=100 will map "0"-100 nits to SDR transfer. Note that libplacebo uses 203 nits as SDR white reference internally, but as I understand you are outputting fully PQ.
If you are outputting PQ from mpv, with higher target-peak, as you do (1000) there is no mapping done by mpv.
On external HDR monitor, in MPV the 100 nits test pattern is darker than SDR UI white, 203 nits test pattern is the same brightness as SDR UI white. Effectively the video is at half the exposure
That means either SDR UI is rendered at 203 or PQ output is misrepresented by macOS by additional tone mapping.
Might also be related to https://github.com/mpv-player/mpv/issues/15138 which has to be debugged by someone with access to the system, at this point, I'm not sure what's going on there.
mpv 0.34.1 Copyright © 2000-2021 mpv/MPlayer/mplayer2 projects
built on Sat Jan 8 16:50:23 CET 2022
We support only latest release version.
Hi Kasper, thanks for the reply.
I ran a recent nightly build and the behaviour is the same. (build info below) HDR video is shown with half the exposure on an external HDR monitor. PQ 203 nits in the media matches SDR white.
I believe this is due to macOS's additional tonemapping (scale linear exposure by 1/2.03), activated when an external HDR display is connected. This is only applied to the video region, SDR UI is unaffected.
Perhaps a temporary solution is to scale linear exposure by 2.03 in MPV, via a user setting. Do you know if this is possible using the MPV config?
I found this line #define MP_REF_WHITE 203.0 in video/csputils.h and I am wondering if changing it to 100.0 helps
andrewke@Andrews-MacBook-Air ~ % /Applications/mpv.app/Contents/MacOS/mpv --version mpv v0.40.0-dev-g040186a1c Copyright © 2000-2025 mpv/MPlayer/mplayer2 projects built on Oct 7 2025 22:04:06 libplacebo version: v7.351.0 FFmpeg version: 8.0 FFmpeg library versions: libavcodec 62.11.100 libavdevice 62.1.100 libavfilter 11.4.100 libavformat 62.3.100 libavutil 60.8.100 libswresample 6.1.100 libswscale 9.1.100 andrewke@Andrews-MacBook-Air ~ %
I believe this is due to macOS's additional tonemapping (scale linear exposure by 1/2.03), activated when an external HDR display is connected. This is only applied to the video region, SDR UI is unaffected.
If that's the cases, this is unfortunate, because PQ scale suppose to be absolute, so I'm not sure why they scale it here, while keeping other things like SDR UI at same level as on the other screen. It may be some assumption on their end, which is unknown to us.
Perhaps a temporary solution is to scale linear exposure by 2.03 in MPV, via a user setting. Do you know if this is possible using the MPV config?
We have --inverse-tone-mapping that could boost the luminance to target. But this is limited to signal > 203 nits, to avoid blowing up low nits contents.
You could use --glsl-shader with simple shader to linearly scale in pq space. Or --contrast which basically multiplies the input.
I found this line #define MP_REF_WHITE 203.0 in video/csputils.h and I am wondering if changing it to 100.0 helps
Not really. The issue for you is fully in PQ -> PQ space, there is no SDR white involved. It just looks like that for some reason.
In fact could you show stats to confirm that (shift+i keybind).
Thanks for the information. Does MPV use a CALayer backed by OpenGL?
Apple allows the app to perform its own tone mapping if it uses a Metal layer https://developer.apple.com/documentation/metal/performing-your-own-tone-mapping
Is there any plan to transition to using a CAMetalLayer?
Thanks for the information. Does MPV use a CALayer backed by OpenGL?
We are using Vulkan to set colorspaces, what happens under the hood in MoltenVK is another story. Though it sets things ok, last time I checked the code. And yes, we render to CAMetalLayer.
Apple allows the app to perform its own tone mapping if it uses a Metal layer https://developer.apple.com/documentation/metal/performing-your-own-tone-mapping
This explain how to output tone mapped linear rgb. We don't need this specifically, because mpv can output tonemapped HDR10, which is the most common case.
Could you show stats page (shift+i) to confirm what output do you use?
I have attached the MPV stats page below.
My understanding is that, if you
- Use a HDR color space like itur_2100_PQ, then Apple might perform HDR tonemapping
- Use an extended linear color space like extendedLinearITUR_2020, then macOS does not perform additional tonemapping https://developer.apple.com/documentation/metal/performing-your-own-tone-mapping
So I agree that conversion to linear RGB is not necessary in an ideal world. The intention is for it to bypass macOS tonemapping.
My idea is that MPV can convert the video to BT 2020 linear color space, with the scaling 100 nit = 1.0 float, and set the CAMetalLayer colorspace to extendedLinearITUR_2020
Use a HDR color space like itur_2100_PQ, then Apple might perform HDR tonemapping
And that's perfectly fine.
Use an extended linear color space like extendedLinearITUR_2020, then macOS does not perform additional tonemapping
Nothing says they don't do any adaptation. It just guidance how to output linear rgb from application.
So I agree that conversion to linear RGB is not necessary in an ideal world. The intention is for it to bypass macOS tonemapping.
We don't need to bypass tonemapping, the issue is that they inconsistently handle HDR luminance. Basically what you are saying is that 100 nits outputted from mpv doesn't look like 100 nits outputted by macOS. It's absolute scale, 100 nits is 100 nits, whether it's HDR10 or linear extended rgb.
My idea is that MPV can convert the video to BT 2020 linear color space, with the scaling 100 nit = 1.0 float, and set the CAMetalLayer colorspace to extendedLinearITUR_2020
I'm not even sure it would fix anything. Because your output is different on different displays. Either way, you can already test that by using --target-trc=linear and see if both outputs agree. It won't be scaled to 100 nits, but regardless in both displays it should look the same.
I tried using --target-trc=linear and this time, both the internal MacBook XDR display and external HDR display shows HDR videos darkened by 1 stop.
| target-trc | Test chart | Internal MacBook XDR | External HDR display |
|---|---|---|---|
| pq | 100 nit test pattern | shows as SDR white (100 nits) | shows as darkened (49 nits) |
| linear | 100 nit test pattern | shows as darkened (49 nits) | shows as darkened (49 nits) |
** Edit: I later realised that the target-trc=linear output is actually SDR. So this test does not represent HDR behavior being consistent between the internal and external HDR displays. See 2 comments below
Thanks for testing. Linear output works as expected. Our reference (1.0 value) is 203. So if you set macOS to use 100 nits, this is exactly what should happen. I believe you can match this setting to what libplacebo outputs.
The issue is with PQ output, which is for some reason misrepresented by macOS. PQ encodes nits value directly, so it doesn't matter what is the reference sdr white. But as we see macOS seems to display it incorrectly.
Possible solution is to indeed implement the extended linear output. Another would be to poke Apple to fix their BT.2100 support.
Another correction, with --target-trc=linear, it appears that the output is actually clipped for values above 203 nits in the media (which is mapped to SDR white). So the output is effectively SDR.
I am guessing that the layer is set as linearITUR_2020 instead of extendedLinearITUR_2020
Furthermore, I have made my own demo app using extendedLinearITUR_2020 and a texture with pixel value of 1.0 float gives SDR white on both the internal XDR display and the external HDR display. So extended linear output seems to be a good solution
Another correction, with --target-trc=linear, it appears that the output is actually clipped for values above 203 nits. So the output is effectively SDR.
Yes, currently we only have implemented linear output, not the "extended" linear output. It is 0-1 SDR output only. I only wanted to test if this gives consistent output between both displays.
andrewke@Andrews-MacBook-Air ~ % /Applications/mpv.app/Contents/MacOS/mpv --version mpv v0.40.0-dev-g040186a1c Copyright © 2000-2025 mpv/MPlayer/mplayer2 projects built on Oct 7 2025 22:04:06 libplacebo version: v7.351.0 FFmpeg version: 8.0 FFmpeg library versions: libavcodec 62.11.100 libavdevice 62.1.100 libavfilter 11.4.100 libavformat 62.3.100 libavutil 60.8.100 libswresample 6.1.100 libswscale 9.1.100 andrewke@Andrews-MacBook-Air ~ %
Libplacebo version too old. It lacks a lot of fixes.. Don't use homebrew.
andrewke@Andrews-MacBook-Air ~ % /Applications/mpv.app/Contents/MacOS/mpv --version mpv v0.40.0-dev-g040186a1c Copyright © 2000-2025 mpv/MPlayer/mplayer2 projects built on Oct 7 2025 22:04:06 libplacebo version: v7.351.0 FFmpeg version: 8.0 FFmpeg library versions: libavcodec 62.11.100 libavdevice 62.1.100 libavfilter 11.4.100 libavformat 62.3.100 libavutil 60.8.100 libswresample 6.1.100 libswscale 9.1.100 andrewke@Andrews-MacBook-Air ~ %
Libplacebo version too old. It lacks a lot of fixes.. Don't use homebrew.
Not relevant, there were no changes to HDR10 output from libpalcebo.