Open Bug 1952603 Opened 10 months ago Updated 8 months ago

HDR video is too bright on macOS (over exposure)

Categories

(Core :: Graphics: Color Management, defect)

Firefox 136
defect

Tracking

()

UNCONFIRMED

People

(Reporter: troyliu0105, Assigned: bradwerth)

References

(Blocks 1 open bug)

Details

Attachments

(4 files)

Attached image IMG_9331.HEIC

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0

Steps to reproduce:

playing video on youtube.
https://youtu.be/qA5RkEqjlUU?t=30

Actual results:

All HDR videos on Youtube is too bright on macOS, but the same videos on safari and chrome is playing perfectly.

Expected results:

The exposure should be the same with safari or chrome.

The Bugbug bot thinks this bug should belong to the 'Core::Audio/Video: Playback' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → Audio/Video: Playback
Product: Firefox → Core

This sounds like bug 1948317?

Blocks: HDR
Component: Audio/Video: Playback → Graphics: Color Management

Hmm, for me, the video plays identically -- correctly -- in Firefox and in Safari. Haven't checked Chrome yet. I'll hold off on taking the Bug until and unless I can reproduce it.

You can set the pref gfx.core-animation.specialize-video.log to true, and then launch Firefox from a Terminal command to see some additional logging when displaying a video.

When I set that pref, and load the video, I get this output:

2025-03-10 12:10:42.265 firefox[6243:7196493] VIDEO_LOG: LogSurface...
2025-03-10 12:10:42.265 firefox[6243:7196493] Surface values are {
    CreationProperties =     {
        IOSurfaceAllocSize = 1246144;
        IOSurfaceBytesPerElement = 1;
        IOSurfaceBytesPerRow = 2596;
        IOSurfaceElementHeight = 1;
        IOSurfaceElementWidth = 1;
        IOSurfaceHeight = 480;
        IOSurfaceIsGlobal = 1;
        IOSurfaceName = decode;
        IOSurfaceOffset = 0;
        IOSurfacePixelFormat = 2016686640;
        IOSurfacePixelSizeCastingAllowed = 1;
        IOSurfacePlaneInfo =         (
                        {
                IOSurfacePlaneBase = 512;
                IOSurfacePlaneBytesPerElement = 2;
                IOSurfacePlaneBytesPerRow = 1728;
                IOSurfacePlaneComponentBitDepths =                 (
                    10
                );
                IOSurfacePlaneComponentNames =                 (
                    5
                );
                IOSurfacePlaneComponentRanges =                 (
                    2
                );
                IOSurfacePlaneElementHeight = 1;
                IOSurfacePlaneElementWidth = 1;
                IOSurfacePlaneHeight = 480;
                IOSurfacePlaneOffset = 512;
                IOSurfacePlaneSize = 829440;
                IOSurfacePlaneWidth = 854;
            },
                        {
                IOSurfacePlaneBase = 829952;
                IOSurfacePlaneBytesPerElement = 4;
                IOSurfacePlaneBytesPerRow = 1728;
                IOSurfacePlaneComponentBitDepths =                 (
                    10,
                    10
                );
                IOSurfacePlaneComponentNames =                 (
                    7,
                    6
                );
                IOSurfacePlaneComponentRanges =                 (
                    2,
                    2
                );
                IOSurfacePlaneElementHeight = 1;
                IOSurfacePlaneElementWidth = 1;
                IOSurfacePlaneHeight = 240;
                IOSurfacePlaneOffset = 829952;
                IOSurfacePlaneSize = 414720;
                IOSurfacePlaneWidth = 427;
            }
        );
        IOSurfacePurgeWhenNotInUse = 1;
        IOSurfaceSubsampling = 3;
        IOSurfaceWidth = 854;
    };
    IOSurfaceChromaLocationBottomField = Left;
    IOSurfaceChromaLocationTopField = Left;
    IOSurfaceColorPrimaries = "ITU_R_2020";
    IOSurfaceColorSpace = kCGColorSpaceCoreMedia709;
    IOSurfaceColorSpaceID = 32;
    IOSurfaceName = decode;
    IOSurfaceTransferFunction = "SMPTE_ST_2084_PQ";
    IOSurfaceYCbCrMatrix = "ITU_R_2020";
    kIOSurfacePixelMetadata =     {
        kIOSurfaceLumaHistogramV1 = {length = 512, bytes = 0xfd0e0000 61050000 e5030000 7a030000 ... 00000000 00000000 };
        kIOSurfaceSessionCookie = 41519218688;
        kIOSurfaceSessionFrameNumber = 135;
    };
}.
2025-03-10 12:10:42.265 firefox[6243:7196493] ColorSpace is <CGColorSpace 0x125d18ac0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; HDTV).
2025-03-10 12:10:42.265 firefox[6243:7196493] Buffer attachments are {
    CGColorSpace = "<CGColorSpace 0x125d18ac0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; HDTV)";
    CVImageBufferChromaLocationBottomField = Left;
    CVImageBufferChromaLocationTopField = Left;
    CVImageBufferColorPrimaries = "ITU_R_2020";
    CVImageBufferTransferFunction = "SMPTE_ST_2084_PQ";
    CVImageBufferYCbCrMatrix = "ITU_R_2020";
}.
2025-03-10 12:10:42.265 firefox[6243:7196493] Codec is x420.
2025-03-10 12:10:42.265 firefox[6243:7196493] Format extensions are {
    CVBytesPerRow = 2596;
    CVImageBufferChromaLocationBottomField = Left;
    CVImageBufferChromaLocationTopField = Left;
    CVImageBufferColorPrimaries = "ITU_R_2020";
    CVImageBufferTransferFunction = "SMPTE_ST_2084_PQ";
    CVImageBufferYCbCrMatrix = "ITU_R_2020";
    Version = 2;
}.

