chromium/ui/ozone/platform/drm/common/drm_util.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/common/drm_util.h"

#include <drm_fourcc.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/not_fatal_until.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/display_features.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/util/display_util.h"
#include "ui/display/util/edid_parser.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/common/tile_property.h"

namespace ui {

namespace {

static const size_t kDefaultCursorWidth = 64;
static const size_t kDefaultCursorHeight = 64;

bool IsCrtcInUse(
    uint32_t crtc,
    const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>&
        displays) {
  for (const auto& display : displays) {
    if (crtc == display->crtc()->crtc_id)
      return true;
  }

  return false;
}

// Returns a CRTC compatible with |connector| and not already used in |displays|
// and the CRTC that's currently connected to the connector.
// If there are multiple compatible CRTCs, the one that supports the majority of
// planes will be returned as best CRTC.
std::pair<uint32_t /* best_crtc */, uint32_t /* connected_crtc */> GetCrtcs(
    const DrmWrapper& drm,
    drmModeConnector* connector,
    drmModeRes* resources,
    const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>& displays,
    const std::vector<ScopedDrmPlanePtr>& planes) {
  DCHECK_GE(32, resources->count_crtcs);
  int most_crtc_planes = -1;
  uint32_t best_crtc = 0;
  uint32_t connected_crtc = 0;

  // Try to find an encoder for the connector.
  for (int i = 0; i < connector->count_encoders; ++i) {
    ScopedDrmEncoderPtr encoder = drm.GetEncoder(connector->encoders[i]);
    if (!encoder)
      continue;

    if (connector->encoder_id == encoder->encoder_id)
      connected_crtc = encoder->crtc_id;

    for (int j = 0; j < resources->count_crtcs; ++j) {
      // Check if the encoder is compatible with this CRTC
      int crtc_bit = 1 << j;
      if (!(encoder->possible_crtcs & crtc_bit) ||
          IsCrtcInUse(resources->crtcs[j], displays))
        continue;

      int supported_planes = base::ranges::count_if(
          planes, [crtc_bit](const ScopedDrmPlanePtr& p) {
            return p->possible_crtcs & crtc_bit;
          });
      if (supported_planes > most_crtc_planes ||
          (supported_planes == most_crtc_planes &&
           connected_crtc == resources->crtcs[j])) {
        most_crtc_planes = supported_planes;
        best_crtc = resources->crtcs[j];
      }
    }
  }

  return std::make_pair(best_crtc, connected_crtc);
}

// Computes the refresh rate for the specific mode. If we have enough
// information use the mode timings to compute a more exact value otherwise
// fallback to using the mode's vertical refresh rate (the kernel computes this
// the same way, however there is a loss in precision since |vrefresh| is sent
// as an integer).
float GetRefreshRate(const drmModeModeInfo& mode) {
  if (!mode.htotal || !mode.vtotal)
    return mode.vrefresh;

  float clock = mode.clock;
  float htotal = mode.htotal;
  float vtotal = mode.vtotal;

  return (clock * 1000.0f) / (htotal * vtotal);
}

display::DisplayConnectionType GetDisplayConnectionType(
    drmModeConnector* connector) {
  switch (connector->connector_type) {
    case DRM_MODE_CONNECTOR_VGA:
      return display::DISPLAY_CONNECTION_TYPE_VGA;
    case DRM_MODE_CONNECTOR_DVII:
    case DRM_MODE_CONNECTOR_DVID:
    case DRM_MODE_CONNECTOR_DVIA:
      return display::DISPLAY_CONNECTION_TYPE_DVI;
    case DRM_MODE_CONNECTOR_LVDS:
    case DRM_MODE_CONNECTOR_eDP:
    case DRM_MODE_CONNECTOR_DSI:
      return display::DISPLAY_CONNECTION_TYPE_INTERNAL;
    case DRM_MODE_CONNECTOR_DisplayPort:
      return display::DISPLAY_CONNECTION_TYPE_DISPLAYPORT;
    case DRM_MODE_CONNECTOR_HDMIA:
    case DRM_MODE_CONNECTOR_HDMIB:
      return display::DISPLAY_CONNECTION_TYPE_HDMI;
    case DRM_MODE_CONNECTOR_VIRTUAL:
      if (base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kDRMVirtualConnectorIsExternal)) {
        return display::DISPLAY_CONNECTION_TYPE_UNKNOWN;
      }
      // A display on VM is treated as an internal display unless flag
      // --drm-virtual-connector-is-external is present.
      return display::DISPLAY_CONNECTION_TYPE_INTERNAL;
    default:
      return display::DISPLAY_CONNECTION_TYPE_UNKNOWN;
  }
}

template <typename T>
int GetDrmProperty(const DrmWrapper& drm,
                   T* object,
                   const std::string& name,
                   ScopedDrmPropertyPtr* property) {
  for (uint32_t i = 0; i < static_cast<uint32_t>(object->count_props); ++i) {
    ScopedDrmPropertyPtr tmp = drm.GetProperty(object->props[i]);
    if (!tmp)
      continue;

    if (name == tmp->name) {
      *property = std::move(tmp);
      return i;
    }
  }

  return -1;
}

std::string GetNameForEnumValue(drmModePropertyRes* property, uint32_t value) {
  for (int i = 0; i < property->count_enums; ++i) {
    if (property->enums[i].value == value)
      return property->enums[i].name;
  }

  return std::string();
}

ScopedDrmPropertyBlobPtr GetDrmPropertyBlob(const DrmWrapper& drm,
                                            drmModeConnector* connector,
                                            const std::string& name) {
  ScopedDrmPropertyPtr property;
  int index = GetDrmProperty(drm, connector, name, &property);
  if (index < 0)
    return nullptr;

  if (property->flags & DRM_MODE_PROP_BLOB) {
    return drm.GetPropertyBlob(connector->prop_values[index]);
  }

  return nullptr;
}

display::PrivacyScreenState GetPrivacyScreenState(const DrmWrapper& drm,
                                                  drmModeConnector* connector) {
  ScopedDrmPropertyPtr sw_property;
  const int sw_index = GetDrmProperty(
      drm, connector, kPrivacyScreenSwStatePropertyName, &sw_property);
  ScopedDrmPropertyPtr hw_property;
  const int hw_index = GetDrmProperty(
      drm, connector, kPrivacyScreenHwStatePropertyName, &hw_property);

  // Both privacy-screen properties (software- and hardware-state) must be
  // present in order for the feature to be supported, but the hardware-state
  // property indicates the true state of the privacy screen.
  if (sw_index >= 0 && hw_index >= 0) {
    const std::string hw_enum_value = GetNameForEnumValue(
        hw_property.get(), connector->prop_values[hw_index]);
    const display::PrivacyScreenState* state =
        GetInternalTypeValueFromDrmEnum(hw_enum_value, kPrivacyScreenStates);
    return state ? *state : display::kNotSupported;
  }

  // If the new privacy screen UAPI properties are missing, try to fetch the
  // legacy privacy screen property.
  ScopedDrmPropertyPtr legacy_property;
  const int legacy_index = GetDrmProperty(
      drm, connector, kPrivacyScreenPropertyNameLegacy, &legacy_property);
  if (legacy_index >= 0) {
    const std::string legacy_enum_value = GetNameForEnumValue(
        legacy_property.get(), connector->prop_values[legacy_index]);
    const display::PrivacyScreenState* state = GetInternalTypeValueFromDrmEnum(
        legacy_enum_value, kPrivacyScreenStates);
    return state ? *state : display::kNotSupported;
  }

  return display::PrivacyScreenState::kNotSupported;
}

bool HasContentProtectionKey(const DrmWrapper& drm,
                             drmModeConnector* connector) {
  ScopedDrmPropertyPtr content_protection_key_property;
  int idx = GetDrmProperty(drm, connector, kContentProtectionKey,
                           &content_protection_key_property);
  return idx > -1;
}

std::vector<uint64_t> GetPathTopology(const DrmWrapper& drm,
                                      drmModeConnector* connector) {
  ScopedDrmPropertyBlobPtr path_blob = drm.GetPropertyBlob(connector, "PATH");

  if (!path_blob) {
    DCHECK_GT(connector->connector_id, 0u);

    // The topology is consisted solely of the connector id.
    return {base::strict_cast<uint64_t>(connector->connector_id)};
  }

  return ParsePathBlob(*path_blob);
}

bool IsAspectPreserving(const DrmWrapper& drm, drmModeConnector* connector) {
  ScopedDrmPropertyPtr property;
  int index = GetDrmProperty(drm, connector, "scaling mode", &property);
  if (index < 0)
    return false;

  return (GetNameForEnumValue(property.get(), connector->prop_values[index]) ==
          "Full aspect");
}

std::optional<TileProperty> GetTileProperty(
    const DrmWrapper& drm,
    const std::optional<display::EdidParser>& edid_parser,
    drmModeConnector* connector) {
  const ScopedDrmPropertyBlobPtr tile_blob =
      drm.GetPropertyBlob(connector, "TILE");
  if (!tile_blob) {
    return std::nullopt;
  }

  std::optional<TileProperty> tile_property = ParseTileBlob(*tile_blob);
  if (!tile_property.has_value()) {
    return std::nullopt;
  }

  if (edid_parser.has_value()) {
    tile_property->scale_to_fit_display = edid_parser->TileCanScaleToFit();
  }

  return tile_property;
}

display::PanelOrientation GetPanelOrientation(const DrmWrapper& drm,
                                              drmModeConnector* connector) {
  ScopedDrmPropertyPtr property;
  int index = GetDrmProperty(drm, connector, "panel orientation", &property);
  if (index < 0)
    return display::PanelOrientation::kNormal;

  // If the DRM driver doesn't provide panel orientation then this property
  // will be DRM_MODE_PANEL_ORIENTATION_UNKNOWN (which is -1, except
  // `prop_values` is unsigned, so compare against max uint64_t). Assume that
  // panels with unknown orientation have normal orientation.
  if (connector->prop_values[index] == std::numeric_limits<uint64_t>::max())
    return display::PanelOrientation::kNormal;

  DCHECK_LE(connector->prop_values[index], display::PanelOrientation::kLast);
  return static_cast<display::PanelOrientation>(connector->prop_values[index]);
}

// Read a file and trim whitespace. If the file can't be read, returns
// nullopt.
std::optional<std::string> ReadFileAndTrim(const base::FilePath& path) {
  std::string data;
  if (!base::ReadFileToString(path, &data))
    return std::nullopt;

  return std::string(
      base::TrimWhitespaceASCII(data, base::TrimPositions::TRIM_ALL));
}

// Sort modes in |modes_in_out| from largest to smallest as defined by
// DisplayMode::operator>().
void SortDisplayModeListDesc(
    display::DisplaySnapshot::DisplayModeList& modes_in_out) {
  std::stable_sort(
      modes_in_out.begin(), modes_in_out.end(),
      [](const std::unique_ptr<const display::DisplayMode>& left,
         const std::unique_ptr<const display::DisplayMode>& right) {
        return *left > *right;
      });
}

// Given all |tiled_infos| belonging to the same display, select the "primary"
// tile that will represent all the tiles. Primary tile is the only active tile
// if the display is configured with a non-tile mode.
const HardwareDisplayControllerInfo* GetPrimaryTileInfo(
    const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>&
        tiled_infos) {
  if (tiled_infos.empty()) {
    return nullptr;
  }
  // 1. Filter for tile switch scale to fit capability
  std::vector<const HardwareDisplayControllerInfo*> scalable_tiles,
      unscalable_tiles;
  for (const auto& tiled_info : tiled_infos) {
    const HardwareDisplayControllerInfo* tiled_info_ptr = tiled_info.get();
    if (tiled_info_ptr->tile_property()->scale_to_fit_display) {
      scalable_tiles.push_back(tiled_info_ptr);
    } else {
      unscalable_tiles.push_back(tiled_info_ptr);
    }
  }

  if (scalable_tiles.size() == 1) {
    return scalable_tiles.front();
  }

  // If there were multiple tiles that could have scaled, then use those for
  // round 2. Only if there were no tiles capable of scaling should we consider
  // all the tiles for round 2.
  std::vector<const HardwareDisplayControllerInfo*> primary_eligible_tiles;
  if (!scalable_tiles.empty()) {
    primary_eligible_tiles = std::move(scalable_tiles);
  } else {
    primary_eligible_tiles = std::move(unscalable_tiles);
  }

  // 2. The tile with the most # of modes should be the primary.
  std::vector<const HardwareDisplayControllerInfo*> max_mode_tiles;
  int max_num_modes = -1;
  for (const auto* tiled_info : primary_eligible_tiles) {
    const int num_modes = tiled_info->connector()->count_modes;
    if (num_modes > max_num_modes) {
      max_num_modes = num_modes;
      max_mode_tiles = {tiled_info};
    } else if (num_modes == max_num_modes) {
      max_mode_tiles.push_back(tiled_info);
    }
  }

  if (max_mode_tiles.size() == 1) {
    return max_mode_tiles.front();
  }

  // 3. Break ties by taking the tile with TileProperty::location closest to the
  // origin. Breaking ties deterministically keeps EDID-based display IDs
  // stable.
  primary_eligible_tiles = std::move(max_mode_tiles);
  const HardwareDisplayControllerInfo* tile_closest_to_origin =
      primary_eligible_tiles.front();
  gfx::Point closest_point = tile_closest_to_origin->tile_property()->location;
  for (const auto* tile : primary_eligible_tiles) {
    const gfx::Point& tile_location = tile->tile_property()->location;
    if (tile_location < closest_point) {
      closest_point = tile_location;
      tile_closest_to_origin = tile;
    }
  }

  return tile_closest_to_origin;
}
bool ContainsModePtr(const display::DisplaySnapshot::DisplayModeList& modes,
                     const display::DisplayMode* target_mode) {
  for (const auto& mode : modes) {
    if (mode.get() == target_mode) {
      return true;
    }
  }
  return false;
}

bool ContainsModeEq(const display::DisplaySnapshot::DisplayModeList& modes,
                    const display::DisplayMode& target_mode) {
  for (const auto& mode : modes) {
    if (*mode == target_mode) {
      return true;
    }
  }
  return false;
}

// Prune all tile modes in |primary_tile_modes_in_out| that doesn't show up in
// all other tiles in the tiled display.
void PruneTileModesNotPresentInAllTiles(
    const HardwareDisplayControllerInfo& primary_tile_info,
    display::DisplaySnapshot::DisplayModeList& primary_tile_modes_in_out) {
  const std::optional<TileProperty>& primary_tile_property =
      primary_tile_info.tile_property();
  if (!primary_tile_property.has_value()) {
    return;
  }

  const gfx::Size& tile_size = primary_tile_property->tile_size;
  for (auto primary_tile_mode_it = primary_tile_modes_in_out.begin();
       primary_tile_mode_it != primary_tile_modes_in_out.end();) {
    // Skip non-tile modes.
    if (!(*primary_tile_mode_it) ||
        (*primary_tile_mode_it)->size() != tile_size) {
      ++primary_tile_mode_it;
      continue;
    }

    bool mode_found_in_all_tiles = true;
    for (const auto& nonprimary_tile_info :
         primary_tile_info.nonprimary_tile_infos()) {
      const display::DisplaySnapshot::DisplayModeList nonprimary_tile_modes =
          nonprimary_tile_info->GetModesOfSize(tile_size);
      if (!ContainsModeEq(nonprimary_tile_modes, **primary_tile_mode_it)) {
        mode_found_in_all_tiles = false;
        break;
      }
    }

    if (mode_found_in_all_tiles) {
      ++primary_tile_mode_it;
    } else {
      primary_tile_mode_it =
          primary_tile_modes_in_out.erase(primary_tile_mode_it);
    }
  }
}

// Prune all tile modes in |modes_in_out| if all tiles in a display are not
// connected to prevent the display from having blank tiles.
void PruneTileModesForIncompleteGroup(
    const HardwareDisplayControllerInfo& tiled_display_info,
    display::DisplaySnapshot::DisplayModeList& modes_in_out) {
  const std::optional<TileProperty>& tile_property =
      tiled_display_info.tile_property();
  if (!tile_property.has_value()) {
    return;
  }

  const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>&
      nonprimary_tiles = tiled_display_info.nonprimary_tile_infos();
  // Prune all tile modes if not all tiles in the display are connected yet.
  if (tile_property->tile_layout.GetArea() !=
      static_cast<int>(nonprimary_tiles.size()) + 1) {
    modes_in_out.erase(
        std::remove_if(
            modes_in_out.begin(), modes_in_out.end(),
            [&tile_property](
                const std::unique_ptr<const display::DisplayMode>& mode) {
              return mode->size() == tile_property->tile_size;
            }),
        modes_in_out.end());
    return;
  }
}

// Replaces all tile modes with the full tile composited mode.
// Note that individual tiles in a tiled display advertise modes with size of
// the tile instead of the full display.
void ConvertTileModesToCompositedModes(
    const HardwareDisplayControllerInfo& tiled_display_info,
    display::DisplaySnapshot::DisplayModeList& modes_in_out,
    const display::DisplayMode*& current_mode_out,
    const display::DisplayMode*& native_mode_out) {
  const std::optional<TileProperty>& tile_property =
      tiled_display_info.tile_property();
  if (!tile_property.has_value()) {
    return;
  }
  // For every mode with same resolution as the tile size, replace with a a new,
  // equivalent mode with the full tile-composited display resolution.
  for (auto& mode : modes_in_out) {
    if (mode->size() != tile_property->tile_size) {
      continue;
    }

    std::unique_ptr<display::DisplayMode> tile_mode =
        mode->CopyWithSize(GetTotalTileDisplaySize(*tile_property));
    if (current_mode_out == mode.get()) {
      current_mode_out = tile_mode.get();
    }
    if (native_mode_out == mode.get()) {
      native_mode_out = tile_mode.get();
    }

    mode = std::move(tile_mode);
  }

  SortDisplayModeListDesc(modes_in_out);
}

std::unique_ptr<HardwareDisplayControllerInfo> PopPrimaryTileInfo(
    const HardwareDisplayControllerInfo* primary_tile_info_ptr,
    std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>& infos) {
  std::unique_ptr<HardwareDisplayControllerInfo> primary_tile_info;
  for (auto tile_info = infos.begin(); tile_info != infos.end(); tile_info++) {
    if (tile_info->get() == primary_tile_info_ptr) {
      primary_tile_info = std::move(*tile_info);
      infos.erase(tile_info);
      break;
    }
  }
  return primary_tile_info;
}
}  // namespace

