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

// Copyright 2018 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_gpu_util.h"

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

#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
#include "ui/display/types/display_color_management.h"
#include "ui/display/types/gamma_ramp_rgb_entry.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/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h"

namespace ui {

namespace {

struct PossibleCrtcsForConnector {
  uint32_t connector_id;
  std::vector<uint32_t> possible_crtcs;
};

// Recursively build out all possible permutations of CRTC-connector pairings
// given a set of connectors and their possible CRTCs. Each CRTC/connector can
// only be used once per permutation (CrtcConnectorPairs).
// |connectors_it| is an iterator of |connectors| that tracks which connector
// has been used (connector left of |connectors_it|). Passing around
// |connectors_it| is safe due to the constness of |connectors|.
// |crtcs_used_in_current_permutation| tracks if a CRTC has already been used as
// part of the current permutation.
// For example:
//   Connector A can have CRTCs 1, 2, 3
//   Connector B can have CRTCs 2, 3
//   Connector C can have CRTCs 1, 3
// Returned pairings would be:
//   {{A, 1}, {B, 2}, {C, 3}},
//   {{A, 2}, {B, 1}, {C, 3}},
//   {{A, 3}, {B, 2}, {C, 1}}
// But not {{A, 1}, {B, 3}, {C, nothing}} as connector C must also be assigned
// to a valid CRTC and permutations like this are discarded.
std::vector<CrtcConnectorPairs> BuildCrtcConnectorPermutations(
    const std::vector<PossibleCrtcsForConnector>& connectors,
    std::vector<PossibleCrtcsForConnector>::iterator connectors_it,
    base::flat_set<uint32_t /*crtc_id*/>& crtcs_used_in_current_permutation) {
  if (connectors_it == connectors.end()) {
    return {};
  }

  std::vector<CrtcConnectorPairs> permutations;
  const PossibleCrtcsForConnector& connector = *connectors_it;
  // Terminate the recursion once |connectors_it| reaches the end of
  // |connectors|. Also ensures that all |permutations| will have all the
  // connectors paired up with a CRTC.
  if (connectors_it == connectors.end() - 1) {
    // Possible permutations at this point are all unused CRTCs + the current
    // connector.
    for (const uint32_t crtc_id : connector.possible_crtcs) {
      if (!crtcs_used_in_current_permutation.contains(crtc_id)) {
        permutations.push_back({CrtcConnectorPair{
            .crtc_id = crtc_id, .connector_id = connector.connector_id}});
      }
    }
    return permutations;
  }

  for (const uint32_t crtc_id : connector.possible_crtcs) {
    // Skip |crtc_id| if it is already being used in this permutation.
    if (crtcs_used_in_current_permutation.contains(crtc_id)) {
      continue;
    }

    // Mark |crtc| as being in use for the current permutation so that it isn't
    // used multiple times per CrtcConnectorPairs.
    crtcs_used_in_current_permutation.insert(crtc_id);
    std::vector<CrtcConnectorPairs> next_connector_permutations =
        BuildCrtcConnectorPermutations(connectors, connectors_it + 1,
                                       crtcs_used_in_current_permutation);
    crtcs_used_in_current_permutation.erase(crtc_id);

    // Add the current |crtc|-|connector| pair to |next_connector_permutations|
    // as part of recursively building up CrtcConnectorPairs.
    for (auto& permutation : next_connector_permutations) {
      permutation.push_back(CrtcConnectorPair{
          .crtc_id = crtc_id, .connector_id = connector.connector_id});
    }
    permutations.insert(permutations.end(), next_connector_permutations.begin(),
                        next_connector_permutations.end());
  }

  return permutations;
}

// Constants for parsing CTM values.
constexpr uint64_t kCtmSignMask = (1ull << 63);
constexpr uint64_t kCtmValueMask = ~(1ull << 63);
constexpr float kCtmValueScale = static_cast<float>(1ull << 32);

}  // namespace

ControllerConfigParams::ControllerConfigParams(
    int64_t display_id,
    scoped_refptr<DrmDevice> drm,
    uint32_t crtc,
    uint32_t connector,
    gfx::Point origin,
    std::unique_ptr<drmModeModeInfo> pmode,
    bool enable_vrr,
    uint64_t base_connector)
    : display_id(display_id),
      drm(drm),
      crtc(crtc),
      connector(connector),
      base_connector_id(base_connector ? base_connector
                                       : static_cast<uint64_t>(connector)),
      origin(origin),
      mode(std::move(pmode)),
      enable_vrr(enable_vrr) {}

ControllerConfigParams::ControllerConfigParams(
    const ControllerConfigParams& other)
    : display_id(other.display_id),
      drm(other.drm),
      crtc(other.crtc),
      connector(other.connector),
      base_connector_id(other.base_connector_id),
      origin(other.origin),
      enable_vrr(other.enable_vrr) {
  if (other.mode) {
    drmModeModeInfo mode_obj = *other.mode.get();
    mode = std::make_unique<drmModeModeInfo>(mode_obj);
  }
}

ControllerConfigParams::ControllerConfigParams(ControllerConfigParams&& other)
    : display_id(other.display_id),
      drm(other.drm),
      crtc(other.crtc),
      connector(other.connector),
      base_connector_id(other.base_connector_id),
      origin(other.origin),
      enable_vrr(other.enable_vrr) {
  if (other.mode) {
    drmModeModeInfo mode_obj = *other.mode.get();
    mode = std::make_unique<drmModeModeInfo>(mode_obj);
  }
}

ControllerConfigParams::~ControllerConfigParams() = default;

bool GetDrmPropertyForName(DrmWrapper* drm,
                           drmModeObjectProperties* properties,
                           const std::string& name,
                           DrmWrapper::Property* property) {
  for (uint32_t i = 0; i < properties->count_props; ++i) {
    ScopedDrmPropertyPtr drm_property(drm->GetProperty(properties->props[i]));
    if (name != drm_property->name)
      continue;

    property->id = drm_property->prop_id;
    property->value = properties->prop_values[i];
    if (property->id)
      return true;
  }

  return false;
}

bool AddPropertyIfValid(drmModeAtomicReq* property_set,
                        uint32_t object_id,
                        const DrmWrapper::Property& property) {
  if (!property.id)
    return true;

  int ret = drmModeAtomicAddProperty(property_set, object_id, property.id,
                                     property.value);
  if (ret < 0) {
    LOG(ERROR) << "Failed to set property object_id=" << object_id
               << " property_id=" << property.id
               << " property_value=" << property.value << " error=" << -ret;
    return false;
  }

  return true;
}

ScopedDrmColorLutPtr CreateLutBlob(const display::GammaCurve& source,
                                   size_t size) {
  TRACE_EVENT0("drm", "CreateLutBlob");
  if (source.IsDefaultIdentity()) {
    return nullptr;
  }

  ScopedDrmColorLutPtr lut(
      static_cast<drm_color_lut*>(malloc(sizeof(drm_color_lut) * size)));
  drm_color_lut* p = lut.get();
  for (size_t i = 0; i < size; ++i) {
    // Be robust to `size` being 1, since some tests do this.
    source.Evaluate(i / std::max(size - 1.f, 1.f), p[i].red, p[i].green,
                    p[i].blue);
  }
  return lut;
}

bool ParseLutBlob(const void* data, size_t size, display::GammaCurve& result) {
  // LUT blobs are an array of drm_color_lut entries, and so the size of the
  // blob must be a multiple of the size of drm_color_lut.
  if (size % sizeof(drm_color_lut) != 0) {
    LOG(ERROR) << "Invalid size for LUT blob.";
    return false;
  }
  size_t entry_count = size / sizeof(drm_color_lut);
  const drm_color_lut* entries = reinterpret_cast<const drm_color_lut*>(data);
  std::vector<display::GammaRampRGBEntry> lut(entry_count);
  for (size_t i = 0; i < entry_count; ++i) {
    lut[i].r = entries[i].red;
    lut[i].g = entries[i].green;
    lut[i].b = entries[i].blue;
  }
  result = display::GammaCurve(lut);
  return true;
}

ScopedDrmColorCtmPtr CreateCTMBlob(const skcms_Matrix3x3& color_matrix,
                                   bool negative_values_broken) {
  ScopedDrmColorCtmPtr ctm(
      static_cast<drm_color_ctm*>(drmMalloc(sizeof(drm_color_ctm))));
  for (size_t i = 0; i < 9; ++i) {
    float value = color_matrix.vals[i / 3][i % 3];
    if (value < 0) {
      if (negative_values_broken) {
        ctm->matrix[i] = 0;
      } else {
        ctm->matrix[i] =
            static_cast<uint64_t>(-value * kCtmValueScale) & kCtmValueMask;
        ctm->matrix[i] |= kCtmSignMask;
      }
    } else {
      ctm->matrix[i] =
          static_cast<uint64_t>(value * kCtmValueScale) & kCtmValueMask;
    }
  }

  return ctm;
}

bool ParseCTMBlob(const void* data, size_t size, skcms_Matrix3x3& result) {
  // CTM blobs must contain exactly 9 (3x3) numbers which are encoded in
  // uint64_ts.
  if (size != 9 * sizeof(uint64_t)) {
    LOG(ERROR) << "Invalid size for CTM blob.";
    return false;
  }
  const uint64_t* data_u64 = reinterpret_cast<const uint64_t*>(data);
  for (size_t i = 0; i < 9; ++i) {
    float sign = (data_u64[i] & kCtmSignMask) ? -1.f : 1.f;
    float value = (data_u64[i] & kCtmValueMask) / kCtmValueScale;
    result.vals[i / 3][i % 3] = sign * value;
  }
  return true;
}

ScopedDrmModeRectPtr CreateDCBlob(const gfx::Rect& rect) {
  // Damage rect can be empty, but sending empty or negative rects can result in
  // artifacting and black screens. Filter them out here.
  if (rect.width() <= 0 || rect.height() <= 0 || rect.x() < 0 || rect.y() < 0) {
    return nullptr;
  }

  ScopedDrmModeRectPtr dmg_rect(
      static_cast<drm_mode_rect*>(malloc(sizeof(drm_mode_rect))));
  dmg_rect->x1 = rect.x();
  dmg_rect->y1 = rect.y();
  dmg_rect->x2 = rect.right();
  dmg_rect->y2 = rect.bottom();
  return dmg_rect;
}

std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>
GetDisplayInfosAndUpdateCrtcs(DrmWrapper& drm) {
  auto [displays, invalid_crtcs] = GetDisplayInfosAndInvalidCrtcs(drm);
  // Disable invalid CRTCs to allow the preferred CRTCs to be enabled later
  // instead.
  for (uint32_t crtc : invalid_crtcs) {
    drm.DisableCrtc(crtc);
    VLOG(1) << "Disabled undesired CRTC " << crtc;
  }
  return std::move(displays);
}

void DrmWriteIntoTraceHelper(const drmModeModeInfo& mode_info,
                             perfetto::TracedValue context) {
  auto dict = std::move(context).WriteDictionary();

  dict.Add("name", mode_info.name);
  dict.Add("type", mode_info.type);
  dict.Add("flags", mode_info.flags);
  dict.Add("clock", mode_info.clock);
  dict.Add("hdisplay", mode_info.hdisplay);
  dict.Add("vdisplay", mode_info.vdisplay);
}

std::vector<CrtcConnectorPairs> GetAllCrtcConnectorPermutations(
    const DrmDevice& drm,
    const std::vector<ControllerConfigParams>& controllers_params) {
  if (controllers_params.empty()) {
    LOG(DFATAL) << "No connectors specified in controllers_params to generate "
                   "CRTC-connector pairings";
    return {};
  }

  std::vector<PossibleCrtcsForConnector> possible_crtcs_for_connectors;
  for (auto params : controllers_params) {
    const uint32_t possible_crtcs_bitmask =
        drm.plane_manager()->GetPossibleCrtcsBitmaskForConnector(
            params.connector);
    std::vector<uint32_t> possible_crtc_ids =
        GetPossibleCrtcIdsFromBitmask(drm, possible_crtcs_bitmask);
    possible_crtcs_for_connectors.push_back(
        {.connector_id = params.connector,
         .possible_crtcs = std::move(possible_crtc_ids)});
  }

  base::flat_set<uint32_t /*crtc_id*/> crtcs_used_in_current_permutation;
  std::vector<CrtcConnectorPairs> permutations = BuildCrtcConnectorPermutations(
      possible_crtcs_for_connectors, possible_crtcs_for_connectors.begin(),
      crtcs_used_in_current_permutation);

  return permutations;
}

void ApplyCrtcColorSpaceConversion(DrmWrapper* drm,
                                   uint32_t crtc_id,
                                   float rgb[3]) {
  // Look up all properties on this CRTC and create a helper lambda to look up
  // their blobs.
  ScopedDrmObjectPropertyPtr props(
      drm->GetObjectProperties(crtc_id, DRM_MODE_OBJECT_CRTC));
  if (!props) {
    return;
  }
  auto get_blob_by_name = [&](const char* name) {
    DrmDevice::Property property;
    if (!GetDrmPropertyForName(drm, props.get(), name, &property)) {
      return ScopedDrmPropertyBlobPtr(nullptr);
    }
    return drm->GetPropertyBlob(property.value);
  };

  // Apply DEGAMMA.
  ScopedDrmPropertyBlobPtr degamma_blob = get_blob_by_name("DEGAMMA_LUT");
  if (degamma_blob) {
    display::GammaCurve curve;
    if (ParseLutBlob(degamma_blob->data, degamma_blob->length, curve)) {
      curve.Evaluate(rgb);
    }
  }

  // Apply CTM.
  ScopedDrmPropertyBlobPtr ctm_blob = get_blob_by_name("CTM");
  if (ctm_blob) {
    skcms_Matrix3x3 ctm;
    if (ParseCTMBlob(ctm_blob->data, ctm_blob->length, ctm)) {
      float temp[3] = {0, 0, 0};
      for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
          temp[i] += ctm.vals[i][j] * rgb[j];
        }
      }
      for (int i = 0; i < 3; ++i) {
        rgb[i] = temp[i];
      }
    }
  }

  // Apply GAMMA.
  ScopedDrmPropertyBlobPtr gamma_blob = get_blob_by_name("GAMMA_LUT");
  if (gamma_blob) {
    display::GammaCurve curve;
    if (ParseLutBlob(gamma_blob->data, gamma_blob->length, curve)) {
      curve.Evaluate(rgb);
    }
  }
}

}  // namespace ui