That looks about right to me. Those color primaries, transfer function, and matrix look like values we should be handling correctly. The only strange bit is IOSurfaceColorSpace = kCGColorSpaceCoreMedia709;. I would expect the colorspace to better match an HDR value, rather than the 709 value. I'll see if we're doing something strange there.

I'd like you to try setting the pref noted in comment 3, then pasting the relevant Terminal output to this Bug. It's possible that YouTube is serving you a different version of the video for some reason.

Flags: needinfo?(troyliu0105)
Severity: -- → S3

(In reply to Brad Werth [:bradwerth] from comment #4)

I'd like you to try setting the pref noted in comment 3, then pasting the relevant Terminal output to this Bug. It's possible that YouTube is serving you a different version of the video for some reason.

Hi Brad, the option gfx.core-animation.specialize-video.log=true set in stable release 136 do not work. So I try the nightly version(138.0a1 2025-03-07) instead (this version has the same issue).

The log like below:
but I do not notice the valuable difference between in the log.

UNSUPPORTED (log once): POSSIBLE ISSUE: unit 1 GLD_TEXTURE_INDEX_2D is unloadable and bound to sampler type (Float) - using zero texture because texture unloadable
2025-03-11 21:12:46.810 firefox[12796:439227] +[IMKClient subclass]: chose IMKClient_Modern
2025-03-11 21:12:46.810 firefox[12796:439227] +[IMKInputSession subclass]: chose IMKInputSession_Modern
2025-03-11 21:12:50.333 firefox[12796:439227] TSM AdjustCapsLockLEDForKeyTransitionHandling - _ISSetPhysicalKeyboardCapsLockLED Inhibit
2025-03-11 21:13:32.855 firefox[12796:439284] VIDEO_LOG: NativeLayerCA: 0x10531fc40 is being created to host an external image, which may force a video layer rebuild.
2025-03-11 21:13:32.855 firefox[12796:439284] VIDEO_LOG: AttachExternalImage: 0x10531fc40 is forcing a video layer rebuild.
2025-03-11 21:13:32.860 firefox[12796:439284] VIDEO_LOG: Rebuilding video layer with AVSampleBufferDisplayLayer.
2025-03-11 21:13:32.863 firefox[12796:439284] VIDEO_LOG: LogSurface...
2025-03-11 21:13:32.863 firefox[12796:439284] Surface values are {
    CreationProperties =     {
        IOSurfaceAllocSize = 24891904;
        IOSurfaceBytesPerElement = 1;
        IOSurfaceBytesPerRow = 11524;
        IOSurfaceElementHeight = 1;
        IOSurfaceElementWidth = 1;
        IOSurfaceHeight = 2160;
        IOSurfaceIsGlobal = 1;
        IOSurfaceName = decode;
        IOSurfaceOffset = 0;
        IOSurfacePixelFormat = 2016686640;
        IOSurfacePixelSizeCastingAllowed = 1;
        IOSurfacePlaneInfo =         (
                        {
                IOSurfacePlaneBase = 512;
                IOSurfacePlaneBytesPerElement = 2;
                IOSurfacePlaneBytesPerRow = 7680;
                IOSurfacePlaneComponentBitDepths =                 (
                    10
                );
                IOSurfacePlaneComponentNames =                 (
                    5
                );
                IOSurfacePlaneComponentRanges =                 (
                    2
                );
                IOSurfacePlaneElementHeight = 1;
                IOSurfacePlaneElementWidth = 1;
                IOSurfacePlaneHeight = 2160;
                IOSurfacePlaneOffset = 512;
                IOSurfacePlaneSize = 16588800;
                IOSurfacePlaneWidth = 3840;
            },
                        {
                IOSurfacePlaneBase = 16589312;
                IOSurfacePlaneBytesPerElement = 4;
                IOSurfacePlaneBytesPerRow = 7680;
                IOSurfacePlaneComponentBitDepths =                 (
                    10,
                    10
                );
                IOSurfacePlaneComponentNames =                 (
                    7,
                    6
                );
                IOSurfacePlaneComponentRanges =                 (
                    2,
                    2
                );
                IOSurfacePlaneElementHeight = 1;
                IOSurfacePlaneElementWidth = 1;
                IOSurfacePlaneHeight = 1080;
                IOSurfacePlaneOffset = 16589312;
                IOSurfacePlaneSize = 8294400;
                IOSurfacePlaneWidth = 1920;
            }
        );
        IOSurfacePurgeWhenNotInUse = 1;
        IOSurfaceSubsampling = 3;
        IOSurfaceWidth = 3840;
    };
    IOSurfaceChromaLocationBottomField = Left;
    IOSurfaceChromaLocationTopField = Left;
    IOSurfaceColorPrimaries = "ITU_R_2020";
    IOSurfaceColorSpace = kCGColorSpaceCoreMedia709;
    IOSurfaceColorSpaceID = 32;
    IOSurfaceName = decode;
    IOSurfaceTransferFunction = "SMPTE_ST_2084_PQ";
    IOSurfaceYCbCrMatrix = "ITU_R_2020";
    kIOSurfacePixelMetadata =     {
        kIOSurfaceLumaHistogramV1 = {length = 512, bytes = 0x1bb90100 886c0000 e54b0000 423f0000 ... 00000000 00000000 };
        kIOSurfaceSessionCookie = 53866721280;
        kIOSurfaceSessionFrameNumber = 101;
    };
}.
2025-03-11 21:13:32.863 firefox[12796:439284] ColorSpace is <CGColorSpace 0x139c88ca0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; HDTV).
2025-03-11 21:13:32.863 firefox[12796:439284] Buffer attachments are {
    CGColorSpace = "<CGColorSpace 0x139c88ca0> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; HDTV)";
    CVImageBufferChromaLocationBottomField = Left;
    CVImageBufferChromaLocationTopField = Left;
    CVImageBufferColorPrimaries = "ITU_R_2020";
    CVImageBufferTransferFunction = "SMPTE_ST_2084_PQ";
    CVImageBufferYCbCrMatrix = "ITU_R_2020";
}.

2025-03-11 21:13:32.863 firefox[12796:439284] Codec is x420.
2025-03-11 21:13:32.863 firefox[12796:439284] Format extensions are {
CVBytesPerRow = 11524;
CVImageBufferChromaLocationBottomField = Left;
CVImageBufferChromaLocationTopField = Left;
CVImageBufferColorPrimaries = "ITU_R_2020";
CVImageBufferTransferFunction = "SMPTE_ST_2084_PQ";
CVImageBufferYCbCrMatrix = "ITU_R_2020";
Version = 2;
}

Flags: needinfo?(troyliu0105)

Thank you, for providing that info. Yes, the difference between your logging and my logging is minor. The only difference is that the video you are being served is higher resolution, a factor of 4.5x larger. I'll experiment further and see if I can replicate your result with a higher-resolution video.

(In reply to Brad Werth [:bradwerth] from comment #6)

Thank you, for providing that info. Yes, the difference between your logging and my logging is minor. The only difference is that the video you are being served is higher resolution, a factor of 4.5x larger. I'll experiment further and see if I can replicate your result with a higher-resolution video.

But I dont think the resolution is the problem. I tried different size in this video, all the same.

See Also: → 1954523

Huh, I just can not find the place where we are setting IOSurfaceColorSpace to kCGColorSpaceCoreMedia709. I have confimed that our default colorSpace of kCGColorSpaceSRGB is not the same value. Very strange. Still trying to sort this out. I may resort to forcing IOSurfaceColorSpace to some more sensible value when we detect that the color primaries are BT2020, something like that.

It's also interesting that the kCGColorSpaceCoreMedia709 CFStringRef is only defined in MacOS 10.15 SDK. Meaning that maybe this is a newly-appearing effect that is happening in macOS 15 -- which I am also running but can't reproduce the original problem.

New theory: SurfacePoolCA::LockedPool::ObtainSurfaceFromPool is only checking if the surface has the same size and GL context as the requested surface. It is specifically not checking any of the IOSurfaceRef properties, some of which likely matter a lot for whether or not the surface is a suitable replacement. This may be the reason why there is a strange mismatch of surface properties. Possible solutions:

  1. Add more parameters and check if the surface is a match for those properties.
  2. Force the matching surface to have the same properties as the requested surface.

Either approach is probably a bit intensive, though it may not matter at the FPS of a typical video. I'll try one or both approaches and see what I can get.

Urgh! Noticed that this call to NextSurface has serious side effects, and therefore should not be in MOZ_RELEASE_ASSERT or any other type of assert. I'll clean this up along the way.

Turning off surface recycling does not appear to be sufficient to prevent the mismatched colorSpace values from being used. So we're intentionally (or by default) setting kCGColorSpaceCoreMedia709 somewhere. I'll keep looking.

Oh, maybe I missed the important information. The issue only exists when I use external display (MSI MPG321URX), but not for builtin display.

(In reply to Troy Liu from comment #15)

Oh, maybe I missed the important information. The issue only exists when I use external display (MSI MPG321URX), but not for builtin display.

No problem. I'll test with an external display and see if that helps me reproduce. Is the window on the external display or the builtin display?

(In reply to Brad Werth [:bradwerth] from comment #16)

(In reply to Troy Liu from comment #15)

Oh, maybe I missed the important information. The issue only exists when I use external display (MSI MPG321URX), but not for builtin display.

No problem. I'll test with an external display and see if that helps me reproduce. Is the window on the external display or the builtin display?

The window is on the external display.

Is this is a Primary/Secondary behavior issue? Try switching primary/secondary, or closing the laptop lid (making external the primary). I've seen many early implementations of new display features (e.g. high-Hz or new bit depths, and HDR) be tied to the primary.

Generally, on both Windows and Mac, I notice Chrome/Safari handles it properly now (e.g. HDR media queries enable/disable when you drag between screens + HDR brightnesses appear/disappear. HDR video gets properly downconverted to SDR on SDR displays). So HDR updates should be synchronized with the resolution/dpi/scaling/etc updates whenever windows are on primary vs secondary screens (etc), given one screen may be HDR and another may not be.

I can reproduce this with the following steps:

  1. Attach an HDR-capable monitor to a Mac.
  2. In macOS System Settings -> Display, set the monitor to be an Extended Display.
  3. Open an HDR video in Firefox.
  4. Drag the window to the other screen.
  5. This may cause the video to oversaturate on the other screen.
  6. Detach the monitor from the Mac.
  7. As the window pops back onto the main screen, it will definitely oversaturate. After a moment, it will display as SDR desaturated content. Moving the cursor over the window will cause it to resaturate or turn completely white.

So there's something going on that is unlikely to be related to IOSurface properties -- though that's still possible. Seems more likely that our dynamic detection of monitor capabilities is getting re-applied to surfaces that the OS is already presenting as HDR. We might be able to do something about that. I'll keep working on it.

Assignee: nobody → bwerth
Attached video FurbyFastflix.mp4

This HDR test video replicates the issue for me, and conveniently does not have the SDR preroll advertisement that precedes the YouTube video.

same here, HDR plays fine on macbook primary monitor, but gets oversaturated on secondary monitor
is there possibly any workaround available ?

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: