// 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.
#include "ui/ozone/platform/drm/gpu/hardware_display_controller.h"
#include <drm_fourcc.h>
#include <stddef.h>
#include <stdint.h>
#include <xf86drmMode.h>
#include <memory>
#include <string>
#include <utility>
#include "base/files/file_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gfx/gpu_fence_handle.h"
#include "ui/gfx/linux/gbm_buffer.h"
#include "ui/gfx/linux/test/mock_gbm_device.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/drm/gpu/crtc_controller.h"
#include "ui/ozone/platform/drm/gpu/drm_dumb_buffer.h"
#include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
#include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
#include "ui/ozone/platform/drm/gpu/drm_overlay_plane.h"
#include "ui/ozone/platform/drm/gpu/fake_drm_device.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"
#include "ui/ozone/platform/drm/gpu/mock_drm_device.h"
#include "ui/ozone/platform/drm/gpu/mock_drm_modifiers_filter.h"
#include "ui/ozone/platform/drm/gpu/page_flip_watchdog.h"
#include "ui/ozone/public/drm_modifiers_filter.h"
namespace ui {
namespace {
using testing::_;
using testing::Return;
constexpr uint32_t kNoModesConnectorId = 404;
// Create a basic mode for a 6x4 screen.
const drmModeModeInfo kDefaultMode = {0, 6, 0, 0, 0, 0, 4, 0,
0, 0, 0, 0, 0, 0, {'\0'}};
const gfx::Size kDefaultModeSize(kDefaultMode.hdisplay, kDefaultMode.vdisplay);
const gfx::Size kOverlaySize(kDefaultMode.hdisplay / 2,
kDefaultMode.vdisplay / 2);
const gfx::SizeF kDefaultModeSizeF(1.0, 1.0);
const std::string kGpuCrashLogTimeout =
"Failed to modeset within " +
base::NumberToString(kWaitForModesetTimeout.InSeconds()) +
" s of the first page flip failure. Crashing GPU process.";
SkBitmap CreateBitmap(int width, int height) {
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
bitmap.eraseColor(SK_ColorTRANSPARENT);
return bitmap;
}
class FakeFenceFD {
public:
FakeFenceFD();
std::unique_ptr<gfx::GpuFence> GetGpuFence() const;
void Signal() const;
private:
base::ScopedFD read_fd;
base::ScopedFD write_fd;
};
} // namespace
FakeFenceFD::FakeFenceFD() {
int fds[2];
base::CreateLocalNonBlockingPipe(fds);
read_fd = base::ScopedFD(fds[0]);
write_fd = base::ScopedFD(fds[1]);
}
std::unique_ptr<gfx::GpuFence> FakeFenceFD::GetGpuFence() const {
gfx::GpuFenceHandle handle;
handle.Adopt(base::ScopedFD(HANDLE_EINTR(dup(read_fd.get()))));
return std::make_unique<gfx::GpuFence>(std::move(handle));
}
void FakeFenceFD::Signal() const {
base::WriteFileDescriptor(write_fd.get(), "a");
}
class HardwareDisplayControllerTest : public testing::Test {
public:
HardwareDisplayControllerTest() = default;
HardwareDisplayControllerTest(const HardwareDisplayControllerTest&) = delete;
HardwareDisplayControllerTest& operator=(
const HardwareDisplayControllerTest&) = delete;
~HardwareDisplayControllerTest() override = default;
void SetUp() override;
void TearDown() override;
void InitializeDrmDevice(
bool use_atomic,
size_t movable_planes = 0,
const std::vector<uint64_t>& supported_modifiers = {},
std::unique_ptr<DrmModifiersFilter> modifiers_filter = nullptr,
bool has_size_hints = false);
void SchedulePageFlip(DrmOverlayPlaneList planes);
void OnSubmission(gfx::SwapResult swap_result,
gfx::GpuFenceHandle release_fence);
void OnPresentation(const gfx::PresentationFeedback& feedback);
uint64_t GetPlanePropertyValue(uint32_t plane,
const std::string& property_name);
scoped_refptr<DrmFramebuffer> CreateBuffer() {
std::unique_ptr<GbmBuffer> buffer = drm_->gbm_device()->CreateBuffer(
DRM_FORMAT_XRGB8888, kDefaultModeSize, GBM_BO_USE_SCANOUT);
return DrmFramebuffer::AddFramebuffer(drm_, buffer.get(), kDefaultModeSize);
}
scoped_refptr<DrmFramebuffer> CreateOverlayBuffer() {
std::unique_ptr<GbmBuffer> buffer = drm_->gbm_device()->CreateBuffer(
DRM_FORMAT_XRGB8888, kOverlaySize, GBM_BO_USE_SCANOUT);
return DrmFramebuffer::AddFramebuffer(drm_, buffer.get(), kOverlaySize);
}
std::vector<HardwareDisplayPlane*> GetMovableOverlays() {
std::vector<HardwareDisplayPlane*> out;
for (const auto& plane : drm_->plane_manager()->planes()) {
if (plane->GetCompatibleCrtcIds().size() > 1) {
out.push_back(plane.get());
}
}
return out;
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
protected:
bool ModesetWithPlanes(const DrmOverlayPlaneList& modeset_planes);
bool DisableController();
std::unique_ptr<HardwareDisplayController> controller_;
scoped_refptr<FakeDrmDevice> drm_;
std::unique_ptr<DrmModifiersFilter> modifiers_filter_;
int successful_page_flips_count_ = 0;
gfx::SwapResult last_swap_result_;
gfx::PresentationFeedback last_presentation_feedback_;
uint32_t primary_crtc_ = 0;
uint32_t secondary_crtc_ = 0;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
void HardwareDisplayControllerTest::SetUp() {
// Enable the |kUseDynamicCursorSize| feature for test.
scoped_feature_list_.InitAndEnableFeature(ui::kUseDynamicCursorSize);
successful_page_flips_count_ = 0;
last_swap_result_ = gfx::SwapResult::SWAP_FAILED;
auto gbm_device = std::make_unique<MockGbmDevice>();
drm_ = new FakeDrmDevice(std::move(gbm_device));
InitializeDrmDevice(/* use_atomic= */ true);
}
void HardwareDisplayControllerTest::TearDown() {
controller_.reset();
drm_->ResetPlaneManagerForTesting();
drm_->ClearCallbacks();
drm_ = nullptr;
}
void HardwareDisplayControllerTest::InitializeDrmDevice(
bool use_atomic,
size_t movable_planes,
const std::vector<uint64_t>& supported_modifiers,
std::unique_ptr<DrmModifiersFilter> modifiers_filter,
bool has_size_hints) {
// This will change the plane_manager of the drm.
// HardwareDisplayController is tied to the plane_manager CRTC states.
// Destruct the controller before destructing the plane manager its CRTC
// controllers are tied to.
controller_ = nullptr;
modifiers_filter_ = std::move(modifiers_filter);
// Set up the default property blob for in formats:
std::vector<drm_format_modifier> drm_format_modifiers;
for (const auto modifier : supported_modifiers) {
drm_format_modifiers.push_back(
{.formats = 1, .offset = 0, .pad = 0, .modifier = modifier});
}
drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc*/ 2, movable_planes,
{DRM_FORMAT_XRGB8888}, drm_format_modifiers);
if (has_size_hints) {
std::vector<gfx::Size> supported_cursor_sizes = {
gfx::Size(64, 64), gfx::Size(128, 128), gfx::Size(256, 256)};
for (FakeDrmDevice::PlaneProperties plane : drm_->plane_properties()) {
for (DrmWrapper::Property property : plane.properties) {
if (property.id == kTypePropId &&
property.value == DRM_PLANE_TYPE_CURSOR) {
ScopedDrmPropertyBlob size_hints_blob =
drm_->CreateSizeHintsBlob(supported_cursor_sizes);
drm_->AddProperty(plane.id, {.id = kSizeHintsPropId,
.value = size_hints_blob->id()});
}
}
}
}
// Add one connected connector with no modes (sterile).
auto& connector_props = drm_->AddConnector();
connector_props.id = kNoModesConnectorId;
connector_props.connection = true;
drm_->AddProperty(drm_->crtc_property(0).id,
{.id = kVrrEnabledPropId, .value = 0});
drm_->InitializeState(use_atomic);
primary_crtc_ = drm_->crtc_property(0).id;
secondary_crtc_ = drm_->crtc_property(1).id;
// Initialize a new HardwareDisplayController with the new Plane Manager of
// the DRM.
controller_ = std::make_unique<HardwareDisplayController>(
std::make_unique<CrtcController>(drm_.get(), primary_crtc_,
kConnectorIdBase),
gfx::Point(), modifiers_filter_.get());
}
bool HardwareDisplayControllerTest::ModesetWithPlanes(
const DrmOverlayPlaneList& modeset_planes) {
CommitRequest commit_request;
controller_->GetModesetProps(&commit_request, modeset_planes, kDefaultMode,
/*enable_vrr=*/false);
CommitRequest request_for_update = commit_request;
bool status = drm_->plane_manager()->Commit(std::move(commit_request),
DRM_MODE_ATOMIC_ALLOW_MODESET);
for (const CrtcCommitRequest& crtc_request : request_for_update)
controller_->UpdateState(crtc_request);
return status;
}
bool HardwareDisplayControllerTest::DisableController() {
CommitRequest commit_request;
controller_->GetDisableProps(&commit_request);
CommitRequest request_for_update = commit_request;
bool status = drm_->plane_manager()->Commit(std::move(commit_request),
DRM_MODE_ATOMIC_ALLOW_MODESET);
for (const CrtcCommitRequest& crtc_request : request_for_update)
controller_->UpdateState(crtc_request);
return status;
}
void HardwareDisplayControllerTest::SchedulePageFlip(
DrmOverlayPlaneList planes) {
controller_->SchedulePageFlip(
std::move(planes),
base::BindOnce(&HardwareDisplayControllerTest::OnSubmission,
base::Unretained(this)),
base::BindOnce(&HardwareDisplayControllerTest::OnPresentation,
base::Unretained(this)));
}
void HardwareDisplayControllerTest::OnSubmission(
gfx::SwapResult result,
gfx::GpuFenceHandle release_fence) {
last_swap_result_ = result;
}
void HardwareDisplayControllerTest::OnPresentation(
const gfx::PresentationFeedback& feedback) {
if (!feedback.failed())
successful_page_flips_count_++;
last_presentation_feedback_ = feedback;
}
uint64_t HardwareDisplayControllerTest::GetPlanePropertyValue(
uint32_t plane,
const std::string& property_name) {
DrmWrapper::Property p{};
ScopedDrmObjectPropertyPtr properties(
drm_->GetObjectProperties(plane, DRM_MODE_OBJECT_PLANE));
EXPECT_TRUE(
GetDrmPropertyForName(drm_.get(), properties.get(), property_name, &p));
return p.value;
}
class HardwareDisplayControllerMockedDeviceTest
: public HardwareDisplayControllerTest {
void SetUp() override {
successful_page_flips_count_ = 0;
last_swap_result_ = gfx::SwapResult::SWAP_FAILED;
auto gbm_device = std::make_unique<MockGbmDevice>();
drm_ = new testing::NiceMock<MockDrmDevice>(base::FilePath(),
std::move(gbm_device), true);
InitializeDrmDevice(/* use_atomic= */ true);
}
};
TEST_F(HardwareDisplayControllerTest, CheckModesettingResult) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
EXPECT_FALSE(
DrmOverlayPlane::GetPrimaryPlane(modeset_planes)->buffer->HasOneRef());
}
TEST_F(HardwareDisplayControllerTest, CrtcPropsAfterModeset) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
ScopedDrmObjectPropertyPtr crtc_props =
drm_->GetObjectProperties(primary_crtc_, DRM_MODE_OBJECT_CRTC);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), crtc_props.get(), "ACTIVE", &prop);
EXPECT_EQ(kActivePropId, prop.id);
EXPECT_EQ(1U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), crtc_props.get(), "MODE_ID", &prop);
EXPECT_EQ(kModePropId, prop.id);
EXPECT_GT(prop.value, 0U);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), crtc_props.get(), "VRR_ENABLED", &prop);
EXPECT_EQ(kVrrEnabledPropId, prop.id);
EXPECT_EQ(0U, prop.value);
}
}
TEST_F(HardwareDisplayControllerTest, ConnectorPropsAfterModeset) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
ScopedDrmObjectPropertyPtr connector_props =
drm_->GetObjectProperties(kConnectorIdBase, DRM_MODE_OBJECT_CONNECTOR);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), connector_props.get(), "CRTC_ID", &prop);
EXPECT_EQ(kCrtcIdPropId, prop.id);
EXPECT_EQ(kCrtcIdBase, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), connector_props.get(), "link-status",
&prop);
EXPECT_EQ(kLinkStatusPropId, prop.id);
EXPECT_EQ(static_cast<uint64_t>(DRM_MODE_LINK_STATUS_GOOD), prop.value);
}
}
TEST_F(HardwareDisplayControllerTest, BadLinkStatusConnectorPropsAfterModeset) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
ScopedDrmObjectPropertyPtr bad_link_connector_props =
drm_->GetObjectProperties(kNoModesConnectorId, DRM_MODE_OBJECT_CONNECTOR);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), bad_link_connector_props.get(),
"link-status", &prop);
EXPECT_EQ(kLinkStatusPropId, prop.id);
EXPECT_EQ(static_cast<uint64_t>(DRM_MODE_LINK_STATUS_BAD), prop.value);
}
}
TEST_F(HardwareDisplayControllerTest, PlanePropsAfterModeset) {
const FakeFenceFD fake_fence_fd;
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(), gfx::ColorSpace::CreateSRGB(),
fake_fence_fd.GetGpuFence()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
ScopedDrmObjectPropertyPtr plane_props =
drm_->GetObjectProperties(kPlaneOffset, DRM_MODE_OBJECT_PLANE);
const DrmOverlayPlane* primary_plane =
DrmOverlayPlane::GetPrimaryPlane(modeset_planes);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_ID", &prop);
EXPECT_EQ(kPlaneCrtcId, prop.id);
EXPECT_EQ(kCrtcIdBase, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_X", &prop);
EXPECT_EQ(kCrtcX, prop.id);
EXPECT_EQ(primary_plane->display_bounds.x(), static_cast<int>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_Y", &prop);
EXPECT_EQ(kCrtcY, prop.id);
EXPECT_EQ(primary_plane->display_bounds.y(), static_cast<int>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_W", &prop);
EXPECT_EQ(kCrtcW, prop.id);
EXPECT_EQ(kDefaultModeSize.width(), static_cast<int>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_H", &prop);
EXPECT_EQ(kCrtcH, prop.id);
EXPECT_EQ(kDefaultModeSize.height(), static_cast<int>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "FB_ID", &prop);
EXPECT_EQ(kPlaneFbId, prop.id);
EXPECT_EQ(primary_plane->buffer->opaque_framebuffer_id(),
static_cast<uint32_t>(prop.value));
}
gfx::RectF crop_rectf = primary_plane->crop_rect;
crop_rectf.Scale(primary_plane->buffer->size().width(),
primary_plane->buffer->size().height());
gfx::Rect crop_rect = gfx::ToNearestRect(crop_rectf);
gfx::Rect fixed_point_rect =
gfx::Rect(crop_rect.x() << 16, crop_rect.y() << 16,
crop_rect.width() << 16, crop_rect.height() << 16);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_X", &prop);
EXPECT_EQ(kSrcX, prop.id);
EXPECT_EQ(fixed_point_rect.x(), static_cast<float>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_Y", &prop);
EXPECT_EQ(kSrcY, prop.id);
EXPECT_EQ(fixed_point_rect.y(), static_cast<float>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_W", &prop);
EXPECT_EQ(kSrcW, prop.id);
EXPECT_EQ(fixed_point_rect.width(), static_cast<int>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_H", &prop);
EXPECT_EQ(kSrcH, prop.id);
EXPECT_EQ(fixed_point_rect.height(), static_cast<int>(prop.value));
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "IN_FENCE_FD", &prop);
EXPECT_EQ(kInFencePropId, prop.id);
EXPECT_GT(static_cast<int>(prop.value), base::kInvalidPlatformFile);
}
}
TEST_F(HardwareDisplayControllerTest, FenceFdValueChange) {
DrmOverlayPlaneList modeset_planes;
DrmOverlayPlane plane(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(plane.Clone());
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
// Test invalid fence fd
{
DrmWrapper::Property fence_fd_prop = {};
ScopedDrmObjectPropertyPtr plane_props =
drm_->GetObjectProperties(kPlaneOffset, DRM_MODE_OBJECT_PLANE);
GetDrmPropertyForName(drm_.get(), plane_props.get(), "IN_FENCE_FD",
&fence_fd_prop);
EXPECT_EQ(kInFencePropId, fence_fd_prop.id);
EXPECT_EQ(base::kInvalidPlatformFile,
static_cast<int>(fence_fd_prop.value));
}
const FakeFenceFD fake_fence_fd;
plane.gpu_fence = fake_fence_fd.GetGpuFence();
std::vector<DrmOverlayPlane> planes = {};
planes.push_back(plane.Clone());
SchedulePageFlip(std::move(planes));
// Verify fence FD after a GPU Fence is added to the plane.
{
DrmWrapper::Property fence_fd_prop = {};
ScopedDrmObjectPropertyPtr plane_props =
drm_->GetObjectProperties(kPlaneOffset, DRM_MODE_OBJECT_PLANE);
GetDrmPropertyForName(drm_.get(), plane_props.get(), "IN_FENCE_FD",
&fence_fd_prop);
EXPECT_EQ(kInFencePropId, fence_fd_prop.id);
EXPECT_LT(base::kInvalidPlatformFile,
static_cast<int>(fence_fd_prop.value));
}
plane.gpu_fence = nullptr;
modeset_planes.clear();
modeset_planes.push_back(plane.Clone());
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
// Test an invalid FD again after the fence is removed.
{
DrmWrapper::Property fence_fd_prop = {};
ScopedDrmObjectPropertyPtr plane_props =
drm_->GetObjectProperties(kPlaneOffset, DRM_MODE_OBJECT_PLANE);
GetDrmPropertyForName(drm_.get(), plane_props.get(), "IN_FENCE_FD",
&fence_fd_prop);
EXPECT_EQ(kInFencePropId, fence_fd_prop.id);
EXPECT_EQ(base::kInvalidPlatformFile,
static_cast<int>(fence_fd_prop.value));
}
}
TEST_F(HardwareDisplayControllerTest, CheckDisableResetsProps) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
// Test props values after disabling.
DisableController();
ScopedDrmObjectPropertyPtr crtc_props =
drm_->GetObjectProperties(primary_crtc_, DRM_MODE_OBJECT_CRTC);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), crtc_props.get(), "ACTIVE", &prop);
EXPECT_EQ(kActivePropId, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), crtc_props.get(), "MODE_ID", &prop);
EXPECT_EQ(kModePropId, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), crtc_props.get(), "VRR_ENABLED", &prop);
EXPECT_EQ(kVrrEnabledPropId, prop.id);
EXPECT_EQ(0U, prop.value);
}
ScopedDrmObjectPropertyPtr connector_props =
drm_->GetObjectProperties(kConnectorIdBase, DRM_MODE_OBJECT_CONNECTOR);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), connector_props.get(), "CRTC_ID", &prop);
EXPECT_EQ(kCrtcIdPropId, prop.id);
EXPECT_EQ(0U, prop.value);
}
ScopedDrmObjectPropertyPtr plane_props =
drm_->GetObjectProperties(kPlaneOffset, DRM_MODE_OBJECT_PLANE);
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_ID", &prop);
EXPECT_EQ(kPlaneCrtcId, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_X", &prop);
EXPECT_EQ(kCrtcX, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_Y", &prop);
EXPECT_EQ(kCrtcY, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_W", &prop);
EXPECT_EQ(kCrtcW, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "CRTC_H", &prop);
EXPECT_EQ(kCrtcH, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "FB_ID", &prop);
EXPECT_EQ(kPlaneFbId, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_X", &prop);
EXPECT_EQ(kSrcX, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_Y", &prop);
EXPECT_EQ(kSrcY, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_W", &prop);
EXPECT_EQ(kSrcW, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "SRC_H", &prop);
EXPECT_EQ(kSrcH, prop.id);
EXPECT_EQ(0U, prop.value);
}
{
DrmWrapper::Property prop = {};
GetDrmPropertyForName(drm_.get(), plane_props.get(), "IN_FENCE_FD", &prop);
EXPECT_EQ(kInFencePropId, prop.id);
EXPECT_EQ(base::kInvalidPlatformFile, static_cast<int>(prop.value));
}
}
TEST_F(HardwareDisplayControllerTest, CheckStateAfterPageFlip) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
EXPECT_EQ(1, drm_->get_commit_count());
DrmOverlayPlane page_flip_plane(DrmOverlayPlane::TestPlane(CreateBuffer()));
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.push_back(page_flip_plane.Clone());
SchedulePageFlip(std::move(page_flip_planes));
drm_->RunCallbacks();
EXPECT_TRUE(
DrmOverlayPlane::GetPrimaryPlane(modeset_planes)->buffer->HasOneRef());
EXPECT_FALSE(page_flip_plane.buffer->HasOneRef());
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
EXPECT_EQ(2, drm_->get_commit_count());
// Verify only the primary display have a valid framebuffer.
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
EXPECT_EQ(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
}
TEST_F(HardwareDisplayControllerTest, CheckStateIfModesetFails) {
InitializeDrmDevice(/* use_atomic */ false);
drm_->set_set_crtc_expectation(false);
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_FALSE(ModesetWithPlanes(modeset_planes));
}
TEST_F(HardwareDisplayControllerTest, CheckOverlayPresent) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
planes.emplace_back(CreateOverlayBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kOverlaySize), gfx::RectF(kDefaultModeSizeF),
true, nullptr);
EXPECT_TRUE(ModesetWithPlanes(planes));
EXPECT_EQ(1, drm_->get_commit_count());
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
EXPECT_EQ(2, drm_->get_commit_count());
// Verify both planes on the primary display have a valid framebuffer.
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
}
TEST_F(HardwareDisplayControllerTest, CheckOverlayTestMode) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
planes.emplace_back(CreateOverlayBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kOverlaySize), gfx::RectF(kDefaultModeSizeF),
true, nullptr);
EXPECT_TRUE(ModesetWithPlanes(planes));
EXPECT_EQ(1, drm_->get_commit_count());
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
EXPECT_EQ(2, drm_->get_commit_count());
// Verify both planes on the primary display have a valid framebuffer.
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
// A test call shouldn't cause new flips, but should succeed.
EXPECT_TRUE(controller_->TestPageFlip(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
EXPECT_EQ(3, drm_->get_commit_count());
// Regular flips should continue on normally.
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(2, successful_page_flips_count_);
EXPECT_EQ(4, drm_->get_commit_count());
// Verify both planes on the primary display have a valid framebuffer.
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
}
TEST_F(HardwareDisplayControllerTest, AcceptUnderlays) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
planes.emplace_back(CreateBuffer(), gfx::ColorSpace::CreateSRGB(), -1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kDefaultModeSize),
gfx::RectF(kDefaultModeSizeF), true, nullptr);
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
}
TEST_F(HardwareDisplayControllerTest, PageflipMirroredControllers) {
controller_->AddCrtc(std::make_unique<CrtcController>(
drm_.get(), secondary_crtc_, drm_->connector_property(1).id));
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
EXPECT_EQ(1, drm_->get_commit_count());
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
EXPECT_EQ(2, drm_->get_commit_count());
// Verify only the displays have a valid framebuffer on the primary plane.
for (const auto& plane : drm_->plane_manager()->planes()) {
if (plane->type() == DRM_PLANE_TYPE_PRIMARY) {
EXPECT_NE(0u, GetPlanePropertyValue(plane->id(), "FB_ID"));
} else {
EXPECT_EQ(0u, GetPlanePropertyValue(plane->id(), "FB_ID"));
}
}
}
TEST_F(HardwareDisplayControllerTest, PlaneStateAfterRemoveCrtc) {
controller_->AddCrtc(std::make_unique<CrtcController>(
drm_.get(), secondary_crtc_, drm_->connector_property(1).id));
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
const HardwareDisplayPlane* primary_crtc_plane = nullptr;
const HardwareDisplayPlane* secondary_crtc_plane = nullptr;
for (const auto& plane : drm_->plane_manager()->planes()) {
if (plane->in_use() && plane->owning_crtc() == primary_crtc_)
primary_crtc_plane = plane.get();
if (plane->in_use() && plane->owning_crtc() == secondary_crtc_)
secondary_crtc_plane = plane.get();
}
ASSERT_NE(nullptr, primary_crtc_plane);
ASSERT_NE(nullptr, secondary_crtc_plane);
EXPECT_EQ(primary_crtc_, primary_crtc_plane->owning_crtc());
EXPECT_EQ(secondary_crtc_, secondary_crtc_plane->owning_crtc());
// Removing the crtc should free the plane.
std::unique_ptr<CrtcController> crtc =
controller_->RemoveCrtc(drm_, primary_crtc_);
EXPECT_FALSE(primary_crtc_plane->in_use());
EXPECT_TRUE(secondary_crtc_plane->in_use());
EXPECT_EQ(secondary_crtc_, secondary_crtc_plane->owning_crtc());
// Check that controller doesn't affect the state of removed plane in
// subsequent page flip.
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(2, successful_page_flips_count_);
EXPECT_FALSE(primary_crtc_plane->in_use());
EXPECT_TRUE(secondary_crtc_plane->in_use());
EXPECT_EQ(secondary_crtc_, secondary_crtc_plane->owning_crtc());
}
TEST_F(HardwareDisplayControllerTest, PlaneStateAfterDestroyingCrtc) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
const HardwareDisplayPlane* owned_plane = nullptr;
for (const auto& plane : drm_->plane_manager()->planes())
if (plane->in_use())
owned_plane = plane.get();
ASSERT_TRUE(owned_plane != nullptr);
EXPECT_EQ(primary_crtc_, owned_plane->owning_crtc());
std::unique_ptr<CrtcController> crtc =
controller_->RemoveCrtc(drm_, primary_crtc_);
// Destroying crtc should free the plane.
crtc.reset();
uint32_t crtc_nullid = 0;
EXPECT_FALSE(owned_plane->in_use());
EXPECT_EQ(crtc_nullid, owned_plane->owning_crtc());
}
TEST_F(HardwareDisplayControllerTest, PlaneStateAfterAddCrtc) {
controller_->AddCrtc(std::make_unique<CrtcController>(
drm_.get(), secondary_crtc_, drm_->connector_property(1).id));
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
HardwareDisplayPlane* primary_crtc_plane = nullptr;
for (const auto& plane : drm_->plane_manager()->planes()) {
if (plane->in_use() && primary_crtc_ == plane->owning_crtc())
primary_crtc_plane = plane.get();
}
ASSERT_TRUE(primary_crtc_plane != nullptr);
auto hdc_controller = std::make_unique<HardwareDisplayController>(
controller_->RemoveCrtc(drm_, primary_crtc_), controller_->origin(),
nullptr);
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(2, successful_page_flips_count_);
EXPECT_FALSE(primary_crtc_plane->in_use());
// We reset state of plane here to test that the plane was actually added to
// hdc_controller. In which case, the right state should be set to plane
// after page flip call is handled by the controller.
primary_crtc_plane->set_in_use(false);
primary_crtc_plane->set_owning_crtc(0);
hdc_controller->SchedulePageFlip(
DrmOverlayPlane::Clone(planes),
base::BindOnce(&HardwareDisplayControllerTest::OnSubmission,
base::Unretained(this)),
base::BindOnce(&HardwareDisplayControllerTest::OnPresentation,
base::Unretained(this)));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(3, successful_page_flips_count_);
EXPECT_TRUE(primary_crtc_plane->in_use());
EXPECT_EQ(primary_crtc_, primary_crtc_plane->owning_crtc());
}
TEST_F(HardwareDisplayControllerTest, ModesetWhilePageFlipping) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
EXPECT_TRUE(ModesetWithPlanes(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
}
TEST_F(HardwareDisplayControllerTest, FailPageFlippingWithNoSavingModeset) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
// Page flip fails, so a GPU process self-destruct sequence is initiated.
drm_->set_commit_expectation(false);
SchedulePageFlip(std::move(page_flip_planes));
// Since no modeset event was detected, death occurs after
// |kWaitForModesetTimeout| seconds.
EXPECT_DEATH_IF_SUPPORTED(
task_environment_.FastForwardBy(kWaitForModesetTimeout),
kGpuCrashLogTimeout);
}
TEST_F(HardwareDisplayControllerTest, FailPageFlippingWithSavingModeset) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
// Page flip fails, so a GPU process self-destruct sequence is initiated.
drm_->set_commit_expectation(false);
SchedulePageFlip(std::move(page_flip_planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_FAILED, last_swap_result_);
EXPECT_EQ(0, successful_page_flips_count_);
// Some time passes.
task_environment_.FastForwardBy(base::Milliseconds(1623));
// A modeset event occurs and prevents the GPU process from crashing.
modeset_planes.clear();
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
// Ensure self-destruct time runs out without process death.
task_environment_.FastForwardBy(kWaitForModesetTimeout);
}
TEST_F(HardwareDisplayControllerTest,
RecreateBuffersOnOldPlanesPageFlipFailure) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(planes));
// Page flip fails due to planes being allocated prior to the last modeset.
drm_->set_commit_expectation(false);
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
// We recreate the buffers.
EXPECT_EQ(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS, last_swap_result_);
EXPECT_EQ(0, successful_page_flips_count_);
// Next page flip passes, so the GPU process is safe.
drm_->set_commit_expectation(true);
planes.clear();
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
SchedulePageFlip(std::move(planes));
// Ensure self-destruct time runs out without process death.
task_environment_.FastForwardBy(kWaitForModesetTimeout);
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
}
TEST_F(HardwareDisplayControllerTest, CheckNoPrimaryPlaneOnFlip) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(CreateBuffer(), gfx::ColorSpace::CreateSRGB(),
1, gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
}
TEST_F(HardwareDisplayControllerTest, PageFlipWithUnassignablePlanes) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(CreateBuffer(), gfx::ColorSpace::CreateSRGB(),
1, gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(CreateBuffer(), gfx::ColorSpace::CreateSRGB(),
1, gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(CreateBuffer(), gfx::ColorSpace::CreateSRGB(),
1, gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
// It's important we don't do any real DRM flips here, since we know
// we can't allocate any planes, we avoid sending bad commits to the
// drivers.
EXPECT_EQ(0, drm_->get_page_flip_call_count());
EXPECT_EQ(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS, last_swap_result_);
}
TEST_F(HardwareDisplayControllerTest, SomePlaneAssignmentFailuresAreOk) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
constexpr int kUnassignableFlips = 3;
for (size_t i = 0; i < kUnassignableFlips; ++i) {
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
EXPECT_EQ(0, successful_page_flips_count_);
EXPECT_EQ(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS, last_swap_result_);
}
for (size_t i = 0; i < kPageFlipWatcherHistorySize - kUnassignableFlips;
++i) {
drm_->set_commit_expectation(true);
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
// +1 because we're comparing an index with a count.
EXPECT_EQ(i + 1, static_cast<size_t>(successful_page_flips_count_));
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
}
// We should still be alive since we didn't submit too many unassignable page
// flips.
task_environment_.FastForwardBy(kWaitForModesetTimeout);
}
TEST_F(HardwareDisplayControllerTest, CrashOnTooManyFlakyPlaneAssignments) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
auto do_successful_flip = [&]() {
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_FALSE(last_presentation_feedback_.failed());
};
auto do_failed_flip = [&]() {
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS, last_swap_result_);
EXPECT_TRUE(last_presentation_feedback_.failed());
};
auto do_flake = [&]() {
do_successful_flip();
do_failed_flip();
};
auto flakes = kPlaneAssignmentFlakeThreshold;
ASSERT_GT(kPageFlipWatcherHistorySize, flakes)
<< "Page flip history is too small to account for the maximum number of "
"flakes";
auto successes = kPageFlipWatcherHistorySize - (2 * flakes);
for (size_t i = 0; i < successes; ++i)
do_successful_flip();
for (size_t i = 0; i < flakes; ++i)
do_flake();
EXPECT_DEATH_IF_SUPPORTED(
do_flake(),
base::StringPrintf("Plane assignment has flaked %d times, but the "
"threshold is %d. Crashing the GPU process.",
flakes, kPlaneAssignmentFlakeThreshold));
}
TEST_F(HardwareDisplayControllerTest, CrashOnTooManyFailedPlaneAssignments) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
auto do_successful_flip = [&]() {
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_FALSE(last_presentation_feedback_.failed());
};
auto do_failed_flip = [&]() {
{
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
page_flip_planes.emplace_back(
CreateBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(), gfx::Rect(kDefaultModeSize),
gfx::RectF(0, 0, 1, 1), true, nullptr);
SchedulePageFlip(std::move(page_flip_planes));
}
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS, last_swap_result_);
EXPECT_TRUE(last_presentation_feedback_.failed());
};
auto failures = kPlaneAssignmentMaximumFailures;
auto successes = kPageFlipWatcherHistorySize - failures;
for (size_t i = 0; i < successes; ++i)
do_successful_flip();
for (size_t i = 0; i < (failures - 1); ++i)
do_failed_flip();
EXPECT_DEATH_IF_SUPPORTED(
do_failed_flip(),
base::StringPrintf("Plane assignment has failed %d/%d times, but the "
"threshold is %d. Crashing the GPU process.",
failures, kPageFlipWatcherHistorySize,
kPlaneAssignmentMaximumFailures));
}
TEST_F(HardwareDisplayControllerTest, AddCrtcMidPageFlip) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(std::move(planes));
controller_->AddCrtc(std::make_unique<CrtcController>(
drm_.get(), secondary_crtc_, kConnectorIdBase + 1));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
}
TEST_F(HardwareDisplayControllerTest, RemoveCrtcMidPageFlip) {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(std::move(planes));
controller_->RemoveCrtc(drm_, primary_crtc_);
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_EQ(1, successful_page_flips_count_);
}
TEST_F(HardwareDisplayControllerTest, Disable) {
// Page flipping overlays is only supported on atomic configurations.
InitializeDrmDevice(/* use_atomic= */ true);
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(planes));
planes.emplace_back(CreateOverlayBuffer(), gfx::ColorSpace::CreateSRGB(), 1,
gfx::OVERLAY_TRANSFORM_NONE, gfx::Rect(),
gfx::Rect(kOverlaySize), gfx::RectF(kDefaultModeSizeF),
true, nullptr);
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
EXPECT_TRUE(DisableController());
int planes_in_use = 0;
for (const auto& plane : drm_->plane_manager()->planes()) {
if (plane->in_use())
planes_in_use++;
}
// No plane should be in use.
ASSERT_EQ(0, planes_in_use);
}
TEST_F(HardwareDisplayControllerTest, PageflipAfterModeset) {
DrmOverlayPlaneList planes;
scoped_refptr<DrmFramebuffer> buffer = CreateBuffer();
planes.push_back(DrmOverlayPlane::TestPlane(buffer));
EXPECT_TRUE(ModesetWithPlanes(planes));
for (const auto& plane : planes) {
EXPECT_TRUE(base::Contains(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers,
plane.buffer));
}
SchedulePageFlip(std::move(planes));
drm_->RunCallbacks();
// modeset_framebuffers should be cleared after the pageflip is complete.
EXPECT_TRUE(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers.empty());
}
TEST_F(HardwareDisplayControllerTest, PageflipBeforeModeset) {
DrmOverlayPlaneList planes;
scoped_refptr<DrmFramebuffer> buffer = CreateBuffer();
planes.push_back(DrmOverlayPlane::TestPlane(buffer));
EXPECT_TRUE(ModesetWithPlanes(planes));
SchedulePageFlip(DrmOverlayPlane::Clone(planes));
EXPECT_TRUE(ModesetWithPlanes(planes));
for (const auto& plane : planes) {
EXPECT_TRUE(base::Contains(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers,
plane.buffer));
}
// modeset_framebuffers should not be cleared when a pageflip callback is run
// after a modeset
drm_->RunCallbacks();
EXPECT_FALSE(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers.empty());
for (const auto& plane : planes) {
EXPECT_TRUE(base::Contains(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers,
plane.buffer));
}
}
TEST_F(HardwareDisplayControllerTest, MultiplePlanesModeset) {
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
EXPECT_EQ(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers.size(),
2UL);
for (const auto& plane : modeset_planes) {
EXPECT_TRUE(base::Contains(drm_->plane_manager()
->GetCrtcStateForCrtcId(primary_crtc_)
.modeset_framebuffers,
plane.buffer));
}
}
TEST_F(HardwareDisplayControllerTest, CheckPinningAfterPageFlip) {
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/1);
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
EXPECT_EQ(1, drm_->get_commit_count());
DrmOverlayPlane page_flip_plane(DrmOverlayPlane::TestPlane(CreateBuffer()));
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.push_back(page_flip_plane.Clone());
page_flip_planes.push_back(page_flip_plane.Clone());
page_flip_planes.push_back(page_flip_plane.Clone());
SchedulePageFlip((std::move(page_flip_planes)));
drm_->RunCallbacks();
EXPECT_EQ(1, successful_page_flips_count_);
size_t in_use_planes = 0;
for (auto& plane : drm_->plane_manager()->planes()) {
if (plane->in_use()) {
EXPECT_EQ(controller_->crtc_controllers()[0]->crtc(),
plane->owning_crtc());
in_use_planes++;
}
}
EXPECT_EQ(3u, in_use_planes);
}
TEST_F(HardwareDisplayControllerTest, CheckPinningAfterFailedPageFlip) {
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/1);
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes));
EXPECT_EQ(1, drm_->get_commit_count());
// InitializeDrmDevice created 2 crtcs with 2 planes, plus a movable plane.
// Try to fill 'em up:
auto flip_all_planes = [&]() {
DrmOverlayPlane page_flip_plane(DrmOverlayPlane::TestPlane(CreateBuffer()));
std::vector<DrmOverlayPlane> page_flip_planes;
page_flip_planes.push_back(page_flip_plane.Clone());
page_flip_planes.push_back(page_flip_plane.Clone());
page_flip_planes.push_back(page_flip_plane.Clone());
SchedulePageFlip((std::move(page_flip_planes)));
drm_->RunCallbacks();
};
flip_all_planes();
EXPECT_EQ(1, successful_page_flips_count_);
EXPECT_FALSE(last_presentation_feedback_.failed());
drm_->set_commit_expectation(false);
flip_all_planes();
EXPECT_TRUE(last_presentation_feedback_.failed());
size_t in_use_planes =
base::ranges::count_if(drm_->plane_manager()->planes(),
[](const auto& plane) { return plane->in_use(); });
EXPECT_EQ(0u, in_use_planes) << "Planes, including pinned planes, should not "
"be in use after a failed flip.";
}
TEST_F(HardwareDisplayControllerTest,
PinnedPlanesAreRespectedDuringModesetting) {
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/1);
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
HardwareDisplayPlane* movable_plane = GetMovableOverlays()[0];
movable_plane->set_in_use(true);
movable_plane->set_owning_crtc(drm_->crtc_property(1).id);
ASSERT_FALSE(controller_->HasCrtc(drm_, movable_plane->owning_crtc()));
EXPECT_FALSE(ModesetWithPlanes(modeset_planes))
<< "Modesetting should fail if it requires a movable plane that is "
"already pinned to a different CRTC.";
EXPECT_EQ(0, drm_->get_commit_count());
movable_plane->set_in_use(false);
movable_plane->set_owning_crtc(0);
EXPECT_TRUE(ModesetWithPlanes(modeset_planes))
<< "Modesetting with movable planes should work once those movable "
"planes are available to use.";
EXPECT_EQ(1, drm_->get_commit_count());
}
TEST_F(HardwareDisplayControllerTest, AddingAndRemovingCrtcsWithMovablePlanes) {
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/1);
controller_->AddCrtc(std::make_unique<CrtcController>(
drm_, secondary_crtc_, drm_->connector_property(1).id));
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_FALSE(ModesetWithPlanes(modeset_planes))
<< "Should not modeset when two CRTCs both need the movable overlay "
"plane.";
modeset_planes.pop_back();
EXPECT_TRUE(ModesetWithPlanes(modeset_planes))
<< "Modesetting should work when neither CRTC needs the movable overlay "
"plane";
{
DrmOverlayPlaneList flip_planes;
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
SchedulePageFlip(std::move(flip_planes));
drm_->RunCallbacks();
EXPECT_TRUE(last_presentation_feedback_.failed())
<< "Only one of the CRTCs should be able to use an additional plane.";
}
{
DrmOverlayPlaneList flip_planes;
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
SchedulePageFlip(std::move(flip_planes));
drm_->RunCallbacks();
EXPECT_FALSE(last_presentation_feedback_.failed())
<< "Both CRTCs should be able to flip with their own overlays.";
}
auto removed_crtc = controller_->RemoveCrtc(drm_, secondary_crtc_);
EXPECT_TRUE(removed_crtc);
{
DrmOverlayPlaneList flip_planes;
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
flip_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
SchedulePageFlip(std::move(flip_planes));
drm_->RunCallbacks();
EXPECT_FALSE(last_presentation_feedback_.failed())
<< "With only one CRTC to flip, we should be able to use the movable "
"plane again.";
}
}
TEST_F(HardwareDisplayControllerTest,
ModesettingWithMirroringAndMultipleMovablePlanes) {
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/2);
controller_->AddCrtc(std::make_unique<CrtcController>(
drm_, secondary_crtc_, drm_->connector_property(1).id));
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
EXPECT_TRUE(ModesetWithPlanes(modeset_planes))
<< "Should be able modeset with two CRTCs and two movable planes.";
}
TEST_F(HardwareDisplayControllerTest, ModifiersFilter) {
std::vector<uint64_t> filter_modifiers = {DRM_FORMAT_MOD_LINEAR,
I915_FORMAT_MOD_X_TILED};
std::unique_ptr<MockDrmModifiersFilter> filter =
std::make_unique<MockDrmModifiersFilter>(filter_modifiers);
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/0,
{I915_FORMAT_MOD_X_TILED, I915_FORMAT_MOD_Y_TILED},
std::move(filter));
std::vector<uint64_t> valid_modifiers =
controller_->GetFormatModifiersForTestModeset(DRM_FORMAT_XRGB8888);
EXPECT_EQ(1u, valid_modifiers.size());
EXPECT_EQ(I915_FORMAT_MOD_X_TILED, valid_modifiers[0]);
}
TEST_F(HardwareDisplayControllerTest, DynamicCursorSize) {
// The dynamic cursor size feature is currently only supported on Intel GPUs.
drm_->SetDriverName("i915");
// Kernel driver may or may not provide cursor "SIZE_HINTS" property.
// Without "SIZE_HINTS", there should be only one supported size by default.
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/0,
/*supported_modifiers=*/{}, /*modifiers_filter=*/nullptr,
/*has_size_hints=*/false);
EXPECT_EQ(1u, controller_->NumOfSupportedCursorSizesForTesting());
// With "SIZE_HINTS", there should be 3 supported sizes.
InitializeDrmDevice(/*use_atomic=*/true, /*movable_planes=*/0,
/*supported_modifiers=*/{}, /*modifiers_filter=*/nullptr,
/*has_size_hints=*/true);
EXPECT_EQ(3u, controller_->NumOfSupportedCursorSizesForTesting());
// HardwareDisplayController should select the smallest size in the supported
// list that fits the input cursor bitmap.
controller_->SetCursor(CreateBitmap(25, 25));
EXPECT_EQ(gfx::Size(64, 64), controller_->CurrentCursorSizeForTesting());
controller_->SetCursor(CreateBitmap(25, 65));
EXPECT_EQ(gfx::Size(128, 128), controller_->CurrentCursorSizeForTesting());
// If the input bitmap exceeds the max supported size, make sure the largest
// size supported is used.
controller_->SetCursor(CreateBitmap(300, 300));
EXPECT_EQ(gfx::Size(256, 256), controller_->CurrentCursorSizeForTesting());
}
TEST_F(HardwareDisplayControllerMockedDeviceTest,
TestSeamlessMode_MatchingSizeFailedProbe) {
MockDrmDevice* mock_drm = static_cast<MockDrmDevice*>(drm_.get());
DrmOverlayPlaneList modeset_planes;
modeset_planes.push_back(DrmOverlayPlane::TestPlane(CreateBuffer()));
ASSERT_TRUE(ModesetWithPlanes(modeset_planes));
// Mode with visible size that matches the size of the currently configured
// mode, and a different refresh rate.
drmModeModeInfo matching_size_mode = kDefaultMode;
matching_size_mode.vrefresh *= 2;
// Even if the size of the mode matches, the driver may reject a
// non-modesetting commit. This test failure should propagate up to
// TestSeamlessMode.
EXPECT_CALL(*mock_drm, CommitProperties(_, DRM_MODE_ATOMIC_TEST_ONLY, 1, _))
.Times(1)
.WillRepeatedly(Return(false));
EXPECT_FALSE(
controller_->TestSeamlessMode(primary_crtc_, matching_size_mode));
}
} // namespace ui