chromium/ui/ozone/platform/drm/gpu/drm_display.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/ozone/platform/drm/gpu/drm_display.h"

#include <xf86drm.h>
#include <xf86drmMode.h>

#include <algorithm>
#include <memory>

#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "build/chromeos_buildflags.h"
#include "ui/display/display_features.h"
#include "ui/display/types/display_color_management.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/gfx/color_space.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/common/hardware_display_controller_info.h"
#include "ui/ozone/platform/drm/common/scoped_drm_types.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/screen_manager.h"

namespace ui {

namespace {

std::vector<drmModeModeInfo> GetDrmModeVector(drmModeConnector* connector) {
  std::vector<drmModeModeInfo> modes;
  for (int i = 0; i < connector->count_modes; ++i)
    modes.push_back(connector->modes[i]);

  return modes;
}

}  // namespace

DrmDisplay::PrivacyScreenProperty::PrivacyScreenProperty(
    const scoped_refptr<DrmDevice>& drm,
    drmModeConnector* connector)
    : drm_(drm), connector_(connector) {
  privacy_screen_hw_state_ =
      drm_->GetProperty(connector_, kPrivacyScreenHwStatePropertyName);
  privacy_screen_sw_state_ =
      drm_->GetProperty(connector_, kPrivacyScreenSwStatePropertyName);

  if (!privacy_screen_hw_state_ || !privacy_screen_sw_state_) {
    privacy_screen_hw_state_.reset();
    privacy_screen_sw_state_.reset();

    property_last_ = display::kPrivacyScreenLegacyStateLast;
    privacy_screen_legacy_ =
        drm_->GetProperty(connector_, kPrivacyScreenPropertyNameLegacy);
  }
}

DrmDisplay::PrivacyScreenProperty::~PrivacyScreenProperty() = default;

bool DrmDisplay::PrivacyScreenProperty::SetPrivacyScreenProperty(bool enabled) {
  drmModePropertyRes* property = GetWritePrivacyScreenProperty();
  if (!property) {
    LOG(ERROR)
        << "Privacy screen is not supported but an attempt to set it was made.";
    return false;
  }

  const display::PrivacyScreenState state_to_set =
      enabled ? display::kEnabled : display::kDisabled;
  if (!drm_->SetProperty(connector_->connector_id, property->prop_id,
                         GetDrmValueForInternalType(state_to_set, *property,
                                                    kPrivacyScreenStates))) {
    LOG(ERROR) << (enabled ? "Enabling" : "Disabling") << " property '"
               << property->name << "' failed!";
    return false;
  }

  return ValidateCurrentStateAgainst(enabled);
}

display::PrivacyScreenState
DrmDisplay::PrivacyScreenProperty::GetPrivacyScreenState() const {
  drmModePropertyRes* property = GetReadPrivacyScreenProperty();
  if (!property) {
    LOG(ERROR) << "Privacy screen is not supported but an attempt to read its "
                  "state was made.";
    return display::kNotSupported;
  }

  ScopedDrmObjectPropertyPtr property_values(drm_->GetObjectProperties(
      connector_->connector_id, DRM_MODE_OBJECT_CONNECTOR));
  if (!property_values) {
    PLOG(INFO) << "Properties no longer valid for connector "
               << connector_->connector_id << ".";
    return display::kNotSupported;
  }

  const std::string privacy_screen_state_name =
      GetEnumNameForProperty(*property, *property_values);
  const display::PrivacyScreenState* state = GetInternalTypeValueFromDrmEnum(
      privacy_screen_state_name, kPrivacyScreenStates);
  return state ? *state : display::kNotSupported;
}

bool DrmDisplay::PrivacyScreenProperty::ValidateCurrentStateAgainst(
    bool enabled) const {
  display::PrivacyScreenState current_state = GetPrivacyScreenState();
  if (current_state == display::kNotSupported)
    return false;

  bool currently_on = false;
  if (current_state == display::kEnabled ||
      current_state == display::kEnabledLocked) {
    currently_on = true;
  }
  return currently_on == enabled;
}

drmModePropertyRes*
DrmDisplay::PrivacyScreenProperty::GetReadPrivacyScreenProperty() const {
  if (privacy_screen_hw_state_ && privacy_screen_sw_state_)
    return privacy_screen_hw_state_.get();
  return privacy_screen_legacy_.get();
}

drmModePropertyRes*
DrmDisplay::PrivacyScreenProperty::GetWritePrivacyScreenProperty() const {
  if (privacy_screen_hw_state_ && privacy_screen_sw_state_)
    return privacy_screen_sw_state_.get();
  return privacy_screen_legacy_.get();
}

DrmDisplay::CrtcConnectorPair::CrtcConnectorPair(
    uint32_t crtc_id,
    ScopedDrmConnectorPtr drm_connector,
    std::optional<gfx::Point> tile_location)
    : crtc_id(crtc_id),
      connector(std::move(drm_connector)),
      tile_location(tile_location) {
  CHECK(connector != nullptr);
}

DrmDisplay::CrtcConnectorPair::CrtcConnectorPair(
    DrmDisplay::CrtcConnectorPair&& other) noexcept = default;
DrmDisplay::CrtcConnectorPair& DrmDisplay::CrtcConnectorPair::operator=(
    DrmDisplay::CrtcConnectorPair&& other) noexcept = default;

DrmDisplay::CrtcConnectorPair::~CrtcConnectorPair() = default;

DrmDisplay::CrtcConnectorPair CreateCrtcConnectorPair(
    HardwareDisplayControllerInfo& info) {
  std::optional<gfx::Point> tile_location = std::nullopt;
  if (info.tile_property().has_value()) {
    tile_location = info.tile_property()->location;
  }

  return DrmDisplay::CrtcConnectorPair(info.crtc()->crtc_id,
                                       info.ReleaseConnector(), tile_location);
}

std::vector<DrmDisplay::CrtcConnectorPair> CreateCrtcConnectorPairs(
    HardwareDisplayControllerInfo* info) {
  CHECK(info != nullptr);

  std::vector<DrmDisplay::CrtcConnectorPair> crtc_connector_pairs;
  // If the display is tiled, then |info| is always the primary info.
  crtc_connector_pairs.push_back(CreateCrtcConnectorPair(*info));

  for (auto& tile_info : info->nonprimary_tile_infos()) {
    crtc_connector_pairs.push_back(CreateCrtcConnectorPair(*tile_info));
  }

  return crtc_connector_pairs;
}

DrmDisplay::DrmDisplay(const scoped_refptr<DrmDevice>& drm,
                       HardwareDisplayControllerInfo* info,
                       const display::DisplaySnapshot& display_snapshot)
    : display_id_(display_snapshot.display_id()),
      base_connector_id_(display_snapshot.base_connector_id()),
      drm_(drm),
      crtc_connector_pairs_(CreateCrtcConnectorPairs(info)),
      primary_crtc_connector_pair_(crtc_connector_pairs_.front()) {
  modes_ = GetDrmModeVector(primary_crtc_connector_pair_->connector.get());
  origin_ = display_snapshot.origin();
  hdr_static_metadata_ = display_snapshot.hdr_static_metadata();
  privacy_screen_property_ = std::make_unique<PrivacyScreenProperty>(
      drm_, primary_crtc_connector_pair_->connector.get());
  tile_property_ = info->tile_property();

  SkColorSpacePrimaries output_primaries =
      display_snapshot.color_info().edid_primaries;
#if BUILDFLAG(IS_CHROMEOS_ASH)
  // Do not allow display_snapshot and connector property state to go out of
  // sync. HDR capability is determined in
  // gfx::DisplayUtil::GetColorSpaceFromEdid
  if (display_snapshot.color_space() == gfx::ColorSpace::CreateHDR10()) {
    output_primaries = SkNamedPrimariesExt::kRec2020;
    SetColorspaceProperty(display_snapshot.color_space());
    SetHdrOutputMetadata(display_snapshot.color_space());
  } else {
    SetColorspaceProperty(gfx::ColorSpace::CreateSRGB());
    ClearHdrOutputMetadata();
  }
#endif
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    drm_->plane_manager()->SetOutputColorSpace(crtc_connector_pair.crtc_id,
                                               output_primaries);
  }

