| // Copyright 2018 The Chromium OS 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 "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; |
| } |
| |
| bool PopulatePlaneConfiguration(int fd, |
| uint32_t plane_id, |
| PlaneConfiguration* conf) { |
| // TODO(andrescj): Handle rotation. |
| std::map<std::string, uint64_t> interesting_props{ |
| {"CRTC_X", 0}, {"CRTC_Y", 0}, {"CRTC_W", 0}, {"CRTC_H", 0}, |
| {"SRC_X", 0}, {"SRC_Y", 0}, {"SRC_W", 0}, {"SRC_H", 0}}; |
| |
| ScopedDrmObjectPropertiesPtr props( |
| drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE)); |
| if (!props) { |
| return false; |
| } |
| |
| int found = 0; |
| for (int i = 0; i < props->count_props; i++) { |
| ScopedDrmPropertyPtr prop(drmModeGetProperty(fd, props->props[i])); |
| if (!prop) { |
| continue; |
| } |
| |
| if (interesting_props.find(prop->name) != interesting_props.end()) { |
| interesting_props[prop->name] = props->prop_values[i]; |
| found++; |
| } |
| } |
| |
| if (found != interesting_props.size()) { |
| return false; |
| } |
| |
| conf->x = static_cast<int32_t>(interesting_props["CRTC_X"]); |
| conf->y = static_cast<int32_t>(interesting_props["CRTC_Y"]); |
| conf->w = static_cast<uint32_t>(interesting_props["CRTC_W"]); |
| conf->h = static_cast<uint32_t>(interesting_props["CRTC_H"]); |
| conf->crop_x = FixedPoint1616ToFloat(interesting_props["SRC_X"]); |
| conf->crop_y = FixedPoint1616ToFloat(interesting_props["SRC_Y"]); |
| conf->crop_w = FixedPoint1616ToFloat(interesting_props["SRC_W"]); |
| conf->crop_h = FixedPoint1616ToFloat(interesting_props["SRC_H"]); |
| return true; |
| } |
| |
| 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. |
| 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; |
| |
| ScopedDrmModeFBPtr fb( |
| drmModeGetFB(file.GetPlatformFile(), crtc->buffer_id)); |
| |
| ScopedDrmModeFB2Ptr fb2( |
| drmModeGetFB2(file.GetPlatformFile(), crtc->buffer_id)); |
| |
| if (!fb && !fb2) { |
| LOG(ERROR) << "getfb failed"; |
| continue; |
| } |
| |
| std::unique_ptr<Crtc> res_crtc; |
| |
| // Multiplane is only handled by egl_capture, so don't bother if |
| // GETFB2 isn't supported. Obtain the |plane_res_| for later use. |
| // |
| // TODO(andrescj): is it possible for |fb2| to be nullptr even if it's |
| // possible to query multiple planes? i.e., is there a case where doing |
| // drmModeGetFB2() for |crtc->buffer_id| fails but doing it for the planes |
| // doesn't? |
| if (fb2 && atomic_modeset) { |
| ScopedDrmPlaneResPtr plane_res( |
| drmModeGetPlaneResources(file.GetPlatformFile())); |
| CHECK(plane_res) << " Failed to get plane resources"; |
| res_crtc = std::make_unique<Crtc>( |
| file.Duplicate(), std::move(connector), std::move(encoder), |
| std::move(crtc), std::move(fb), std::move(fb2), |
| std::move(plane_res)); |
| } |
| |
| if (!res_crtc) { |
| res_crtc = std::make_unique<Crtc>( |
| file.Duplicate(), std::move(connector), std::move(encoder), |
| std::move(crtc), std::move(fb), std::move(fb2), nullptr); |
| } |
| |
| crtcs.emplace_back(std::move(res_crtc)); |
| } |
| } |
| |
| return crtcs; |
| } |
| |
| } // namespace |
| |
| Crtc::Crtc(base::File file, |
| ScopedDrmModeConnectorPtr connector, |
| ScopedDrmModeEncoderPtr encoder, |
| ScopedDrmModeCrtcPtr crtc, |
| ScopedDrmModeFBPtr fb, |
| ScopedDrmModeFB2Ptr fb2, |
| ScopedDrmPlaneResPtr plane_res) |
| : file_(std::move(file)), |
| connector_(std::move(connector)), |
| encoder_(std::move(encoder)), |
| crtc_(std::move(crtc)), |
| fb_(std::move(fb)), |
| fb2_(std::move(fb2)), |
| plane_res_(std::move(plane_res)) {} |
| |
| 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"; |
| return false; |
| } |
| |
| 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)); |
| 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 |