Commit 9babb5af authored by Tom Anderson's avatar Tom Anderson Committed by Commit Bot

[X11] Obtain colorspace info from EDID when no ICC profile is set up

This fixes an issue where display::Display::color_space()::IsValid()
would always return false when no ICC profile was set up.  This was
causing HDR video playback to fallback to SDR even on HDR/WCG
monitors.  The solution is to obtain the colorspace information from
the monitor's EDID.

BUG=1021659
R=sky

Change-Id: Iea05bc42f05112d6809887abf2b725807632e820
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1907427Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714306}
parent 93b2ed40
......@@ -13,9 +13,13 @@
#include "base/logging.h"
#include "ui/base/x/x11_util.h"
#include "ui/display/util/display_util.h"
#include "ui/display/util/x11/edid_parser_x11.h"
#include "ui/display/util/edid_parser.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/matrix3_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector3d_f.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
namespace ui {
......@@ -149,6 +153,55 @@ int DefaultBitsPerComponent(XDisplay* xdisplay) {
return visual->bits_per_rgb;
}
bool IsRandRAvailable() {
int randr_version_major = 0;
int randr_version_minor = 0;
static bool is_randr_available = XRRQueryVersion(
gfx::GetXDisplay(), &randr_version_major, &randr_version_minor);
return is_randr_available;
}
// Get the EDID data from the |output| and stores to |edid|.
void GetEDIDProperty(XID output, std::vector<uint8_t>* edid) {
if (!IsRandRAvailable())
return;
Display* display = gfx::GetXDisplay();
Atom edid_property = gfx::GetAtom(RR_PROPERTY_RANDR_EDID);
bool has_edid_property = false;
int num_properties = 0;
gfx::XScopedPtr<Atom[]> properties(
XRRListOutputProperties(display, output, &num_properties));
for (int i = 0; i < num_properties; ++i) {
if (properties[i] == edid_property) {
has_edid_property = true;
break;
}
}
if (!has_edid_property)
return;
Atom actual_type;
int actual_format;
unsigned long bytes_after;
unsigned long nitems = 0;
unsigned char* prop = nullptr;
XRRGetOutputProperty(display, output, edid_property,
0, // offset
128, // length
false, // _delete
false, // pending
AnyPropertyType, // req_type
&actual_type, &actual_format, &nitems, &bytes_after,
&prop);
DCHECK_EQ(XA_INTEGER, actual_type);
DCHECK_EQ(8, actual_format);
edid->assign(prop, prop + nitems);
XFree(prop);
}
} // namespace
int GetXrandrVersion(XDisplay* xdisplay) {
......@@ -243,13 +296,14 @@ std::vector<display::Display> BuildDisplaysFromXRandRInfo(
gfx::XObjectDeleter<XRRCrtcInfo, void, XRRFreeCrtcInfo>>
crtc(XRRGetCrtcInfo(xdisplay, resources.get(), output_info->crtc));
int64_t display_id = -1;
if (!display::EDIDParserX11(output_id).GetDisplayId(
static_cast<uint8_t>(i), &display_id)) {
// It isn't ideal, but if we can't parse the EDID data, fall back on the
// display number.
std::vector<uint8_t> edid_bytes;
GetEDIDProperty(output_id, &edid_bytes);
display::EdidParser edid_parser(edid_bytes);
int64_t display_id = edid_parser.GetDisplayId(output_id);
// It isn't ideal, but if we can't parse the EDID data, fall back on the
// display number.
if (!display_id)
display_id = i;
}
gfx::Rect crtc_bounds(crtc->x, crtc->y, crtc->width, crtc->height);
display::Display display(display_id, crtc_bounds);
......@@ -286,7 +340,16 @@ std::vector<display::Display> BuildDisplaysFromXRandRInfo(
gfx::ICCProfile icc_profile = ui::GetICCProfileForMonitor(
monitor_iter == output_to_monitor.end() ? 0 : monitor_iter->second);
icc_profile.HistogramDisplay(display.id());
display.set_color_space(icc_profile.GetPrimariesOnlyColorSpace());
gfx::ColorSpace color_space = icc_profile.GetPrimariesOnlyColorSpace();
// Most folks do not have an ICC profile set up, but we still want to
// detect if a display has a wide color gamut so that HDR videos can be
// enabled. Only do this if |bits_per_component| > 8 or else SDR
// screens may have washed out colors.
if (bits_per_component > 8 && !color_space.IsValid())
color_space = display::GetColorSpaceFromEdid(edid_parser);
display.set_color_space(color_space);
}
display.set_color_depth(depth);
......
......@@ -29,17 +29,6 @@ jumbo_component("util") {
"//ui/gfx/geometry",
]
if (use_x11 || ozone_platform_x11) {
sources += [
"x11/edid_parser_x11.cc",
"x11/edid_parser_x11.h",
]
configs += [
"//build/config/linux:x11",
"//build/config/linux:xrandr",
]
deps += [ "//ui/gfx/x" ]
}
if (is_chromeos) {
deps += [ "//ui/display/types" ]
} else if (is_mac) {
......
......@@ -7,7 +7,9 @@
#include <stddef.h>
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/stl_util.h"
#include "ui/display/util/edid_parser.h"
namespace display {
......@@ -17,12 +19,19 @@ namespace {
// See crbug.com/136533. The first element maintains the minimum
// size required to be valid size.
const int kInvalidDisplaySizeList[][2] = {
{40, 30},
{50, 40},
{160, 90},
{160, 100},
{40, 30},
{50, 40},
{160, 90},
{160, 100},
};
// Used in the GetColorSpaceFromEdid function to collect data on whether the
// color space extracted from an EDID blob passed the sanity checks.
void EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome outcome) {
base::UmaHistogramEnumeration("DrmUtil.GetColorSpaceFromEdid.ChecksOutcome",
outcome);
}
} // namespace
bool IsDisplaySizeBlackListed(const gfx::Size& physical_size) {
......@@ -50,4 +59,84 @@ int64_t GenerateDisplayID(uint16_t manufacturer_id,
(static_cast<int64_t>(product_code_hash) << 8) | output_index);
}
gfx::ColorSpace GetColorSpaceFromEdid(const display::EdidParser& edid_parser) {
const SkColorSpacePrimaries primaries = edid_parser.primaries();
// Sanity check: primaries should verify By <= Ry <= Gy, Bx <= Rx and Gx <=
// Rx, to guarantee that the R, G and B colors are each in the correct region.
if (!(primaries.fBX <= primaries.fRX && primaries.fGX <= primaries.fRX &&
primaries.fBY <= primaries.fRY && primaries.fRY <= primaries.fGY)) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBadCoordinates);
return gfx::ColorSpace();
}
// Sanity check: the area spawned by the primaries' triangle is too small,
// i.e. less than half the surface of the triangle spawned by sRGB/BT.709.
constexpr double kBT709PrimariesArea = 0.0954;
const float primaries_area_twice =
(primaries.fRX * primaries.fGY) + (primaries.fBX * primaries.fRY) +
(primaries.fGX * primaries.fBY) - (primaries.fBX * primaries.fGY) -
(primaries.fGX * primaries.fRY) - (primaries.fRX * primaries.fBY);
if (primaries_area_twice < kBT709PrimariesArea) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorPrimariesAreaTooSmall);
return gfx::ColorSpace();
}
// Sanity check: https://crbug.com/809909, the blue primary coordinates should
// not be too far left/upwards of the expected location (namely [0.15, 0.06]
// for sRGB/ BT.709/ Adobe RGB/ DCI-P3, and [0.131, 0.046] for BT.2020).
constexpr float kExpectedBluePrimaryX = 0.15f;
constexpr float kBluePrimaryXDelta = 0.02f;
constexpr float kExpectedBluePrimaryY = 0.06f;
constexpr float kBluePrimaryYDelta = 0.031f;
const bool is_blue_primary_broken =
(std::abs(primaries.fBX - kExpectedBluePrimaryX) > kBluePrimaryXDelta) ||
(std::abs(primaries.fBY - kExpectedBluePrimaryY) > kBluePrimaryYDelta);
if (is_blue_primary_broken) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBluePrimaryIsBroken);
return gfx::ColorSpace();
}
skcms_Matrix3x3 color_space_as_matrix;
if (!primaries.toXYZD50(&color_space_as_matrix)) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorCannotExtractToXYZD50);
return gfx::ColorSpace();
}
const double gamma = edid_parser.gamma();
if (gamma < 1.0) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBadGamma);
return gfx::ColorSpace();
}
EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome::kSuccess);
gfx::ColorSpace::TransferID transfer_id =
gfx::ColorSpace::TransferID::INVALID;
if (base::Contains(edid_parser.supported_color_primary_ids(),
gfx::ColorSpace::PrimaryID::BT2020)) {
if (base::Contains(edid_parser.supported_color_transfer_ids(),
gfx::ColorSpace::TransferID::SMPTEST2084)) {
transfer_id = gfx::ColorSpace::TransferID::SMPTEST2084;
} else if (base::Contains(edid_parser.supported_color_transfer_ids(),
gfx::ColorSpace::TransferID::ARIB_STD_B67)) {
transfer_id = gfx::ColorSpace::TransferID::ARIB_STD_B67;
}
} else if (gamma == 2.2f) {
transfer_id = gfx::ColorSpace::TransferID::GAMMA22;
} else if (gamma == 2.4f) {
transfer_id = gfx::ColorSpace::TransferID::GAMMA24;
}
if (transfer_id != gfx::ColorSpace::TransferID::INVALID)
return gfx::ColorSpace::CreateCustom(color_space_as_matrix, transfer_id);
skcms_TransferFunction transfer = {gamma, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
return gfx::ColorSpace::CreateCustom(color_space_as_matrix, transfer);
}
} // namespace display
......@@ -8,13 +8,28 @@
#include <stdint.h>
#include "ui/display/util/display_util_export.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
namespace display {
class EdidParser;
// 1 inch in mm.
constexpr float kInchInMm = 25.4f;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class EdidColorSpaceChecksOutcome {
kSuccess = 0,
kErrorBadCoordinates = 1,
kErrorPrimariesAreaTooSmall = 2,
kErrorBluePrimaryIsBroken = 3,
kErrorCannotExtractToXYZD50 = 4,
kErrorBadGamma = 5,
kMaxValue = kErrorBadGamma
};
// Returns true if a given size is in the list of bogus sizes in mm that should
// be ignored.
DISPLAY_UTIL_EXPORT bool IsDisplaySizeBlackListed(
......@@ -31,6 +46,11 @@ DISPLAY_UTIL_EXPORT int64_t GenerateDisplayID(uint16_t manufacturer_id,
uint32_t product_code_hash,
uint8_t output_index);
// Uses |edid_parser| to extract a gfx::ColorSpace which will be IsValid() if
// both gamma and the color primaries were correctly found.
DISPLAY_UTIL_EXPORT gfx::ColorSpace GetColorSpaceFromEdid(
const display::EdidParser& edid_parser);
} // namespace display
#endif // UI_DISPLAY_UTIL_DISPLAY_UTIL_H_
This diff is collapsed.
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/display/util/x11/edid_parser_x11.h"
#include "base/strings/string_util.h"
#include "ui/display/util/edid_parser.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
namespace display {
namespace {
bool IsRandRAvailable() {
int randr_version_major = 0;
int randr_version_minor = 0;
static bool is_randr_available = XRRQueryVersion(
gfx::GetXDisplay(), &randr_version_major, &randr_version_minor);
return is_randr_available;
}
// Get the EDID data from the |output| and stores to |edid|.
// Returns true if EDID property is successfully obtained. Otherwise returns
// false and does not touch |edid|.
bool GetEDIDProperty(XID output, std::vector<uint8_t>* edid) {
if (!IsRandRAvailable())
return false;
Display* display = gfx::GetXDisplay();
Atom edid_property = gfx::GetAtom(RR_PROPERTY_RANDR_EDID);
bool has_edid_property = false;
int num_properties = 0;
gfx::XScopedPtr<Atom[]> properties(
XRRListOutputProperties(display, output, &num_properties));
for (int i = 0; i < num_properties; ++i) {
if (properties[i] == edid_property) {
has_edid_property = true;
break;
}
}
if (!has_edid_property)
return false;
Atom actual_type;
int actual_format;
unsigned long bytes_after;
unsigned long nitems = 0;
unsigned char* prop = nullptr;
XRRGetOutputProperty(display,
output,
edid_property,
0, // offset
128, // length
false, // _delete
false, // pending
AnyPropertyType, // req_type
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&prop);
DCHECK_EQ(XA_INTEGER, actual_type);
DCHECK_EQ(8, actual_format);
edid->assign(prop, prop + nitems);
XFree(prop);
return true;
}
} // namespace
EDIDParserX11::EDIDParserX11(XID output_id) : output_id_(output_id) {
GetEDIDProperty(output_id_, &edid_);
}
EDIDParserX11::~EDIDParserX11() {}
bool EDIDParserX11::GetDisplayId(uint8_t index, int64_t* out_display_id) const {
if (edid_.empty())
return false;
*out_display_id = EdidParser(edid_).GetDisplayId(output_id_);
return true;
}
} // namespace display
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_DISPLAY_UTIL_X11_EDID_PARSER_X11_H_
#define UI_DISPLAY_UTIL_X11_EDID_PARSER_X11_H_
#include <stdint.h>
#include <string>
#include <vector>
#include "base/macros.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/util/display_util_export.h"
#include "ui/display/util/edid_parser.h"
typedef unsigned long XID;
typedef XID RROutput;
// Xrandr utility functions to help get EDID information.
namespace display {
// Xrandr utility class to help get EDID information.
class DISPLAY_UTIL_EXPORT EDIDParserX11 {
public:
EDIDParserX11(XID output_id);
~EDIDParserX11();
// Sets |out_display_id| to the display ID from the EDID of this output.
// Returns true if successful, false otherwise.
bool GetDisplayId(uint8_t index, int64_t* out_display_id) const;
XID output_id() const { return output_id_; }
const std::vector<uint8_t>& edid() const { return edid_; }
private:
const XID output_id_;
// This will be an empty vector upon failure to get the EDID from the
// |output_id_|.
std::vector<uint8_t> edid_;
DISALLOW_COPY_AND_ASSIGN(EDIDParserX11);
};
} // namespace display
#endif // UI_DISPLAY_UTIL_X11_EDID_PARSER_X11_H_
......@@ -19,6 +19,7 @@
#include "base/metrics/histogram_functions.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/util/display_util.h"
#include "ui/display/util/edid_parser.h"
namespace ui {
......@@ -28,13 +29,6 @@ namespace {
static const size_t kDefaultCursorWidth = 64;
static const size_t kDefaultCursorHeight = 64;
// Used in the GetColorSpaceFromEdid function to collect data on whether the
// color space extracted from an EDID blob passed the sanity checks.
void EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome outcome) {
base::UmaHistogramEnumeration("DrmUtil.GetColorSpaceFromEdid.ChecksOutcome",
outcome);
}
bool IsCrtcInUse(
uint32_t crtc,
const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>&
......@@ -482,7 +476,7 @@ std::unique_ptr<display::DisplaySnapshot> CreateDisplaySnapshot(
year_of_manufacture = edid_parser.year_of_manufacture();
has_overscan =
edid_parser.has_overscan_flag() && edid_parser.overscan_flag();
display_color_space = GetColorSpaceFromEdid(edid_parser);
display_color_space = display::GetColorSpaceFromEdid(edid_parser);
base::UmaHistogramBoolean("DrmUtil.CreateDisplaySnapshot.IsHDR",
display_color_space.IsHDR());
bits_per_channel = std::max(edid_parser.bits_per_channel(), 0);
......@@ -653,84 +647,4 @@ std::vector<OverlayCheckReturn_Params> CreateParamsFromOverlayStatusList(
return params;
}
gfx::ColorSpace GetColorSpaceFromEdid(const display::EdidParser& edid_parser) {
const SkColorSpacePrimaries primaries = edid_parser.primaries();
// Sanity check: primaries should verify By <= Ry <= Gy, Bx <= Rx and Gx <=
// Rx, to guarantee that the R, G and B colors are each in the correct region.
if (!(primaries.fBX <= primaries.fRX && primaries.fGX <= primaries.fRX &&
primaries.fBY <= primaries.fRY && primaries.fRY <= primaries.fGY)) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBadCoordinates);
return gfx::ColorSpace();
}
// Sanity check: the area spawned by the primaries' triangle is too small,
// i.e. less than half the surface of the triangle spawned by sRGB/BT.709.
constexpr double kBT709PrimariesArea = 0.0954;
const float primaries_area_twice =
(primaries.fRX * primaries.fGY) + (primaries.fBX * primaries.fRY) +
(primaries.fGX * primaries.fBY) - (primaries.fBX * primaries.fGY) -
(primaries.fGX * primaries.fRY) - (primaries.fRX * primaries.fBY);
if (primaries_area_twice < kBT709PrimariesArea) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorPrimariesAreaTooSmall);
return gfx::ColorSpace();
}
// Sanity check: https://crbug.com/809909, the blue primary coordinates should
// not be too far left/upwards of the expected location (namely [0.15, 0.06]
// for sRGB/ BT.709/ Adobe RGB/ DCI-P3, and [0.131, 0.046] for BT.2020).
constexpr float kExpectedBluePrimaryX = 0.15f;
constexpr float kBluePrimaryXDelta = 0.02f;
constexpr float kExpectedBluePrimaryY = 0.06f;
constexpr float kBluePrimaryYDelta = 0.031f;
const bool is_blue_primary_broken =
(std::abs(primaries.fBX - kExpectedBluePrimaryX) > kBluePrimaryXDelta) ||
(std::abs(primaries.fBY - kExpectedBluePrimaryY) > kBluePrimaryYDelta);
if (is_blue_primary_broken) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBluePrimaryIsBroken);
return gfx::ColorSpace();
}
skcms_Matrix3x3 color_space_as_matrix;
if (!primaries.toXYZD50(&color_space_as_matrix)) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorCannotExtractToXYZD50);
return gfx::ColorSpace();
}
const double gamma = edid_parser.gamma();
if (gamma < 1.0) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBadGamma);
return gfx::ColorSpace();
}
EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome::kSuccess);
gfx::ColorSpace::TransferID transfer_id =
gfx::ColorSpace::TransferID::INVALID;
if (base::Contains(edid_parser.supported_color_primary_ids(),
gfx::ColorSpace::PrimaryID::BT2020)) {
if (base::Contains(edid_parser.supported_color_transfer_ids(),
gfx::ColorSpace::TransferID::SMPTEST2084)) {
transfer_id = gfx::ColorSpace::TransferID::SMPTEST2084;
} else if (base::Contains(edid_parser.supported_color_transfer_ids(),
gfx::ColorSpace::TransferID::ARIB_STD_B67)) {
transfer_id = gfx::ColorSpace::TransferID::ARIB_STD_B67;
}
} else if (gamma == 2.2f) {
transfer_id = gfx::ColorSpace::TransferID::GAMMA22;
} else if (gamma == 2.4f) {
transfer_id = gfx::ColorSpace::TransferID::GAMMA24;
}
if (transfer_id != gfx::ColorSpace::TransferID::INVALID)
return gfx::ColorSpace::CreateCustom(color_space_as_matrix, transfer_id);
skcms_TransferFunction transfer = {gamma, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
return gfx::ColorSpace::CreateCustom(color_space_as_matrix, transfer);
}
} // namespace ui
......@@ -22,7 +22,6 @@ typedef struct _drmModeModeInfo drmModeModeInfo;
namespace display {
class DisplayMode;
class EdidParser;
} // namespace display
namespace gfx {
......@@ -31,18 +30,6 @@ class Point;
namespace ui {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class EdidColorSpaceChecksOutcome {
kSuccess = 0,
kErrorBadCoordinates = 1,
kErrorPrimariesAreaTooSmall = 2,
kErrorBluePrimaryIsBroken = 3,
kErrorCannotExtractToXYZD50 = 4,
kErrorBadGamma = 5,
kMaxValue = kErrorBadGamma
};
// Representation of the information required to initialize and configure a
// native display. |index| is the position of the connection and will be
// used to generate a unique identifier for the display.
......@@ -136,10 +123,6 @@ OverlayStatusList CreateOverlayStatusListFrom(
std::vector<OverlayCheckReturn_Params> CreateParamsFromOverlayStatusList(
const OverlayStatusList& returns);
// Uses |edid_parser| to extract a gfx::ColorSpace which will be IsValid() if
// both gamma and the color primaries were correctly found.
gfx::ColorSpace GetColorSpaceFromEdid(const display::EdidParser& edid_parser);
} // namespace ui
#endif // UI_OZONE_PLATFORM_DRM_COMMON_DRM_UTIL_H_
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