ScopedDrmPropertyPtr FindDrmProperty(const DrmWrapper& drm,
                                     drmModeObjectProperties* properties,
                                     const char* name) {
  for (uint32_t i = 0; i < properties->count_props; ++i) {
    ScopedDrmPropertyPtr property = drm.GetProperty(properties->props[i]);
    if (property && !strcmp(property->name, name))
      return property;
  }
  return nullptr;
}

bool HasColorCorrectionMatrix(const DrmWrapper& drm, drmModeCrtc* crtc) {
  ScopedDrmObjectPropertyPtr crtc_props =
      drm.GetObjectProperties(crtc->crtc_id, DRM_MODE_OBJECT_CRTC);
  return !!FindDrmProperty(drm, crtc_props.get(), "CTM");
}

const gfx::Size ModeSize(const drmModeModeInfo& mode) {
  return gfx::Size(mode.hdisplay, mode.vdisplay);
}

float ModeRefreshRate(const drmModeModeInfo& mode) {
  return GetRefreshRate(mode);
}

bool ModeIsInterlaced(const drmModeModeInfo& mode) {
  return mode.flags & DRM_MODE_FLAG_INTERLACE;
}

const std::optional<float> ModeVSyncRateMin(
    const drmModeModeInfo& mode,
    const std::optional<uint16_t>& vsync_rate_min_from_edid) {
  if (!vsync_rate_min_from_edid.has_value() ||
      vsync_rate_min_from_edid.value() == 0) {
    return std::nullopt;
  }

  if (!mode.htotal) {
    return vsync_rate_min_from_edid;
  }

  float clock_hz = mode.clock * 1000.0f;
  float htotal = mode.htotal;

  // Calculate the vtotal from the imprecise min vsync rate.
  float vtotal_extended =
      clock_hz / (htotal * vsync_rate_min_from_edid.value());
  // Clamp the calculated vtotal and determine the precise min vsync rate.
  return clock_hz / (htotal * std::floor(vtotal_extended));
}

gfx::Size GetMaximumCursorSize(const DrmWrapper& drm) {
  uint64_t width = 0, height = 0;
  // Querying cursor dimensions is optional and is unsupported on older Chrome
  // OS kernels.
  if (!drm.GetCapability(DRM_CAP_CURSOR_WIDTH, &width) ||
      !drm.GetCapability(DRM_CAP_CURSOR_HEIGHT, &height)) {
    return gfx::Size(kDefaultCursorWidth, kDefaultCursorHeight);
  }
  return gfx::Size(width, height);
}

bool IsVrrCapable(const DrmWrapper& drm, drmModeConnector* connector) {
  ScopedDrmPropertyPtr vrr_capable_property;
  const int vrr_capable_index = GetDrmProperty(
      drm, connector, kVrrCapablePropertyName, &vrr_capable_property);
  return vrr_capable_index >= 0 && connector->prop_values[vrr_capable_index];
}

bool IsVrrEnabled(const DrmWrapper& drm, drmModeCrtc* crtc) {
  ScopedDrmObjectPropertyPtr crtc_props =
      drm.GetObjectProperties(crtc->crtc_id, DRM_MODE_OBJECT_CRTC);
  ScopedDrmPropertyPtr vrr_enabled_property;
  const int vrr_enabled_index = GetDrmProperty(
      drm, crtc_props.get(), kVrrEnabledPropertyName, &vrr_enabled_property);
  return vrr_enabled_index >= 0 && crtc_props->prop_values[vrr_enabled_index];
}

display::VariableRefreshRateState GetVariableRefreshRateState(
    const DrmWrapper& drm,
    HardwareDisplayControllerInfo* info) {
  if (!IsVrrCapable(drm, info->connector())) {
    return display::VariableRefreshRateState::kVrrNotCapable;
  }
  if (!info->edid_parser()->vsync_rate_min().has_value() ||
      info->edid_parser()->vsync_rate_min().value() == 0) {
    return display::VariableRefreshRateState::kVrrNotCapable;
  }

  if (IsVrrEnabled(drm, info->crtc())) {
    return display::VariableRefreshRateState::kVrrEnabled;
  }

  return display::VariableRefreshRateState::kVrrDisabled;
}

std::pair<std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>,
          std::vector<uint32_t>>
GetDisplayInfosAndInvalidCrtcs(const DrmWrapper& drm) {
  ScopedDrmResourcesPtr resources = drm.GetResources();
  DCHECK(resources) << "Failed to get DRM resources";
  std::vector<std::unique_ptr<HardwareDisplayControllerInfo>> displays;
  std::vector<uint32_t> invalid_crtcs;

  std::vector<ScopedDrmConnectorPtr> connectors;
  std::vector<drmModeConnector*> available_connectors;
  const size_t count_connectors = resources->count_connectors;
  for (size_t i = 0; i < count_connectors; ++i) {
    if (i >= kMaxDrmConnectors) {
      LOG(WARNING) << "Reached the current limit of " << kMaxDrmConnectors
                   << " connectors per DRM. Ignoring the remaining "
                   << count_connectors - kMaxDrmConnectors << " connectors.";
      break;
    }

    ScopedDrmConnectorPtr connector =
        drm.GetConnector(resources->connectors[i]);
    // In case of zombie connectors, verify that the connector is valid by
    // checking if it has props.
    // Zombie connectors can occur when an MST (which creates a new connector ID
    // upon connection) is disconnected but the kernel hasn't cleaned up the old
    // connector ID yet.
    if (!connector || !drm.GetObjectProperties(resources->connectors[i],
                                               DRM_MODE_OBJECT_CONNECTOR)) {
      continue;
    }

    if (connector->connection == DRM_MODE_CONNECTED) {
      if (connector->count_modes != 0) {
        available_connectors.push_back(connector.get());
      } else {
        LOG(WARNING) << "[CONNECTOR:" << connector->connector_id
                     << "] is connected but has no modes. Connector ignored.";
      }
    }

    connectors.emplace_back(std::move(connector));
  }

  base::flat_map<drmModeConnector*, int> connector_crtcs;
  for (auto* connector : available_connectors) {
    std::vector<uint32_t> encoder_ids(
        connector->encoders, connector->encoders + connector->count_encoders);
    connector_crtcs[connector] =
        GetPossibleCrtcsBitmaskFromEncoders(drm, encoder_ids);
  }
  // Make sure to start assigning a crtc to the connector that supports the
  // fewest crtcs first.
  std::stable_sort(available_connectors.begin(), available_connectors.end(),
                   [&connector_crtcs](drmModeConnector* const c1,
                                      drmModeConnector* const c2) {
                     // When c1 supports a proper subset of the crtcs of c2, we
                     // should process c1 first (return true).
                     int c1_crtcs = connector_crtcs[c1];
                     int c2_crtcs = connector_crtcs[c2];
                     return (c1_crtcs & c2_crtcs) == c1_crtcs &&
                            c1_crtcs != c2_crtcs;
                   });

  ScopedDrmPlaneResPtr plane_resources = drm.GetPlaneResources();
  std::vector<ScopedDrmPlanePtr> planes;
  for (uint32_t i = 0; i < plane_resources->count_planes; i++)
    planes.emplace_back(drm.GetPlane(plane_resources->planes[i]));

  for (auto* c : available_connectors) {
    uint32_t best_crtc, connected_crtc;
    std::tie(best_crtc, connected_crtc) =
        GetCrtcs(drm, c, resources.get(), displays, planes);
    if (!best_crtc)
      continue;

    // If the currently connected CRTC isn't the best CRTC for the connector,
    // add the CRTC to the list of Invalid CRTCs.
    if (connected_crtc && connected_crtc != best_crtc)
      invalid_crtcs.push_back((connected_crtc));

    ScopedDrmCrtcPtr crtc = drm.GetCrtc(best_crtc);
    auto connector_iter =
        base::ranges::find(connectors, c, &ScopedDrmConnectorPtr::get);
    CHECK(connector_iter != connectors.end(), base::NotFatalUntil::M130);
    // |connectors.size()| <= 256, so |index| should be between 0-255.
    const uint8_t index = connector_iter - connectors.begin();
    DCHECK_LT(index, connectors.size());

    drmModeConnector* connector = connector_iter->get();
    ScopedDrmPropertyBlobPtr edid_blob(
        GetDrmPropertyBlob(drm, connector, "EDID"));
    std::optional<display::EdidParser> edid_parser;
    if (edid_blob) {
      uint8_t* edid_blob_ptr = static_cast<uint8_t*>(edid_blob->data);
      std::vector<uint8_t> edid(edid_blob_ptr,
                                edid_blob_ptr + edid_blob->length);
      const bool is_external = GetDisplayConnectionType(connector) !=
                               display::DISPLAY_CONNECTION_TYPE_INTERNAL;
      edid_parser = display::EdidParser(std::move(edid), is_external);
    } else {
      VLOG(1) << "Failed to get EDID blob for connector "
              << connector->connector_id;
    }

    std::optional<TileProperty> tile_property;
    if (display::features::IsTiledDisplaySupportEnabled()) {
      tile_property = GetTileProperty(drm, edid_parser, connector);
    }

    displays.push_back(std::make_unique<HardwareDisplayControllerInfo>(
        std::move(*connector_iter), std::move(crtc), index,
        std::move(edid_parser), std::move(tile_property)));
  }

  return std::make_pair(std::move(displays), std::move(invalid_crtcs));
}

std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>
GetAvailableDisplayControllerInfos(const DrmWrapper& drm) {
  return GetDisplayInfosAndInvalidCrtcs(drm).first;
}

uint32_t GetPossibleCrtcsBitmaskFromEncoders(
    const DrmWrapper& drm,
    const std::vector<uint32_t>& encoder_ids) {
  uint32_t possible_crtcs = 0;
  for (uint32_t encoder_id : encoder_ids) {
    ScopedDrmEncoderPtr encoder = drm.GetEncoder(encoder_id);
    if (!encoder) {
      continue;
    }
    possible_crtcs |= encoder->possible_crtcs;
  }

  return possible_crtcs;
}

std::vector<uint32_t> GetPossibleCrtcIdsFromBitmask(
    const DrmWrapper& drm,
    const uint32_t possible_crtcs_bitmask) {
  std::vector<uint32_t> crtcs;
  ScopedDrmResourcesPtr resources = drm.GetResources();
  for (int i = 0; i < resources->count_crtcs; i++) {
    // CRTC mask of |possible_crtcs_bitmask| is just 1 offset by the index in
    // drm_crtc_index().
    const uint32_t current_crtc_mask = 1 << i;
    if (possible_crtcs_bitmask & current_crtc_mask) {
      crtcs.push_back(resources->crtcs[i]);
    }
  }

  return crtcs;
}

bool SameMode(const drmModeModeInfo& lhs, const drmModeModeInfo& rhs) {
  return lhs.clock == rhs.clock && lhs.hdisplay == rhs.hdisplay &&
         lhs.vdisplay == rhs.vdisplay && lhs.vrefresh == rhs.vrefresh &&
         lhs.hsync_start == rhs.hsync_start && lhs.hsync_end == rhs.hsync_end &&
         lhs.htotal == rhs.htotal && lhs.hskew == rhs.hskew &&
         lhs.vsync_start == rhs.vsync_start && lhs.vsync_end == rhs.vsync_end &&
         lhs.vtotal == rhs.vtotal && lhs.vscan == rhs.vscan &&
         lhs.flags == rhs.flags && strcmp(lhs.name, rhs.name) == 0;
}

std::unique_ptr<display::DisplayMode> CreateDisplayMode(
    const drmModeModeInfo& mode,
    const std::optional<uint16_t>& vsync_rate_min_from_edid) {
  return std::make_unique<display::DisplayMode>(
      gfx::Size{mode.hdisplay, mode.vdisplay},
      mode.flags & DRM_MODE_FLAG_INTERLACE, GetRefreshRate(mode),
      ModeVSyncRateMin(mode, vsync_rate_min_from_edid));
}

std::unique_ptr<drmModeModeInfo> CreateVirtualMode(
    const drmModeModeInfo& base_mode,
    float virtual_refresh_rate) {
  if (!base_mode.htotal) {
    return nullptr;
  }

  float clock_hz = base_mode.clock * 1000.0f;
  float htotal = base_mode.htotal;

  uint16_t virtual_vtotal =
      std::round(clock_hz / (htotal * virtual_refresh_rate));
  // Vtotal can only be increased from the base mode because virtual modes rely
  // on VRR capabilities (i.e. the back porch can be extended but not
  // diminished).
  if (virtual_vtotal < base_mode.vtotal) {
    return nullptr;
  }

  auto out_mode = std::make_unique<drmModeModeInfo>();
  *out_mode = base_mode;
  out_mode->vtotal = virtual_vtotal;
  return out_mode;
}

display::DisplaySnapshot::DisplayModeList ExtractDisplayModes(
    HardwareDisplayControllerInfo* info,
    const gfx::Size& active_pixel_size,
    const display::DisplayMode** out_current_mode,
    const display::DisplayMode** out_native_mode) {
  DCHECK(out_current_mode);
  DCHECK(out_native_mode);

  *out_current_mode = nullptr;
  *out_native_mode = nullptr;
  display::DisplaySnapshot::DisplayModeList modes;
  for (int i = 0; i < info->connector()->count_modes; ++i) {
    const drmModeModeInfo& mode = info->connector()->modes[i];
    modes.push_back(CreateDisplayMode(
        mode, info->edid_parser() ? info->edid_parser()->vsync_rate_min()
                                  : std::nullopt));

    if (info->crtc()->mode_valid && SameMode(info->crtc()->mode, mode))
      *out_current_mode = modes.back().get();

    if (mode.type & DRM_MODE_TYPE_PREFERRED) {
      if (*out_native_mode == nullptr) {
        *out_native_mode = modes.back().get();
      } else {
        LOG(WARNING) << "Found more than one preferred modes. The first one "
                        "will be used.";
      }
    }
  }

  // If we couldn't find a preferred mode, then try to find a mode that has the
  // same size as the first detailed timing descriptor in the EDID.
  if (!*out_native_mode && !active_pixel_size.IsEmpty()) {
    for (const auto& mode : modes) {
      if (mode->size() == active_pixel_size) {
        *out_native_mode = mode.get();
        break;
      }
    }
  }

  // If we still have no preferred mode, then use the first one since it should
  // be the best mode.
  if (!*out_native_mode && !modes.empty())
    *out_native_mode = modes.front().get();

  return modes;
}

std::unique_ptr<display::DisplaySnapshot> CreateDisplaySnapshot(
    const DrmWrapper& drm,
    HardwareDisplayControllerInfo* info,
    uint8_t device_index) {
  const uint8_t display_index =
      display::ConnectorIndex8(device_index, info->index());
  const uint16_t connector_index =
      display::ConnectorIndex16(device_index, info->index());
  const gfx::Size physical_size =
      gfx::Size(info->connector()->mmWidth, info->connector()->mmHeight);
  const display::DisplayConnectionType type =
      GetDisplayConnectionType(info->connector());
  uint64_t base_connector_id = 0u;
  std::vector<uint64_t> path_topology = GetPathTopology(drm, info->connector());
  if (!path_topology.empty()) {
    base_connector_id = path_topology.front();
    path_topology.erase(path_topology.begin());
  }
  const bool is_aspect_preserving_scaling =
      IsAspectPreserving(drm, info->connector());
  const display::PanelOrientation panel_orientation =
      GetPanelOrientation(drm, info->connector());
  const display::PrivacyScreenState privacy_screen_state =
      GetPrivacyScreenState(drm, info->connector());
  const bool has_content_protection_key =
      HasContentProtectionKey(drm, info->connector());
  display::DisplaySnapshot::ColorInfo color_info;
  color_info.supports_color_temperature_adjustment =
      HasColorCorrectionMatrix(drm, info->crtc());
  const gfx::Size maximum_cursor_size = GetMaximumCursorSize(drm);
  const display::VariableRefreshRateState variable_refresh_rate_state =
      GetVariableRefreshRateState(drm, info);

  std::string display_name;
  // Make sure the ID contains non index part.
  int64_t port_display_id = display_index | 0x100;
  int64_t edid_display_id = port_display_id;
  int64_t product_code = display::DisplaySnapshot::kInvalidProductCode;
  int32_t year_of_manufacture = display::kInvalidYearOfManufacture;
  bool has_overscan = false;
  color_info.bits_per_channel = 8u;
  // Active pixels size from the first detailed timing descriptor in the EDID.
  gfx::Size active_pixel_size;

  const std::optional<display::EdidParser>& edid_parser = info->edid_parser();
  base::UmaHistogramBoolean("DrmUtil.CreateDisplaySnapshot.HasEdidBlob",
                            edid_parser.has_value());
  const std::vector<uint8_t>& edid = edid_parser.has_value()
                                         ? edid_parser->edid_blob()
                                         : std::vector<uint8_t>();
  if (edid_parser.has_value()) {
    display_name = edid_parser->display_name();
    active_pixel_size = edid_parser->active_pixel_size();
    product_code = edid_parser->GetProductCode();
    port_display_id = edid_parser->GetIndexBasedDisplayId(display_index);
    edid_display_id = edid_parser->GetEdidBasedDisplayId();
    year_of_manufacture = edid_parser->year_of_manufacture();
    has_overscan =
        edid_parser->has_overscan_flag() && edid_parser->overscan_flag();
    color_info.color_space = display::GetColorSpaceFromEdid(*edid_parser);
    // Populate the EDID primaries and gamma from the gfx::ColorSpace.
    // TODO(crbug.com/40945652): Extract this directly.
    if (auto sk_color_space = color_info.color_space.ToSkColorSpace()) {
      skcms_TransferFunction fn;
      skcms_Matrix3x3 to_xyzd50;
      sk_color_space->toXYZD50(&to_xyzd50);
      sk_color_space->transferFn(&fn);
      color_info.edid_primaries =
          skia::GetD65PrimariesFromToXYZD50Matrix(to_xyzd50);
      color_info.edid_gamma = fn.g;
    }
    base::UmaHistogramBoolean("DrmUtil.CreateDisplaySnapshot.IsHDR",
                              color_info.color_space.IsHDR());
    color_info.bits_per_channel = std::max(edid_parser->bits_per_channel(), 0);
    base::UmaHistogramCounts100("DrmUtil.CreateDisplaySnapshot.BitsPerChannel",
                                color_info.bits_per_channel);
    color_info.hdr_static_metadata = edid_parser->hdr_static_metadata();
  }

  const display::DisplayMode* current_mode = nullptr;
  const display::DisplayMode* native_mode = nullptr;
  display::DisplaySnapshot::DisplayModeList modes =
      ExtractDisplayModes(info, active_pixel_size, &current_mode, &native_mode);

  const display::DrmFormatsAndModifiers drm_formats_and_modifiers =
      drm.GetFormatsAndModifiersForCrtc(info->crtc()->crtc_id);

  if (info->tile_property().has_value()) {
    PruneTileModesForIncompleteGroup(*info, modes);
    PruneTileModesNotPresentInAllTiles(*info, modes);
    ConvertTileModesToCompositedModes(*info, modes, current_mode, native_mode);

    if (!ContainsModePtr(modes, native_mode)) {
      // Fall back to first mode in |modes|.
      native_mode = modes.front().get();
    }

    if (!ContainsModePtr(modes, current_mode)) {
      // Fall back to using |native_mode|.
      current_mode = native_mode;
    }
  }

  return std::make_unique<display::DisplaySnapshot>(
      port_display_id, port_display_id, edid_display_id, connector_index,
      gfx::Point(), physical_size, type, base_connector_id, path_topology,
      is_aspect_preserving_scaling, has_overscan, privacy_screen_state,
      has_content_protection_key, color_info, display_name, drm.device_path(),
      std::move(modes), panel_orientation, edid, current_mode, native_mode,
      product_code, year_of_manufacture, maximum_cursor_size,
      variable_refresh_rate_state, drm_formats_and_modifiers);
}

