// 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.h>
#include <string.h>
#include <xf86drm.h>
#include <ios>
#include <memory>
#include <type_traits>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/trace_event/typed_macros.h"
#include "third_party/libdrm/src/include/drm/drm_fourcc.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gpu_fence_handle.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/gfx/native_pixmap.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/gfx/swap_result.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/drm/gpu/crtc_controller.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/drm_dumb_buffer.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_plane.h"
#include "ui/ozone/platform/drm/gpu/page_flip_request.h"
#include "ui/ozone/platform/drm/gpu/page_flip_watchdog.h"
namespace ui {
namespace {
void CompletePageFlip(
base::WeakPtr<HardwareDisplayController> hardware_display_controller_,
int modeset_sequence,
PresentationOnceCallback callback,
DrmOverlayPlaneList plane_list,
const gfx::PresentationFeedback& presentation_feedback) {
if (hardware_display_controller_) {
hardware_display_controller_->OnPageFlipComplete(
modeset_sequence, std::move(plane_list), presentation_feedback);
}
std::move(callback).Run(presentation_feedback);
}
void DrawCursor(DrmDumbBuffer* cursor, const SkBitmap& image) {
SkRect damage;
image.getBounds(&damage);
// Clear to transparent in case |image| is smaller than the canvas.
SkCanvas* canvas = cursor->GetCanvas();
canvas->clear(SK_ColorTRANSPARENT);
canvas->drawImageRect(image.asImage(), damage, SkSamplingOptions());
}
template <typename T>
std::string NumberToHexString(const T value) {
static_assert(std::is_unsigned<T>::value,
"Can only convert unsigned ints to hex");
std::stringstream ss;
ss << "0x" << std::hex << std::uppercase << value;
return ss.str();
}
bool IsRockchipAfbc(uint64_t modifier) {
return modifier ==
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_YTR);
}
std::unique_ptr<DrmDumbBuffer> MakeCursorDrmBuffer(
gfx::Size size,
scoped_refptr<DrmDevice> drm_device) {
SkImageInfo info = SkImageInfo::MakeN32Premul(size.width(), size.height());
auto buffer = std::make_unique<DrmDumbBuffer>(drm_device);
// Don't register a framebuffer for cursors since they are special (they
// aren't modesetting buffers and drivers may fail to register them due to
// their small sizes).
if (!buffer->Initialize(info)) {
LOG(FATAL) << "Failed to initialize cursor buffer";
}
return buffer;
}
} // namespace
HardwareDisplayController::HardwareDisplayController(
std::unique_ptr<CrtcController> controller,
const gfx::Point& origin,
raw_ptr<DrmModifiersFilter> drm_modifiers_filter)
: origin_(origin), drm_modifiers_filter_(drm_modifiers_filter) {
AddCrtc(std::move(controller));
InitSupportedCursorSizes();
AllocateCursorBuffers();
}
HardwareDisplayController::~HardwareDisplayController() = default;
void HardwareDisplayController::GetModesetProps(
CommitRequest* commit_request,
const DrmOverlayPlaneList& modeset_planes,
const drmModeModeInfo& mode,
bool enable_vrr) {
GetModesetPropsForCrtcs(commit_request, modeset_planes,
/*use_current_crtc_mode=*/false, mode, enable_vrr);
}
void HardwareDisplayController::GetEnableProps(
CommitRequest* commit_request,
const DrmOverlayPlaneList& modeset_planes) {
// TODO(markyacoub): Simplify and remove the use of empty_mode.
drmModeModeInfo empty_mode = {};
GetModesetPropsForCrtcs(commit_request, modeset_planes,
/*use_current_crtc_mode=*/true, empty_mode,
/*enable_vrr=*/std::nullopt);
}
void HardwareDisplayController::GetModesetPropsForCrtcs(
CommitRequest* commit_request,
const DrmOverlayPlaneList& modeset_planes,
bool use_current_crtc_mode,
const drmModeModeInfo& mode,
std::optional<bool> enable_vrr) {
DCHECK(commit_request);
GetDrmDevice()->plane_manager()->BeginFrame(&owned_hardware_planes_);
for (const auto& controller : crtc_controllers_) {
drmModeModeInfo modeset_mode =
use_current_crtc_mode ? controller->mode() : mode;
DrmOverlayPlaneList overlays = DrmOverlayPlane::Clone(modeset_planes);
CrtcCommitRequest request = CrtcCommitRequest::EnableCrtcRequest(
controller->crtc(), controller->connector(), modeset_mode, origin_,
&owned_hardware_planes_, std::move(overlays),
enable_vrr.value_or(controller->vrr_enabled()));
commit_request->push_back(std::move(request));
}
}
void HardwareDisplayController::GetDisableProps(CommitRequest* commit_request) {
for (const auto& controller : crtc_controllers_) {
CrtcCommitRequest request = CrtcCommitRequest::DisableCrtcRequest(
controller->crtc(), controller->connector(), &owned_hardware_planes_);
commit_request->push_back(std::move(request));
}
}
void HardwareDisplayController::UpdateState(
const CrtcCommitRequest& crtc_request) {
watchdog_.Disarm();
// Verify that the current state matches the requested state.
if (crtc_request.should_enable_crtc() && IsEnabled()) {
DCHECK(!crtc_request.overlays().empty());
// TODO(markyacoub): This should be absorbed in the commit request.
ResetCursor();
OnModesetComplete(crtc_request.overlays());
}
}
void HardwareDisplayController::SchedulePageFlip(
DrmOverlayPlaneList plane_list,
SwapCompletionOnceCallback submission_callback,
PresentationOnceCallback presentation_callback) {
DCHECK(!page_flip_request_);
TRACE_EVENT0("drm", "HDC::SchedulePageFlip");
scoped_refptr<PageFlipRequest> page_flip_request =
base::MakeRefCounted<PageFlipRequest>(GetRefreshInterval());
gfx::GpuFenceHandle release_fence;
PageFlipResult result =
ScheduleOrTestPageFlip(plane_list, page_flip_request, &release_fence);
UMA_HISTOGRAM_ENUMERATION(
"Compositing.Display.HardwareDisplayController.SchedulePageFlipResult",
result);
if (PageFlipResult::kFailedPlaneAssignment == result) {
watchdog_.CrashOnFailedPlaneAssignment();
// Plane assignment is usually an intermittent problem that recovers itself
// within a few frames. Send back a NAK and hope for the best--the
// watchdog will handle things if this problem is persistent.
std::move(submission_callback)
.Run(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS,
/*release_fence=*/gfx::GpuFenceHandle());
std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure());
return;
} else if (PageFlipResult::kFailedCommit == result) {
for (const auto& plane : plane_list) {
// If the page flip failed and we see that the buffer has been allocated
// before the latest modeset, it could mean it was an in-flight buffer
// carrying an obsolete configuration.
// Request a buffer reallocation to reflect the new change.
if (plane.buffer &&
plane.buffer->modeset_sequence_id_at_allocation() <
plane.buffer->drm_device()->modeset_sequence_id()) {
std::move(submission_callback)
.Run(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS,
/*release_fence=*/gfx::GpuFenceHandle());
std::move(presentation_callback)
.Run(gfx::PresentationFeedback::Failure());
return;
}
}
// No outdated buffers detected which makes this a true page flip failure.
// Alert the watchdog.
watchdog_.ArmForFailedCommit();
std::move(submission_callback)
.Run(gfx::SwapResult::SWAP_FAILED,
/*release_fence=*/gfx::GpuFenceHandle());
std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure());
return;
}
if (page_flip_request->page_flip_count() == 0) {
// Apparently, there was nothing to do. This probably should not be
// able to happen but both CrtcController::AssignOverlayPlanes and
// HardwareDisplayPlaneManagerLegacy::Commit appear to have cases
// where we ACK without actually scheduling a page flip.
std::move(submission_callback)
.Run(gfx::SwapResult::SWAP_ACK,
/*release_fence=*/gfx::GpuFenceHandle());
std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure());
return;
}
std::move(submission_callback)
.Run(gfx::SwapResult::SWAP_ACK, std::move(release_fence));
watchdog_.OnSuccessfulPageFlip();
// Everything was submitted successfully, wait for asynchronous completion.
page_flip_request->TakeCallback(
base::BindOnce(&CompletePageFlip, weak_ptr_factory_.GetWeakPtr(),
GetDrmDevice()->modeset_sequence_id(),
std::move(presentation_callback), std::move(plane_list)));
page_flip_request_ = std::move(page_flip_request);
}
bool HardwareDisplayController::TestPageFlip(
const DrmOverlayPlaneList& plane_list) {
TRACE_EVENT0("drm", "HDC::TestPageFlip");
return PageFlipResult::kSuccess ==
ScheduleOrTestPageFlip(plane_list, nullptr, nullptr);
}
HardwareDisplayController::PageFlipResult
HardwareDisplayController::ScheduleOrTestPageFlip(
const DrmOverlayPlaneList& plane_list,
scoped_refptr<PageFlipRequest> page_flip_request,
gfx::GpuFenceHandle* release_fence) {
TRACE_EVENT0("drm", "HDC::ScheduleOrTestPageFlip");
DCHECK(IsEnabled());
// Ignore requests with no planes to schedule.
if (plane_list.empty())
return PageFlipResult::kSuccess;
DrmOverlayPlaneList pending_planes = DrmOverlayPlane::Clone(plane_list);
std::sort(pending_planes.begin(), pending_planes.end(),
[](const DrmOverlayPlane& l, const DrmOverlayPlane& r) {
return l.z_order < r.z_order;
});
GetDrmDevice()->plane_manager()->BeginFrame(&owned_hardware_planes_);
for (const auto& controller : crtc_controllers_) {
if (!controller->AssignOverlayPlanes(
&owned_hardware_planes_, pending_planes, /*is_modesetting=*/false))
return PageFlipResult::kFailedPlaneAssignment;
}
bool commit_success = GetDrmDevice()->plane_manager()->Commit(
&owned_hardware_planes_, page_flip_request, release_fence);
return commit_success ? PageFlipResult::kSuccess
: PageFlipResult::kFailedCommit;
}
bool HardwareDisplayController::TestSeamlessMode(int32_t crtc_id,
const drmModeModeInfo& mode) {
return GetDrmDevice()->plane_manager()->TestSeamlessMode(crtc_id, mode);
}
std::vector<uint64_t> HardwareDisplayController::GetFormatModifiers(
uint32_t fourcc_format) const {
if (crtc_controllers_.empty())
return std::vector<uint64_t>();
std::vector<uint64_t> modifiers =
crtc_controllers_[0]->GetFormatModifiers(fourcc_format);
if (drm_modifiers_filter_) {
gfx::BufferFormat buffer_format =
GetBufferFormatFromFourCCFormat(fourcc_format);
modifiers = drm_modifiers_filter_->Filter(buffer_format, modifiers);
}
for (size_t i = 1; i < crtc_controllers_.size(); ++i) {
std::vector<uint64_t> other =
crtc_controllers_[i]->GetFormatModifiers(fourcc_format);
std::vector<uint64_t> intersection;
std::set_intersection(modifiers.begin(), modifiers.end(), other.begin(),
other.end(), std::back_inserter(intersection));
modifiers = std::move(intersection);
}
return modifiers;
}
std::vector<uint64_t> HardwareDisplayController::GetSupportedModifiers(
uint32_t fourcc_format,
bool is_modeset) const {
if (preferred_format_modifier_.empty())
return std::vector<uint64_t>();
auto it = preferred_format_modifier_.find(fourcc_format);
if (it != preferred_format_modifier_.end()) {
uint64_t supported_modifier = it->second;
// AFBC for modeset buffers doesn't work correctly, as we can't fill them
// with a valid AFBC buffer (b/172227166).
// For now, don't use AFBC for modeset buffers.
if (is_modeset && IsRockchipAfbc(supported_modifier)) {
supported_modifier = DRM_FORMAT_MOD_LINEAR;
}
return std::vector<uint64_t>{supported_modifier};
}
return GetFormatModifiers(fourcc_format);
}
std::vector<uint64_t>
HardwareDisplayController::GetFormatModifiersForTestModeset(
uint32_t fourcc_format) {
// If we're about to test, clear the current preferred modifier.
preferred_format_modifier_.clear();
return GetFormatModifiers(fourcc_format);
}
void HardwareDisplayController::UpdatePreferredModifierForFormat(
gfx::BufferFormat buffer_format,
uint64_t modifier) {
uint32_t fourcc_format = GetFourCCFormatFromBufferFormat(buffer_format);
preferred_format_modifier_[fourcc_format] = modifier;
uint32_t opaque_fourcc_format =
GetFourCCFormatForOpaqueFramebuffer(buffer_format);
preferred_format_modifier_[opaque_fourcc_format] = modifier;
}
void HardwareDisplayController::MoveCursor(const gfx::Point& location) {
cursor_location_ = location;
UpdateCursorLocation();
}
void HardwareDisplayController::SetCursor(SkBitmap bitmap) {
if (bitmap.drawsNothing()) {
current_cursor_ = nullptr;
} else {
current_cursor_ = NextCursorBuffer(bitmap);
DrawCursor(current_cursor_, bitmap);
}
UpdateCursorImage();
}
void HardwareDisplayController::AddCrtc(
std::unique_ptr<CrtcController> controller) {
scoped_refptr<DrmDevice> drm = controller->drm();
DCHECK(crtc_controllers_.empty() || drm == GetDrmDevice());
// Check if this controller owns any planes and ensure we keep track of them.
const std::vector<std::unique_ptr<HardwareDisplayPlane>>& all_planes =
drm->plane_manager()->planes();
uint32_t crtc = controller->crtc();
for (const auto& plane : all_planes) {
if (plane->in_use() && (plane->owning_crtc() == crtc))
owned_hardware_planes_.old_plane_list.push_back(plane.get());
}
crtc_controllers_.push_back(std::move(controller));
}
std::unique_ptr<CrtcController> HardwareDisplayController::RemoveCrtc(
const scoped_refptr<DrmDevice>& drm,
uint32_t crtc) {
auto controller_it = base::ranges::find_if(
crtc_controllers_,
[drm, crtc](const std::unique_ptr<CrtcController>& crtc_controller) {
return crtc_controller->drm() == drm && crtc_controller->crtc() == crtc;
});
if (controller_it == crtc_controllers_.end())
return nullptr;
std::unique_ptr<CrtcController> controller(std::move(*controller_it));
crtc_controllers_.erase(controller_it);
// Move all the planes that have been committed in the last pageflip for this
// CRTC at the end of the collection.
auto first_plane_to_disable_it =
std::partition(owned_hardware_planes_.old_plane_list.begin(),
owned_hardware_planes_.old_plane_list.end(),
[crtc](const HardwareDisplayPlane* plane) {
return plane->owning_crtc() != crtc;
});
// Disable the planes enabled with the last commit on |crtc|, otherwise
// the planes will be visible if the crtc is reassigned to another connector.
HardwareDisplayPlaneList hardware_plane_list;
std::copy(first_plane_to_disable_it,
owned_hardware_planes_.old_plane_list.end(),
std::back_inserter(hardware_plane_list.old_plane_list));
drm->plane_manager()->DisableOverlayPlanes(&hardware_plane_list);
// Remove the planes assigned to |crtc|.
owned_hardware_planes_.old_plane_list.erase(
first_plane_to_disable_it, owned_hardware_planes_.old_plane_list.end());
return controller;
}
bool HardwareDisplayController::HasCrtc(const scoped_refptr<DrmDevice>& drm,
uint32_t crtc) const {
for (const auto& controller : crtc_controllers_) {
if (controller->drm() == drm && controller->crtc() == crtc)
return true;
}
return false;
}
bool HardwareDisplayController::IsMirrored() const {
return crtc_controllers_.size() > 1;
}
bool HardwareDisplayController::IsEnabled() const {
bool is_enabled = true;
for (const auto& controller : crtc_controllers_)
is_enabled &= controller->is_enabled();
return is_enabled;
}
gfx::Size HardwareDisplayController::GetModeSize() const {
// If there are multiple CRTCs they should all have the same size.
return gfx::Size(crtc_controllers_[0]->mode().hdisplay,
crtc_controllers_[0]->mode().vdisplay);
}
float HardwareDisplayController::GetRefreshRate() const {
// If there are multiple CRTCs they should all have the same refresh rate.
return ModeRefreshRate(crtc_controllers_[0]->mode());
}
base::TimeDelta HardwareDisplayController::GetRefreshInterval() const {
// If there are multiple CRTCs they should all have the same refresh rate.
float vrefresh = GetRefreshRate();
return vrefresh ? base::Seconds(1) / vrefresh : base::TimeDelta();
}
base::TimeTicks HardwareDisplayController::GetTimeOfLastFlip() const {
return time_of_last_flip_;
}
scoped_refptr<DrmDevice> HardwareDisplayController::GetDrmDevice() const {
DCHECK(!crtc_controllers_.empty());
// TODO(dnicoara) When we support mirroring across DRM devices, figure out
// which device should be used for allocations.
return crtc_controllers_[0]->drm();
}
void HardwareDisplayController::OnPageFlipComplete(
int modeset_sequence,
DrmOverlayPlaneList pending_planes,
const gfx::PresentationFeedback& presentation_feedback) {
if (!page_flip_request_)
return; // Modeset occurred during this page flip.
time_of_last_flip_ = presentation_feedback.timestamp;
current_planes_ = std::move(pending_planes);
for (const auto& controller : crtc_controllers_) {
// Only reset the modeset buffer of the crtcs for pageflips that were
// committed after the modeset.
if (modeset_sequence == GetDrmDevice()->modeset_sequence_id()) {
GetDrmDevice()->plane_manager()->ResetModesetStateForCrtc(
controller->crtc());
}
}
page_flip_request_ = nullptr;
}
void HardwareDisplayController::WriteIntoTrace(
perfetto::TracedValue context) const {
auto dict = std::move(context).WriteDictionary();
dict.Add("origin", origin_.ToString());
dict.Add("cursor_location", cursor_location_.ToString());
dict.Add("has_page_flip_request", page_flip_request_ != nullptr);
dict.Add("owned_hardware_planes", owned_hardware_planes_);
dict.Add("crtc_controllers", crtc_controllers_);
{
auto array = dict.AddArray("preferred_format_modifiers");
for (const auto& format_modifier : preferred_format_modifier_) {
auto format_dict = array.AppendDictionary();
format_dict.Add("format", NumberToHexString(format_modifier.first));
format_dict.Add("modifier", NumberToHexString(format_modifier.second));
}
}
}
size_t HardwareDisplayController::NumOfSupportedCursorSizesForTesting() const {
return supported_cursor_sizes_.size();
}
gfx::Size HardwareDisplayController::CurrentCursorSizeForTesting() const {
return current_cursor_ ? current_cursor_->GetSize() : gfx::Size();
}
void HardwareDisplayController::OnModesetComplete(
const DrmOverlayPlaneList& modeset_planes) {
// Modesetting is blocking so it has an immediate effect. We can assume that
// the current planes have been updated. However, if a page flip is still
// pending, set the pending planes to the same values so that the callback
// keeps the correct state.
page_flip_request_ = nullptr;
owned_hardware_planes_.legacy_page_flips.clear();
current_planes_ = DrmOverlayPlane::Clone(modeset_planes);
time_of_last_flip_ = base::TimeTicks::Now();
}
void HardwareDisplayController::AllocateCursorBuffers() {
TRACE_EVENT0("drm", "HDC::AllocateCursorBuffers");
constexpr int kActiveBufferCount = 2;
for (auto& size : supported_cursor_sizes_) {
for (int i = 0; i < kActiveBufferCount; i++) {
cursor_buffer_map_[size].push_back(
MakeCursorDrmBuffer(size, GetDrmDevice()));
}
}
}
DrmDumbBuffer* HardwareDisplayController::NextCursorBuffer(
const SkBitmap& image) {
// Use the largest buffer as default.
gfx::Size buffer_size = supported_cursor_sizes_.back();
// Find the smallest buffer size that fits the |image| size.
for (auto size : supported_cursor_sizes_) {
if (image.width() <= size.width() && image.height() <= size.width()) {
buffer_size = size;
break;
}
}
// Return the not in-use buffer with the |buffer_size|.
auto& active_buffers = cursor_buffer_map_[buffer_size];
DrmDumbBuffer* next_buffer = active_buffers.front().get();
if (next_buffer == current_cursor_) {
return active_buffers.back().get();
}
return next_buffer;
}
void HardwareDisplayController::UpdateCursorImage() {
uint32_t handle = 0;
gfx::Size size;
if (current_cursor_) {
handle = current_cursor_->GetHandle();
size = current_cursor_->GetSize();
}
for (const auto& controller : crtc_controllers_) {
controller->SetCursor(handle, size);
}
}
void HardwareDisplayController::UpdateCursorLocation() {
for (const auto& controller : crtc_controllers_)
controller->MoveCursor(cursor_location_);
}
void HardwareDisplayController::ResetCursor() {
UpdateCursorLocation();
UpdateCursorImage();
}
void HardwareDisplayController::InitSupportedCursorSizes() {
// Only use dynamic cursor size on Intel GPUs.
std::optional<std::string> driver = GetDrmDevice()->GetDriverName();
bool use_dynamic_cursor_size = IsUseDynamicCursorSizeEnabled() &&
driver.has_value() && *driver == "i915";
if (use_dynamic_cursor_size) {
const std::vector<std::unique_ptr<HardwareDisplayPlane>>& planes =
GetDrmDevice()->plane_manager()->planes();
for (const auto& plane : planes) {
// Currently on Intel, if there are multiple CRTCs they should all have
// the same supported cursor sizes.
if (plane->type() == DRM_PLANE_TYPE_CURSOR) {
const std::vector<gfx::Size>& supported_cursor_sizes =
plane->supported_cursor_sizes();
supported_cursor_sizes_.assign(supported_cursor_sizes.begin(),
supported_cursor_sizes.end());
break;
}
}
}
if (supported_cursor_sizes_.empty()) {
// Get the maximum cursor size supported by the GPU.
const gfx::Size max_cursor_size_supported =
GetMaximumCursorSize(*GetDrmDevice());
// max_cursor_size_supported can be as large as 4096 depending on platform
// and driver capabilities, but we don't need huge buffer like that for the
// cursor.
supported_cursor_sizes_.push_back(gfx::Size(
std::min(max_cursor_size_supported.width(), kMaxCursorBufferSize),
std::min(max_cursor_size_supported.height(), kMaxCursorBufferSize)));
}
// Sort the supported cursor sizes in ascending order so that we can use the
// smallest buffer.
DCHECK(!supported_cursor_sizes_.empty());
std::sort(supported_cursor_sizes_.begin(), supported_cursor_sizes_.end(),
CursorSizeComparator());
}
} // namespace ui