  vsync_rate_min_from_edid_ = info->edid_parser().has_value()
                                  ? info->edid_parser()->vsync_rate_min()
                                  : std::nullopt;
}

DrmDisplay::~DrmDisplay() = default;

uint32_t DrmDisplay::GetPrimaryConnectorId() const {
  DCHECK(primary_crtc_connector_pair_->connector);
  return primary_crtc_connector_pair_->connector->connector_id;
}

const std::vector<DrmDisplay::CrtcConnectorPair>&
DrmDisplay::crtc_connector_pairs() const {
  return crtc_connector_pairs_;
}

bool DrmDisplay::ReplaceCrtcs(
    const base::flat_map<uint32_t /*current_crtc*/, uint32_t /*new_crtc*/>&
        current_to_new_crtc_ids) {
  std::vector<CrtcConnectorPair*> target_crtc_connector_pairs;
  // Check that there are no overlaps in new CRTC IDs
  base::flat_set<uint32_t> new_crtc_ids;
  for (auto [current_crtc_id, new_crtc_id] : current_to_new_crtc_ids) {
    std::pair<base::flat_set<uint32_t>::iterator, bool> insert_it =
        new_crtc_ids.insert(new_crtc_id);
    // Fail out if insertion failed due to existing key (duplication).
    if (!insert_it.second) {
      return false;
    }

    // Find CrtcConnectorPair corresponding to |current_crtc_id|.
    CrtcConnectorPair* target_crtc_connector_pair = nullptr;
    for (auto& crtc_connector_pair : crtc_connector_pairs_) {
      if (crtc_connector_pair.crtc_id == current_crtc_id) {
        target_crtc_connector_pair = &crtc_connector_pair;
        break;
      }
    }
    if (!target_crtc_connector_pair) {
      return false;
    }
    target_crtc_connector_pairs.push_back(target_crtc_connector_pair);
  }

  // Ensure that all CrtcConnectorPairs are getting replaced.
  if (target_crtc_connector_pairs.size() != crtc_connector_pairs_.size()) {
    return false;
  }

  for (auto& crtc_connector_pair : target_crtc_connector_pairs) {
    crtc_connector_pair->crtc_id =
        current_to_new_crtc_ids.at(crtc_connector_pair->crtc_id);
  }

  return true;
}