int GetFourCCFormatForOpaqueFramebuffer(gfx::BufferFormat format) {
  // DRM atomic interface doesn't currently support specifying an alpha
  // blending. We can simulate disabling alpha blending creating an fb
  // with a format without the alpha channel.
  switch (format) {
    case gfx::BufferFormat::RGBA_8888:
    case gfx::BufferFormat::RGBX_8888:
      return DRM_FORMAT_XBGR8888;
    case gfx::BufferFormat::BGRA_8888:
    case gfx::BufferFormat::BGRX_8888:
      return DRM_FORMAT_XRGB8888;
    case gfx::BufferFormat::BGRA_1010102:
      return DRM_FORMAT_XRGB2101010;
    case gfx::BufferFormat::RGBA_1010102:
      return DRM_FORMAT_XBGR2101010;
    case gfx::BufferFormat::BGR_565:
      return DRM_FORMAT_RGB565;
    case gfx::BufferFormat::YUV_420_BIPLANAR:
      return DRM_FORMAT_NV12;
    case gfx::BufferFormat::YVU_420:
      return DRM_FORMAT_YVU420;
    case gfx::BufferFormat::P010:
      return DRM_FORMAT_P010;
    default:
      NOTREACHED_IN_MIGRATION();
      return 0;
  }
}

const char* GetNameForColorspace(const gfx::ColorSpace color_space) {
  if (color_space == gfx::ColorSpace::CreateHDR10())
    return kColorSpaceBT2020RGBEnumName;

  return kColorSpaceDefaultEnumName;
}

uint64_t GetEnumValueForName(const DrmWrapper& drm,
                             int property_id,
                             const char* str) {
  ScopedDrmPropertyPtr res = drm.GetProperty(property_id);
  for (int i = 0; i < res->count_enums; ++i) {
    if (strcmp(res->enums[i].name, str) == 0) {
      return res->enums[i].value;
    }
  }
  NOTREACHED_IN_MIGRATION();
  return 0;
}

// Returns a vector that holds the path topology of the display. Returns an
// empty vector upon failure.
//
// A path topology c-string is of the format:
//    mst:{DRM_BASE_CONNECTOR_ID#}-{BRANCH_1_PORT#}-...-{BRANCH_N_PORT#}\0
//
// For example, the display configuration:
//    Device <--conn6-- MST1 <--port2-- MST2 <--port1-- Display
// may produce the following topology c-string:
//     "mst:6-2-1"
//
// To see how this string is constructed in the DRM:
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_dp_mst_topology.c?h=v5.10-rc3#n2229
std::vector<uint64_t> ParsePathBlob(const drmModePropertyBlobRes& path_blob) {
  if (!path_blob.length) {
    LOG(ERROR) << "PATH property blob is empty.";
    return {};
  }

  std::string path_str(
      static_cast<char*>(path_blob.data),
      base::strict_cast<std::string::size_type>(path_blob.length));
  std::string_view path_string_piece(path_str);
  path_string_piece = base::TrimString(path_string_piece, std::string("\0", 1u),
                                       base::TRIM_TRAILING);

  const std::string prefix("mst:");
  if (!base::StartsWith(path_string_piece, prefix,
                        base::CompareCase::SENSITIVE)) {
    LOG(ERROR) << "Invalid PATH string prefix. Does not contain '" << prefix
               << "'. Input: '" << path_str << "'";
    return {};
  }
  path_string_piece.remove_prefix(prefix.length());

  std::vector<uint64_t> path;
  for (const auto& string_port :
       base::SplitStringPiece(path_string_piece, "-", base::KEEP_WHITESPACE,
                              base::SPLIT_WANT_ALL)) {
    uint64_t int_port = 0;
    if (base::StringToUint64(string_port, &int_port) && int_port > 0) {
      path.push_back(int_port);
    } else {
      LOG(ERROR)
          << "One or more port values in the PATH string are invalid. Input: '"
          << path_str << "'";
      return {};
    }
  }

  if (path.size() < 2) {
    LOG(ERROR)
        << "Insufficient number of ports (should be at least 2 but found "
        << path.size() << "). Input: '" << path_str << "'";
    return {};
  }

  return path;
}

