blob: ad62730fe2b25d87fd3b4970b8db172b6210300d [file]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "screen-capture-utils/crtc.h"
#include <algorithm>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/stl_util.h>
namespace screenshot {
namespace {
constexpr const char kDrmDeviceDir[] = "/dev/dri";
constexpr const char kDrmDeviceGlob[] = "card?";
float FixedPoint1616ToFloat(uint32_t n) {
float result = (n & 0xFFFF0000) >> 16;
result += (n & 0xFFFF) / 65536.0f;
return result;
}
// Translates |property_value| to a name assuming that it corresponds to the
// value of an enum DRM property described by |property_metadata|. This function
// crashes if |property_metadata| does not correspond to an enum property or if
// |property_value| is not a valid enum value.
std::string DRMEnumValueToString(const drmModePropertyRes& property_metadata,
uint64_t property_value) {
LOG_ASSERT(property_metadata.flags & DRM_MODE_PROP_ENUM)
<< "DRMEnumValueToString() is being called for a non-enum property";
std::optional<std::string> drm_enum_name;
for (int i = 0; i < property_metadata.count_enums; i++) {
const drm_mode_property_enum& drm_enum = property_metadata.enums[i];
if (drm_enum.value == property_value) {
drm_enum_name = std::string(drm_enum.name);
break;
}
}
LOG_ASSERT(drm_enum_name.has_value())
<< "|property_value| does not correspond to a valid enum value";
return drm_enum_name.value();
}
// Assuming that |property_value| is the value of the COLOR_ENCODING DRM
// property described by |property_metadata|, this function converts that value
// to a ColorEncoding enum value.
ColorEncoding DRMColorEncodingPropertyToEnum(
const drmModePropertyRes& property_metadata, uint64_t property_value) {
LOG_ASSERT(strcmp(property_metadata.name, "COLOR_ENCODING") == 0);
const std::string drm_enum_name =
DRMEnumValueToString(property_metadata, property_value);
if (drm_enum_name == "ITU-R BT.601 YCbCr") {
return ColorEncoding::kYCbCrBT601;
} else if (drm_enum_name == "ITU-R BT.709 YCbCr") {
return ColorEncoding::kYCbCrBT709;
} else if (drm_enum_name == "ITU-R BT.2020 YCbCr") {
return ColorEncoding::kYCbCrBT2020;
}
return ColorEncoding::kUnknown;
}
// Assuming that |property_value| is the value of the COLOR_RANGE DRM property
// described by |property_metadata|, this function converts that value to a
// ColorRange enum value.
ColorRange DRMColorRangePropertyToEnum(
const drmModePropertyRes& property_metadata, uint64_t property_value) {
LOG_ASSERT(strcmp(property_metadata.name, "COLOR_RANGE") == 0);
const std::string drm_enum_name =
DRMEnumValueToString(property_metadata, property_value);
if (drm_enum_name == "YCbCr limited range") {
return ColorRange::kYCbCrLimited;
} else if (drm_enum_name == "YCbCr full range") {
return ColorRange::kYCbCrFull;
}
return ColorRange::kUnknown;
}
bool PopulatePlaneConfiguration(int fd,
uint32_t plane_id,
PlaneConfiguration* conf) {
// TODO(andrescj): Handle rotation.
static const std::array<std::string, 8> required_props{
"CRTC_X", "CRTC_Y", "CRTC_W", "CRTC_H",
"SRC_X", "SRC_Y", "SRC_W", "SRC_H",
};
static const std::array<std::string, 2> optional_props{
"COLOR_ENCODING",
"COLOR_RANGE",
};
std::map<std::string,
std::pair<ScopedDrmPropertyPtr /* metadata */, uint64_t /* value */>>
interesting_props;
ScopedDrmObjectPropertiesPtr props(
drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE));
if (!props) {
return false;
}
size_t num_required_props_found = 0;
for (int i = 0; i < props->count_props; i++) {
ScopedDrmPropertyPtr prop(drmModeGetProperty(fd, props->props[i]));
if (!prop) {
continue;
}
const std::string prop_name(prop->name);
if (std::find(required_props.cbegin(), required_props.cend(), prop_name) !=
required_props.cend()) {
num_required_props_found++;
} else if (std::find(optional_props.cbegin(), optional_props.cend(),
prop_name) == optional_props.cend()) {
// We don't care about this property as it's neither required nor
// optional.
continue;
}
if (!interesting_props
.emplace(prop_name,
std::make_pair(/*metadata=*/std::move(prop),
/*value=*/props->prop_values[i]))
.second) {
LOG(ERROR) << "Detected a duplicate property";
return false;
}
}
if (num_required_props_found != required_props.size()) {
LOG(ERROR) << "Could not find all required properties";
return false;
}
conf->x = static_cast<int32_t>(interesting_props["CRTC_X"].second);
conf->y = static_cast<int32_t>(interesting_props["CRTC_Y"].second);
conf->w = static_cast<uint32_t>(interesting_props["CRTC_W"].second);
conf->h = static_cast<uint32_t>(interesting_props["CRTC_H"].second);
conf->crop_x = FixedPoint1616ToFloat(interesting_props["SRC_X"].second);
conf->crop_y = FixedPoint1616ToFloat(interesting_props["SRC_Y"].second);
conf->crop_w = FixedPoint1616ToFloat(interesting_props["SRC_W"].second);
conf->crop_h = FixedPoint1616ToFloat(interesting_props["SRC_H"].second);
// While the COLOR_ENCODING and COLOR_RANGE properties are optional, we do
// expect consistency: either both are present or both are absent.
if (interesting_props.count("COLOR_ENCODING") !=
interesting_props.count("COLOR_RANGE")) {
LOG(ERROR)
<< "Detected an inconsistency between the COLOR_ENCODING and the "
"COLOR_RANGE properties";
return false;
}
if (interesting_props.count("COLOR_ENCODING")) {
const auto color_encoding_prop_it =
interesting_props.find("COLOR_ENCODING");
conf->color_encoding = DRMColorEncodingPropertyToEnum(
/*property_metadata=*/*color_encoding_prop_it->second.first,
/*property_value=*/color_encoding_prop_it->second.second);
}
if (interesting_props.count("COLOR_RANGE")) {
const auto color_range_prop_it = interesting_props.find("COLOR_RANGE");
conf->color_range = DRMColorRangePropertyToEnum(
/*property_metadata=*/*color_range_prop_it->second.first,
/*property_value=*/color_range_prop_it->second.second);
}
return true;
}
PanelRotation GetPanelOrientation(int fd, uint32_t connector_id) {
ScopedDrmObjectPropertiesPtr props(
drmModeObjectGetProperties(fd, connector_id, DRM_MODE_OBJECT_CONNECTOR));
if (!props) {
return PanelRotation::k0;
}
for (uint32_t p = 0; p < props->count_props; p++) {
ScopedDrmPropertyPtr prop(drmModeGetProperty(fd, props->props[p]));
if (!prop) {
continue;
}
if (strcmp("panel orientation", prop->name) == 0) {
/* enum is internal to the kernel and not exposed.
enum drm_panel_orientation {
DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1,
DRM_MODE_PANEL_ORIENTATION_NORMAL = 0,
DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP,
DRM_MODE_PANEL_ORIENTATION_LEFT_UP,
DRM_MODE_PANEL_ORIENTATION_RIGHT_UP,
};
*/
switch (props->prop_values[p]) {
case 0:
VLOG(1) << "panel orientation 0 degrees.";
return PanelRotation::k0;
case 1:
VLOG(1) << "panel orientation 180 degrees.";
return PanelRotation::k180;
case 2:
VLOG(1) << "panel orientation 270 degrees.";
return PanelRotation::k270;
case 3:
VLOG(1) << "panel orientation 90 degrees.";
return PanelRotation::k90;
default:
VLOG(1) << "unable to detect panel orientation, using 0 degrees.";
return PanelRotation::k0;
}
}
}
return PanelRotation::k0;
}
std::vector<std::unique_ptr<Crtc>> GetConnectedCrtcs() {
std::vector<std::unique_ptr<Crtc>> crtcs;
std::vector<base::FilePath> paths;
{
base::FileEnumerator lister(base::FilePath(kDrmDeviceDir), false,
base::FileEnumerator::FILES, kDrmDeviceGlob);
for (base::FilePath name = lister.Next(); !name.empty();
name = lister.Next()) {
paths.emplace_back(name);
}
}
std::sort(paths.begin(), paths.end());
for (base::FilePath path : paths) {
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE);
if (!file.IsValid()) {
continue;
}
// Set CAP_ATOMIC so we can query all planes and plane properties.
// TODO(b/290543296): Revisit if we still need this check after Hana EOL.
bool atomic_modeset =
drmSetClientCap(file.GetPlatformFile(), DRM_CLIENT_CAP_ATOMIC, 1) == 0;
ScopedDrmModeResPtr resources(drmModeGetResources(file.GetPlatformFile()));
if (!resources) {
continue;
}
for (int index_connector = 0; index_connector < resources->count_connectors;
++index_connector) {
ScopedDrmModeConnectorPtr connector(drmModeGetConnector(
file.GetPlatformFile(), resources->connectors[index_connector]));
if (!connector || connector->encoder_id == 0) {
continue;
}
ScopedDrmModeEncoderPtr encoder(
drmModeGetEncoder(file.GetPlatformFile(), connector->encoder_id));
if (!encoder || encoder->crtc_id == 0) {
continue;
}
ScopedDrmModeCrtcPtr crtc(
drmModeGetCrtc(file.GetPlatformFile(), encoder->crtc_id));
if (!crtc || !crtc->mode_valid || crtc->buffer_id == 0) {
continue;
}
ScopedDrmModeFB2Ptr fb2(
drmModeGetFB2(file.GetPlatformFile(), crtc->buffer_id),
file.GetPlatformFile());
if (!fb2) {
LOG(ERROR) << "getfb2 failed";
continue;
}
const PanelRotation panel_orientation = GetPanelOrientation(
file.GetPlatformFile(), resources->connectors[index_connector]);
std::unique_ptr<Crtc> res_crtc;
// Keep around a file for next display if needed.
base::File file_dup = file.Duplicate();
if (!file_dup.IsValid()) {
continue;
}
// Multiplane is only supported when atomic_modeset is available. Obtain
// the |plane_res_| for later use.
if (atomic_modeset) {
ScopedDrmPlaneResPtr plane_res(
drmModeGetPlaneResources(file.GetPlatformFile()));
CHECK(plane_res) << " Failed to get plane resources";
res_crtc = std::make_unique<Crtc>(std::move(file), std::move(connector),
std::move(encoder), std::move(crtc),
std::move(fb2), std::move(plane_res),
panel_orientation);
} else {
res_crtc = std::make_unique<Crtc>(
std::move(file), std::move(connector), std::move(encoder),
std::move(crtc), std::move(fb2), nullptr, panel_orientation);
}
file = std::move(file_dup);
crtcs.emplace_back(std::move(res_crtc));
}
}
return crtcs;
}
} // namespace
Crtc::Crtc(base::File file,
ScopedDrmModeConnectorPtr connector,
ScopedDrmModeEncoderPtr encoder,
ScopedDrmModeCrtcPtr crtc,
ScopedDrmModeFB2Ptr fb2,
ScopedDrmPlaneResPtr plane_res,
PanelRotation panel_orientation)
: file_(std::move(file)),
connector_(std::move(connector)),
encoder_(std::move(encoder)),
crtc_(std::move(crtc)),
fb2_(std::move(fb2)),
plane_res_(std::move(plane_res)),
panel_orientation_(panel_orientation) {}
bool Crtc::IsInternalDisplay() const {
switch (connector_->connector_type) {
case DRM_MODE_CONNECTOR_eDP:
case DRM_MODE_CONNECTOR_LVDS:
case DRM_MODE_CONNECTOR_DSI:
case DRM_MODE_CONNECTOR_VIRTUAL:
return true;
default:
return false;
}
}
std::unique_ptr<Crtc> CrtcFinder::Find() const {
auto crtcs = GetConnectedCrtcs();
for (auto& crtc : crtcs) {
if (MatchesSpec(crtc.get())) {
return std::move(crtc);
}
}
return nullptr;
}
bool CrtcFinder::MatchesSpec(const Crtc* crtc) const {
switch (spec_) {
case Spec::kAnyDisplay:
return true;
case Spec::kInternalDisplay:
return crtc->IsInternalDisplay();
case Spec::kExternalDisplay:
return !crtc->IsInternalDisplay();
case Spec::kById:
return crtc->crtc()->crtc_id == crtc_id_;
}
NOTREACHED() << "Invalid spec";
}
std::vector<Crtc::PlaneInfo> Crtc::GetConnectedPlanes() const {
CHECK(fb2())
<< "This code path is supported only if drmModeGetFB2() succeeded "
"for the CRTC.";
std::vector<Crtc::PlaneInfo> planes;
if (!plane_res_.get()) {
// Return the empty list if we decided not to query the plane resources or
// if doing so failed.
return planes;
}
for (uint32_t i = 0; i < plane_res_->count_planes; i++) {
ScopedDrmPlanePtr plane(
drmModeGetPlane(file_.GetPlatformFile(), plane_res_->planes[i]));
if (plane->crtc_id != crtc_->crtc_id) {
continue;
}
PlaneConfiguration conf{};
bool res = PopulatePlaneConfiguration(file_.GetPlatformFile(),
plane->plane_id, &conf);
if (!res) {
LOG(WARNING) << "Failed to query plane position, skipping.\n";
continue;
}
ScopedDrmModeFB2Ptr fb_info(
drmModeGetFB2(file_.GetPlatformFile(), plane->fb_id),
file_.GetPlatformFile());
if (!fb_info) {
LOG(WARNING) << "Failed to query plane fb info, skipping.\n";
continue;
}
planes.emplace_back(std::make_pair(std::move(fb_info), conf));
}
return planes;
}
} // namespace screenshot