bool DrmDisplay::SetHdcpKeyProp(const std::string& key) {
  DCHECK(primary_crtc_connector_pair_->connector);

  TRACE_EVENT1("drm", "DrmDisplay::SetHdcpKeyProp", "connector",
               primary_crtc_connector_pair_->connector->connector_id);

  // The HDCP key is secret, we want to create it as write only so the user
  // space can't read it back. (i.e. through `modetest`)
  ScopedDrmPropertyBlob key_blob;
  // TODO(markyacoub): the flag requires being merged to libdrm then backported
  // to CrOS. Remove the #if once that happens.
#if defined(DRM_MODE_CREATE_BLOB_WRITE_ONLY)
  key_blob = drm_->CreatePropertyBlobWithFlags(key.data(), key.size(),
                                               DRM_MODE_CREATE_BLOB_WRITE_ONLY);
#endif

  if (!key_blob) {
    LOG(ERROR) << "Failed to create HDCP Key property blob";
    return false;
  }

  ScopedDrmPropertyPtr hdcp_key_property(drm_->GetProperty(
      primary_crtc_connector_pair_->connector.get(), kContentProtectionKey));
  DCHECK(hdcp_key_property);

  return drm_->SetProperty(
      primary_crtc_connector_pair_->connector->connector_id,
      hdcp_key_property->prop_id, key_blob->id());
}

