Commit 111548d2 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

Add support for HDR displays on macOS 10.15

Detect HDR support using the -[NSScreen
maximumPotentialExtendedDynamicRangeColorComponentValue] API introduced
in 10.15. Use the hard-coded kDefaultBitsPerPixel/Component constants
instead of reading from the NSScreen. Move the constants to a common
location.

We can only promote an IOSurface to an overlay if we can set the
color space on the IOSurface in a way that will make CoreAnimation
draw it the same way that GLRenderer would.

Merge the various checks for color space compatibility into an
IOSurfaceCanSetColorSpace function, and call this from the SDR
and HDR paths.

Update IOSurfaceSetColorSpace to use the new constants
kCGColorSpaceITUR_2020_PQ_EOTF and kCGColorSpaceITUR_2020_HLG
introduced in 10.15.

Note that this will not trigger HDR mode. Doing so requires making
visible a CAMetalLayer with wantsExtendedDynamicRangeContent set.
The next patch in this sequence will do that.

Bug: 976426
Change-Id: Ic93302aa895f182713658f1eaea89c0e91953857
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1863449
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarMiguel Casas <mcasas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#707125}
parent 28856894
...@@ -363,7 +363,15 @@ typedef NSString* NSAppearanceName; ...@@ -363,7 +363,15 @@ typedef NSString* NSAppearanceName;
BASE_EXPORT extern NSAppearanceName const NSAppearanceNameDarkAqua; BASE_EXPORT extern NSAppearanceName const NSAppearanceNameDarkAqua;
#endif #endif // MAC_OS_X_VERSION_10_14
#if !defined(MAC_OS_X_VERSION_10_15) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_15
@interface NSScreen (ForwardDeclare)
@property(readonly)
CGFloat maximumPotentialExtendedDynamicRangeColorComponentValue;
@end
#endif // MAC_OS_X_VERSION_10_15
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// The symbol for kCWSSIDDidChangeNotification is available in the // The symbol for kCWSSIDDidChangeNotification is available in the
......
...@@ -38,6 +38,11 @@ ...@@ -38,6 +38,11 @@
#include "ui/gfx/color_space.h" #include "ui/gfx/color_space.h"
#include "ui/gl/trace_util.h" #include "ui/gl/trace_util.h"
#if defined(OS_MACOSX)
#include "base/mac/mac_util.h"
#include "ui/gfx/mac/io_surface.h"
#endif
namespace media { namespace media {
// Implementation of a pool of GpuMemoryBuffers used to back VideoFrames. // Implementation of a pool of GpuMemoryBuffers used to back VideoFrames.
...@@ -563,14 +568,7 @@ void GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame( ...@@ -563,14 +568,7 @@ void GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame(
bool passthrough = false; bool passthrough = false;
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
// GPU memory buffers do not support full-range YUV video on mac. if (!IOSurfaceCanSetColorSpace(video_frame->ColorSpace()))
// Fortunately, the hardware decoders never produce full-range video.
// https://crbug/882627
gfx::ColorSpace color_space = video_frame->ColorSpace();
gfx::ColorSpace as_rgb = color_space.GetAsRGB();
gfx::ColorSpace as_full_range_rgb = color_space.GetAsFullRangeRGB();
if (color_space != as_rgb && as_rgb == as_full_range_rgb)
passthrough = true; passthrough = true;
#endif #endif
if (output_format_ == GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED) if (output_format_ == GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED)
...@@ -937,6 +935,9 @@ void GpuMemoryBufferVideoFramePool::PoolImpl:: ...@@ -937,6 +935,9 @@ void GpuMemoryBufferVideoFramePool::PoolImpl::
case GpuVideoAcceleratorFactories::OutputFormat::XB30: case GpuVideoAcceleratorFactories::OutputFormat::XB30:
// TODO(mcasas): Enable this for ChromeOS https://crbug.com/776093. // TODO(mcasas): Enable this for ChromeOS https://crbug.com/776093.
allow_overlay = false; allow_overlay = false;
#if defined(OS_MACOSX)
allow_overlay = IOSurfaceCanSetColorSpace(video_frame->ColorSpace());
#endif
// We've converted the YUV to RGB, fix the color space. // We've converted the YUV to RGB, fix the color space.
// TODO(hubbe): The libyuv YUV to RGB conversion may not have // TODO(hubbe): The libyuv YUV to RGB conversion may not have
// honored the color space conversion 100%. We should either fix // honored the color space conversion 100%. We should either fix
......
...@@ -21,15 +21,6 @@ ...@@ -21,15 +21,6 @@
namespace display { namespace display {
namespace { namespace {
constexpr int kDefaultBitsPerPixel = 24;
constexpr int kDefaultBitsPerComponent = 8;
constexpr int kHDR10BitsPerPixel = 30;
constexpr int kHDR10BitsPerComponent = 10;
constexpr int kSCRGBLinearBitsPerPixel = 48;
constexpr int kSCRGBLinearBitsPerComponent = 16;
// This variable tracks whether the forced device scale factor switch needs to // This variable tracks whether the forced device scale factor switch needs to
// be read from the command line, i.e. if it is set to -1 then the command line // be read from the command line, i.e. if it is set to -1 then the command line
// is checked. // is checked.
......
...@@ -237,6 +237,15 @@ class DISPLAY_EXPORT Display final { ...@@ -237,6 +237,15 @@ class DISPLAY_EXPORT Display final {
const gfx::ColorSpace& color_space, const gfx::ColorSpace& color_space,
float sdr_white_level = gfx::ColorSpace::kDefaultSDRWhiteLevel); float sdr_white_level = gfx::ColorSpace::kDefaultSDRWhiteLevel);
// Default values for color_depth and depth_per_component.
static constexpr int kDefaultBitsPerPixel = 24;
static constexpr int kDefaultBitsPerComponent = 8;
// The following values are abused by media query APIs to detect HDR
// capability.
static constexpr int kHDR10BitsPerPixel = 30;
static constexpr int kHDR10BitsPerComponent = 10;
// The number of bits per pixel. Used by media query APIs. // The number of bits per pixel. Used by media query APIs.
int color_depth() const { return color_depth_; } int color_depth() const { return color_depth_; }
void set_color_depth(int color_depth) { void set_color_depth(int color_depth) {
...@@ -267,6 +276,9 @@ class DISPLAY_EXPORT Display final { ...@@ -267,6 +276,9 @@ class DISPLAY_EXPORT Display final {
private: private:
friend struct mojo::StructTraits<mojom::DisplayDataView, Display>; friend struct mojo::StructTraits<mojom::DisplayDataView, Display>;
static constexpr int kSCRGBLinearBitsPerPixel = 48;
static constexpr int kSCRGBLinearBitsPerComponent = 16;
int64_t id_ = kInvalidDisplayId; int64_t id_ = kInvalidDisplayId;
gfx::Rect bounds_; gfx::Rect bounds_;
// If non-empty, then should be same size as |bounds_|. Used to avoid rounding // If non-empty, then should be same size as |bounds_|. Used to avoid rounding
......
...@@ -106,8 +106,16 @@ Display BuildDisplayForScreen(NSScreen* screen) { ...@@ -106,8 +106,16 @@ Display BuildDisplayForScreen(NSScreen* screen) {
display.set_color_space(screen_color_space); display.set_color_space(screen_color_space);
} }
display.set_color_depth(NSBitsPerPixelFromDepth([screen depth])); display.set_color_depth(Display::kDefaultBitsPerPixel);
display.set_depth_per_component(NSBitsPerSampleFromDepth([screen depth])); display.set_depth_per_component(Display::kDefaultBitsPerComponent);
if ([screen respondsToSelector:@selector
(maximumPotentialExtendedDynamicRangeColorComponentValue)]) {
if ([screen maximumPotentialExtendedDynamicRangeColorComponentValue] >=
2.0) {
display.set_color_depth(Display::kHDR10BitsPerPixel);
display.set_depth_per_component(Display::kHDR10BitsPerComponent);
}
}
display.set_is_monochrome(CGDisplayUsesForceToGray()); display.set_is_monochrome(CGDisplayUsesForceToGray());
if (auto display_link = ui::DisplayLinkMac::GetForDisplay(display_id)) if (auto display_link = ui::DisplayLinkMac::GetForDisplay(display_id))
......
...@@ -21,6 +21,13 @@ namespace gfx { ...@@ -21,6 +21,13 @@ namespace gfx {
namespace { namespace {
#if !defined(MAC_OS_X_VERSION_10_15) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_15
CFStringRef kCGColorSpaceITUR_2020_PQ_EOTF =
CFSTR("kCGColorSpaceITUR_2020_PQ_EOTF");
CFStringRef kCGColorSpaceITUR_2020_HLG = CFSTR("kCGColorSpaceITUR_2020_HLG");
#endif // MAC_OS_X_VERSION_10_15
void AddIntegerValue(CFMutableDictionaryRef dictionary, void AddIntegerValue(CFMutableDictionaryRef dictionary,
const CFStringRef key, const CFStringRef key,
int32_t value) { int32_t value) {
...@@ -116,6 +123,71 @@ void IOSurfaceMachPortTraits::Release(mach_port_t port) { ...@@ -116,6 +123,71 @@ void IOSurfaceMachPortTraits::Release(mach_port_t port) {
<< "IOSurfaceMachPortTraits::Release mach_port_mod_refs"; << "IOSurfaceMachPortTraits::Release mach_port_mod_refs";
} }
// Common method used by IOSurfaceSetColorSpace and IOSurfaceCanSetColorSpace.
bool IOSurfaceSetColorSpace(IOSurfaceRef io_surface,
const ColorSpace& color_space) {
// Allow but ignore invalid color spaces.
if (!color_space.IsValid())
return true;
// Prefer using named spaces.
CFStringRef color_space_name = nullptr;
if (__builtin_available(macos 10.12, *)) {
if (color_space == ColorSpace::CreateSRGB()) {
color_space_name = kCGColorSpaceSRGB;
} else if (color_space == ColorSpace::CreateDisplayP3D65()) {
color_space_name = kCGColorSpaceDisplayP3;
} else if (color_space == ColorSpace::CreateExtendedSRGB()) {
color_space_name = kCGColorSpaceExtendedSRGB;
} else if (color_space == ColorSpace::CreateSCRGBLinear()) {
color_space_name = kCGColorSpaceExtendedLinearSRGB;
}
}
if (__builtin_available(macos 10.15, *)) {
if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020,
ColorSpace::TransferID::SMPTEST2084,
ColorSpace::MatrixID::BT2020_NCL,
ColorSpace::RangeID::LIMITED)) {
color_space_name = kCGColorSpaceITUR_2020_PQ_EOTF;
} else if (color_space == ColorSpace(ColorSpace::PrimaryID::BT2020,
ColorSpace::TransferID::ARIB_STD_B67,
ColorSpace::MatrixID::BT2020_NCL,
ColorSpace::RangeID::LIMITED)) {
color_space_name = kCGColorSpaceITUR_2020_HLG;
}
}
if (color_space_name) {
if (io_surface) {
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"),
color_space_name);
}
return true;
}
gfx::ColorSpace as_rgb = color_space.GetAsRGB();
gfx::ColorSpace as_full_range_rgb = color_space.GetAsFullRangeRGB();
// IOSurfaces do not support full-range YUV video. Fortunately, the hardware
// decoders never produce full-range video.
// https://crbug.com/882627
if (color_space != as_rgb && as_rgb == as_full_range_rgb)
return false;
// Generate an ICCProfile from the parametric color space.
ICCProfile icc_profile = ICCProfile::FromColorSpace(as_full_range_rgb);
if (!icc_profile.IsValid())
return false;
// Package it as a CFDataRef and send it to the IOSurface.
std::vector<char> icc_profile_data = icc_profile.GetData();
base::ScopedCFTypeRef<CFDataRef> cf_data_icc_profile(CFDataCreate(
nullptr, reinterpret_cast<const UInt8*>(icc_profile_data.data()),
icc_profile_data.size()));
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"),
cf_data_icc_profile);
return true;
}
} // namespace internal } // namespace internal
IOSurfaceRef CreateIOSurface(const gfx::Size& size, IOSurfaceRef CreateIOSurface(const gfx::Size& size,
...@@ -185,61 +257,30 @@ IOSurfaceRef CreateIOSurface(const gfx::Size& size, ...@@ -185,61 +257,30 @@ IOSurfaceRef CreateIOSurface(const gfx::Size& size,
} }
// Ensure that all IOSurfaces start as sRGB. // Ensure that all IOSurfaces start as sRGB.
CGColorSpaceRef color_space = base::mac::GetSRGBColorSpace(); if (__builtin_available(macos 10.12, *)) {
base::ScopedCFTypeRef<CFDataRef> color_space_icc( IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB);
CGColorSpaceCopyICCProfile(color_space)); } else {
IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), color_space_icc); CGColorSpaceRef color_space = base::mac::GetSRGBColorSpace();
base::ScopedCFTypeRef<CFDataRef> color_space_icc(
CGColorSpaceCopyICCProfile(color_space));
IOSurfaceSetValue(surface, CFSTR("IOSurfaceColorSpace"), color_space_icc);
}
UMA_HISTOGRAM_TIMES("GPU.IOSurface.CreateTime", UMA_HISTOGRAM_TIMES("GPU.IOSurface.CreateTime",
base::TimeTicks::Now() - start_time); base::TimeTicks::Now() - start_time);
return surface; return surface;
} }
bool IOSurfaceCanSetColorSpace(const ColorSpace& color_space) {
return internal::IOSurfaceSetColorSpace(nullptr, color_space);
}
void IOSurfaceSetColorSpace(IOSurfaceRef io_surface, void IOSurfaceSetColorSpace(IOSurfaceRef io_surface,
const ColorSpace& color_space) { const ColorSpace& color_space) {
// Special-case sRGB. if (!internal::IOSurfaceSetColorSpace(io_surface, color_space)) {
if (color_space == ColorSpace::CreateSRGB()) { DLOG(ERROR) << "Failed to set color space for IOSurface: "
base::ScopedCFTypeRef<CFDataRef> srgb_icc(
CGColorSpaceCopyICCProfile(base::mac::GetSRGBColorSpace()));
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"), srgb_icc);
return;
}
// Special-case BT2020_NCL.
if (__builtin_available(macos 10.12, *)) {
const ColorSpace kBt2020(
ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::SMPTEST2084,
ColorSpace::MatrixID::BT2020_NCL, ColorSpace::RangeID::LIMITED);
if (color_space == kBt2020) {
base::ScopedCFTypeRef<CGColorSpaceRef> cg_color_space(
CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020));
DCHECK(cg_color_space);
base::ScopedCFTypeRef<CFDataRef> cf_data_icc_profile(
CGColorSpaceCopyICCData(cg_color_space));
DCHECK(cf_data_icc_profile);
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"),
cf_data_icc_profile);
return;
}
}
// Generate an ICCProfile from the parametric color space.
ICCProfile icc_profile =
ICCProfile::FromColorSpace(color_space.GetAsFullRangeRGB());
if (!icc_profile.IsValid()) {
DLOG(ERROR) << "Failed to set color space for IOSurface: no ICC profile: "
<< color_space.ToString(); << color_space.ToString();
return;
} }
// Package it as a CFDataRef and send it to the IOSurface.
std::vector<char> icc_profile_data = icc_profile.GetData();
base::ScopedCFTypeRef<CFDataRef> cf_data_icc_profile(CFDataCreate(
nullptr, reinterpret_cast<const UInt8*>(icc_profile_data.data()),
icc_profile_data.size()));
IOSurfaceSetValue(io_surface, CFSTR("IOSurfaceColorSpace"),
cf_data_icc_profile);
} }
} // namespace gfx } // namespace gfx
...@@ -62,8 +62,12 @@ using ScopedRefCountedIOSurfaceMachPort = ...@@ -62,8 +62,12 @@ using ScopedRefCountedIOSurfaceMachPort =
using ScopedInUseIOSurface = using ScopedInUseIOSurface =
base::ScopedTypeRef<IOSurfaceRef, internal::ScopedInUseIOSurfaceTraits>; base::ScopedTypeRef<IOSurfaceRef, internal::ScopedInUseIOSurfaceTraits>;
// Set color space for given IOSurface. Color space must have an associated ICC // Return true if there exists a value for IOSurfaceColorSpace or
// color profile otherwise this function does nothing. // IOSurfaceICCProfile that will make CoreAnimation render using |color_space|.
GFX_EXPORT bool IOSurfaceCanSetColorSpace(const gfx::ColorSpace& color_space);
// Set color space for given IOSurface. IOSurfaceCanSetColorSpace must return
// true for |color_space| otherwise this does nothing.
GFX_EXPORT void IOSurfaceSetColorSpace(IOSurfaceRef io_surface, GFX_EXPORT void IOSurfaceSetColorSpace(IOSurfaceRef io_surface,
const gfx::ColorSpace& color_space); const gfx::ColorSpace& color_space);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment