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

#include <utility>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "skia/ext/legacy_display_globals.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/linux/gbm_device.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h"

namespace ui {

namespace {

constexpr uint32_t kTestModesetFlags =
    DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET;

constexpr uint32_t kCommitModesetFlags = DRM_MODE_ATOMIC_ALLOW_MODESET;

// Seamless modeset is defined by the lack of DRM_MODE_ATOMIC_ALLOW_MODESET.
// This also happens to be the same set of flags as would be used for a
// pageflip, or other atomic property changes that do not require modesetting.
constexpr uint32_t kSeamlessModesetFlags = 0;

const std::vector<uint32_t> kBlobPropertyIds = {kEdidBlobPropId};

const ResolutionAndRefreshRate kStandardMode =
    ResolutionAndRefreshRate{gfx::Size(1920, 1080), 60u};

const std::map<uint32_t, std::string> kCrtcRequiredPropertyNames = {
    {kActivePropId, "ACTIVE"},
    {kModePropId, "MODE_ID"},
};

const std::map<uint32_t, std::string> kCrtcOptionalPropertyNames = {
    {kBackgroundColorPropId, "BACKGROUND_COLOR"},
    {kCtmPropId, "CTM"},
    {kGammaLutPropId, "GAMMA_LUT"},
    {kGammaLutSizePropId, "GAMMA_LUT_SIZE"},
    {kDegammaLutPropId, "DEGAMMA_LUT"},
    {kDegammaLutSizePropId, "DEGAMMA_LUT_SIZE"},
    {kOutFencePtrPropId, "OUT_FENCE_PTR"},
    {kVrrEnabledPropId, "VRR_ENABLED"},
};

const std::map<uint32_t, std::string> kConnectorRequiredPropertyNames = {
    {kCrtcIdPropId, "CRTC_ID"},
    {kLinkStatusPropId, "link-status"},
    {kEdidBlobPropId, "EDID"},
};

const std::map<uint32_t, std::string> kConnectorOptionalPropertyNames = {
    {kTileBlobPropId, "TILE"},
    {kVrrCapablePropId, "vrr_capable"},
};

const std::map<uint32_t, std::string> kPlaneRequiredPropertyNames = {
    // Add all required properties.
    {kPlaneCrtcId, "CRTC_ID"},
    {kCrtcX, "CRTC_X"},
    {kCrtcY, "CRTC_Y"},
    {kCrtcW, "CRTC_W"},
    {kCrtcH, "CRTC_H"},
    {kPlaneFbId, "FB_ID"},
    {kSrcX, "SRC_X"},
    {kSrcY, "SRC_Y"},
    {kSrcW, "SRC_W"},
    {kSrcH, "SRC_H"},
    {kInFencePropId, "IN_FENCE_FD"},
    {kTypePropId, "type"},
    {kInFormatsPropId, "IN_FORMATS"},
    {kRotationPropId, "rotation"},
};

const std::map<uint32_t, std::string> kPlaneOptionalPropertyNames = {
    {kColorEncodingPropId, "COLOR_ENCODING"},
    {kColorRangePropId, "COLOR_RANGE"},
    {kSizeHintsPropId, "SIZE_HINTS"},
};

template <class T>
uint32_t GetNextId(const std::vector<T>& collection, uint32_t base) {
  uint32_t max = 0;
  for (const auto t : collection) {
    max = std::max(t.id, max);
  }
  return max == 0 ? base : max + 1;
}

ScopedDrmObjectPropertyPtr CreatePropertyObject(
    const std::vector<DrmDevice::Property>& properties) {
  ScopedDrmObjectPropertyPtr drm_properties(
      DrmAllocator<drmModeObjectProperties>());
  drm_properties->count_props = properties.size();
  drm_properties->props = static_cast<uint32_t*>(
      drmMalloc(sizeof(uint32_t) * drm_properties->count_props));
  drm_properties->prop_values = static_cast<uint64_t*>(
      drmMalloc(sizeof(uint64_t) * drm_properties->count_props));
  for (size_t i = 0; i < properties.size(); ++i) {
    drm_properties->props[i] = properties[i].id;
    drm_properties->prop_values[i] = properties[i].value;
  }

  return drm_properties;
}

template <class Type>
Type* FindObjectById(uint32_t id, std::vector<Type>& properties) {
  auto it = base::ranges::find(properties, id, &Type::id);
  return it != properties.end() ? &(*it) : nullptr;
}

// The const version of FindObjectById().
template <class Type>
const Type* FindObjectById(uint32_t id, const std::vector<Type>& properties) {
  auto it = base::ranges::find(properties, id, &Type::id);
  return it != properties.end() ? &(*it) : nullptr;
}

// TODO(dnicoara): Generate all IDs internal to FakeDrmDevice.
// For now generate something with a high enough ID to be unique in tests.
uint32_t GetUniqueNumber() {
  static uint32_t value_generator = 0xff000000;
  return ++value_generator;
}

bool IsPropertyValueBlob(uint32_t prop_id) {
  return base::Contains(kBlobPropertyIds, prop_id);
}

}  // namespace

FakeDrmDevice::CrtcProperties::CrtcProperties() = default;
FakeDrmDevice::CrtcProperties::CrtcProperties(const CrtcProperties&) = default;
FakeDrmDevice::CrtcProperties::~CrtcProperties() = default;

FakeDrmDevice::ConnectorProperties::ConnectorProperties() = default;
FakeDrmDevice::ConnectorProperties::ConnectorProperties(
    const ConnectorProperties&) = default;
FakeDrmDevice::ConnectorProperties::~ConnectorProperties() = default;

FakeDrmDevice::EncoderProperties::EncoderProperties() = default;
FakeDrmDevice::EncoderProperties::EncoderProperties(const EncoderProperties&) =
    default;
FakeDrmDevice::EncoderProperties::~EncoderProperties() = default;

FakeDrmDevice::PlaneProperties::PlaneProperties() = default;
FakeDrmDevice::PlaneProperties::PlaneProperties(const PlaneProperties&) =
    default;
FakeDrmDevice::PlaneProperties::~PlaneProperties() = default;

FakeDrmDevice::FakeDrmState::FakeDrmState() = default;
FakeDrmDevice::FakeDrmState::~FakeDrmState() = default;

void FakeDrmDevice::ResetStateWithNoProperties() {
  allocated_blobs_.clear();
  plane_manager_.reset();

  drm_state_.crtc_properties.clear();
  drm_state_.connector_properties.clear();
  drm_state_.encoder_properties.clear();
  drm_state_.plane_properties.clear();
  drm_state_.property_names.clear();
}

void FakeDrmDevice::ResetStateWithAllProperties() {
  ResetStateWithNoProperties();
  drm_state_.property_names.insert(kCrtcRequiredPropertyNames.begin(),
                                   kCrtcRequiredPropertyNames.end());
  drm_state_.property_names.insert(kConnectorRequiredPropertyNames.begin(),
                                   kConnectorRequiredPropertyNames.end());
  drm_state_.property_names.insert(kPlaneRequiredPropertyNames.begin(),
                                   kPlaneRequiredPropertyNames.end());

  // Separately add optional properties that will be used in some tests, but the
  // tests will append the property to the planes on a case-by-case basis.
  drm_state_.property_names.insert(kCrtcOptionalPropertyNames.begin(),
                                   kCrtcOptionalPropertyNames.end());
  drm_state_.property_names.insert(kConnectorOptionalPropertyNames.begin(),
                                   kConnectorOptionalPropertyNames.end());
  drm_state_.property_names.insert(kPlaneOptionalPropertyNames.begin(),
                                   kPlaneOptionalPropertyNames.end());
}

FakeDrmDevice::FakeDrmState& FakeDrmDevice::ResetStateWithDefaultObjects(
    size_t crtc_count,
    size_t planes_per_crtc,
    size_t movable_planes,
    std::vector<uint32_t> plane_supported_formats,
    std::vector<drm_format_modifier> plane_supported_format_modifiers) {
  ResetStateWithAllProperties();
  std::vector<uint32_t> crtc_ids;
  for (size_t i = 0; i < crtc_count; ++i) {
    const auto& props = AddCrtcAndConnector();

    // Add at least one mode, so the connector is not sterile.
    ConnectorProperties& connector = props.second;
    connector.connection = true;
    connector.modes = std::vector<ResolutionAndRefreshRate>{kStandardMode};

    // Add CRTC planes.
    uint32_t crtc_id = props.first.id;
    crtc_ids.push_back(crtc_id);

    AddPlane(crtc_id, DRM_PLANE_TYPE_PRIMARY, plane_supported_formats,
             plane_supported_format_modifiers);
    for (size_t j = 0; j < planes_per_crtc - 1; ++j) {
      AddPlane(crtc_id, DRM_PLANE_TYPE_OVERLAY, plane_supported_formats,
               plane_supported_format_modifiers);
    }
    AddPlane(crtc_id, DRM_PLANE_TYPE_CURSOR, plane_supported_formats,
             plane_supported_format_modifiers);
  }

  for (size_t i = 0; i < movable_planes; ++i) {
    AddPlane(crtc_ids, DRM_PLANE_TYPE_OVERLAY, plane_supported_formats,
             plane_supported_format_modifiers);
  }

  return drm_state_;
}

FakeDrmDevice::ConnectorProperties& FakeDrmDevice::AddConnector() {
  DCHECK(!IsInitialized());
  uint32_t next_connector_id =
      GetNextId(drm_state_.connector_properties, kConnectorIdBase);
  auto& connector_property = drm_state_.connector_properties.emplace_back();
  connector_property.connection = false;
  connector_property.id = next_connector_id;
  for (const auto& pair : kConnectorRequiredPropertyNames) {
    connector_property.properties.push_back({.id = pair.first, .value = 0});
    if (!base::Contains(drm_state_.property_names, pair.first)) {
      drm_state_.property_names.emplace(pair.first, pair.second);
    }
  }

  return {connector_property};
}

FakeDrmDevice::EncoderProperties& FakeDrmDevice::AddEncoder() {
  DCHECK(!IsInitialized());
  uint32_t next_encoder_id =
      GetNextId(drm_state_.encoder_properties, kEncoderIdBase);
  auto& encoder_property = drm_state_.encoder_properties.emplace_back();
  encoder_property.id = next_encoder_id;

  return {encoder_property};
}

FakeDrmDevice::CrtcProperties& FakeDrmDevice::AddCrtc() {
  DCHECK(!IsInitialized());
  uint32_t next_crtc_id = GetNextId(drm_state_.crtc_properties, kCrtcIdBase);
  auto& crtc_property = drm_state_.crtc_properties.emplace_back();
  crtc_property.id = next_crtc_id;
  for (const auto& pair : kCrtcRequiredPropertyNames) {
    crtc_property.properties.push_back({.id = pair.first, .value = 0});
    if (!base::Contains(drm_state_.property_names, pair.first)) {
      drm_state_.property_names.emplace(pair.first, pair.second);
    }
  }

  return crtc_property;
}

std::pair<FakeDrmDevice::CrtcProperties&, FakeDrmDevice::ConnectorProperties&>
FakeDrmDevice::AddCrtcAndConnector() {
  DCHECK(!IsInitialized());
  return {AddCrtc(), AddConnector()};
}

FakeDrmDevice::PlaneProperties& FakeDrmDevice::AddPlane(
    uint32_t crtc_id,
    uint32_t type,
    std::vector<uint32_t> supported_formats,
    std::vector<drm_format_modifier> supported_format_modifiers) {
  DCHECK(!IsInitialized());
  return AddPlane(std::vector<uint32_t>{crtc_id}, type, supported_formats,
                  supported_format_modifiers);
}

FakeDrmDevice::PlaneProperties& FakeDrmDevice::AddPlane(
    const std::vector<uint32_t>& crtc_ids,
    uint32_t type,
    std::vector<uint32_t> supported_formats,
    std::vector<drm_format_modifier> supported_format_modifiers) {
  DCHECK(!IsInitialized());
  uint32_t next_plane_id = GetNextId(drm_state_.plane_properties, kPlaneOffset);

  size_t crtc_mask = 0u;
  for (size_t i = 0; i < drm_state_.crtc_properties.size(); ++i) {
    if (base::Contains(crtc_ids, drm_state_.crtc_properties[i].id)) {
      crtc_mask |= (1 << i);
    }
  }
  CHECK(crtc_mask != 0) << "Unable to create crtc_mask";

  auto& plane = drm_state_.plane_properties.emplace_back();
  plane.id = next_plane_id;
  plane.crtc_mask = crtc_mask;
  for (const auto& pair : kPlaneRequiredPropertyNames) {
    plane.properties.push_back({.id = pair.first, .value = 0});
    if (!base::Contains(drm_state_.property_names, pair.first)) {
      drm_state_.property_names.emplace(pair.first, pair.second);
    }
  }

  AddProperty(plane.id, {.id = kTypePropId, .value = type});

  auto in_formats_blob =
      CreateInFormatsBlob(supported_formats, supported_format_modifiers);
  AddProperty(plane.id,
              {.id = kInFormatsPropId, .value = in_formats_blob->id()});

  return plane;
}

bool FakeDrmDevice::FakeDrmState::HasResources() const {
  return !connector_properties.empty() || !crtc_properties.empty() ||
         !encoder_properties.empty();
}

FakeDrmDevice::CrtcProperties&
FakeDrmDevice::AddCrtcWithPrimaryAndCursorPlanes() {
  DCHECK(!IsInitialized());
  auto& crtc = AddCrtc();
  AddPlane(crtc.id, DRM_PLANE_TYPE_PRIMARY);
  AddPlane(crtc.id, DRM_PLANE_TYPE_CURSOR);
  return crtc;
}

FakeDrmDevice::FakeDrmDevice(std::unique_ptr<GbmDevice> gbm_device)
    : DrmDevice(base::FilePath(),
                base::ScopedFD(),
                true /* is_primary_device */,
                std::move(gbm_device)) {
}

FakeDrmDevice::FakeDrmDevice(const base::FilePath& path,
                             std::unique_ptr<GbmDevice> gbm_device,
                             bool is_primary_device)
    : DrmDevice(std::move(path),
                base::ScopedFD(),
                is_primary_device,
                std::move(gbm_device)) {
}

FakeDrmDevice::~FakeDrmDevice() {
  if (plane_manager_) {
    plane_manager_.reset();
  }
}

ScopedDrmPropertyBlob FakeDrmDevice::CreateInFormatsBlob(
    const std::vector<uint32_t>& supported_formats,
    const std::vector<drm_format_modifier>& supported_format_modifiers) {
  drm_format_modifier_blob header;
  header.count_formats = supported_formats.size();
  header.formats_offset = sizeof(header);
  header.count_modifiers = supported_format_modifiers.size();
  header.modifiers_offset =
      header.formats_offset + sizeof(uint32_t) * header.count_formats;

  std::vector<uint8_t> data(header.modifiers_offset +
                            sizeof(drm_format_modifier) *
                                header.count_modifiers);
  memcpy(data.data(), &header, sizeof(header));
  memcpy(data.data() + header.formats_offset, supported_formats.data(),
         sizeof(uint32_t) * header.count_formats);
  memcpy(data.data() + header.modifiers_offset,
         supported_format_modifiers.data(),
         sizeof(drm_format_modifier) * header.count_modifiers);

  return CreatePropertyBlob(data.data(), data.size());
}

ScopedDrmPropertyBlob FakeDrmDevice::CreateSizeHintsBlob(
    const std::vector<gfx::Size>& sizes) {
  std::vector<drm_plane_size_hint> hints(sizes.size());
  for (size_t i = 0; i < sizes.size(); i++) {
    hints[i].width = sizes[i].width();
    hints[i].height = sizes[i].height();
  }
  std::vector<uint8_t> data(sizeof(drm_plane_size_hint) * hints.size());
  memcpy(data.data(), hints.data(), sizeof(drm_plane_size_hint) * hints.size());
  return CreatePropertyBlob(data.data(), data.size());
}

void FakeDrmDevice::InitializeState(bool use_atomic) {
  CHECK(InitializeStateWithResult(use_atomic));
}

bool FakeDrmDevice::InitializeStateWithResult(bool use_atomic) {
  DCHECK(!plane_manager_);
  if (use_atomic) {
    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerAtomic>(this);
  } else {
    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerLegacy>(this);
  }
  SetCapability(DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);

  // Update the connectors' link statuses to bad if they have no modes (probably
  // due to unsuccessful link-training.
  for (FakeDrmDevice::ConnectorProperties& connector :
       drm_state_.connector_properties) {
    if (connector.connection && connector.modes.empty()) {
      DrmWrapper::Property* connector_link_status =
          FindObjectById(kLinkStatusPropId, connector.properties);
      if (connector_link_status) {
        connector_link_status->value = DRM_MODE_LINK_STATUS_BAD;
      }
    }
  }

  // Set EDID blobs as property blobs so they can be fetched when needed via
  // GetPropertyBlob().
  for (auto& mock_connector : drm_state_.connector_properties) {
    const std::vector<uint8_t> edid_blob = mock_connector.edid_blob;
    if (edid_blob.empty()) {
      continue;
    }

    auto edid_property_blob =
        CreatePropertyBlob(edid_blob.data(), edid_blob.size());
    UpdateProperty(mock_connector.id, kEdidBlobPropId,
                   edid_property_blob->id());
  }

  return plane_manager_->Initialize();
}

void FakeDrmDevice::SetModifiersOverhead(
    base::flat_map<uint64_t /*modifier*/, int /*overhead*/>
        modifiers_overhead) {
  modifiers_overhead_ = modifiers_overhead;
}

void FakeDrmDevice::SetSystemLimitOfModifiers(uint64_t limit) {
  system_watermark_limitations_ = limit;
}

ScopedDrmResourcesPtr FakeDrmDevice::GetResources() const {
  if (!drm_state_.HasResources())
    return nullptr;

  ScopedDrmResourcesPtr resources(DrmAllocator<drmModeRes>());
  resources->count_crtcs = drm_state_.crtc_properties.size();
  resources->crtcs = static_cast<uint32_t*>(
      drmMalloc(sizeof(uint32_t) * resources->count_crtcs));
  for (size_t i = 0; i < drm_state_.crtc_properties.size(); ++i)
    resources->crtcs[i] = drm_state_.crtc_properties[i].id;

  resources->count_connectors = drm_state_.connector_properties.size();
  resources->connectors = static_cast<uint32_t*>(
      drmMalloc(sizeof(uint32_t) * resources->count_connectors));
  for (size_t i = 0; i < drm_state_.connector_properties.size(); ++i)
    resources->connectors[i] = drm_state_.connector_properties[i].id;

  resources->count_encoders = drm_state_.encoder_properties.size();
  resources->encoders = static_cast<uint32_t*>(
      drmMalloc(sizeof(uint32_t) * resources->count_encoders));
  for (size_t i = 0; i < drm_state_.encoder_properties.size(); ++i)
    resources->encoders[i] = drm_state_.encoder_properties[i].id;

  return resources;
}

ScopedDrmPlaneResPtr FakeDrmDevice::GetPlaneResources() const {
  ScopedDrmPlaneResPtr resources(DrmAllocator<drmModePlaneRes>());
  resources->count_planes = drm_state_.plane_properties.size();
  resources->planes = static_cast<uint32_t*>(
      drmMalloc(sizeof(uint32_t) * resources->count_planes));
  for (size_t i = 0; i < drm_state_.plane_properties.size(); ++i)
    resources->planes[i] = drm_state_.plane_properties[i].id;

  return resources;
}

ScopedDrmObjectPropertyPtr FakeDrmDevice::GetObjectProperties(
    uint32_t object_id,
    uint32_t object_type) const {
  if (object_type == DRM_MODE_OBJECT_PLANE) {
    const PlaneProperties* properties =
        FindObjectById(object_id, drm_state_.plane_properties);
    if (properties)
      return CreatePropertyObject(properties->properties);
  } else if (object_type == DRM_MODE_OBJECT_CRTC) {
    const CrtcProperties* properties =
        FindObjectById(object_id, drm_state_.crtc_properties);
    if (properties)
      return CreatePropertyObject(properties->properties);
  } else if (object_type == DRM_MODE_OBJECT_CONNECTOR) {
    const ConnectorProperties* properties =
        FindObjectById(object_id, drm_state_.connector_properties);
    if (properties)
      return CreatePropertyObject(properties->properties);
  }

  return nullptr;
}

ScopedDrmCrtcPtr FakeDrmDevice::GetCrtc(uint32_t crtc_id) const {
  const CrtcProperties* mock_crtc =
      FindObjectById(crtc_id, drm_state_.crtc_properties);
  if (!mock_crtc)
    return nullptr;

  ScopedDrmCrtcPtr crtc(DrmAllocator<drmModeCrtc>());
  crtc->crtc_id = mock_crtc->id;

  return crtc;
}

bool FakeDrmDevice::SetCrtc(uint32_t crtc_id,
                            uint32_t framebuffer,
                            std::vector<uint32_t> connectors,
                            const drmModeModeInfo& mode) {
  crtc_fb_[crtc_id] = framebuffer;
  current_framebuffer_ = framebuffer;
  set_crtc_call_count_++;
  return set_crtc_expectation_;
}

bool FakeDrmDevice::DisableCrtc(uint32_t crtc_id) {
  current_framebuffer_ = 0;
  return true;
}

ScopedDrmConnectorPtr FakeDrmDevice::GetConnector(uint32_t connector_id) const {
  const ConnectorProperties* mock_connector =
      FindObjectById(connector_id, drm_state_.connector_properties);
  if (!mock_connector)
    return nullptr;

  ScopedDrmConnectorPtr connector(DrmAllocator<drmModeConnector>());
  connector->connector_id = mock_connector->id;
  connector->connection =
      mock_connector->connection ? DRM_MODE_CONNECTED : DRM_MODE_DISCONNECTED;

  // Copy props.
  const uint32_t count_props = mock_connector->properties.size();
  connector->count_props = count_props;
  connector->props = DrmAllocator<uint32_t>(count_props);
  connector->prop_values = DrmAllocator<uint64_t>(count_props);
  for (uint32_t i = 0; i < count_props; ++i) {
    connector->props[i] = mock_connector->properties[i].id;
    connector->prop_values[i] = mock_connector->properties[i].value;
  }

  // Copy modes.
  const uint32_t count_modes = mock_connector->modes.size();
  connector->count_modes = count_modes;
  connector->modes = DrmAllocator<drmModeModeInfo>(count_modes);
  for (uint32_t i = 0; i < count_modes; ++i) {
    const gfx::Size resolution = mock_connector->modes[i].first;
    const uint32_t vrefresh = mock_connector->modes[i].second;
    connector->modes[i].clock = resolution.GetArea() * vrefresh / 1000;
    connector->modes[i].hdisplay = resolution.width();
    connector->modes[i].htotal = resolution.width();
    connector->modes[i].vdisplay = resolution.height();
    connector->modes[i].vtotal = resolution.height();
    connector->modes[i].vrefresh = vrefresh;
  }

  // Copy associated encoders.
  const uint32_t count_encoders = mock_connector->encoders.size();
  connector->count_encoders = count_encoders;
  connector->encoders = DrmAllocator<uint32_t>(count_encoders);
  for (uint32_t i = 0; i < count_encoders; ++i)
    connector->encoders[i] = mock_connector->encoders[i];

  return connector;
}

ScopedDrmEncoderPtr FakeDrmDevice::GetEncoder(uint32_t encoder_id) const {
  const EncoderProperties* mock_encoder =
      FindObjectById(encoder_id, drm_state_.encoder_properties);
  if (!mock_encoder)
    return nullptr;

  ScopedDrmEncoderPtr encoder(DrmAllocator<drmModeEncoder>());
  encoder->encoder_id = mock_encoder->id;
  encoder->possible_crtcs = mock_encoder->possible_crtcs;

  return encoder;
}

bool FakeDrmDevice::AddFramebuffer2(uint32_t width,
                                    uint32_t height,
                                    uint32_t format,
                                    uint32_t handles[4],
                                    uint32_t strides[4],
                                    uint32_t offsets[4],
                                    uint64_t modifiers[4],
                                    uint32_t* framebuffer,
                                    uint32_t flags) {
  add_framebuffer_call_count_++;
  *framebuffer = GetUniqueNumber();
  framebuffer_ids_.insert(*framebuffer);
  fb_props_[*framebuffer] = {width, height, modifiers[0]};
  return add_framebuffer_expectation_;
}

bool FakeDrmDevice::RemoveFramebuffer(uint32_t framebuffer) {
  {
    auto it = framebuffer_ids_.find(framebuffer);
    CHECK(it != framebuffer_ids_.end());
    framebuffer_ids_.erase(it);
  }
  {
    auto it = fb_props_.find(framebuffer);
    CHECK(it != fb_props_.end());
    fb_props_.erase(it);
  }
  remove_framebuffer_call_count_++;
  std::vector<uint32_t> crtcs_to_clear;
  for (auto crtc_fb : crtc_fb_) {
    if (crtc_fb.second == framebuffer)
      crtcs_to_clear.push_back(crtc_fb.first);
  }
  for (auto crtc : crtcs_to_clear)
    crtc_fb_[crtc] = 0;
  return true;
}

ScopedDrmFramebufferPtr FakeDrmDevice::GetFramebuffer(
    uint32_t framebuffer) const {
  return ScopedDrmFramebufferPtr();
}

bool FakeDrmDevice::PageFlip(uint32_t crtc_id,
                             uint32_t framebuffer,
                             scoped_refptr<PageFlipRequest> page_flip_request) {
  page_flip_call_count_++;
  DCHECK(page_flip_request);
  crtc_fb_[crtc_id] = framebuffer;
  current_framebuffer_ = framebuffer;
  if (page_flip_expectation_)
    callbacks_.push(page_flip_request->AddPageFlip());
  return page_flip_expectation_;
}

ScopedDrmPlanePtr FakeDrmDevice::GetPlane(uint32_t plane_id) const {
  const PlaneProperties* properties =
      FindObjectById(plane_id, drm_state_.plane_properties);
  if (!properties)
    return nullptr;

  ScopedDrmPlanePtr plane(DrmAllocator<drmModePlane>());
  plane->possible_crtcs = properties->crtc_mask;
  return plane;
}

ScopedDrmPropertyPtr FakeDrmDevice::GetProperty(drmModeConnector* connector,
                                                const char* name) const {
  return ScopedDrmPropertyPtr(DrmAllocator<drmModePropertyRes>());
}

ScopedDrmPropertyPtr FakeDrmDevice::GetProperty(uint32_t id) const {
  auto it = drm_state_.property_names.find(id);
  if (it == drm_state_.property_names.end())
    return nullptr;

  ScopedDrmPropertyPtr property(DrmAllocator<drmModePropertyRes>());
  property->prop_id = id;
  strcpy(property->name, it->second.c_str());

  if (IsPropertyValueBlob(property->prop_id)) {
    property->flags = DRM_MODE_PROP_BLOB;
  } else if (IsPropertyValueEnum(property->prop_id)) {
    FillPossibleValuesForEnumProperty(property.get());
    property->flags = DRM_MODE_PROP_ENUM;
  }

  return property;
}

bool FakeDrmDevice::SetProperty(uint32_t connector_id,
                                uint32_t property_id,
                                uint64_t value) {
  return true;
}

ScopedDrmPropertyBlob FakeDrmDevice::CreatePropertyBlob(const void* blob,
                                                        size_t size) {
  uint32_t id = GetUniqueNumber();
  auto& blob_state = allocated_blobs_[id];
  DCHECK(blob_state.ref_count == 0);
  blob_state.ref_count = 1;
  const uint8_t* blob_uint8 = reinterpret_cast<const uint8_t*>(blob);
  blob_state.data.assign(blob_uint8, blob_uint8 + size);
  return std::make_unique<DrmPropertyBlobMetadata>(this, id);
}

void FakeDrmDevice::DestroyPropertyBlob(uint32_t id) {
  bool did_release = ReleaseBlob(id);
  // ReleaseBlob will return true if there exists a blob with `id`. It should
  // never be the case that we are are destroying a blob that does not exist.
  DCHECK(did_release);
}

FakeDrmDevice::BlobState::BlobState() = default;

FakeDrmDevice::BlobState::BlobState(const BlobState&) = default;

FakeDrmDevice::BlobState::~BlobState() = default;

bool FakeDrmDevice::RetainBlob(uint32_t id) {
  auto it = allocated_blobs_.find(id);
  if (it == allocated_blobs_.end()) {
    return false;
  }
  DCHECK(it->second.ref_count > 0);
  it->second.ref_count += 1;
  return true;
}

bool FakeDrmDevice::ReleaseBlob(uint32_t id) {
  auto it = allocated_blobs_.find(id);
  if (it == allocated_blobs_.end()) {
    return false;
  }
  DCHECK(it->second.ref_count > 0);
  it->second.ref_count -= 1;
  if (it->second.ref_count == 0) {
    allocated_blobs_.erase(it);
  }
  return true;
}

bool FakeDrmDevice::GetCapability(uint64_t capability, uint64_t* value) const {
  const auto it = capabilities_.find(capability);
  if (it == capabilities_.end())
    return false;

  *value = it->second;
  return true;
}

ScopedDrmPropertyBlobPtr FakeDrmDevice::GetPropertyBlob(
    uint32_t property_id) const {
  auto it = allocated_blobs_.find(property_id);
  if (it == allocated_blobs_.end()) {
    return nullptr;
  }

  ScopedDrmPropertyBlobPtr blob(DrmAllocator<drmModePropertyBlobRes>());
  blob->id = property_id;
  blob->length = it->second.data.size();
  blob->data = drmMalloc(blob->length);
  memcpy(blob->data, it->second.data.data(), blob->length);

  return blob;
}

ScopedDrmPropertyBlobPtr FakeDrmDevice::GetPropertyBlob(
    drmModeConnector* connector,
    const char* name) const {
  const ConnectorProperties* mock_connector =
      FindObjectById(connector->connector_id, drm_state_.connector_properties);
  if (!mock_connector)
    return nullptr;

  ScopedDrmPropertyBlobPtr blob(DrmAllocator<drmModePropertyBlobRes>());
  for (const auto& prop : mock_connector->properties) {
    auto prop_name_it = drm_state_.property_names.find(prop.id);
    if (prop_name_it == drm_state_.property_names.end())
      continue;

    if (prop_name_it->second.compare(name) != 0)
      continue;

    return GetPropertyBlob(prop.value);
  }

  return nullptr;
}

bool FakeDrmDevice::SetObjectProperty(uint32_t object_id,
                                      uint32_t object_type,
                                      uint32_t property_id,
                                      uint32_t property_value) {
  UpdateProperty(object_id, property_id, property_value,
                 /*add_property_if_needed=*/false);
  set_object_property_count_++;
  return true;
}

bool FakeDrmDevice::SetCursor(uint32_t crtc_id,
                              uint32_t handle,
                              const gfx::Size& size) {
  crtc_cursor_map_[crtc_id] = handle;
  return true;
}

bool FakeDrmDevice::MoveCursor(uint32_t crtc_id, const gfx::Point& point) {
  return true;
}

bool FakeDrmDevice::CreateDumbBuffer(const SkImageInfo& info,
                                     uint32_t* handle,
                                     uint32_t* stride) {
  if (!create_dumb_buffer_expectation_)
    return false;

  // |handle| should start from 1. 0 is considered an invalid handle.
  *handle = ++allocate_buffer_count_;
  *stride = info.minRowBytes();
  void* pixels = new char[info.computeByteSize(*stride)];
  SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps();
  buffers_[*handle] = SkSurfaces::WrapPixels(
      info, pixels, *stride,
      [](void* pixels, void* context) { delete[] static_cast<char*>(pixels); },
      /*context=*/nullptr, &props);
  buffers_[*handle]->getCanvas()->clear(SK_ColorBLACK);

  return true;
}

bool FakeDrmDevice::DestroyDumbBuffer(uint32_t handle) {
  if (handle > buffers_.size() || !buffers_[handle]) {
    return false;
  }

  buffers_[handle].reset();
  return true;
}

bool FakeDrmDevice::MapDumbBuffer(uint32_t handle, size_t size, void** pixels) {
  if (handle > buffers_.size() || !buffers_[handle]) {
    return false;
  }

  SkPixmap pixmap;
  buffers_[handle]->peekPixels(&pixmap);
  *pixels = const_cast<void*>(pixmap.addr());
  return true;
}

bool FakeDrmDevice::UnmapDumbBuffer(void* pixels, size_t size) {
  return true;
}

bool FakeDrmDevice::CloseBufferHandle(uint32_t handle) {
  return true;
}

bool FakeDrmDevice::CommitProperties(
    drmModeAtomicReq* request,
    uint32_t flags,
    uint32_t crtc_count,
    scoped_refptr<PageFlipRequest> page_flip_request) {
  commit_count_++;
  const bool test_only = flags & DRM_MODE_ATOMIC_TEST_ONLY;
  switch (flags) {
    case kTestModesetFlags:
      ++test_modeset_count_;
      break;
    case kCommitModesetFlags:
      ++commit_modeset_count_;
      break;
    case kSeamlessModesetFlags:
      ++seamless_modeset_count_;
      break;
  }

  if ((!test_only && !set_crtc_expectation_) ||
      (flags & DRM_MODE_ATOMIC_NONBLOCK && !commit_expectation_)) {
    return false;
  }

  uint64_t requested_resources = 0;
  base::flat_map<uint64_t, int> crtc_planes_counter;

  for (uint32_t i = 0; i < request->cursor; ++i) {
    const drmModeAtomicReqItem& item = request->items[i];
    if (!ValidatePropertyValue(item.property_id, item.value))
      return false;

    if (fb_props_.find(item.value) != fb_props_.end()) {
      const FramebufferProps& props = fb_props_[item.value];
      requested_resources += modifiers_overhead_[props.modifier];
    }

    if (item.property_id == kPlaneCrtcId) {
      if (++crtc_planes_counter[item.value] > 1 &&
          !modeset_with_overlays_expectation_)
        return false;
    }
  }

  if (requested_resources > system_watermark_limitations_) {
    LOG(ERROR) << "Requested display configuration exceeds system watermark "
                  "limitations";
    return false;
  }

  if (page_flip_request)
    callbacks_.push(page_flip_request->AddPageFlip());

  if (test_only)
    return true;

  // Only update values if not testing.
  for (uint32_t i = 0; i < request->cursor; ++i) {
    bool res =
        UpdateProperty(request->items[i].object_id,
                       request->items[i].property_id, request->items[i].value);
    if (!res)
      return false;
  }

  // Increment modeset sequence ID upon success.
  if (flags == DRM_MODE_ATOMIC_ALLOW_MODESET)
    ++modeset_sequence_id_;

  // Count all committed planes at the end just before returning true to
  // reflect the number of planes that have successfully been committed.
  last_planes_committed_count_ = 0;
  for (const auto& planes_counter : crtc_planes_counter)
    last_planes_committed_count_ += planes_counter.second;

  return true;
}

bool FakeDrmDevice::SetGammaRamp(uint32_t crtc_id,
                                 const display::GammaCurve& curve) {
  set_gamma_ramp_count_++;
  return legacy_gamma_ramp_expectation_;
}

std::optional<std::string> FakeDrmDevice::GetDriverName() const {
  return driver_name_;
}

void FakeDrmDevice::SetDriverName(std::optional<std::string> name) {
  driver_name_ = name;
}

bool FakeDrmDevice::SetCapability(uint64_t capability, uint64_t value) {
  capabilities_.insert({capability, value});
  return true;
}

uint32_t FakeDrmDevice::GetFramebufferForCrtc(uint32_t crtc_id) const {
  auto it = crtc_fb_.find(crtc_id);
  return it != crtc_fb_.end() ? it->second : 0u;
}

void FakeDrmDevice::RunCallbacks() {
  while (!callbacks_.empty()) {
    PageFlipCallback callback = std::move(callbacks_.front());
    callbacks_.pop();
    std::move(callback).Run(0, base::TimeTicks());
  }
}

void FakeDrmDevice::AddProperty(uint32_t object_id,
                                const DrmWrapper::Property& property) {
  DCHECK(!IsInitialized());
  UpdateProperty(object_id, property.id, property.value,
                 /*add_property_if_needed=*/true);
}

void FakeDrmDevice::SetPossibleValuesForEnumProperty(
    uint32_t property_id,
    std::vector<std::pair<uint64_t /* value */, std::string /* name */>>
        values) {
  DCHECK(!IsInitialized());
  DCHECK(!values.empty());
  drm_state_.enum_values[property_id] = std::move(values);
}

bool FakeDrmDevice::IsPropertyValueEnum(uint32_t prop_id) const {
  return drm_state_.enum_values.find(prop_id) != drm_state_.enum_values.end();
}

void FakeDrmDevice::FillPossibleValuesForEnumProperty(
    drmModePropertyRes* property) const {
  DCHECK(IsPropertyValueEnum(property->prop_id));

  const std::vector<std::pair<uint64_t, std::string>>& enum_values =
      drm_state_.enum_values.find(property->prop_id)->second;

  property->count_enums = enum_values.size();
  property->enums = DrmAllocator<drm_mode_property_enum>(enum_values.size());

  for (size_t i = 0; i < enum_values.size(); i++) {
    property->enums[i].value = enum_values[i].first;
    strcpy(property->enums[i].name, enum_values[i].second.c_str());
  }
}

bool FakeDrmDevice::UpdateProperty(uint32_t id,
                                   uint64_t value,
                                   std::vector<DrmDevice::Property>* properties,
                                   bool add_property_if_needed) {
  DrmDevice::Property* property = FindObjectById(id, *properties);
  if (!property) {
    if (!add_property_if_needed) {
      return false;
    }

    properties->push_back({.id = id, .value = 0});
    property = &properties->back();
  }

  // Retain the blob corresponding to the new value (if one exists). This
  // ensures that the blob will remain valid even if DestroyPropertyBlob is
  // subsequently called on it.
  RetainBlob(value);

  // Release the blob corresponding to the old value (if one exists). This may
  // trigger the destruction of the blob, if DestroyPropertyBlob has already
  // been called on it.
  ReleaseBlob(property->value);

  property->value = value;
  return true;
}

bool FakeDrmDevice::UpdateProperty(uint32_t object_id,
                                   uint32_t property_id,
                                   uint64_t value,
                                   bool add_property_if_needed) {
  PlaneProperties* plane_properties =
      FindObjectById(object_id, drm_state_.plane_properties);
  if (plane_properties) {
    return UpdateProperty(property_id, value, &plane_properties->properties,
                          add_property_if_needed);
  }

  CrtcProperties* crtc_properties =
      FindObjectById(object_id, drm_state_.crtc_properties);
  if (crtc_properties) {
    return UpdateProperty(property_id, value, &crtc_properties->properties,
                          add_property_if_needed);
  }

  ConnectorProperties* connector_properties =
      FindObjectById(object_id, drm_state_.connector_properties);
  if (connector_properties) {
    return UpdateProperty(property_id, value, &connector_properties->properties,
                          add_property_if_needed);
  }

  return false;
}

bool FakeDrmDevice::ValidatePropertyValue(uint32_t id, uint64_t value) {
  auto it = drm_state_.property_names.find(id);
  if (it == drm_state_.property_names.end())
    return false;

  if (value == 0)
    return true;

  std::vector<std::string> blob_properties = {"CTM", "DEGAMMA_LUT", "GAMMA_LUT",
                                              "PLANE_CTM"};
  if (base::Contains(blob_properties, it->second))
    return base::Contains(allocated_blobs_, value);

  return true;
}

int FakeDrmDevice::modeset_sequence_id() const {
  return modeset_sequence_id_;
}

void FakeDrmDevice::ResetPlaneManagerForTesting() {
  plane_manager_.reset();
}

void FakeDrmDevice::ClearCallbacks() {
  base::queue<PageFlipCallback> empty;
  callbacks_.swap(empty);
}

}  // namespace ui