// When reading DRM state always check that it's still valid. Any sort of events
// (such as disconnects) may invalidate the state.
bool DrmDisplay::GetHDCPState(
    display::HDCPState* hdcp_state,
    display::ContentProtectionMethod* protection_method) {
  DCHECK(primary_crtc_connector_pair_->connector);

  TRACE_EVENT1("drm", "DrmDisplay::GetHDCPState", "connector",
               primary_crtc_connector_pair_->connector->connector_id);
  ScopedDrmPropertyPtr hdcp_property(drm_->GetProperty(
      primary_crtc_connector_pair_->connector.get(), kContentProtection));
  if (!hdcp_property) {
    PLOG(INFO) << "'" << kContentProtection << "' property doesn't exist.";
    return false;
  }

  ScopedDrmObjectPropertyPtr property_values(drm_->GetObjectProperties(
      primary_crtc_connector_pair_->connector->connector_id,
      DRM_MODE_OBJECT_CONNECTOR));
  if (!property_values) {
    PLOG(INFO) << "Properties no longer valid for connector "
               << primary_crtc_connector_pair_->connector->connector_id << ".";
    return false;
  }

  const display::HDCPState* hw_hdcp_state =
      GetDrmPropertyCurrentValueAsInternalType(
          kContentProtectionStates, *hdcp_property, *property_values);
  if (hw_hdcp_state) {
    VLOG(3) << "HDCP state: " << *hw_hdcp_state << ".";
    *hdcp_state = *hw_hdcp_state;
  } else {
    LOG(ERROR) << "Unknown content protection value.";
    return false;
  }

  if (*hdcp_state == display::HDCP_STATE_UNDESIRED) {
    // ProtectionMethod doesn't matter if we don't have it desired/enabled.
    *protection_method = display::CONTENT_PROTECTION_METHOD_NONE;
    return true;
  }

  ScopedDrmPropertyPtr content_type_property(drm_->GetProperty(
      primary_crtc_connector_pair_->connector.get(), kHdcpContentType));
  if (!content_type_property) {
    // This won't exist if the driver doesn't support HDCP 2.2, so default it in
    // that case.
    VLOG(3) << "HDCP Content Type not supported, default to Type 0";
    *protection_method = display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_0;
    return true;
  }

  const display::ContentProtectionMethod* hw_protection_method =
      GetDrmPropertyCurrentValueAsInternalType(
          kHdcpContentTypeStates, *content_type_property, *property_values);
  if (hw_protection_method) {
    VLOG(3) << "Content Protection Method: " << *protection_method << ".";
    *protection_method = *hw_protection_method;
  } else {
    LOG(ERROR) << "Unknown HDCP content type value.";
    return false;
  }

  return true;
}

bool DrmDisplay::SetHDCPState(
    display::HDCPState state,
    display::ContentProtectionMethod protection_method) {
  DCHECK(primary_crtc_connector_pair_->connector);

  if (protection_method != display::CONTENT_PROTECTION_METHOD_NONE) {
    ScopedDrmPropertyPtr content_type_property(drm_->GetProperty(
        primary_crtc_connector_pair_->connector.get(), kHdcpContentType));
    if (!content_type_property) {
      // If the driver doesn't support HDCP 2.2, this won't exist.
      if (protection_method & display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_1) {
        // We can't do this, since we can't specify the content type.
        VLOG(3)
            << "Cannot set HDCP Content Type 1 since driver doesn't support it";
        return false;
      }
      VLOG(3) << "HDCP Content Type not supported, default to Type 0";
    } else {
      if (!drm_->SetProperty(
              primary_crtc_connector_pair_->connector->connector_id,
              content_type_property->prop_id,
              GetDrmValueForInternalType(protection_method,
                                         *content_type_property,
                                         kHdcpContentTypeStates))) {
        // Failed setting HDCP Content Type.
        return false;
      }
    }
  }

  ScopedDrmPropertyPtr hdcp_property(drm_->GetProperty(
      primary_crtc_connector_pair_->connector.get(), kContentProtection));
  if (!hdcp_property) {
    PLOG(INFO) << "'" << kContentProtection << "' property doesn't exist.";
    return false;
  }

  return drm_->SetProperty(
      primary_crtc_connector_pair_->connector->connector_id,
      hdcp_property->prop_id,
      GetDrmValueForInternalType(state, *hdcp_property,
                                 kContentProtectionStates));
}

void DrmDisplay::SetColorTemperatureAdjustment(
    const display::ColorTemperatureAdjustment& cta) {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    drm_->plane_manager()->SetColorTemperatureAdjustment(
        crtc_connector_pair.crtc_id, cta);
  }
}

void DrmDisplay::SetColorCalibration(
    const display::ColorCalibration& calibration) {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    drm_->plane_manager()->SetColorCalibration(crtc_connector_pair.crtc_id,
                                               calibration);
  }
}

void DrmDisplay::SetGammaAdjustment(
    const display::GammaAdjustment& adjustment) {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    drm_->plane_manager()->SetGammaAdjustment(crtc_connector_pair.crtc_id,
                                              adjustment);
  }
}

void DrmDisplay::SetBackgroundColor(const uint64_t background_color) {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    drm_->plane_manager()->SetBackgroundColor(crtc_connector_pair.crtc_id,
                                              background_color);
  }
}

