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

// Copyright 2014 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/hardware_display_plane_manager.h"

#include <drm_fourcc.h>

#include <cstdint>
#include <memory>
#include <set>
#include <utility>

#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "skia/ext/skia_utils_base.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
#include "ui/display/display_features.h"
#include "ui/display/types/display_color_management.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
#include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"

namespace ui {

namespace {

gfx::Rect OverlayPlaneToDrmSrcRect(const DrmOverlayPlane& plane) {
  const gfx::Size& size = plane.buffer->size();
  gfx::RectF crop_rectf = plane.crop_rect;
  crop_rectf.Scale(size.width(), size.height());
  // DrmOverlayManager::CanHandleCandidate guarantees this is safe.
  gfx::Rect crop_rect = gfx::ToNearestRect(crop_rectf);
  // Convert to 16.16 fixed point required by the DRM overlay APIs.
  return gfx::Rect(crop_rect.x() << 16, crop_rect.y() << 16,
                   crop_rect.width() << 16, crop_rect.height() << 16);
}

skcms_Matrix3x3 PlaneToOutputMatrix(
    const HardwareDisplayPlaneManager::CrtcState& crtc_state) {
  skcms_Matrix3x3 plane_to_xyzd50;
  crtc_state.planes_primaries.toXYZD50(&plane_to_xyzd50);

  skcms_Matrix3x3 output_to_xyzd50;
  crtc_state.output_primaries.toXYZD50(&output_to_xyzd50);

  skcms_Matrix3x3 xyzd50_to_output;
  skcms_Matrix3x3_invert(&output_to_xyzd50, &xyzd50_to_output);

  return skcms_Matrix3x3_concat(&xyzd50_to_output, &plane_to_xyzd50);
}

}  // namespace

HardwareDisplayPlaneList::HardwareDisplayPlaneList() = default;

HardwareDisplayPlaneList::~HardwareDisplayPlaneList() = default;

HardwareDisplayPlaneList::PageFlipInfo::PageFlipInfo(uint32_t crtc_id,
                                                     uint32_t framebuffer)
    : crtc_id(crtc_id), framebuffer(framebuffer) {}

HardwareDisplayPlaneList::PageFlipInfo::PageFlipInfo(
    const PageFlipInfo& other) = default;

HardwareDisplayPlaneList::PageFlipInfo::~PageFlipInfo() = default;

void HardwareDisplayPlaneList::WriteIntoTrace(
    perfetto::TracedValue context) const {
  auto dict = std::move(context).WriteDictionary();

  dict.Add("plane_list", plane_list);
  dict.Add("old_plane_list", old_plane_list);
}

HardwareDisplayPlaneManager::CrtcProperties::CrtcProperties() = default;
HardwareDisplayPlaneManager::CrtcProperties::CrtcProperties(
    const CrtcProperties& other) = default;
HardwareDisplayPlaneManager::CrtcProperties::~CrtcProperties() = default;

HardwareDisplayPlaneManager::CrtcState::CrtcState() = default;

HardwareDisplayPlaneManager::CrtcState::~CrtcState() = default;

HardwareDisplayPlaneManager::CrtcState::CrtcState(CrtcState&&) = default;

HardwareDisplayPlaneManager::HardwareDisplayPlaneManager(DrmDevice* drm)
    : drm_(drm) {}

HardwareDisplayPlaneManager::~HardwareDisplayPlaneManager() = default;

bool HardwareDisplayPlaneManager::Initialize() {
  // Try to get all of the planes if possible, so we don't have to try to
  // discover hidden primary planes.
  uint64_t value = 0;
  has_universal_planes_ =
      drm_->GetCapability(DRM_CLIENT_CAP_UNIVERSAL_PLANES, &value) && value;

  // Mediatek drivers produce broken results when given negative values. It
  // is suspected that this is due to incorrect parsing of the CTM blob.
  // TODO(b/324594144): Address clamping in the driver/kernel
  ctm_negative_values_broken_ = drm_->GetDriverName() == "mediatek";

  // This is to test whether or not it is safe to remove non-universal planes
  // supporting code in a following CL. See crbug.com/1129546 for more details.
  CHECK(has_universal_planes_);

  if (!InitializeCrtcState())
    return false;

  if (!InitializePlanes())
    return false;

  std::sort(planes_.begin(), planes_.end(),
            [](const std::unique_ptr<HardwareDisplayPlane>& l,
               const std::unique_ptr<HardwareDisplayPlane>& r) {
              return l->id() < r->id();
            });

  PopulateSupportedFormats();
  return true;
}

std::unique_ptr<HardwareDisplayPlane> HardwareDisplayPlaneManager::CreatePlane(
    uint32_t id) {
  return std::make_unique<HardwareDisplayPlane>(id);
}

std::optional<int> HardwareDisplayPlaneManager::LookupCrtcIndex(
    uint32_t crtc_id) const {
  for (size_t i = 0; i < crtc_state_.size(); ++i) {
    if (crtc_state_[i].properties.id == crtc_id)
      return i;
  }
  return {};
}

std::optional<int> HardwareDisplayPlaneManager::LookupConnectorIndex(
    uint32_t connector_id) const {
  for (size_t i = 0; i < connectors_props_.size(); ++i) {
    if (connectors_props_[i].id == connector_id)
      return i;
  }
  return {};
}

base::flat_set<uint32_t> HardwareDisplayPlaneManager::CrtcMaskToCrtcIds(
    uint32_t crtc_mask) const {
  base::flat_set<uint32_t> crtc_ids;
  for (uint32_t idx = 0; idx < crtc_state_.size(); idx++) {
    if (crtc_mask & (1 << idx))
      crtc_ids.insert(crtc_state_[idx].properties.id);
  }

  return crtc_ids;
}

bool HardwareDisplayPlaneManager::IsCompatible(HardwareDisplayPlane* plane,
                                               const DrmOverlayPlane& overlay,
                                               uint32_t crtc_id) const {
  bool ownership_compatible =
      plane->owning_crtc() == 0 || plane->owning_crtc() == crtc_id;
  if (plane->in_use() || !ownership_compatible ||
      plane->type() == DRM_PLANE_TYPE_CURSOR ||
      !plane->CanUseForCrtcId(crtc_id)) {
    return false;
  }

  const uint32_t format =
      overlay.enable_blend ? overlay.buffer->framebuffer_pixel_format()
                           : overlay.buffer->opaque_framebuffer_pixel_format();
  if (!plane->IsSupportedFormat(format))
    return false;

  // TODO(kalyank): We should check for z-order and any needed transformation
  // support. Driver doesn't expose any property to check for z-order, can we
  // rely on the sorting we do based on plane ids ?

  return true;
}

void HardwareDisplayPlaneManager::PopulateSupportedFormats() {
  std::set<uint32_t> supported_formats;

  for (const auto& plane : planes_) {
    const std::vector<uint32_t>& formats = plane->supported_formats();
    supported_formats.insert(formats.begin(), formats.end());
  }

  supported_formats_.reserve(supported_formats.size());
  supported_formats_.assign(supported_formats.begin(), supported_formats.end());
}

void HardwareDisplayPlaneManager::ResetCurrentPlaneList(
    HardwareDisplayPlaneList* plane_list) const {
  for (auto* hardware_plane : plane_list->plane_list) {
    hardware_plane->set_in_use(false);
    hardware_plane->set_owning_crtc(0);
  }

  plane_list->plane_list.clear();
  plane_list->legacy_page_flips.clear();
}

void HardwareDisplayPlaneManager::RestoreCurrentPlaneList(
    HardwareDisplayPlaneList* plane_list) const {
  for (auto* plane : plane_list->plane_list) {
    plane->set_in_use(false);
  }
  for (auto* plane : plane_list->old_plane_list) {
    plane->set_in_use(true);
  }
  plane_list->plane_list.clear();
  plane_list->legacy_page_flips.clear();
}

void HardwareDisplayPlaneManager::BeginFrame(
    HardwareDisplayPlaneList* plane_list) {
  for (auto* plane : plane_list->old_plane_list) {
    plane->set_in_use(false);
  }
}

bool HardwareDisplayPlaneManager::AssignOverlayPlanes(
    HardwareDisplayPlaneList* plane_list,
    const DrmOverlayPlaneList& overlay_list,
    uint32_t crtc_id) {
  auto hw_planes_iter = planes_.begin();
  for (const auto& plane : overlay_list) {
    HardwareDisplayPlane* hw_plane = nullptr;
    for (; hw_planes_iter != planes_.end(); ++hw_planes_iter) {
      auto* current = hw_planes_iter->get();
      if (IsCompatible(current, plane, crtc_id)) {
        hw_plane = current;
        ++hw_planes_iter;  // bump so we don't assign the same plane twice
        break;
      }
    }

    if (!hw_plane) {
      RestoreCurrentPlaneList(plane_list);
      return false;
    }

    if (!SetPlaneData(plane_list, hw_plane, plane, crtc_id,
                      OverlayPlaneToDrmSrcRect(plane))) {
      RestoreCurrentPlaneList(plane_list);
      return false;
    }

    // Set the color space for all planes based on the color space of the plane
    // with z-index 0. This assumes that all planes have the same primaries.
    // This assumption will need to be enforced in the compositor's overlay
    // processor.
    if (plane.z_order == 0 && plane.color_space.IsValid()) {
      SetColorSpaceForAllPlanes(crtc_id, plane.color_space.GetPrimaries());
    }

    plane_list->plane_list.push_back(hw_plane);
    hw_plane->set_owning_crtc(crtc_id);
    hw_plane->set_in_use(true);
  }

  return true;
}

const std::vector<uint32_t>& HardwareDisplayPlaneManager::GetSupportedFormats()
    const {
  return supported_formats_;
}

std::vector<uint64_t> HardwareDisplayPlaneManager::GetFormatModifiers(
    uint32_t crtc_id,
    uint32_t format) const {
  for (const auto& plane : planes_) {
    if (plane->CanUseForCrtcId(crtc_id) &&
        plane->type() == DRM_PLANE_TYPE_PRIMARY) {
      return plane->ModifiersForFormat(format);
    }
  }

  return {};
}

base::flat_set<uint32_t>
HardwareDisplayPlaneManager::ResetConnectorsCacheAndGetValidIds(
    const ScopedDrmResourcesPtr& resources) {
  connectors_props_.clear();
  base::flat_set<uint32_t> valid_ids;

  for (int i = 0; i < resources->count_connectors; ++i) {
    const uint32_t connector_id = resources->connectors[i];

    ScopedDrmObjectPropertyPtr props(
        drm_->GetObjectProperties(connector_id, DRM_MODE_OBJECT_CONNECTOR));
    if (!props) {
      PLOG(ERROR) << "Failed to get Connector properties for connector="
                  << connector_id;
      continue;
    }
    // Getting the connector is guaranteed if we survived getting the
    // connector's properties.
    ScopedDrmConnectorPtr connector = drm_->GetConnector(connector_id);
    DCHECK(connector);

    ConnectorProperties state_props;
    state_props.id = connector_id;
    state_props.connection = connector->connection;
    state_props.count_modes = connector->count_modes;
    GetDrmPropertyForName(drm_, props.get(), "CRTC_ID", &state_props.crtc_id);
    DCHECK(!drm_->is_atomic() || state_props.crtc_id.id);
    GetDrmPropertyForName(drm_, props.get(), "link-status",
                          &state_props.link_status);

    const std::vector<uint32_t> possible_encoder_ids(
        connector->encoders, connector->encoders + connector->count_encoders);
    state_props.possible_crtcs_bitmask =
        GetPossibleCrtcsBitmaskFromEncoders(*drm_, possible_encoder_ids);

    connectors_props_.emplace_back(std::move(state_props));
    valid_ids.emplace(connector_id);
  }

  return valid_ids;
}

void HardwareDisplayPlaneManager::SetOutputColorSpace(
    uint32_t crtc_id,
    const SkColorSpacePrimaries& primaries) {
  if (primaries == SkNamedPrimariesExt::kInvalid) {
    LOG(ERROR) << "Invalid output primaries for CRTC " << crtc_id;
    return;
  }
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  if (crtc_state.output_primaries == primaries) {
    return;
  }
  crtc_state.output_primaries = primaries;
  if (base::FeatureList::IsEnabled(display::features::kCtmColorManagement)) {
    UpdatePendingCrtcState(crtc_state);
  }
}

void HardwareDisplayPlaneManager::SetColorSpaceForAllPlanes(
    uint32_t crtc_id,
    const SkColorSpacePrimaries& primaries) {
  if (primaries == SkNamedPrimariesExt::kInvalid) {
    LOG(ERROR) << "Invalid plane primaries for CRTC " << crtc_id;
    return;
  }
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  if (crtc_state.planes_primaries == primaries) {
    return;
  }
  CHECK(primaries != SkNamedPrimariesExt::kInvalid);
  crtc_state.planes_primaries = primaries;
  if (base::FeatureList::IsEnabled(display::features::kCtmColorManagement)) {
    UpdatePendingCrtcState(crtc_state);
  }
}

void HardwareDisplayPlaneManager::SetColorTemperatureAdjustment(
    uint32_t crtc_id,
    const display::ColorTemperatureAdjustment& cta) {
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  crtc_state.color_temperature_adjustment = cta;
  UpdatePendingCrtcState(crtc_state);
  CommitPendingCrtcState(crtc_state);
}

void HardwareDisplayPlaneManager::SetColorCalibration(
    uint32_t crtc_id,
    const display::ColorCalibration& calibration) {
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  crtc_state.color_calibration = calibration;
  UpdatePendingCrtcState(crtc_state);
  CommitPendingCrtcState(crtc_state);
}

void HardwareDisplayPlaneManager::SetGammaAdjustment(
    uint32_t crtc_id,
    const display::GammaAdjustment& adjustment) {
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  crtc_state.gamma_adjustment = adjustment;
  UpdatePendingCrtcState(crtc_state);
  CommitPendingCrtcState(crtc_state);
}

void HardwareDisplayPlaneManager::SetBackgroundColor(
    uint32_t crtc_id,
    const uint64_t background_color) {
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  crtc_state.properties.background_color.value = background_color;
}

bool HardwareDisplayPlaneManager::InitializeCrtcState() {
  ScopedDrmResourcesPtr resources(drm_->GetResources());
  if (!resources) {
    PLOG(ERROR) << "Failed to get resources.";
    return false;
  }

  DisableConnectedConnectorsToCrtcs(resources);
  ResetConnectorsCacheAndGetValidIds(resources);

  unsigned int num_crtcs_with_out_fence_ptr = 0;

  for (int i = 0; i < resources->count_crtcs; ++i) {
    CrtcState state;
    state.properties.id = resources->crtcs[i];

    ScopedDrmObjectPropertyPtr props(
        drm_->GetObjectProperties(resources->crtcs[i], DRM_MODE_OBJECT_CRTC));
    if (!props) {
      PLOG(ERROR) << "Failed to get CRTC properties for crtc_id="
                  << state.properties.id;
      continue;
    }

    GetDrmPropertyForName(drm_, props.get(), "ACTIVE",
                          &state.properties.active);
    DCHECK(!drm_->is_atomic() || state.properties.active.id);
    GetDrmPropertyForName(drm_, props.get(), "MODE_ID",
                          &state.properties.mode_id);
    DCHECK(!drm_->is_atomic() || state.properties.mode_id.id);
    // These properties are optional. If they don't exist we can tell by the
    // invalid ID.
    GetDrmPropertyForName(drm_, props.get(), "CTM", &state.properties.ctm);
    GetDrmPropertyForName(drm_, props.get(), "GAMMA_LUT",
                          &state.properties.gamma_lut);
    GetDrmPropertyForName(drm_, props.get(), "GAMMA_LUT_SIZE",
                          &state.properties.gamma_lut_size);
    GetDrmPropertyForName(drm_, props.get(), "DEGAMMA_LUT",
                          &state.properties.degamma_lut);
    GetDrmPropertyForName(drm_, props.get(), "DEGAMMA_LUT_SIZE",
                          &state.properties.degamma_lut_size);
    GetDrmPropertyForName(drm_, props.get(), "OUT_FENCE_PTR",
                          &state.properties.out_fence_ptr);
    GetDrmPropertyForName(drm_, props.get(), "BACKGROUND_COLOR",
                          &state.properties.background_color);
    GetDrmPropertyForName(drm_, props.get(), kVrrEnabledPropertyName,
                          &state.properties.vrr_enabled);

    num_crtcs_with_out_fence_ptr += (state.properties.out_fence_ptr.id != 0);

    crtc_state_.emplace_back(std::move(state));
  }

  // Check that either all or none of the crtcs support the OUT_FENCE_PTR
  // property. Otherwise we will get an incomplete, and thus not useful,
  // out-fence set when we perform a commit involving the problematic
  // crtcs.
  if (num_crtcs_with_out_fence_ptr != 0 &&
      num_crtcs_with_out_fence_ptr != crtc_state_.size()) {
    LOG(ERROR) << "Only some of the crtcs support the OUT_FENCE_PTR property";
    return false;
  }

  return true;
}

void HardwareDisplayPlaneManager::DisableConnectedConnectorsToCrtcs(
    const ScopedDrmResourcesPtr& resources) {
  // Should only be called when no CRTC state has been set yet because we
  // hard-disable CRTCs.
  DCHECK(crtc_state_.empty());

  for (int i = 0; i < resources->count_connectors; ++i) {
    ScopedDrmConnectorPtr connector =
        drm_->GetConnector(resources->connectors[i]);
    if (!connector)
      continue;
    // Disable Zombie connectors (disconnected connectors but holding to an
    // encoder).
    if (connector->encoder_id &&
        connector->connection == DRM_MODE_DISCONNECTED) {
      ScopedDrmEncoderPtr encoder = drm_->GetEncoder(connector->encoder_id);
      if (encoder)
        drm_->DisableCrtc(encoder->crtc_id);
    }
  }
}

const HardwareDisplayPlaneManager::CrtcState&
HardwareDisplayPlaneManager::GetCrtcStateForCrtcId(uint32_t crtc_id) {
  return CrtcStateForCrtcId(crtc_id);
}

HardwareDisplayPlaneManager::CrtcState&
HardwareDisplayPlaneManager::CrtcStateForCrtcId(uint32_t crtc_id) {
  auto crtc_index = LookupCrtcIndex(crtc_id);
  DCHECK(crtc_index.has_value());
  return crtc_state_[*crtc_index];
}

void HardwareDisplayPlaneManager::UpdateCrtcAndPlaneStatesAfterModeset(
    const CommitRequest& commit_request) {
  base::flat_set<HardwareDisplayPlaneList*> disable_planes_lists;

  for (const auto& crtc_request : commit_request) {
    bool is_enabled = crtc_request.should_enable_crtc();

    auto connector_index = LookupConnectorIndex(crtc_request.connector_id());
    DCHECK(connector_index.has_value());
    ConnectorProperties& connector_props = connectors_props_[*connector_index];
    connector_props.crtc_id.value = is_enabled ? crtc_request.crtc_id() : 0;

    CrtcState& crtc_state = CrtcStateForCrtcId(crtc_request.crtc_id());
    crtc_state.properties.active.value = static_cast<uint64_t>(is_enabled);
    crtc_state.properties.vrr_enabled.value = crtc_request.enable_vrr();

    if (is_enabled) {
      crtc_state.mode = crtc_request.mode();
      crtc_state.modeset_framebuffers.clear();
      for (const auto& overlay : crtc_request.overlays())
        crtc_state.modeset_framebuffers.push_back(overlay.buffer);

    } else {
      if (crtc_request.plane_list())
        disable_planes_lists.insert(crtc_request.plane_list());

      // TODO(crbug.com/40151802): Use atomic APIs to reset cursor plane.
      if (!drm_->SetCursor(crtc_request.crtc_id(), 0, gfx::Size())) {
        PLOG(ERROR) << "Failed to drmModeSetCursor: device:"
                    << drm_->device_path().value()
                    << " crtc:" << crtc_request.crtc_id();
      }
    }
  }

  // TODO(markyacoub): DisableOverlayPlanes should be part of the commit
  // request.
  for (HardwareDisplayPlaneList* list : disable_planes_lists) {
    bool status = DisableOverlayPlanes(list);
    LOG_IF(ERROR, !status) << "Can't disable overlays when disabling HDC.";
    list->plane_list.clear();
  }
}

void HardwareDisplayPlaneManager::ResetModesetStateForCrtc(uint32_t crtc_id) {
  CrtcState& crtc_state = CrtcStateForCrtcId(crtc_id);
  crtc_state.modeset_framebuffers.clear();
}

HardwareCapabilities HardwareDisplayPlaneManager::GetHardwareCapabilities(
    uint32_t crtc_id) {
  std::optional<std::string> driver = drm_->GetDriverName();
  HardwareCapabilities hc;
  if (!driver.has_value()) {
    hc.is_valid = false;
    return hc;
  }

  base::ranges::for_each(
      planes_, [crtc_id, &num_overlay_planes = hc.num_overlay_capable_planes,
                &buffer_formats = hc.supported_buffer_formats](
                   const std::unique_ptr<HardwareDisplayPlane>& plane) {
        if (plane->type() != DRM_PLANE_TYPE_CURSOR &&
            plane->CanUseForCrtcId(crtc_id)) {
          num_overlay_planes++;
          for (const auto& format : plane->supported_formats()) {
            if (ui::IsValidBufferFormat(format)) {
              buffer_formats.emplace(GetBufferFormatFromFourCCFormat(format));
            }
          }
        }
      });

  // While AMD advertises a cursor plane, it's actually a "fake" plane that the
  // display hardware blits to the topmost plane at presentation time. If that
  // topmost plane is scaled/translated (e.g. video), the cursor will then be
  // transformed along with it, leading to an incorrect cursor location in the
  // final presentation. For more info, see b/194335274.
  hc.has_independent_cursor_plane = *driver != "amdgpu" && *driver != "radeon";
  hc.is_valid = true;
  return hc;
}

uint32_t HardwareDisplayPlaneManager::GetPossibleCrtcsBitmaskForConnector(
    uint32_t connector_id) const {
  const auto& connector_prop =
      std::find_if(connectors_props_.begin(), connectors_props_.end(),
                   [connector_id](const ConnectorProperties& prop) {
                     return prop.id == connector_id;
                   });
  if (connector_prop == connectors_props_.end()) {
    LOG(WARNING) << __func__
                 << ": Failed to retrieve connector property for id "
                 << connector_id;
    return {};
  }
  return connector_prop->possible_crtcs_bitmask;
}

void HardwareDisplayPlaneManager::UpdatePendingCrtcState(
    CrtcState& crtc_state) {
  const auto& crtc_props = crtc_state.properties;

  // Set the CTM to convert from the planes' color space primaries to the
  // output color space primaries, followed by application of the color
  // temperature adjustment matrix. This is not the correct math to perform
  // color conversion in the following ways:
  //   * The primary conversion should be done in linear space. This can only
  //     be done if both DEGAMMA and GAMMA are functional, but DEGAMMA is
  //     very often broken.
  //   * The color temperature adjustment matrix is computed to be applied in
  //     sRGB space, not the output space.
  // This is being done as a trade-off sacrificing precise correctness in
  // color conversion for power savings.
  const skcms_Matrix3x3 plane_to_device_matrix =
      base::FeatureList::IsEnabled(display::features::kCtmColorManagement)
          ? PlaneToOutputMatrix(crtc_state)
          : crtc_state.color_calibration.srgb_to_device_matrix;
  const skcms_Matrix3x3 ctm = skcms_Matrix3x3_concat(
      &plane_to_device_matrix,
      &crtc_state.color_temperature_adjustment.srgb_matrix);
  if (crtc_state.properties.ctm.id) {
    ScopedDrmColorCtmPtr ctm_blob_data =
        CreateCTMBlob(ctm, ctm_negative_values_broken_);
    crtc_state.pending_ctm_blob =
        drm_->CreatePropertyBlob(ctm_blob_data.get(), sizeof(drm_color_ctm));
  }

  // Set the DEGAMMA curve to the one specified in the color profile, only if
  // we will also be setting the GAMMA curve.
  // TODO(crbug.com/40945652): This always has to be the identity because
  // many devices have broken implementations. Identitify devices where this
  // functionality is not broken.
  if (crtc_props.gamma_lut.id && crtc_props.gamma_lut_size.id &&
      crtc_props.degamma_lut.id && crtc_props.degamma_lut_size.id) {
    const auto& degamma_curve = crtc_state.color_calibration.srgb_to_linear;
    if (degamma_curve.IsDefaultIdentity()) {
      crtc_state.pending_degamma_lut_blob = nullptr;
    } else {
      ScopedDrmColorLutPtr degamma_blob_data =
          CreateLutBlob(degamma_curve, crtc_props.degamma_lut_size.value);
      crtc_state.pending_degamma_lut_blob = drm_->CreatePropertyBlob(
          degamma_blob_data.get(),
          sizeof(drm_color_lut) * crtc_props.degamma_lut_size.value);
    }
  }

  // Set the GAMMA curve to the concatenation of the color profile with the
  // gamma adjustment.
  // TODO(crbug.com/40945652): Identify devices where this functionality
  // is reliable.
  const auto gamma_curve = display::GammaCurve::MakeConcat(
      crtc_state.color_calibration.linear_to_device,
      crtc_state.gamma_adjustment.curve);
  if (crtc_props.gamma_lut.id && crtc_props.gamma_lut_size.id) {
    if (gamma_curve.IsDefaultIdentity()) {
      crtc_state.pending_gamma_lut_blob = nullptr;
    } else {
      ScopedDrmColorLutPtr gamma_blob_data =
          CreateLutBlob(gamma_curve, crtc_props.gamma_lut_size.value);
      crtc_state.pending_gamma_lut_blob = drm_->CreatePropertyBlob(
          gamma_blob_data.get(),
          sizeof(drm_color_lut) * crtc_props.gamma_lut_size.value);
    }
  } else {
    // Fall back to legacy gamma if needed.
    drm_->SetGammaRamp(crtc_props.id, gamma_curve);
  }
}

}  // namespace ui