// Parses tiled display properties from the TILE connector property
// |tile_blob|. TileProperty::scale_to_fit_display is not populated here as this
// information is not available in the TILE blob. Tile property blob is encoded
// as:
// "group_id:tile_is_single_monitor:num_h_tile:num_v_tile:tile_h_loc:tile_v_loc
//  :tile_h_size:tile_v_size"
// e.g. 313a313a323a313a303a303a323536303a3238383000 == 1:1:2:1:0:0:2560:2880
// tile_is_single_monitor is not used as all tiles in a single group are to be
// treated as a single monitor for simplicity.
std::optional<TileProperty> ParseTileBlob(
    const drmModePropertyBlobRes& tile_blob) {
  if (!tile_blob.length) {
    LOG(ERROR) << "TILE property blob is empty.";
    return std::nullopt;
  }

  const std::string tile_str(
      static_cast<char*>(tile_blob.data),
      base::strict_cast<std::string::size_type>(tile_blob.length));
  std::string_view tile_string_piece(tile_str);
  tile_string_piece = base::TrimString(tile_string_piece, std::string("\0", 1u),
                                       base::TRIM_TRAILING);

  std::vector<std::string_view> tile_properties = base::SplitStringPiece(
      tile_string_piece, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  if (tile_properties.size() != 8) {
    LOG(ERROR) << "Some of the values in the TILE property are missing. "
                  "Expected 8, got "
               << tile_properties.size() << ". TILE blob: " << tile_str;
    return std::nullopt;
  }

  TileProperty tile_property;
  int num_tiles_horiz, num_tiles_vert, tile_loc_horiz, tile_loc_vert,
      tile_size_horiz, tile_size_vert;
  std::vector<std::pair<size_t /*tile properties index*/, int*>>
      tile_properties_ptrs = {{0, &tile_property.group_id},
                              // Skip {1, tile_is_single_monitor}
                              {2, &num_tiles_horiz},
                              {3, &num_tiles_vert},
                              {4, &tile_loc_horiz},
                              {5, &tile_loc_vert},
                              {6, &tile_size_horiz},
                              {7, &tile_size_vert}};

  for (auto& [index, property_ptr] : tile_properties_ptrs) {
    if (!base::StringToInt(tile_properties[index], property_ptr)) {
      LOG(ERROR) << "Could not convert string \"" << tile_properties[index]
                 << "\" at index #" << index
                 << " of the TILE property to an int. TILE blob: " << tile_str;
      return std::nullopt;
    }
  }

  tile_property.tile_size.SetSize(tile_size_horiz, tile_size_vert);
  tile_property.tile_layout.SetSize(num_tiles_horiz, num_tiles_vert);
  tile_property.location.SetPoint(tile_loc_horiz, tile_loc_vert);

  return tile_property;
}

bool IsAddfb2ModifierCapable(const DrmWrapper& drm) {
  uint64_t addfb2_mod_cap = 0;
  return drm.GetCapability(DRM_CAP_ADDFB2_MODIFIERS, &addfb2_mod_cap) &&
         addfb2_mod_cap;
}

std::string GetEnumNameForProperty(
    const drmModePropertyRes& property,
    const drmModeObjectProperties& property_values) {
  for (uint32_t prop_idx = 0; prop_idx < property_values.count_props;
       ++prop_idx) {
    if (property_values.props[prop_idx] != property.prop_id)
      continue;

    for (int enum_idx = 0; enum_idx < property.count_enums; ++enum_idx) {
      const drm_mode_property_enum& property_enum = property.enums[enum_idx];
      if (property_enum.value == property_values.prop_values[prop_idx])
        return property_enum.name;
    }
  }

  NOTREACHED_IN_MIGRATION();
  return std::string();
}

std::optional<std::string> GetDrmDriverNameFromFd(int fd) {
  ScopedDrmVersionPtr version(drmGetVersion(fd));
  if (!version) {
    LOG(ERROR) << "Failed to query DRM version";
    return std::nullopt;
  }

  return std::string(version->name, version->name_len);
}

std::optional<std::string> GetDrmDriverNameFromPath(
    const char* device_file_name) {
  base::ScopedFD fd(open(device_file_name, O_RDWR));
  if (!fd.is_valid()) {
    LOG(ERROR) << "Failed to open DRM device " << device_file_name;
    return std::nullopt;
  }

  return GetDrmDriverNameFromFd(fd.get());
}

std::vector<const char*> GetPreferredDrmDrivers() {
  const base::FilePath dmi_dir("/sys/class/dmi/id");

  const auto sys_vendor = ReadFileAndTrim(dmi_dir.Append("sys_vendor"));
  const auto product_name = ReadFileAndTrim(dmi_dir.Append("product_name"));

  // The iMac 12.1 and 12.2 have an integrated Intel GPU that isn't connected
  // to any real outputs. Prefer the Radeon card instead.
  if (sys_vendor == "Apple Inc." &&
      (product_name == "iMac12,1" || product_name == "iMac12,2")) {
    return {"radeon"};
  }

  // Default order.
  return {"i915", "amdgpu", "virtio_gpu"};
}

void ConsolidateTiledDisplayInfo(
    std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>&
        display_infos) {
  // Ignore all non-tiled displays, group all tile displays into |tile_groups|
  // by tile group IDs.
  std::vector<std::unique_ptr<HardwareDisplayControllerInfo>> new_display_infos;
  std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>
      nontiled_display_infos;
  std::unordered_map<
      int /*tile_group_id*/,
      std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>>
      tile_groups;
  for (auto& info : display_infos) {
    const std::optional<TileProperty>& tile_property = info->tile_property();
    if (tile_property.has_value()) {
      tile_groups[tile_property->group_id].push_back(std::move(info));
    } else {
      nontiled_display_infos.push_back(std::move(info));
    }
  }
  new_display_infos = std::move(nontiled_display_infos);

  // For each tile display group, determine the primary tile and drop others in
  // the group.
  for (auto& [_, tile_infos] : tile_groups) {
    const HardwareDisplayControllerInfo* primary_tile_info_ptr =
        GetPrimaryTileInfo(tile_infos);
    std::unique_ptr<HardwareDisplayControllerInfo> primary_tile_info =
        PopPrimaryTileInfo(primary_tile_info_ptr, tile_infos);

    for (auto& nonprimary_tile_info : tile_infos) {
      primary_tile_info->AcquireNonprimaryTileInfo(
          std::move(nonprimary_tile_info));
    }

    new_display_infos.push_back(std::move(primary_tile_info));
  }

  display_infos = std::move(new_display_infos);
}

gfx::Size GetTotalTileDisplaySize(const TileProperty& tile_property) {
  const gfx::Size& layout = tile_property.tile_layout;
  const gfx::Size& tile_size = tile_property.tile_size;
  return gfx::Size(tile_size.width() * layout.width(),
                   tile_size.height() * layout.height());
}

}  // namespace ui