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

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

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

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

#include <utility>

#include "base/files/file_path.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/manager/test/fake_display_snapshot.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/linux/test/mock_gbm_device.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/common/tile_property.h"
#include "ui/ozone/platform/drm/gpu/drm_device_manager.h"
#include "ui/ozone/platform/drm/gpu/fake_drm_device.h"
#include "ui/ozone/platform/drm/gpu/fake_drm_device_generator.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"

using ::testing::_;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::Return;
using ::testing::UnorderedElementsAre;

namespace ui {
namespace {

constexpr gfx::Size kNativeDisplaySize(1920, 1080);

constexpr unsigned char kTiledDisplay[] =
    "\x00\xff\xff\xff\xff\xff\xff\x00\x10\xac\x47\x41\x4c\x34\x37\x41"
    "\x0b\x21\x01\x04\xb5\x46\x27\x78\x3a\x76\x45\xae\x51\x33\xba\x26"
    "\x0d\x50\x54\xa5\x4b\x00\x81\x00\xb3\x00\xd1\x00\xa9\x40\x81\x80"
    "\xd1\xc0\x01\x01\x01\x01\x4d\xd0\x00\xa0\xf0\x70\x3e\x80\x30\x20"
    "\x35\x00\xba\x89\x21\x00\x00\x1a\x00\x00\x00\xff\x00\x4a\x48\x4e"
    "\x34\x4a\x33\x33\x47\x41\x37\x34\x4c\x0a\x00\x00\x00\xfc\x00\x44"
    "\x45\x4c\x4c\x20\x55\x50\x33\x32\x31\x38\x4b\x0a\x00\x00\x00\xfd"
    "\x00\x18\x4b\x1e\xb4\x6c\x01\x0a\x20\x20\x20\x20\x20\x20\x02\x79"
    "\x02\x03\x1d\xf1\x50\x10\x1f\x20\x05\x14\x04\x13\x12\x11\x03\x02"
    "\x16\x15\x07\x06\x01\x23\x09\x1f\x07\x83\x01\x00\x00\xa3\x66\x00"
    "\xa0\xf0\x70\x1f\x80\x30\x20\x35\x00\xba\x89\x21\x00\x00\x1a\x56"
    "\x5e\x00\xa0\xa0\xa0\x29\x50\x30\x20\x35\x00\xba\x89\x21\x00\x00"
    "\x1a\x7c\x39\x00\xa0\x80\x38\x1f\x40\x30\x20\x3a\x00\xba\x89\x21"
    "\x00\x00\x1a\xa8\x16\x00\xa0\x80\x38\x13\x40\x30\x20\x3a\x00\xba"
    "\x89\x21\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x47"
    "\x70\x12\x79\x00\x00\x12\x00\x16\x82\x10\x10\x00\xff\x0e\xdf\x10"
    "\x00\x00\x00\x00\x00\x44\x45\x4c\x47\x41\x4c\x34\x37\x41\x03\x01"
    "\x50\x70\x92\x01\x84\xff\x1d\xc7\x00\x1d\x80\x09\x00\xdf\x10\x2f"
    "\x00\x02\x00\x04\x00\xc1\x42\x01\x84\xff\x1d\xc7\x00\x2f\x80\x1f"
    "\x00\xdf\x10\x30\x00\x02\x00\x04\x00\xa8\x4e\x01\x04\xff\x0e\xc7"
    "\x00\x2f\x80\x1f\x00\xdf\x10\x61\x00\x02\x00\x09\x00\x97\x9d\x01"
    "\x04\xff\x0e\xc7\x00\x2f\x80\x1f\x00\xdf\x10\x2f\x00\x02\x00\x09"
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78\x90";
constexpr size_t kTiledDisplayLength = std::size(kTiledDisplay);

std::unique_ptr<HardwareDisplayControllerInfo> GetDisplayInfo(
    uint32_t connector_id = 123,
    uint32_t crtc_id = 456,
    uint8_t index = 0,
    const std::optional<TileProperty>& tile_property = std::nullopt) {
  // Initialize a list of display modes.
  constexpr size_t kNumModes = 5;
  drmModeModeInfo modes[kNumModes] = {
      {.hdisplay = 640, .vdisplay = 400},
      {.hdisplay = 640, .vdisplay = 480},
      {.hdisplay = 800, .vdisplay = 600},
      {.hdisplay = 1024, .vdisplay = 768},
      // Last mode, which should be the largest, is the native mode.
      {.hdisplay = kNativeDisplaySize.width(),
       .vdisplay = kNativeDisplaySize.height()}};

  // Initialize a connector.
  ScopedDrmConnectorPtr connector(DrmAllocator<drmModeConnector>());
  connector->connector_id = connector_id;
  connector->connection = DRM_MODE_CONNECTED;
  connector->count_props = 0;
  connector->count_modes = kNumModes;
  connector->modes = DrmAllocator<drmModeModeInfo>(kNumModes);
  std::memcpy(connector->modes, &modes[0], kNumModes * sizeof(drmModeModeInfo));

  // Initialize a CRTC.
  ScopedDrmCrtcPtr crtc(DrmAllocator<drmModeCrtc>());
  crtc->crtc_id = crtc_id;
  crtc->mode_valid = 1;
  crtc->mode = connector->modes[kNumModes - 1];

  return std::make_unique<HardwareDisplayControllerInfo>(
      std::move(connector), std::move(crtc), index,
      /*edid_parser=*/std::nullopt, tile_property);
}

testing::Matcher<TileProperty> EqTileProperty(const TileProperty& expected) {
  return AllOf(Field(&TileProperty::group_id, Eq(expected.group_id)),
               Field(&TileProperty::scale_to_fit_display,
                     Eq(expected.scale_to_fit_display)),
               Field(&TileProperty::tile_size, Eq(expected.tile_size)),
               Field(&TileProperty::tile_layout, Eq(expected.tile_layout)),
               Field(&TileProperty::location, Eq(expected.location)));
}
}  // namespace

TEST(DrmDisplayTest, TiledDisplay) {
  std::unique_ptr<DrmDeviceGenerator> fake_device_generator =
      std::make_unique<FakeDrmDeviceGenerator>();
  scoped_refptr<DrmDevice> device = fake_device_generator->CreateDevice(
      base::FilePath("/test/dri/card0"), base::ScopedFD(),
      /*is_primary_device=*/true);
  FakeDrmDevice* fake_drm = static_cast<FakeDrmDevice*>(device.get());
  fake_drm->ResetStateWithAllProperties();

  // Primary tile at (0,0)
  uint32_t primary_crtc_id, primary_connector_id;
  {
    primary_crtc_id = fake_drm->AddCrtcWithPrimaryAndCursorPlanes().id;

    auto& primary_encoder = fake_drm->AddEncoder();
    primary_encoder.possible_crtcs = 0b1;

    auto& primary_connector = fake_drm->AddConnector();
    primary_connector.connection = true;
    primary_connector.modes = std::vector<ResolutionAndRefreshRate>{
        {gfx::Size(3840, 4320), 60}, {gfx::Size(1920, 1080), 60}};
    primary_connector.encoders = std::vector<uint32_t>{primary_encoder.id};
    primary_connector.edid_blob = std::vector<uint8_t>(
        kTiledDisplay, kTiledDisplay + kTiledDisplayLength);
    primary_connector.properties.push_back(
        {.id = kTileBlobPropId, .value = kTileBlobId});

    primary_connector_id = primary_connector.id;
  }

  // Non-primary tile at (0,1) - Identical to the primary tile except for tile
  // location.
  uint32_t nonprimary_crtc_id, nonprimary_connector_id;
  {
    nonprimary_crtc_id = fake_drm->AddCrtcWithPrimaryAndCursorPlanes().id;

    auto& nonprimary_encoder = fake_drm->AddEncoder();
    nonprimary_encoder.possible_crtcs = 0b10;

    auto& nonprimary_connector = fake_drm->AddConnector();
    nonprimary_connector.connection = true;
    nonprimary_connector.modes = std::vector<ResolutionAndRefreshRate>{
        {gfx::Size(3840, 4320), 60}, {gfx::Size(1920, 1080), 60}};
    nonprimary_connector.encoders =
        std::vector<uint32_t>{nonprimary_encoder.id};
    nonprimary_connector.edid_blob = std::vector<uint8_t>(
        kTiledDisplay, kTiledDisplay + kTiledDisplayLength);
    nonprimary_connector.properties.push_back(
        {.id = kTileBlobPropId, .value = kTileBlobId + 1});
    nonprimary_connector_id = nonprimary_connector.id;
  }

  fake_drm->InitializeState(/*use_atomic=*/true);

  TileProperty primary_tile_prop = {.group_id = 1,
                                    .scale_to_fit_display = true,
                                    .tile_size = gfx::Size(3840, 4320),
                                    .tile_layout = gfx::Size(2, 1),
                                    .location = gfx::Point(0, 0)};
  std::unique_ptr<ui::HardwareDisplayControllerInfo> primary_info =
      GetDisplayInfo(primary_connector_id, primary_crtc_id, /*index=*/1,
                     primary_tile_prop);

  TileProperty nonprimary_tile_prop = primary_tile_prop;
  nonprimary_tile_prop.location = gfx::Point(1, 0);
  primary_info->AcquireNonprimaryTileInfo(
      GetDisplayInfo(nonprimary_connector_id, nonprimary_crtc_id, /*index=*/2,
                     nonprimary_tile_prop));

  std::unique_ptr<display::FakeDisplaySnapshot> snapshot =
      display::FakeDisplaySnapshot::Builder()
          .SetId(123456)
          .SetBaseConnectorId(primary_info->connector()->connector_id)
          .SetNativeMode(kNativeDisplaySize)
          .SetCurrentMode(kNativeDisplaySize)
          .SetColorSpace(gfx::ColorSpace::CreateSRGB())
          .Build();

  DrmDisplay drm_display(fake_drm, primary_info.get(), *snapshot);

  EXPECT_THAT(drm_display.crtc_connector_pairs(),
              UnorderedElementsAre(
                  AllOf(Field(&DrmDisplay::CrtcConnectorPair::connector,
                              Pointee(Field(&drmModeConnector::connector_id,
                                            Eq(primary_connector_id)))),
                        Field(&DrmDisplay::CrtcConnectorPair::crtc_id,
                              Eq(primary_crtc_id)),
                        Field(&DrmDisplay::CrtcConnectorPair::tile_location,
                              Eq(gfx::Point(0, 0)))),
                  AllOf(Field(&DrmDisplay::CrtcConnectorPair::connector,
                              Pointee(Field(&drmModeConnector::connector_id,
                                            Eq(nonprimary_connector_id)))),
                        Field(&DrmDisplay::CrtcConnectorPair::crtc_id,
                              Eq(nonprimary_crtc_id)),
                        Field(&DrmDisplay::CrtcConnectorPair::tile_location,
                              Eq(gfx::Point(1, 0))))));
  EXPECT_EQ(drm_display.GetPrimaryConnectorId(), primary_connector_id);
  EXPECT_EQ(drm_display.GetPrimaryCrtcId(), primary_crtc_id);

  std::optional<TileProperty> actual_tile_property =
      drm_display.GetTileProperty();
  ASSERT_TRUE(actual_tile_property.has_value());
  EXPECT_THAT(*actual_tile_property, EqTileProperty(primary_tile_prop));
}
}  // namespace ui