// 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 <drm_fourcc.h>
#include <stdint.h>
#include <unistd.h>
#include <xf86drmMode.h>
#include <memory>
#include <utility>
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/posix/eintr_wrapper.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display_features.h"
#include "ui/display/types/display_color_management.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/overlay_transform.h"
#include "ui/ozone/platform/drm/common/drm_util.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/fake_drm_device.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane_atomic.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"
#include "ui/ozone/platform/drm/gpu/mock_drm_device.h"
#include "ui/ozone/platform/drm/gpu/page_flip_request.h"
namespace ui {
namespace {
using testing::_;
using testing::Return;
// TODO(crbug.com/40945652): These tests should not use a single-point
// curve as the non-empty value (it is arguably not a valid input).
const display::GammaCurve kNonemptyGammaCurve({{0, 0, 0}});
const display::GammaCurve kEmptyGammaCurve;
const gfx::Size kDefaultBufferSize(2, 2);
// Create a basic mode for a 6x4 screen.
drmModeModeInfo kDefaultMode = {.hdisplay = 6, .vdisplay = 4};
} // namespace
class HardwareDisplayPlaneManagerTest
: public testing::Test,
public testing::WithParamInterface<bool> {
public:
HardwareDisplayPlaneManagerTest() = default;
HardwareDisplayPlaneManagerTest(const HardwareDisplayPlaneManagerTest&) =
delete;
HardwareDisplayPlaneManagerTest& operator=(
const HardwareDisplayPlaneManagerTest&) = delete;
uint64_t GetObjectPropertyValue(uint32_t object_id,
uint32_t object_type,
const std::string& property_name);
uint64_t GetCrtcPropertyValue(uint32_t crtc,
const std::string& property_name);
uint64_t GetPlanePropertyValue(uint32_t plane,
const std::string& property_name);
void PerformPageFlip(size_t crtc_idx, HardwareDisplayPlaneList* state);
void PerformPageFlip(size_t crtc_idx,
HardwareDisplayPlaneList* state,
DrmOverlayPlaneList& assigns);
void PerformFailingPageFlip(size_t crtc_idx,
HardwareDisplayPlaneList* state,
DrmOverlayPlaneList& assigns);
uint32_t AddConnector(uint32_t possible_crtcs) {
FakeDrmDevice::EncoderProperties& encoder = fake_drm_->AddEncoder();
encoder.possible_crtcs = possible_crtcs;
const uint32_t encoder_id = encoder.id;
FakeDrmDevice::ConnectorProperties& connector = fake_drm_->AddConnector();
connector.connection = true;
connector.encoders = std::vector<uint32_t>{encoder_id};
return connector.id;
}
void SetUp() override;
void TearDown() override;
scoped_refptr<DrmFramebuffer> CreateBuffer(const gfx::Size& size) {
return CreateBufferWithFormat(size, DRM_FORMAT_XRGB8888);
}
scoped_refptr<DrmFramebuffer> CreateBufferWithFormat(const gfx::Size& size,
uint32_t format) {
std::unique_ptr<GbmBuffer> buffer =
fake_drm_->gbm_device()->CreateBuffer(format, size, GBM_BO_USE_SCANOUT);
return DrmFramebuffer::AddFramebuffer(fake_drm_, buffer.get(), size);
}
protected:
HardwareDisplayPlaneList state_;
scoped_refptr<DrmFramebuffer> fake_buffer_;
scoped_refptr<FakeDrmDevice> fake_drm_;
bool use_atomic_ = false;
};
void HardwareDisplayPlaneManagerTest::SetUp() {
use_atomic_ = GetParam();
auto gbm_device = std::make_unique<MockGbmDevice>();
fake_drm_ = new FakeDrmDevice(std::move(gbm_device));
fake_buffer_ = CreateBuffer(kDefaultBufferSize);
}
void HardwareDisplayPlaneManagerTest::TearDown() {
fake_drm_->ResetPlaneManagerForTesting();
}
void HardwareDisplayPlaneManagerTest::PerformPageFlip(
size_t crtc_idx,
HardwareDisplayPlaneList* state) {
DrmOverlayPlaneList assigns;
scoped_refptr<DrmFramebuffer> xrgb_buffer = CreateBuffer(kDefaultBufferSize);
assigns.push_back(DrmOverlayPlane::TestPlane(xrgb_buffer));
PerformPageFlip(crtc_idx, state, assigns);
}
void HardwareDisplayPlaneManagerTest::PerformPageFlip(
size_t crtc_idx,
HardwareDisplayPlaneList* state,
DrmOverlayPlaneList& assigns) {
fake_drm_->plane_manager()->BeginFrame(state);
ASSERT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
state, assigns, fake_drm_->crtc_property(crtc_idx).id));
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
fake_drm_->set_commit_expectation(true);
ASSERT_TRUE(
fake_drm_->plane_manager()->Commit(state, page_flip_request, nullptr));
}
void HardwareDisplayPlaneManagerTest::PerformFailingPageFlip(
size_t crtc_idx,
HardwareDisplayPlaneList* state,
DrmOverlayPlaneList& assigns) {
fake_drm_->plane_manager()->BeginFrame(state);
ASSERT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
state, assigns, fake_drm_->crtc_property(crtc_idx).id));
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
fake_drm_->set_commit_expectation(false);
ASSERT_FALSE(
fake_drm_->plane_manager()->Commit(state, page_flip_request, nullptr));
}
uint64_t HardwareDisplayPlaneManagerTest::GetObjectPropertyValue(
uint32_t object_id,
uint32_t object_type,
const std::string& property_name) {
DrmDevice::Property p{};
ScopedDrmObjectPropertyPtr properties(
fake_drm_->GetObjectProperties(object_id, object_type));
EXPECT_TRUE(GetDrmPropertyForName(fake_drm_.get(), properties.get(),
property_name, &p));
return p.value;
}
uint64_t HardwareDisplayPlaneManagerTest::GetCrtcPropertyValue(
uint32_t crtc,
const std::string& property_name) {
return GetObjectPropertyValue(crtc, DRM_MODE_OBJECT_CRTC, property_name);
}
uint64_t HardwareDisplayPlaneManagerTest::GetPlanePropertyValue(
uint32_t plane,
const std::string& property_name) {
return GetObjectPropertyValue(plane, DRM_MODE_OBJECT_PLANE, property_name);
}
using HardwareDisplayPlaneManagerLegacyTest = HardwareDisplayPlaneManagerTest;
using HardwareDisplayPlaneManagerAtomicTest = HardwareDisplayPlaneManagerTest;
TEST_P(HardwareDisplayPlaneManagerTest, ResettingConnectorCache) {
const int connector_and_crtc_count = 3;
auto& drm_state =
fake_drm_->ResetStateWithDefaultObjects(connector_and_crtc_count,
/*planes_per_crtc=*/1);
drm_state.connector_properties.clear();
// Create 3 connectors, kConnectorIdBase + 0/1/2
for (size_t i = 0; i < connector_and_crtc_count; ++i) {
auto& connector_properties = drm_state.connector_properties.emplace_back();
connector_properties.id = kConnectorIdBase + i;
connector_properties.properties.push_back(
{.id = kCrtcIdPropId, .value = 0});
}
fake_drm_->InitializeState(/*use_atomic=*/true);
HardwareDisplayPlaneList state;
{
CommitRequest commit_request;
fake_drm_->plane_manager()->BeginFrame(&state);
// Check all 3 connectors exist
for (size_t i = 0; i < connector_and_crtc_count; ++i) {
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
CrtcCommitRequest request = CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(i).id, fake_drm_->connector_property(i).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/false);
commit_request.push_back(std::move(request));
}
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
}
// Replace last connector and update state.
drm_state.connector_properties[connector_and_crtc_count - 1].id =
kConnectorIdBase + 3;
fake_drm_->plane_manager()->ResetConnectorsCacheAndGetValidIds(
fake_drm_->GetResources());
{
CommitRequest commit_request;
fake_drm_->plane_manager()->BeginFrame(&state);
{
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, kConnectorIdBase, kDefaultMode,
gfx::Point(), &state, std::move(overlays), /*enable_vrr=*/false));
}
{
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(1).id, kConnectorIdBase + 1, kDefaultMode,
gfx::Point(), &state, std::move(overlays), /*enable_vrr=*/false));
}
{
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(2).id, kConnectorIdBase + 3, kDefaultMode,
gfx::Point(), &state, std::move(overlays), /*enable_vrr=*/false));
}
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
}
}
TEST_P(HardwareDisplayPlaneManagerTest, SequenceIncrementOnModesetOnly) {
fake_drm_->ResetStateWithNoProperties();
// Add some resources so HardwareDisplayPlaneManager can properly initialize
// within |fake_drm_|.
fake_drm_->AddCrtcAndConnector();
fake_drm_->InitializeState(/*use_atomic=*/true);
// Modeset Test
{
int pre_test_sequence_id = fake_drm_->modeset_sequence_id();
ASSERT_TRUE(fake_drm_->plane_manager()->Commit(
CommitRequest(),
DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET));
EXPECT_EQ(pre_test_sequence_id, fake_drm_->modeset_sequence_id());
}
// Successful Modeset
{
int pre_modeset_sequence_id = fake_drm_->modeset_sequence_id();
ASSERT_TRUE(fake_drm_->plane_manager()->Commit(
CommitRequest(), DRM_MODE_ATOMIC_ALLOW_MODESET));
EXPECT_EQ(pre_modeset_sequence_id + 1, fake_drm_->modeset_sequence_id());
}
// Failed Modeset
{
int pre_modeset_sequence_id = fake_drm_->modeset_sequence_id();
fake_drm_->set_set_crtc_expectation(false);
ASSERT_FALSE(fake_drm_->plane_manager()->Commit(
CommitRequest(), DRM_MODE_ATOMIC_ALLOW_MODESET));
fake_drm_->set_set_crtc_expectation(true);
EXPECT_EQ(pre_modeset_sequence_id, fake_drm_->modeset_sequence_id());
}
// Page Flip
{
int pre_flip_sequence_id = fake_drm_->modeset_sequence_id();
ASSERT_TRUE(fake_drm_->plane_manager()->Commit(CommitRequest(),
DRM_MODE_ATOMIC_NONBLOCK));
EXPECT_EQ(pre_flip_sequence_id, fake_drm_->modeset_sequence_id());
}
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, Modeset) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/false);
fake_drm_->set_set_crtc_expectation(false);
HardwareDisplayPlaneList state;
DrmOverlayPlane plane(DrmOverlayPlane::TestPlane(fake_buffer_));
CommitRequest commit_request;
DrmOverlayPlaneList overlays;
overlays.push_back(plane.Clone());
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/false));
EXPECT_FALSE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
EXPECT_EQ(plane.buffer->framebuffer_id(), fake_drm_->current_framebuffer());
EXPECT_EQ(1, fake_drm_->get_set_crtc_call_count());
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, DisableModeset) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/false);
HardwareDisplayPlaneList state;
CommitRequest commit_request;
commit_request.push_back(CrtcCommitRequest::DisableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
&state));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, SinglePlaneAssignment) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_EQ(1u, state_.plane_list.size());
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, AddCursor) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
bool cursor_found = false;
for (const auto& plane : fake_drm_->plane_manager()->planes()) {
if (plane->type() == DRM_PLANE_TYPE_CURSOR) {
cursor_found = true;
break;
}
}
EXPECT_TRUE(cursor_found);
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, BadCrtc) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
EXPECT_FALSE(
fake_drm_->plane_manager()->AssignOverlayPlanes(&state_, assigns, 0));
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, NotEnoughPlanes) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, MultipleCrtcs) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(1).id));
EXPECT_EQ(2u, state_.plane_list.size());
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, MultiplePlanesAndCrtcs) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(1).id));
EXPECT_EQ(0u, state_.plane_list.size());
}
TEST_P(HardwareDisplayPlaneManagerLegacyTest, CheckFramebufferFormatMatch) {
DrmOverlayPlaneList assigns;
scoped_refptr<DrmFramebuffer> buffer =
CreateBufferWithFormat(kDefaultBufferSize, DRM_FORMAT_NV12);
assigns.push_back(DrmOverlayPlane::TestPlane(buffer));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->BeginFrame(&state_);
// This should return false as plane manager creates planes which support
// DRM_FORMAT_XRGB8888 while buffer returns kDummyFormat as its pixelFormat.
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
assigns.clear();
scoped_refptr<DrmFramebuffer> xrgb_buffer = CreateBuffer(kDefaultBufferSize);
assigns.push_back(DrmOverlayPlane::TestPlane(xrgb_buffer));
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, Modeset) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
HardwareDisplayPlaneList state;
CommitRequest commit_request;
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/false));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
EXPECT_EQ(1, fake_drm_->get_commit_count());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, DisableModeset) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
HardwareDisplayPlaneList state;
CommitRequest commit_request;
commit_request.push_back(CrtcCommitRequest::DisableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
&state));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
EXPECT_EQ(1, fake_drm_->get_commit_count());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckPropsAfterModeset) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
HardwareDisplayPlaneList state;
CommitRequest commit_request;
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/false));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
// Test props values after modesetting.
DrmDevice::Property connector_prop_crtc_id;
ScopedDrmObjectPropertyPtr connector_props = fake_drm_->GetObjectProperties(
kConnectorIdBase, DRM_MODE_OBJECT_CONNECTOR);
GetDrmPropertyForName(fake_drm_.get(), connector_props.get(), "CRTC_ID",
&connector_prop_crtc_id);
EXPECT_EQ(kCrtcIdPropId, connector_prop_crtc_id.id);
DrmDevice::Property crtc_prop_for_name;
ScopedDrmObjectPropertyPtr crtc_props =
fake_drm_->GetObjectProperties(kCrtcIdBase, DRM_MODE_OBJECT_CRTC);
GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "ACTIVE",
&crtc_prop_for_name);
EXPECT_EQ(kActivePropId, crtc_prop_for_name.id);
EXPECT_EQ(1U, crtc_prop_for_name.value);
GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "MODE_ID",
&crtc_prop_for_name);
EXPECT_EQ(kModePropId, crtc_prop_for_name.id);
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckPropsAfterDisable) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
HardwareDisplayPlaneList state;
{
CommitRequest commit_request;
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/false));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
}
// Test props values after disabling.
{
CommitRequest commit_request;
commit_request.push_back(CrtcCommitRequest::DisableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
&state));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
}
DrmDevice::Property crtc_prop_for_name;
ScopedDrmObjectPropertyPtr crtc_props =
fake_drm_->GetObjectProperties(kCrtcIdBase, DRM_MODE_OBJECT_CRTC);
GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "ACTIVE",
&crtc_prop_for_name);
EXPECT_EQ(kActivePropId, crtc_prop_for_name.id);
EXPECT_EQ(0U, crtc_prop_for_name.value);
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckVrrAfterModeset) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/2);
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kVrrEnabledPropId, .value = 0});
fake_drm_->InitializeState(/*use_atomic=*/true);
HardwareDisplayPlaneList state;
// Check initial VRR_ENABLED state.
{
DrmDevice::Property crtc_prop_vrr_enabled;
ScopedDrmObjectPropertyPtr crtc_props =
fake_drm_->GetObjectProperties(kCrtcIdBase, DRM_MODE_OBJECT_CRTC);
GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "VRR_ENABLED",
&crtc_prop_vrr_enabled);
EXPECT_EQ(kVrrEnabledPropId, crtc_prop_vrr_enabled.id);
EXPECT_EQ(0U, crtc_prop_vrr_enabled.value);
}
// Check VRR_ENABLED state is set by modeset.
{
CommitRequest commit_request;
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/true));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
DrmDevice::Property crtc_prop_vrr_enabled;
ScopedDrmObjectPropertyPtr crtc_props =
fake_drm_->GetObjectProperties(kCrtcIdBase, DRM_MODE_OBJECT_CRTC);
GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "VRR_ENABLED",
&crtc_prop_vrr_enabled);
EXPECT_EQ(kVrrEnabledPropId, crtc_prop_vrr_enabled.id);
EXPECT_EQ(1U, crtc_prop_vrr_enabled.value);
}
// Check VRR_ENABLED is reset by modeset.
{
CommitRequest commit_request;
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
commit_request.push_back(CrtcCommitRequest::EnableCrtcRequest(
fake_drm_->crtc_property(0).id, fake_drm_->connector_property(0).id,
kDefaultMode, gfx::Point(), &state, std::move(overlays),
/*enable_vrr=*/false));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET));
DrmDevice::Property crtc_prop_vrr_enabled;
ScopedDrmObjectPropertyPtr crtc_props =
fake_drm_->GetObjectProperties(kCrtcIdBase, DRM_MODE_OBJECT_CRTC);
GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "VRR_ENABLED",
&crtc_prop_vrr_enabled);
EXPECT_EQ(kVrrEnabledPropId, crtc_prop_vrr_enabled.id);
EXPECT_EQ(0U, crtc_prop_vrr_enabled.value);
}
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, MultiplePlaneAssignment) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_EQ(2u, state_.plane_list.size());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, MultiplePlanesAndCrtcs) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(1).id));
EXPECT_EQ(4u, state_.plane_list.size());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, SharedPlanes) {
DrmOverlayPlaneList assigns;
scoped_refptr<DrmFramebuffer> buffer = CreateBuffer(gfx::Size(1, 1));
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns.push_back(DrmOverlayPlane::TestPlane(buffer));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
auto in_formats_blob =
fake_drm_->CreateInFormatsBlob({DRM_FORMAT_XRGB8888}, {});
auto plane_prop = fake_drm_->AddPlane(
{fake_drm_->crtc_property(0).id, fake_drm_->crtc_property(1).id},
DRM_PLANE_TYPE_OVERLAY);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(1).id));
EXPECT_EQ(2u, state_.plane_list.size());
// The shared plane is now unavailable for use by the other CRTC.
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, UnusedPlanesAreReleased) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
DrmOverlayPlaneList assigns;
scoped_refptr<DrmFramebuffer> primary_buffer =
CreateBuffer(kDefaultBufferSize);
scoped_refptr<DrmFramebuffer> overlay_buffer = CreateBuffer(gfx::Size(1, 1));
assigns.push_back(DrmOverlayPlane::TestPlane(primary_buffer));
assigns.push_back(DrmOverlayPlane::TestPlane(overlay_buffer));
HardwareDisplayPlaneList hdpl;
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
fake_drm_->plane_manager()->BeginFrame(&hdpl);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(
fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request, nullptr));
assigns.clear();
assigns.push_back(DrmOverlayPlane::TestPlane(primary_buffer));
fake_drm_->plane_manager()->BeginFrame(&hdpl);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
EXPECT_TRUE(
fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request, nullptr));
EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
EXPECT_EQ(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, AssignPlanesRestoresInUse) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
DrmOverlayPlaneList assigns;
scoped_refptr<DrmFramebuffer> primary_buffer =
CreateBuffer(kDefaultBufferSize);
scoped_refptr<DrmFramebuffer> overlay_buffer = CreateBuffer(gfx::Size(1, 1));
assigns.push_back(DrmOverlayPlane::TestPlane(primary_buffer));
assigns.push_back(DrmOverlayPlane::TestPlane(overlay_buffer));
HardwareDisplayPlaneList hdpl;
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
fake_drm_->plane_manager()->BeginFrame(&hdpl);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(
fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request, nullptr));
EXPECT_TRUE(fake_drm_->plane_manager()->planes().front()->in_use());
assigns.push_back(DrmOverlayPlane::TestPlane(overlay_buffer));
fake_drm_->plane_manager()->BeginFrame(&hdpl);
// Assign overlay planes will fail since there aren't enough planes.
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
// The primary plane should still be in use since we failed to assign
// planes and did not commit a new configuration.
EXPECT_TRUE(fake_drm_->plane_manager()->planes().front()->in_use());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, PageflipTestRestoresInUse) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
DrmOverlayPlaneList assigns;
scoped_refptr<DrmFramebuffer> primary_buffer =
CreateBuffer(kDefaultBufferSize);
scoped_refptr<DrmFramebuffer> overlay_buffer = CreateBuffer(gfx::Size(1, 1));
assigns.push_back(DrmOverlayPlane::TestPlane(primary_buffer));
assigns.push_back(DrmOverlayPlane::TestPlane(overlay_buffer));
HardwareDisplayPlaneList hdpl;
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
fake_drm_->plane_manager()->BeginFrame(&hdpl);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(
fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request, nullptr));
assigns.clear();
fake_drm_->plane_manager()->BeginFrame(&hdpl);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&hdpl, nullptr, nullptr));
// The primary plane should still be in use since the commit was
// a pageflip test and did not change any KMS state.
EXPECT_TRUE(fake_drm_->plane_manager()->planes().front()->in_use());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest,
PageFlipOnlySwapsPlaneListsOnSuccess) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
DrmOverlayPlaneList single_assign;
single_assign.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
DrmOverlayPlaneList overlay_assigns;
overlay_assigns.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
overlay_assigns.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
HardwareDisplayPlaneList hdpl;
auto flip_with_assigns = [&](bool commit_status,
const auto& assigns) -> bool {
auto page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
fake_drm_->plane_manager()->BeginFrame(&hdpl);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&hdpl, assigns, fake_drm_->crtc_property(0).id));
fake_drm_->set_commit_expectation(commit_status);
return fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request,
nullptr);
};
// Flipping with an overlay should mark both as old planes:
EXPECT_TRUE(flip_with_assigns(/*commit_status=*/true, overlay_assigns));
EXPECT_EQ(2u, hdpl.old_plane_list.size());
EXPECT_EQ(0u, hdpl.plane_list.size());
// We shouldn't see a change to the old plane list on a force-failed commit,
// even though we only are trying to flip a single plane.
EXPECT_FALSE(flip_with_assigns(/*commit_status=*/false, single_assign));
EXPECT_EQ(2u, hdpl.old_plane_list.size());
EXPECT_EQ(0u, hdpl.plane_list.size());
// Once we do successfully flip a single plane, the old plane list should
// reflect it.
EXPECT_TRUE(flip_with_assigns(/*commit_status=*/true, single_assign));
EXPECT_EQ(1u, hdpl.old_plane_list.size());
EXPECT_EQ(0u, hdpl.plane_list.size());
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, MultipleFrames) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_EQ(1u, state_.plane_list.size());
// Pretend we committed the frame.
state_.plane_list.swap(state_.old_plane_list);
fake_drm_->plane_manager()->BeginFrame(&state_);
HardwareDisplayPlane* old_plane = state_.old_plane_list[0];
// The same plane should be used.
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_EQ(1u, state_.plane_list.size());
EXPECT_EQ(state_.plane_list[0], old_plane);
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, MultipleFramesDifferentPlanes) {
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/2);
fake_drm_->InitializeState(use_atomic_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_EQ(1u, state_.plane_list.size());
// The other plane should be used.
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
EXPECT_EQ(2u, state_.plane_list.size());
EXPECT_NE(state_.plane_list[0], state_.plane_list[1]);
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, PlanePinningAndUnpinning) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count*/ 2,
/*planes_per_crtc=*/1,
/*movable_planes=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
DrmOverlayPlaneList assigns_with_overlay;
assigns_with_overlay.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns_with_overlay.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(gfx::Size(1, 1))));
auto get_overlay_owner = [&]() {
for (const auto& plane : fake_drm_->plane_manager()->planes()) {
if (plane->type() == DRM_PLANE_TYPE_OVERLAY) {
return plane->owning_crtc();
}
}
NOTREACHED_IN_MIGRATION();
return UINT32_MAX;
};
uint32_t crtc_0 = fake_drm_->crtc_property(0).id;
uint32_t crtc_1 = fake_drm_->crtc_property(1).id;
HardwareDisplayPlaneList list_0;
HardwareDisplayPlaneList list_1;
EXPECT_EQ(0u, get_overlay_owner());
PerformPageFlip(0, &list_0, assigns_with_overlay);
EXPECT_EQ(crtc_0, get_overlay_owner())
<< "Assigning a plane should pin it to the CRTC.";
fake_drm_->RunCallbacks();
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&list_1, assigns_with_overlay, crtc_1))
<< "Pinned planes should be unassignable while they're pinned.";
PerformPageFlip(0, &list_0, assigns);
EXPECT_EQ(0u, get_overlay_owner())
<< "Assigning without overlays should unpin the overlay.";
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&list_1, assigns_with_overlay, crtc_1))
<< "Previously pinned planes should be available for use after "
"unpinning.";
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, PlanesUnpinnedOnFailedFlip) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count*/ 2,
/*planes_per_crtc=*/1,
/*movable_planes=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
DrmOverlayPlaneList assigns_with_overlay;
assigns_with_overlay.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns_with_overlay.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(gfx::Size(1, 1))));
auto get_overlay_owner = [&]() {
for (const auto& plane : fake_drm_->plane_manager()->planes()) {
if (plane->type() == DRM_PLANE_TYPE_OVERLAY) {
return plane->owning_crtc();
}
}
NOTREACHED_IN_MIGRATION();
return UINT32_MAX;
};
uint32_t crtc_0 = fake_drm_->crtc_property(0).id;
HardwareDisplayPlaneList hdpl;
EXPECT_EQ(0u, get_overlay_owner());
PerformPageFlip(0, &hdpl, assigns_with_overlay);
EXPECT_EQ(crtc_0, get_overlay_owner())
<< "Assigning a plane should pin it to the CRTC.";
fake_drm_->RunCallbacks();
PerformFailingPageFlip(0, &hdpl, assigns_with_overlay);
EXPECT_EQ(0u, get_overlay_owner())
<< "A failed flip should result in the overlay being freed again.";
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, PlanesUnpinnedOnDisable) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count*/ 2,
/*planes_per_crtc=*/1,
/*movable_planes=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
DrmOverlayPlaneList assigns_with_overlay;
assigns_with_overlay.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
assigns_with_overlay.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(gfx::Size(1, 1))));
auto get_overlay_owner = [&]() {
for (const auto& plane : fake_drm_->plane_manager()->planes()) {
if (plane->type() == DRM_PLANE_TYPE_OVERLAY) {
return plane->owning_crtc();
}
}
NOTREACHED_IN_MIGRATION();
return UINT32_MAX;
};
uint32_t crtc_0 = fake_drm_->crtc_property(0).id;
HardwareDisplayPlaneList hdpl;
EXPECT_EQ(0u, get_overlay_owner());
PerformPageFlip(0, &hdpl, assigns_with_overlay);
EXPECT_EQ(crtc_0, get_overlay_owner())
<< "Assigning a plane should pin it to the CRTC.";
fake_drm_->RunCallbacks();
fake_drm_->plane_manager()->DisableOverlayPlanes(&hdpl);
EXPECT_EQ(0u, get_overlay_owner())
<< "After disabling, the pinned overlay owner should be reset.";
}
TEST_P(HardwareDisplayPlaneManagerTest, ColorManagement_Temperature) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kGammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kGammaLutPropId, .value = 0});
// Color temperature adjustment will set all properties.
fake_drm_->InitializeState(use_atomic_);
display::ColorTemperatureAdjustment cta;
cta.srgb_matrix.vals[0][0] = 1.0;
cta.srgb_matrix.vals[1][1] = 0.7;
cta.srgb_matrix.vals[2][2] = 0.3;
fake_drm_->plane_manager()->SetColorTemperatureAdjustment(
fake_drm_->crtc_property(0).id, cta);
if (use_atomic_) {
// The color temperature adjustment will get its own commit.
EXPECT_EQ(1, fake_drm_->get_commit_count());
EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
HardwareDisplayPlaneList state;
PerformPageFlip(/*crtc_idx=*/0, &state);
EXPECT_EQ(2, fake_drm_->get_commit_count());
EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
EXPECT_EQ(
0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "GAMMA_LUT"));
EXPECT_EQ(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id,
"DEGAMMA_LUT"));
} else {
EXPECT_EQ(3, fake_drm_->get_set_object_property_count());
}
}
TEST_P(HardwareDisplayPlaneManagerTest, ColorManagement_Profile) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kGammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kGammaLutPropId, .value = 0});
// Color profile change will set all properties.
fake_drm_->InitializeState(use_atomic_);
display::ColorCalibration calibration;
calibration.srgb_to_linear = display::GammaCurve::MakeGamma(2.2f);
calibration.linear_to_device = display::GammaCurve::MakeGamma(1.f / 2.2);
fake_drm_->plane_manager()->SetColorCalibration(
fake_drm_->crtc_property(0).id, calibration);
if (use_atomic_) {
HardwareDisplayPlaneList state;
PerformPageFlip(/*crtc_idx=*/0, &state);
EXPECT_EQ(2, fake_drm_->get_commit_count());
EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
EXPECT_NE(
0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "GAMMA_LUT"));
EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id,
"DEGAMMA_LUT"));
} else {
EXPECT_EQ(3, fake_drm_->get_set_object_property_count());
}
}
// The effect of color temperature adjustment (night light) on the CTM.
TEST_P(HardwareDisplayPlaneManagerTest,
CtmColorManagement_ColorTemperatureAdjustment) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
{});
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
uint32_t crtc_id = fake_drm_->crtc_property(0).id;
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
// Color profile change will set all properties.
fake_drm_->InitializeState(use_atomic_);
// Apply color temperature adjustment. The CTM should be updated
// immediately.
display::ColorTemperatureAdjustment cta;
cta.srgb_matrix.vals[0][0] = 0.1f;
cta.srgb_matrix.vals[1][1] = 0.2f;
cta.srgb_matrix.vals[2][2] = 0.3f;
fake_drm_->plane_manager()->SetColorTemperatureAdjustment(
fake_drm_->crtc_property(0).id, cta);
{
constexpr float kEpsilon = 0.001f;
float rgb[3] = {0.4f, 0.5f, 0.6f};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], 0.1f * 0.4f, kEpsilon);
EXPECT_NEAR(rgb[1], 0.2f * 0.5f, kEpsilon);
EXPECT_NEAR(rgb[2], 0.3f * 0.6f, kEpsilon);
}
}
// The effect of gamma adjustment on the CTM.
TEST_P(HardwareDisplayPlaneManagerTest, CtmColorManagement_GammaAdjustment) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
{});
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
uint32_t crtc_id = fake_drm_->crtc_property(0).id;
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
// Color profile change will set all properties.
fake_drm_->InitializeState(use_atomic_);
// Apply gamma adjustment.
display::GammaAdjustment gamma_adjustment;
gamma_adjustment.curve = display::GammaCurve::MakeScale(0.9, 0.8, 0.7);
fake_drm_->plane_manager()->SetGammaAdjustment(crtc_id, gamma_adjustment);
{
constexpr float kEpsilon = 0.001f;
float rgb[3] = {0.6f, 0.5f, 0.4f};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], 0.9f * 0.6f, kEpsilon);
EXPECT_NEAR(rgb[1], 0.8f * 0.5f, kEpsilon);
EXPECT_NEAR(rgb[2], 0.7f * 0.4f, kEpsilon);
}
}
// The effect of color conversion (from input plane space to output space) on
// the CTM.
TEST_P(HardwareDisplayPlaneManagerAtomicTest,
CtmColorManagement_ColorConversion) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
{});
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
uint32_t crtc_id = fake_drm_->crtc_property(0).id;
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
// Color profile change will set all properties.
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->SetOutputColorSpace(crtc_id,
SkNamedPrimariesExt::kP3);
// We should not have committed the CTM yet.
EXPECT_EQ(0, fake_drm_->get_commit_count());
EXPECT_EQ(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
// Commit a plane that is sRGB. Colors should be converted.
{
HardwareDisplayPlaneList state;
auto buffer = CreateBuffer(kDefaultBufferSize);
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(buffer));
planes[0].color_space = gfx::ColorSpace::CreateSRGB();
PerformPageFlip(/*crtc_idx=*/0, &state, planes);
}
// This is the conversion of color(--display-p3-linear 0.25 0.5 0.75) to
// srgb-linear using https://colorjs.io/apps/convert/.
{
constexpr float kEpsilon = 0.001f;
float rgb[3] = {0.1937649f, 0.51051424f, 0.77947779f};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], 0.25f, kEpsilon);
EXPECT_NEAR(rgb[1], 0.5f, kEpsilon);
EXPECT_NEAR(rgb[2], 0.75f, kEpsilon);
}
}
// The combined effects of color conversion, color temperature adjustment, and
// gamma adjustment, on the CTM.
TEST_P(HardwareDisplayPlaneManagerAtomicTest, CtmColorManagement_Combined) {
constexpr float kEpsilon = 0.001f;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({display::features::kCtmColorManagement},
{});
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
uint32_t crtc_id = fake_drm_->crtc_property(0).id;
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(crtc_id, {.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutSizePropId, .value = 33});
fake_drm_->AddProperty(crtc_id, {.id = kGammaLutPropId, .value = 0});
// Color profile change will set all properties.
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->SetOutputColorSpace(crtc_id,
SkNamedPrimariesExt::kP3);
// We should not have committed the CTM yet.
EXPECT_EQ(0, fake_drm_->get_commit_count());
EXPECT_EQ(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
HardwareDisplayPlaneList state;
auto buffer = CreateBuffer(kDefaultBufferSize);
// Constants for a test color value in P3 and sRGB. This is the conversion of
// color(--display-p3-linear 0.25 0.5 0.75) to srgb-linear using
// https://colorjs.io/apps/convert/.
const float kColorP3[3] = {0.25, 0.5, 0.75};
const float kColorSRGB[3] = {0.1937649f, 0.51051424f, 0.77947779f};
// Commit a plane that is P3. Color conversion should be a no-op.
{
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(buffer));
planes[0].color_space = gfx::ColorSpace::CreateDisplayP3D65();
PerformPageFlip(/*crtc_idx=*/0, &state, planes);
EXPECT_EQ(1, fake_drm_->get_commit_count());
EXPECT_NE(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
float rgb[3] = {kColorP3[0], kColorP3[1], kColorP3[2]};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
}
// Commit a plane that is sRGB. Colors should be converted.
{
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(buffer));
planes[0].color_space = gfx::ColorSpace::CreateSRGB();
PerformPageFlip(/*crtc_idx=*/0, &state, planes);
EXPECT_NE(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
// This is the conversion of color(--display-p3-linear 0.25 0.5 0.75) to
// srgb-linear using https://colorjs.io/apps/convert/.
float rgb[3] = {kColorSRGB[0], kColorSRGB[1], kColorSRGB[2]};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
}
// Apply color temperature adjustment. The CTM should be updated
// immediately.
{
display::ColorTemperatureAdjustment cta;
cta.srgb_matrix.vals[0][0] = 0.5;
cta.srgb_matrix.vals[1][1] = 1.0;
cta.srgb_matrix.vals[2][2] = 1.0;
fake_drm_->plane_manager()->SetColorTemperatureAdjustment(
fake_drm_->crtc_property(0).id, cta);
float rgb[3] = {2.f * kColorSRGB[0], kColorSRGB[1], kColorSRGB[2]};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
}
// Change the output color space. Nothing should change yet.
{
fake_drm_->plane_manager()->SetOutputColorSpace(crtc_id,
SkNamedPrimariesExt::kSRGB);
float rgb[3] = {2.f * kColorSRGB[0], kColorSRGB[1], kColorSRGB[2]};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], kColorP3[0], kEpsilon);
EXPECT_NEAR(rgb[1], kColorP3[1], kEpsilon);
EXPECT_NEAR(rgb[2], kColorP3[2], kEpsilon);
}
// Commit the plane with the same color space as the last flip. The CTM
// should change now.
{
DrmOverlayPlaneList planes;
planes.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
planes[0].color_space = gfx::ColorSpace::CreateSRGB();
planes[0].z_order = 1;
PerformPageFlip(/*crtc_idx=*/0, &state, planes);
EXPECT_NE(0u, GetCrtcPropertyValue(crtc_id, "CTM"));
float rgb[3] = {1.0f, 0.75f, 0.25f};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], 0.5f, kEpsilon);
EXPECT_NEAR(rgb[1], 0.75f, kEpsilon);
EXPECT_NEAR(rgb[2], 0.25f, kEpsilon);
}
// Apply gamma adjustment.
{
display::GammaAdjustment gamma_adjustment;
gamma_adjustment.curve =
display::GammaCurve::MakeScale(0.5, 0.25 / 0.75, 0.1 / 0.25);
fake_drm_->plane_manager()->SetGammaAdjustment(crtc_id, gamma_adjustment);
float rgb[3] = {1.0f, 0.75f, 0.25f};
ApplyCrtcColorSpaceConversion(fake_drm_.get(), crtc_id, rgb);
EXPECT_NEAR(rgb[0], 0.25f, kEpsilon);
EXPECT_NEAR(rgb[1], 0.25f, kEpsilon);
EXPECT_NEAR(rgb[2], 0.1f, kEpsilon);
}
}
TEST_P(HardwareDisplayPlaneManagerTest, ColorManagement_GammaAdjustment) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
// This test has full CTM, DEGAMMA, and GAMMA.
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kGammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kGammaLutPropId, .value = 0});
// Gamma adjustment will set all properties.
fake_drm_->InitializeState(use_atomic_);
display::GammaAdjustment gamma_adjustment;
gamma_adjustment.curve = display::GammaCurve::MakeGamma(1.1);
fake_drm_->plane_manager()->SetGammaAdjustment(fake_drm_->crtc_property(0).id,
gamma_adjustment);
if (use_atomic_) {
// The gamma adjustment will get its own commit.
EXPECT_EQ(1, fake_drm_->get_commit_count());
EXPECT_NE(
0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "GAMMA_LUT"));
HardwareDisplayPlaneList state;
PerformPageFlip(/*crtc_idx=*/0, &state);
EXPECT_EQ(2, fake_drm_->get_commit_count());
EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
EXPECT_NE(
0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "GAMMA_LUT"));
EXPECT_EQ(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id,
"DEGAMMA_LUT"));
} else {
EXPECT_EQ(3, fake_drm_->get_set_object_property_count());
}
}
TEST_P(HardwareDisplayPlaneManagerTest, ColorManagement_LegacyGamma) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
// This test is missing GAMMA.
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kCtmPropId, .value = 0});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutSizePropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kDegammaLutPropId, .value = 0});
// Gamma adjustment should call the legacy method.
fake_drm_->InitializeState(use_atomic_);
display::GammaAdjustment gamma_adjustment;
gamma_adjustment.curve = display::GammaCurve::MakeGamma(1.1);
fake_drm_->plane_manager()->SetGammaAdjustment(fake_drm_->crtc_property(0).id,
gamma_adjustment);
if (use_atomic_) {
HardwareDisplayPlaneList state;
PerformPageFlip(/*crtc_idx=*/0, &state);
EXPECT_EQ(2, fake_drm_->get_commit_count());
EXPECT_NE(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id, "CTM"));
// If we have DEGAMMA but no GAMMA, we still ignore the DEGAMMA.
EXPECT_EQ(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id,
"DEGAMMA_LUT"));
} else {
EXPECT_EQ(1, fake_drm_->get_set_object_property_count());
}
EXPECT_EQ(1, fake_drm_->get_set_gamma_ramp_count());
}
TEST_P(HardwareDisplayPlaneManagerTest, SetBackgroundColor_Success) {
{
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kBackgroundColorPropId, .value = 0});
}
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->SetBackgroundColor(fake_drm_->crtc_property(0).id,
0);
if (use_atomic_) {
HardwareDisplayPlaneList state;
PerformPageFlip(/*crtc_idx=*/0, &state);
EXPECT_EQ(1, fake_drm_->get_commit_count());
EXPECT_EQ(0u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id,
"BACKGROUND_COLOR"));
} else {
EXPECT_EQ(0, fake_drm_->get_set_object_property_count());
}
{
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kBackgroundColorPropId, .value = 1});
}
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->SetBackgroundColor(fake_drm_->crtc_property(0).id,
1);
if (use_atomic_) {
HardwareDisplayPlaneList state;
PerformPageFlip(/*crtc_idx=*/0, &state);
EXPECT_EQ(2, fake_drm_->get_commit_count());
EXPECT_EQ(1u, GetCrtcPropertyValue(fake_drm_->crtc_property(0).id,
"BACKGROUND_COLOR"));
} else {
EXPECT_EQ(0, fake_drm_->get_set_object_property_count());
}
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest,
CommitReturnsNullOutFenceIfOutFencePtrNotSupported) {
scoped_refptr<DrmFramebuffer> fake_buffer2 = CreateBuffer(kDefaultBufferSize);
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/2, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
DrmOverlayPlaneList assigns1;
assigns1.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
DrmOverlayPlaneList assigns2;
assigns2.push_back(DrmOverlayPlane::TestPlane(fake_buffer2));
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns1, fake_drm_->crtc_property(0).id));
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns2, fake_drm_->crtc_property(1).id));
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
gfx::GpuFenceHandle release_fence;
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
&release_fence));
EXPECT_TRUE(release_fence.is_null());
}
TEST_P(HardwareDisplayPlaneManagerTest,
InitializationFailsIfSupportForOutFencePropertiesIsPartial) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/3, /*planes_per_crtc=*/1);
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kOutFencePtrPropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(2).id,
{.id = kOutFencePtrPropId, .value = 2});
EXPECT_FALSE(fake_drm_->InitializeStateWithResult(use_atomic_));
}
TEST_P(HardwareDisplayPlaneManagerTest,
InitializationSucceedsIfSupportForOutFencePropertiesIsComplete) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/3, /*planes_per_crtc=*/1);
fake_drm_->AddProperty(fake_drm_->crtc_property(0).id,
{.id = kOutFencePtrPropId, .value = 1});
fake_drm_->AddProperty(fake_drm_->crtc_property(1).id,
{.id = kOutFencePtrPropId, .value = 2});
fake_drm_->AddProperty(fake_drm_->crtc_property(2).id,
{.id = kOutFencePtrPropId, .value = 3});
EXPECT_TRUE(fake_drm_->InitializeStateWithResult(use_atomic_));
}
// Verifies that formats with 2 bits of alpha decay to opaques for AddFB2().
TEST_P(HardwareDisplayPlaneManagerTest, ForceOpaqueFormatsForAddFramebuffer) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/3, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
struct {
uint32_t input_fourcc; // FourCC presented to AddFramebuffer.
uint32_t used_fourcc; // FourCC expected to be used in AddFramebuffer.
} kFourCCFormats[] = {
{DRM_FORMAT_ABGR2101010, DRM_FORMAT_XBGR2101010},
{DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB2101010},
};
for (const auto& format_pair : kFourCCFormats) {
scoped_refptr<DrmFramebuffer> drm_fb =
CreateBufferWithFormat(kDefaultBufferSize, format_pair.input_fourcc);
EXPECT_EQ(drm_fb->framebuffer_pixel_format(), format_pair.used_fourcc);
EXPECT_EQ(drm_fb->opaque_framebuffer_pixel_format(),
format_pair.used_fourcc);
}
// If DRM supports high-bitdepth formats with Alpha, there's no need for
// opaque decaying. Note that we have to support all |kFourCCFormats|.
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/3, /*planes_per_crtc=*/1, /*movable_planes=*/0,
{DRM_FORMAT_ARGB2101010, DRM_FORMAT_ABGR2101010}, {});
fake_drm_->InitializeState(use_atomic_);
for (const auto& format_pair : kFourCCFormats) {
scoped_refptr<DrmFramebuffer> drm_fb =
CreateBufferWithFormat(kDefaultBufferSize, format_pair.input_fourcc);
EXPECT_EQ(drm_fb->framebuffer_pixel_format(), format_pair.input_fourcc);
EXPECT_EQ(drm_fb->opaque_framebuffer_pixel_format(),
format_pair.used_fourcc);
}
}
TEST_P(HardwareDisplayPlaneManagerTest, GetHardwareCapabilities) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/4, /*planes_per_crtc=*/7);
fake_drm_->InitializeState(use_atomic_);
for (int i = 0; i < 4; ++i) {
auto hc =
fake_drm_->plane_manager()->GetHardwareCapabilities(kCrtcIdBase + i);
EXPECT_TRUE(hc.is_valid);
// Legacy doesn't support OVERLAY planes.
int expected_planes = use_atomic_ ? 7 : 1;
EXPECT_EQ(hc.num_overlay_capable_planes, expected_planes);
}
{
auto& drm_state = fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/4, /*planes_per_crtc=*/7);
// Change the last (CURSOR) plane into a PRIMARY plane that is available to
// only the first two CRTCs.
auto& last_props =
drm_state.plane_properties[drm_state.plane_properties.size() - 1];
last_props.crtc_mask = (1 << 0) | (1 << 1);
// Find the type property and change it to PRIMARY.
for (auto& property : last_props.properties) {
if (property.id == kTypePropId) {
property.value = DRM_PLANE_TYPE_PRIMARY;
break;
}
}
fake_drm_->InitializeState(use_atomic_);
}
for (int i = 0; i < 4; ++i) {
auto hc =
fake_drm_->plane_manager()->GetHardwareCapabilities(kCrtcIdBase + i);
EXPECT_TRUE(hc.is_valid);
// Legacy doesn't support OVERLAY planes.
int expected_planes = use_atomic_ ? 7 : 1;
// First two CRTCs have the newly added plane available.
if (i == 0 || i == 1) {
expected_planes++;
}
EXPECT_EQ(hc.num_overlay_capable_planes, expected_planes);
}
{
fake_drm_->SetDriverName(std::nullopt);
auto hc = fake_drm_->plane_manager()->GetHardwareCapabilities(kCrtcIdBase);
EXPECT_FALSE(hc.is_valid);
fake_drm_->SetDriverName("amdgpu");
hc = fake_drm_->plane_manager()->GetHardwareCapabilities(kCrtcIdBase);
EXPECT_TRUE(hc.is_valid);
EXPECT_FALSE(hc.has_independent_cursor_plane);
fake_drm_->SetDriverName("generic");
hc = fake_drm_->plane_manager()->GetHardwareCapabilities(kCrtcIdBase);
EXPECT_TRUE(hc.is_valid);
EXPECT_TRUE(hc.has_independent_cursor_plane);
}
}
INSTANTIATE_TEST_SUITE_P(All,
HardwareDisplayPlaneManagerTest,
testing::Values(false, true));
// TODO(dnicoara): Migrate as many tests as possible to the general list above.
INSTANTIATE_TEST_SUITE_P(All,
HardwareDisplayPlaneManagerLegacyTest,
testing::Values(false));
INSTANTIATE_TEST_SUITE_P(All,
HardwareDisplayPlaneManagerAtomicTest,
testing::Values(true));
class FakeFenceFD {
public:
FakeFenceFD();
std::unique_ptr<gfx::GpuFence> GetGpuFence() const;
void Signal() const;
private:
base::ScopedFD read_fd;
base::ScopedFD write_fd;
};
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 HardwareDisplayPlaneManagerPlanesReadyTest : public testing::Test {
public:
HardwareDisplayPlaneManagerPlanesReadyTest(
const HardwareDisplayPlaneManagerPlanesReadyTest&) = delete;
HardwareDisplayPlaneManagerPlanesReadyTest& operator=(
const HardwareDisplayPlaneManagerPlanesReadyTest&) = delete;
protected:
HardwareDisplayPlaneManagerPlanesReadyTest() = default;
void SetUp() override {
auto gbm_device = std::make_unique<MockGbmDevice>();
fake_drm_ = new FakeDrmDevice(std::move(gbm_device));
drm_framebuffer_ = CreateBuffer(kDefaultBufferSize);
planes_without_fences_ = CreatePlanesWithoutFences();
planes_with_fences_ = CreatePlanesWithFences();
}
void UseLegacyManager();
void UseAtomicManager();
void RequestPlanesReady(DrmOverlayPlaneList planes);
scoped_refptr<DrmFramebuffer> CreateBuffer(const gfx::Size& size) {
std::unique_ptr<GbmBuffer> buffer = fake_drm_->gbm_device()->CreateBuffer(
DRM_FORMAT_XRGB8888, size, GBM_BO_USE_SCANOUT);
return DrmFramebuffer::AddFramebuffer(fake_drm_, buffer.get(), size);
}
DrmOverlayPlaneList CreatePlanesWithoutFences() {
DrmOverlayPlaneList planes;
planes.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
planes.push_back(
DrmOverlayPlane::TestPlane(CreateBuffer(kDefaultBufferSize)));
return planes;
}
DrmOverlayPlaneList CreatePlanesWithFences() {
DrmOverlayPlaneList planes;
planes.push_back(DrmOverlayPlane::TestPlane(
CreateBuffer(kDefaultBufferSize), gfx::ColorSpace::CreateSRGB(),
fake_fence_fd1_.GetGpuFence()));
planes.push_back(DrmOverlayPlane::TestPlane(
CreateBuffer(kDefaultBufferSize), gfx::ColorSpace::CreateSRGB(),
fake_fence_fd2_.GetGpuFence()));
return planes;
}
scoped_refptr<FakeDrmDevice> fake_drm_;
std::unique_ptr<HardwareDisplayPlaneManager> plane_manager_;
bool callback_called = false;
base::test::TaskEnvironment task_env_{
base::test::TaskEnvironment::MainThreadType::DEFAULT,
base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
scoped_refptr<DrmFramebuffer> drm_framebuffer_;
const FakeFenceFD fake_fence_fd1_;
const FakeFenceFD fake_fence_fd2_;
DrmOverlayPlaneList planes_without_fences_;
DrmOverlayPlaneList planes_with_fences_;
};
void HardwareDisplayPlaneManagerPlanesReadyTest::RequestPlanesReady(
DrmOverlayPlaneList planes) {
auto set_true = [](bool* b, DrmOverlayPlaneList planes) { *b = true; };
plane_manager_->RequestPlanesReadyCallback(
std::move(planes), base::BindOnce(set_true, &callback_called));
}
void HardwareDisplayPlaneManagerPlanesReadyTest::UseLegacyManager() {
plane_manager_ =
std::make_unique<HardwareDisplayPlaneManagerLegacy>(fake_drm_.get());
}
void HardwareDisplayPlaneManagerPlanesReadyTest::UseAtomicManager() {
plane_manager_ =
std::make_unique<HardwareDisplayPlaneManagerAtomic>(fake_drm_.get());
}
TEST_F(HardwareDisplayPlaneManagerPlanesReadyTest,
LegacyWithoutFencesIsAsynchronousWithoutFenceWait) {
UseLegacyManager();
RequestPlanesReady(DrmOverlayPlane::Clone(planes_without_fences_));
EXPECT_FALSE(callback_called);
task_env_.RunUntilIdle();
EXPECT_TRUE(callback_called);
}
TEST_F(HardwareDisplayPlaneManagerPlanesReadyTest,
LegacyWithFencesIsAsynchronousWithFenceWait) {
UseLegacyManager();
RequestPlanesReady(DrmOverlayPlane::Clone(planes_with_fences_));
EXPECT_FALSE(callback_called);
fake_fence_fd1_.Signal();
fake_fence_fd2_.Signal();
EXPECT_FALSE(callback_called);
task_env_.RunUntilIdle();
EXPECT_TRUE(callback_called);
}
TEST_F(HardwareDisplayPlaneManagerPlanesReadyTest,
AtomicWithoutFencesIsAsynchronousWithoutFenceWait) {
UseAtomicManager();
RequestPlanesReady(DrmOverlayPlane::Clone(planes_without_fences_));
EXPECT_FALSE(callback_called);
task_env_.RunUntilIdle();
EXPECT_TRUE(callback_called);
}
TEST_F(HardwareDisplayPlaneManagerPlanesReadyTest,
AtomicWithFencesIsAsynchronousWithoutFenceWait) {
UseAtomicManager();
RequestPlanesReady(DrmOverlayPlane::Clone(planes_with_fences_));
EXPECT_FALSE(callback_called);
task_env_.RunUntilIdle();
EXPECT_TRUE(callback_called);
}
TEST_P(HardwareDisplayPlaneManagerTest, GetPossibleCrtcsBitmaskForConnector) {
fake_drm_->ResetStateWithAllProperties();
fake_drm_->AddCrtc();
fake_drm_->AddCrtc();
fake_drm_->AddCrtc();
const uint32_t connector_1_id = AddConnector(/*possible_crtcs=*/0b101u);
const uint32_t connector_2_id = AddConnector(/*possible_crtcs=*/0b110u);
const uint32_t connector_3_id = AddConnector(/*possible_crtcs=*/0b011u);
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->ResetConnectorsCacheAndGetValidIds(
fake_drm_->GetResources());
EXPECT_EQ(fake_drm_->plane_manager()->GetPossibleCrtcsBitmaskForConnector(
connector_1_id),
0b101u);
EXPECT_EQ(fake_drm_->plane_manager()->GetPossibleCrtcsBitmaskForConnector(
connector_2_id),
0b110u);
EXPECT_EQ(fake_drm_->plane_manager()->GetPossibleCrtcsBitmaskForConnector(
connector_3_id),
0b011u);
}
TEST_P(HardwareDisplayPlaneManagerTest,
GetPossibleCrtcsBitmaskForConnectorInvalidConnector) {
fake_drm_->ResetStateWithAllProperties();
fake_drm_->AddCrtc();
FakeDrmDevice::EncoderProperties& encoder = fake_drm_->AddEncoder();
encoder.possible_crtcs = 0b1;
const uint32_t encoder_id = encoder.id;
FakeDrmDevice::ConnectorProperties& connector = fake_drm_->AddConnector();
connector.connection = true;
connector.encoders = std::vector<uint32_t>{encoder_id};
const uint32_t connector_id = connector.id;
fake_drm_->InitializeState(use_atomic_);
fake_drm_->plane_manager()->ResetConnectorsCacheAndGetValidIds(
fake_drm_->GetResources());
EXPECT_EQ(fake_drm_->plane_manager()->GetPossibleCrtcsBitmaskForConnector(
connector_id + 1),
0u);
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, OriginalModifiersSupportOnly) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1, /*movable_planes=*/0,
{DRM_FORMAT_NV12}, {});
fake_drm_->InitializeState(use_atomic_);
{
DrmOverlayPlaneList assigns;
// Create as NV12 since this is required for rotation support.
std::unique_ptr<GbmBuffer> buffer = fake_drm_->gbm_device()->CreateBuffer(
DRM_FORMAT_NV12, kDefaultBufferSize, GBM_BO_USE_SCANOUT);
scoped_refptr<DrmFramebuffer> framebuffer_original =
DrmFramebuffer::AddFramebuffer(fake_drm_, buffer.get(),
kDefaultBufferSize, {}, true);
assigns.push_back(DrmOverlayPlane::TestPlane(framebuffer_original));
assigns.back().plane_transform =
gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270;
fake_drm_->plane_manager()->BeginFrame(&state_);
// Rotation should be supported for this buffer as it is the original buffer
// with the original modifiers.
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
gfx::GpuFenceHandle release_fence;
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
&release_fence));
}
{
DrmOverlayPlaneList assigns;
assigns.clear();
fake_drm_->plane_manager()->BeginFrame(&state_);
// The test buffer would not have accurate modifiers and therefore should
// fail rotation.
std::unique_ptr<GbmBuffer> buffer = fake_drm_->gbm_device()->CreateBuffer(
DRM_FORMAT_NV12, kDefaultBufferSize, GBM_BO_USE_SCANOUT);
scoped_refptr<DrmFramebuffer> framebuffer_non_original =
DrmFramebuffer::AddFramebuffer(fake_drm_, buffer.get(),
kDefaultBufferSize, {}, false);
assigns.push_back(DrmOverlayPlane::TestPlane(framebuffer_non_original));
assigns.back().plane_transform =
gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270;
EXPECT_FALSE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
}
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, OverlaySourceCrop) {
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1);
fake_drm_->InitializeState(use_atomic_);
{
DrmOverlayPlaneList assigns;
assigns.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
gfx::GpuFenceHandle release_fence;
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
&release_fence));
EXPECT_EQ(2u << 16, GetPlanePropertyValue(kPlaneOffset, "SRC_W"));
EXPECT_EQ(2u << 16, GetPlanePropertyValue(kPlaneOffset, "SRC_H"));
}
{
DrmOverlayPlaneList assigns;
assigns.emplace_back(fake_buffer_, gfx::ColorSpace::CreateSRGB(), 0,
gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE,
gfx::Rect(), gfx::Rect(kDefaultBufferSize),
gfx::RectF(0, 0, .5, 1), false, nullptr);
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
gfx::GpuFenceHandle release_fence;
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
&release_fence));
EXPECT_EQ(1u << 16, GetPlanePropertyValue(kPlaneOffset, "SRC_W"));
EXPECT_EQ(2u << 16, GetPlanePropertyValue(kPlaneOffset, "SRC_H"));
}
{
DrmOverlayPlaneList assigns;
assigns.emplace_back(fake_buffer_, gfx::ColorSpace::CreateSRGB(), 0,
gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE,
gfx::Rect(), gfx::Rect(kDefaultBufferSize),
gfx::RectF(0, 0, .999, .501), false, nullptr);
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
gfx::GpuFenceHandle release_fence;
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
&release_fence));
EXPECT_EQ(2u << 16, GetPlanePropertyValue(kPlaneOffset, "SRC_W"));
EXPECT_EQ(1u << 16, GetPlanePropertyValue(kPlaneOffset, "SRC_H"));
}
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, ColorEncodingAndRange) {
// These values are chosen arbitrarily % avoiding 0 in order to test that the
// classes under test don't assume that a value of 0 is special,
constexpr uint64_t kColorEncodingBT601 = 2u;
constexpr uint64_t kColorEncodingBT709 = 3u;
constexpr uint64_t kColorRangeLimited = 4u;
constexpr uint64_t kColorRangeFull = 5u;
fake_drm_->ResetStateWithDefaultObjects(
/*crtc_count=*/1, /*planes_per_crtc=*/1, /*movable_planes=*/0,
/*plane_supported_formats=*/{DRM_FORMAT_NV12});
fake_drm_->SetPossibleValuesForEnumProperty(
/*property_id=*/kColorEncodingPropId, /*values=*/{
{kColorEncodingBT601, "ITU-R BT.601 YCbCr"},
{kColorEncodingBT709, "ITU-R BT.709 YCbCr"},
});
fake_drm_->AddProperty(
/*object_id=*/fake_drm_->plane_property(0).id,
{.id = kColorEncodingPropId, .value = kColorEncodingBT601});
fake_drm_->SetPossibleValuesForEnumProperty(
/*property_id=*/kColorRangePropId, /*values=*/{
{kColorRangeLimited, "YCbCr limited range"},
{kColorRangeFull, "YCbCr full range"},
});
fake_drm_->AddProperty(/*object_id=*/fake_drm_->plane_property(0).id,
{.id = kColorRangePropId, .value = kColorRangeFull});
fake_drm_->InitializeState(use_atomic_);
// TODO: bug 233667677 - Notice that the `expected_color_range` is always
// limited regardless of the `color_space`. That's because we're currently not
// confident about having widespread support for full range, so the
// HardwareDisplayPlaneAtomic should always set the range to limited.
// Eventually, we'll probably want to plumb the right range.
struct TestCase {
gfx::ColorSpace color_space;
uint64_t expected_color_encoding;
uint64_t expected_color_range;
};
std::vector<TestCase> test_cases = {
{
.color_space = gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT709,
gfx::ColorSpace::TransferID::BT709,
gfx::ColorSpace::MatrixID::BT709,
gfx::ColorSpace::RangeID::LIMITED),
.expected_color_encoding = kColorEncodingBT709,
.expected_color_range = kColorRangeLimited,
},
{
.color_space = gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT709,
gfx::ColorSpace::TransferID::BT709,
gfx::ColorSpace::MatrixID::BT709,
gfx::ColorSpace::RangeID::FULL),
.expected_color_encoding = kColorEncodingBT709,
.expected_color_range = kColorRangeLimited,
},
{
.color_space = gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::SMPTE170M,
gfx::ColorSpace::RangeID::LIMITED),
.expected_color_encoding = kColorEncodingBT601,
.expected_color_range = kColorRangeLimited,
},
{
.color_space = gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::SMPTE170M,
gfx::ColorSpace::RangeID::FULL),
.expected_color_encoding = kColorEncodingBT601,
.expected_color_range = kColorRangeLimited,
},
};
for (const auto& test_case : test_cases) {
DrmOverlayPlaneList assigns;
std::unique_ptr<GbmBuffer> buffer = fake_drm_->gbm_device()->CreateBuffer(
DRM_FORMAT_NV12, kDefaultBufferSize, GBM_BO_USE_SCANOUT);
scoped_refptr<DrmFramebuffer> framebuffer_original =
DrmFramebuffer::AddFramebuffer(fake_drm_, buffer.get(),
kDefaultBufferSize, {}, true);
assigns.push_back(DrmOverlayPlane::TestPlane(framebuffer_original));
assigns.back().color_space = test_case.color_space;
fake_drm_->plane_manager()->BeginFrame(&state_);
EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
&state_, assigns, fake_drm_->crtc_property(0).id));
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(base::TimeDelta());
gfx::GpuFenceHandle release_fence;
EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
&release_fence));
EXPECT_EQ(test_case.expected_color_encoding,
GetPlanePropertyValue(kPlaneOffset, "COLOR_ENCODING"));
EXPECT_EQ(test_case.expected_color_range,
GetPlanePropertyValue(kPlaneOffset, "COLOR_RANGE"));
}
}
TEST_P(HardwareDisplayPlaneManagerAtomicTest, OldPlaneInAnotherList) {
fake_drm_->ResetStateWithDefaultObjects(/*connector_and_crtc_count=*/2,
/*planes_per_crtc=*/1);
fake_drm_->InitializeState(/*use_atomic=*/true);
const uint32_t crtc_0_id = fake_drm_->crtc_property(0).id;
const uint32_t crtc_1_id = fake_drm_->crtc_property(1).id;
CommitRequest commit_request;
HardwareDisplayPlaneList plane_list_0;
HardwareDisplayPlaneList plane_list_1;
const auto& planes = fake_drm_->plane_manager()->planes();
ASSERT_THAT(planes, testing::SizeIs(4));
HardwareDisplayPlane *plane_0, *plane_1;
for (auto& plane : planes) {
// Skip non-primary planes.
if ((plane->type() & DRM_PLANE_TYPE_PRIMARY) == 0) {
continue;
}
// Primary planes created by FakeDrmDevice::ResetStateWithDefaultObjects()
// should only be compatible with one CRTC.
if (plane->CanUseForCrtcId(crtc_0_id)) {
plane_0 = plane.get();
} else if (plane->CanUseForCrtcId(crtc_1_id)) {
plane_1 = plane.get();
}
}
ASSERT_NE(plane_0, nullptr);
ASSERT_NE(plane_1, nullptr);
ASSERT_NE(plane_0, plane_1);
plane_list_0.plane_list.push_back(plane_0);
plane_list_0.old_plane_list.push_back(plane_1);
plane_list_1.plane_list.push_back(plane_1);
plane_list_1.old_plane_list.push_back(plane_0);
{
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
CrtcCommitRequest request = CrtcCommitRequest::EnableCrtcRequest(
crtc_0_id, fake_drm_->connector_property(0).id, kDefaultMode,
gfx::Point(), &plane_list_0, std::move(overlays),
/*enable_vrr=*/false);
commit_request.push_back(std::move(request));
}
{
DrmOverlayPlaneList overlays;
overlays.push_back(DrmOverlayPlane::TestPlane(fake_buffer_));
CrtcCommitRequest request = CrtcCommitRequest::EnableCrtcRequest(
crtc_1_id, fake_drm_->connector_property(1).id, kDefaultMode,
gfx::Point(), &plane_list_1, std::move(overlays),
/*enable_vrr=*/false);
commit_request.push_back(std::move(request));
}
ASSERT_TRUE(fake_drm_->plane_manager()->Commit(
std::move(commit_request),
DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET));
EXPECT_EQ(plane_0->owning_crtc(), crtc_0_id);
EXPECT_EQ(plane_1->owning_crtc(), crtc_1_id);
}
class HardwareDisplayPlaneAtomicMock : public HardwareDisplayPlaneAtomic {
public:
HardwareDisplayPlaneAtomicMock() : HardwareDisplayPlaneAtomic(1) {}
~HardwareDisplayPlaneAtomicMock() override = default;
bool AssignPlaneProps(DrmDevice* drm,
uint32_t crtc_id,
uint32_t framebuffer,
const gfx::Rect& crtc_rect,
const gfx::Rect& src_rect,
const gfx::Rect& damage_rect,
const gfx::OverlayTransform transform,
const gfx::ColorSpace& color_space,
int in_fence_fd,
uint32_t format_fourcc,
bool is_original_buffer) override {
framebuffer_ = framebuffer;
return true;
}
uint32_t framebuffer() const { return framebuffer_; }
private:
uint32_t framebuffer_ = 0;
};
TEST(HardwareDisplayPlaneManagerAtomic, EnableBlend) {
auto gbm_device = std::make_unique<MockGbmDevice>();
auto drm_device = base::MakeRefCounted<FakeDrmDevice>(std::move(gbm_device));
auto plane_manager =
std::make_unique<HardwareDisplayPlaneManagerAtomic>(drm_device.get());
HardwareDisplayPlaneList plane_list;
HardwareDisplayPlaneAtomicMock hw_plane;
std::unique_ptr<GbmBuffer> buffer = drm_device->gbm_device()->CreateBuffer(
DRM_FORMAT_XRGB8888, kDefaultBufferSize, GBM_BO_USE_SCANOUT);
scoped_refptr<DrmFramebuffer> framebuffer = DrmFramebuffer::AddFramebuffer(
drm_device, buffer.get(), kDefaultBufferSize);
DrmOverlayPlane overlay(DrmOverlayPlane::TestPlane(framebuffer));
overlay.enable_blend = true;
plane_manager->SetPlaneData(&plane_list, &hw_plane, overlay, 1, gfx::Rect());
EXPECT_EQ(hw_plane.framebuffer(), framebuffer->framebuffer_id());
overlay.enable_blend = false;
plane_manager->SetPlaneData(&plane_list, &hw_plane, overlay, 1, gfx::Rect());
EXPECT_EQ(hw_plane.framebuffer(), framebuffer->opaque_framebuffer_id());
}
class HardwareDisplayPlaneManagerSeamlessModeTest : public testing::Test {
protected:
void SetUp() override {
drm_device_ = MockDrmDevice::Create();
// Initialize FakeDrmDevice state to have a single configured display.
drm_device_->ResetStateWithAllProperties();
crtc_id_ = drm_device_->AddCrtcWithPrimaryAndCursorPlanes().id;
auto& encoder = drm_device_->AddEncoder();
encoder.possible_crtcs = 0b1;
auto& connector = drm_device_->AddConnector();
connector.connection = true;
connector.modes = {
ResolutionAndRefreshRate{gfx::Size(3840, 2160), 120u},
};
connector.encoders = std::vector<uint32_t>{encoder.id};
drm_device_->InitializeState(/* use_atomic */ true);
plane_manager_ =
std::make_unique<HardwareDisplayPlaneManagerAtomic>(drm_device_.get());
CHECK(plane_manager_->Initialize());
}
int64_t crtc_id_;
scoped_refptr<MockDrmDevice> drm_device_;
std::unique_ptr<HardwareDisplayPlaneManagerAtomic> plane_manager_;
};
TEST_F(HardwareDisplayPlaneManagerSeamlessModeTest, TestSeamlessMode) {
// Any arbitrary mode to be tested for seamless configuration.
drmModeModeInfo arbitrary_mode = {
.hdisplay = 1234, .vdisplay = 567, .vrefresh = 19u};
// CommitProperties is called with DRM_MODE_ATOMIC_TEST_ONLY. The result of
// CommitProperties propagates to the result of TestSeamlessMode.
// CommitProperties returns false.
EXPECT_CALL(*drm_device_,
CommitProperties(_, DRM_MODE_ATOMIC_TEST_ONLY, 1, _))
.Times(1)
.WillRepeatedly(Return(false));
EXPECT_FALSE(plane_manager_->TestSeamlessMode(crtc_id_, arbitrary_mode));
// CommitProperties returns true.
EXPECT_CALL(*drm_device_,
CommitProperties(_, DRM_MODE_ATOMIC_TEST_ONLY, 1, _))
.Times(1)
.WillRepeatedly(Return(true));
EXPECT_TRUE(plane_manager_->TestSeamlessMode(crtc_id_, arbitrary_mode));
}
TEST_F(HardwareDisplayPlaneManagerSeamlessModeTest,
TestSeamlessMode_InvalidCrtcId) {
// Any arbitrary mode to be tested for seamless configuration.
drmModeModeInfo arbitrary_mode = {
.hdisplay = 1234, .vdisplay = 567, .vrefresh = 19u};
// Invalid crtc will result in a DCHECK.
int32_t wrong_crtc_id = 9999;
EXPECT_NE(wrong_crtc_id, crtc_id_);
EXPECT_DCHECK_DEATH(
plane_manager_->TestSeamlessMode(wrong_crtc_id, arbitrary_mode));
}
} // namespace ui