bool DrmDisplay::SetPrivacyScreen(bool enabled) {
  return privacy_screen_property_->SetPrivacyScreenProperty(enabled);
}

gfx::HDRStaticMetadata::Eotf DrmDisplay::GetEotf(
    const gfx::ColorSpace::TransferID transfer_id) {
  switch (transfer_id) {
    case gfx::ColorSpace::TransferID::SRGB:
      return gfx::HDRStaticMetadata::Eotf::kGammaSdrRange;
    case gfx::ColorSpace::TransferID::PQ:
      return gfx::HDRStaticMetadata::Eotf::kPq;
    case gfx::ColorSpace::TransferID::HLG:
      return gfx::HDRStaticMetadata::Eotf::kHlg;
    case gfx::ColorSpace::TransferID::SRGB_HDR:
    case gfx::ColorSpace::TransferID::LINEAR_HDR:
    case gfx::ColorSpace::TransferID::CUSTOM_HDR:
    case gfx::ColorSpace::TransferID::PIECEWISE_HDR:
    case gfx::ColorSpace::TransferID::SCRGB_LINEAR_80_NITS:
      return gfx::HDRStaticMetadata::Eotf::kGammaHdrRange;
    default:
      NOTREACHED_IN_MIGRATION();
      return gfx::HDRStaticMetadata::Eotf::kGammaSdrRange;
  }
}

bool DrmDisplay::ClearHdrOutputMetadata() {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    DCHECK(crtc_connector_pair.connector);

    ScopedDrmPropertyPtr hdr_output_metadata_property(drm_->GetProperty(
        crtc_connector_pair.connector.get(), kHdrOutputMetadata));
    if (!hdr_output_metadata_property) {
      PLOG(INFO) << "'" << kHdrOutputMetadata
                 << "' property doesn't exist for connector "
                 << crtc_connector_pair.connector->connector_id;
      return false;
    }

    // TODO(b/342617770): Atomically set connector properties across all
    // connectors owned by DrmDisplay to prevent scenarios where SetProperty()
    // succeeds for a subset of the connectors and creates inconsistencies.
    if (!drm_->SetProperty(crtc_connector_pair.connector->connector_id,
                           hdr_output_metadata_property->prop_id, 0)) {
      PLOG(INFO) << "Cannot set '" << kHdrOutputMetadata
                 << "' property on connector "
                 << crtc_connector_pair.connector->connector_id;
      return false;
    }
  }

  return true;
}

bool DrmDisplay::SetHdrOutputMetadata(const gfx::ColorSpace color_space) {
  DCHECK(hdr_static_metadata_.has_value());
  DCHECK(color_space.IsValid());

  ScopedDrmHdrOutputMetadataPtr hdr_output_metadata(
      static_cast<drm_hdr_output_metadata*>(
          drmMalloc(sizeof(drm_hdr_output_metadata))));
  hdr_output_metadata->metadata_type = 0;
  hdr_output_metadata->hdmi_metadata_type1.metadata_type = 0;

  gfx::HDRStaticMetadata::Eotf eotf = GetEotf(color_space.GetTransferID());
  DCHECK(hdr_static_metadata_->IsEotfSupported(eotf));
  hdr_output_metadata->hdmi_metadata_type1.eotf = static_cast<uint8_t>(eotf);

  hdr_output_metadata->hdmi_metadata_type1.max_cll = 0;
  hdr_output_metadata->hdmi_metadata_type1.max_fall =
      hdr_static_metadata_->max_avg;
  // This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
  // where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
  hdr_output_metadata->hdmi_metadata_type1.max_display_mastering_luminance =
      hdr_static_metadata_->max;
  // This value is coded as an unsigned 16-bit value in units of 0.0001 cd/m2,
  // where 0x0001 represents 0.0001 cd/m2 and 0xFFFF represents 6.5535 cd/m2.
  hdr_output_metadata->hdmi_metadata_type1.min_display_mastering_luminance =
      hdr_static_metadata_->min * 10000.0;

  SkColorSpacePrimaries primaries = color_space.GetPrimaries();
  constexpr int kPrimariesFixedPoint = 50000;
  hdr_output_metadata->hdmi_metadata_type1.display_primaries[0].x =
      primaries.fRX * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.display_primaries[0].y =
      primaries.fRY * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.display_primaries[1].x =
      primaries.fGX * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.display_primaries[1].y =
      primaries.fGY * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.display_primaries[2].x =
      primaries.fBX * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.display_primaries[2].y =
      primaries.fBY * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.white_point.x =
      primaries.fWX * kPrimariesFixedPoint;
  hdr_output_metadata->hdmi_metadata_type1.white_point.y =
      primaries.fWY * kPrimariesFixedPoint;

  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    DCHECK(crtc_connector_pair.connector);

    ScopedDrmPropertyBlob hdr_output_metadata_property_blob =
        drm_->CreatePropertyBlob(hdr_output_metadata.get(),
                                 sizeof(drm_hdr_output_metadata));
    if (!hdr_output_metadata_property_blob) {
      PLOG(INFO) << "Cannot create '" << kHdrOutputMetadata
                 << "' property blob.";
      return false;
    }

    ScopedDrmPropertyPtr hdr_output_metadata_property(drm_->GetProperty(
        crtc_connector_pair.connector.get(), kHdrOutputMetadata));
    if (!hdr_output_metadata_property) {
      PLOG(INFO) << "'" << kHdrOutputMetadata
                 << "' property doesn't exist for connector "
                 << crtc_connector_pair.connector->connector_id;
      return false;
    }

    // TODO(b/342617770): Atomically set connector properties across all
    // connectors owned by DrmDisplay to prevent scenarios where SetProperty()
    // succeeds for a subset of the connectors and creates inconsistencies.
    if (!hdr_output_metadata_property->prop_id ||
        !drm_->SetProperty(crtc_connector_pair.connector->connector_id,
                           hdr_output_metadata_property->prop_id,
                           hdr_output_metadata_property_blob->id())) {
      PLOG(ERROR) << "Cannot set '" << kHdrOutputMetadata << "' property.";
      return false;
    }
  }

  return true;
}

bool DrmDisplay::SetColorspaceProperty(const gfx::ColorSpace color_space) {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    DCHECK(crtc_connector_pair.connector);

    ScopedDrmPropertyPtr color_space_property(
        drm_->GetProperty(crtc_connector_pair.connector.get(), kColorSpace));
    if (!color_space_property) {
      PLOG(INFO) << "'" << kColorSpace << "' property doesn't exist.";
      return false;
    }

    // TODO(b/342617770): Atomically set connector properties across all
    // connectors owned by DrmDisplay to prevent scenarios where SetProperty()
    // succeeds for a subset of the connectors and creates inconsistencies.
    if (!color_space_property->prop_id ||
        !drm_->SetProperty(
            crtc_connector_pair.connector->connector_id,
            color_space_property->prop_id,
            GetEnumValueForName(*drm_, color_space_property->prop_id,
                                GetNameForColorspace(color_space)))) {
      PLOG(ERROR) << "Cannot set '" << GetNameForColorspace(color_space)
                  << "' to '" << kColorSpace << "' property for connector "
                  << crtc_connector_pair.connector->connector_id;
      return false;
    }
  }
  return true;
}

bool DrmDisplay::IsVrrCapable() const {
  const bool is_vsync_rate_min_positive =
      vsync_rate_min_from_edid_.has_value() && *vsync_rate_min_from_edid_ > 0;
  if (!is_vsync_rate_min_positive) {
    return false;
  }

  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    drmModeConnector* connector = crtc_connector_pair.connector.get();
    if (!connector || !ui::IsVrrCapable(*drm_, connector)) {
      return false;
    }
  }

  return true;
}

std::optional<TileProperty> DrmDisplay::GetTileProperty() const {
  return tile_property_;
}

const DrmDisplay::CrtcConnectorPair*
DrmDisplay::GetCrtcConnectorPairForConnectorId(uint32_t connector_id) const {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    DCHECK(crtc_connector_pair.connector);

    if (crtc_connector_pair.connector->connector_id == connector_id) {
      return &crtc_connector_pair;
    }
  }
  return nullptr;
}

bool DrmDisplay::ContainsCrtc(uint32_t crtc_id) const {
  for (const auto& crtc_connector_pair : crtc_connector_pairs_) {
    if (crtc_connector_pair.crtc_id == crtc_id) {
      return true;
    }
  }
  return false;
}

}  // namespace ui