blob: 62edc86e29d8839949896995dd05ce8082a521b5 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/screen_manager.h"
#include <xf86drmMode.h>
#include <memory>
#include <utility>
#include "base/files/platform_file.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gfx/linux/gbm_buffer.h"
#include "ui/gfx/skia_util.h"
#include "ui/ozone/platform/drm/common/drm_util.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/drm_framebuffer.h"
#include "ui/ozone/platform/drm/gpu/drm_window.h"
#include "ui/ozone/platform/drm/gpu/hardware_display_controller.h"
namespace ui {
namespace {
// Copies the contents of the saved framebuffer from the CRTCs in |controller|
// to the surface for the new modeset buffer |surface|.
bool FillModesetBuffer(const scoped_refptr<DrmDevice>& drm,
HardwareDisplayController* controller,
SkSurface* surface,
uint32_t fourcc_format) {
DCHECK(!controller->crtc_controllers().empty());
CrtcController* first_crtc = controller->crtc_controllers()[0].get();
ScopedDrmCrtcPtr saved_crtc(drm->GetCrtc(first_crtc->crtc()));
if (!saved_crtc || !saved_crtc->buffer_id) {
VLOG(2) << "Crtc has no saved state or wasn't modeset";
return false;
}
const auto& modifiers = controller->GetFormatModifiers(fourcc_format);
for (const uint64_t modifier : modifiers) {
// A value of 0 means DRM_FORMAT_MOD_NONE. If the CRTC has any other
// modifier (tiling, compression, etc.) we can't read the fb and assume it's
// a linear buffer.
if (modifier) {
VLOG(2) << "Crtc has a modifier and we might not know how to interpret "
"the fb.";
return false;
}
}
// If the display controller is in mirror mode, the CRTCs should be sharing
// the same framebuffer.
DrmDumbBuffer saved_buffer(drm);
if (!saved_buffer.InitializeFromFramebuffer(saved_crtc->buffer_id)) {
VLOG(2) << "Failed to grab saved framebuffer " << saved_crtc->buffer_id;
return false;
}
// Don't copy anything if the sizes mismatch. This can happen when the user
// changes modes.
if (saved_buffer.GetCanvas()->getBaseLayerSize() !=
surface->getCanvas()->getBaseLayerSize()) {
VLOG(2) << "Previous buffer has a different size than modeset buffer";
return false;
}
SkPaint paint;
// Copy the source buffer. Do not perform any blending.
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawImage(saved_buffer.surface()->makeImageSnapshot(),
0, 0, &paint);
return true;
}
CrtcController* GetCrtcController(HardwareDisplayController* controller,
const scoped_refptr<DrmDevice>& drm,
uint32_t crtc) {
for (const auto& crtc_controller : controller->crtc_controllers()) {
if (crtc_controller->crtc() == crtc)
return crtc_controller.get();
}
NOTREACHED();
return nullptr;
}
std::vector<uint64_t> GetModifiersForPrimaryFormat(
HardwareDisplayController* controller) {
gfx::BufferFormat format = display::DisplaySnapshot::PrimaryFormat();
uint32_t fourcc_format = ui::GetFourCCFormatForOpaqueFramebuffer(format);
return controller->GetFormatModifiersForModesetting(fourcc_format);
}
bool AreAllStatusesTrue(base::flat_map<int64_t, bool>& display_statuses) {
auto it = find_if(display_statuses.begin(), display_statuses.end(),
[](const auto status) { return status.second == false; });
return (it == display_statuses.end());
}
} // namespace
ScreenManager::ScreenManager() = default;
ScreenManager::~ScreenManager() {
DCHECK(window_map_.empty());
}
ScreenManager::ControllerConfigParams::ControllerConfigParams(
int64_t display_id,
scoped_refptr<DrmDevice> drm,
uint32_t crtc,
uint32_t connector,
gfx::Point origin,
std::unique_ptr<drmModeModeInfo> pmode)
: display_id(display_id),
drm(drm),
crtc(crtc),
connector(connector),
origin(origin),
mode(std::move(pmode)) {}
ScreenManager::ControllerConfigParams::ControllerConfigParams(
const ControllerConfigParams& other)
: display_id(other.display_id),
drm(other.drm),
crtc(other.crtc),
connector(other.connector),
origin(other.origin) {
if (other.mode) {
drmModeModeInfo mode_obj = *other.mode.get();
mode = std::make_unique<drmModeModeInfo>(mode_obj);
}
}
ScreenManager::ControllerConfigParams::ControllerConfigParams(
ControllerConfigParams&& other)
: display_id(other.display_id),
drm(other.drm),
crtc(other.crtc),
connector(other.connector),
origin(other.origin) {
if (other.mode) {
drmModeModeInfo mode_obj = *other.mode.get();
mode = std::make_unique<drmModeModeInfo>(mode_obj);
}
}
ScreenManager::ControllerConfigParams::~ControllerConfigParams() = default;
void ScreenManager::AddDisplayController(const scoped_refptr<DrmDevice>& drm,
uint32_t crtc,
uint32_t connector) {
HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc);
// TODO(dnicoara): Turn this into a DCHECK when async display configuration is
// properly supported. (When there can't be a race between forcing initial
// display configuration in ScreenManager and display::NativeDisplayDelegate
// creating the display controllers.)
if (it != controllers_.end()) {
LOG(WARNING) << "Display controller (crtc=" << crtc << ") already present.";
return;
}
controllers_.push_back(std::make_unique<HardwareDisplayController>(
std::make_unique<CrtcController>(drm, crtc, connector), gfx::Point()));
}
void ScreenManager::RemoveDisplayControllers(
const CrtcsWithDrmList& controllers_to_remove) {
TRACE_EVENT1("drm", "ScreenManager::RemoveDisplayControllers",
"display_count", controllers_to_remove.size());
// Split them to different lists unique to each DRM Device.
base::flat_map<scoped_refptr<DrmDevice>, CrtcsWithDrmList>
controllers_for_drm_devices;
for (const auto& controller : controllers_to_remove) {
auto drm = controller.second;
auto it = controllers_for_drm_devices.find(drm);
if (it == controllers_for_drm_devices.end()) {
controllers_for_drm_devices.insert(
std::make_pair(drm, CrtcsWithDrmList()));
}
controllers_for_drm_devices[drm].emplace_back(controller);
}
bool should_update_controllers_to_window_mapping = false;
for (const auto& controllers_on_drm : controllers_for_drm_devices) {
CrtcsWithDrmList controllers_to_remove = controllers_on_drm.second;
CommitRequest commit_request;
auto drm = controllers_on_drm.first;
for (const auto& controller : controllers_to_remove) {
uint32_t crtc_id = controller.first;
auto it = FindDisplayController(drm, crtc_id);
if (it == controllers_.end())
continue;
bool is_mirrored = (*it)->IsMirrored();
std::unique_ptr<CrtcController> crtc = (*it)->RemoveCrtc(drm, crtc_id);
if (crtc->is_enabled()) {
commit_request.push_back(CrtcCommitRequest::DisableCrtcRequest(
crtc->crtc(), crtc->connector()));
}
if (!is_mirrored) {
controllers_.erase(it);
should_update_controllers_to_window_mapping = true;
}
}
if (!commit_request.empty()) {
drm->plane_manager()->Commit(std::move(commit_request),
DRM_MODE_ATOMIC_ALLOW_MODESET);
}
}
if (should_update_controllers_to_window_mapping)
UpdateControllerToWindowMapping();
}
base::flat_map<int64_t, bool> ScreenManager::ConfigureDisplayControllers(
const ControllerConfigsList& controllers_params) {
TRACE_EVENT0("drm", "ScreenManager::ConfigureDisplayControllers");
// Split them to different lists unique to each DRM Device.
base::flat_map<scoped_refptr<DrmDevice>, ControllerConfigsList>
displays_for_drm_devices;
for (auto& params : controllers_params) {
auto it = displays_for_drm_devices.find(params.drm);
if (it == displays_for_drm_devices.end()) {
displays_for_drm_devices.insert(
std::make_pair(params.drm, ControllerConfigsList()));
}
displays_for_drm_devices[params.drm].emplace_back(params);
}
base::flat_map<int64_t, bool> statuses;
// Perform display configurations together for the same DRM only.
for (const auto& configs_on_drm : displays_for_drm_devices) {
auto display_statuses = TestAndModeset(configs_on_drm.second);
statuses.insert(display_statuses.begin(), display_statuses.end());
}
if (AreAllStatusesTrue(statuses))
UpdateControllerToWindowMapping();
return statuses;
}
base::flat_map<int64_t, bool> ScreenManager::TestAndModeset(
const ControllerConfigsList& controllers_params) {
if (!TestModeset(controllers_params)) {
base::flat_map<int64_t, bool> statuses;
for (const auto& params : controllers_params)
statuses.insert(std::make_pair(params.display_id, false));
return statuses;
}
return Modeset(controllers_params);
}
bool ScreenManager::TestModeset(
const ControllerConfigsList& controllers_params) {
TRACE_EVENT1("drm", "ScreenManager::TestModeset", "display_count",
controllers_params.size());
CommitRequest commit_request;
auto drm = controllers_params[0].drm;
for (const auto& params : controllers_params) {
auto it = FindDisplayController(params.drm, params.crtc);
DCHECK(controllers_.end() != it);
HardwareDisplayController* controller = it->get();
if (params.mode) {
DrmOverlayPlane primary_plane = GetModesetBuffer(
controller, gfx::Rect(params.origin, ModeSize(*params.mode)),
GetModifiersForPrimaryFormat(controller), /*is_testing=*/true);
if (!primary_plane.buffer)
return false;
GetModesetControllerProps(&commit_request, controller, params.origin,
*params.mode, primary_plane);
} else {
controller->GetDisableProps(&commit_request);
}
}
return drm->plane_manager()->Commit(
std::move(commit_request),
DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET);
}
base::flat_map<int64_t, bool> ScreenManager::Modeset(
const ControllerConfigsList& controllers_params) {
TRACE_EVENT0("drm", "ScreenManager::Modeset");
base::flat_map<int64_t, bool> statuses;
for (const auto& params : controllers_params) {
// Commit one controller at a time.
CommitRequest commit_request;
bool status = true;
if (params.mode) {
auto it = FindDisplayController(params.drm, params.crtc);
DCHECK(controllers_.end() != it);
HardwareDisplayController* controller = it->get();
DrmOverlayPlane primary_plane = GetModesetBuffer(
controller, gfx::Rect(params.origin, ModeSize(*params.mode)),
GetModifiersForPrimaryFormat(controller), /*is_testing=*/false);
if (primary_plane.buffer) {
SetDisplayControllerForEnableAndGetProps(
&commit_request, params.drm, params.crtc, params.connector,
params.origin, *params.mode, primary_plane);
} else {
status = false;
}
} else {
status = SetDisableDisplayControllerForDisableAndGetProps(
&commit_request, params.drm, params.crtc);
}
CommitRequest request_for_update = commit_request;
if (status) {
status &= params.drm->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET);
UpdateControllerStateAfterModeset(params, request_for_update, status);
}
statuses.insert(std::make_pair(params.display_id, status));
}
return statuses;
}
void ScreenManager::SetDisplayControllerForEnableAndGetProps(
CommitRequest* commit_request,
const scoped_refptr<DrmDevice>& drm,
uint32_t crtc,
uint32_t connector,
const gfx::Point& origin,
const drmModeModeInfo& mode,
const DrmOverlayPlane& primary) {
HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc);
DCHECK(controllers_.end() != it)
<< "Display controller (crtc=" << crtc << ") doesn't exist.";
HardwareDisplayController* controller = it->get();
CrtcController* crtc_controller = GetCrtcController(controller, drm, crtc);
// If nothing changed just enable the controller. Note, we perform an exact
// comparison on the mode since the refresh rate may have changed.
if (SameMode(mode, crtc_controller->mode()) &&
origin == controller->origin()) {
if (!controller->IsEnabled()) {
// Even if there is a mirrored display, Modeset the CRTC with its mode in
// the original controller so that only this CRTC is affected by the mode.
// Otherwise it could apply a mode with the same resolution and refresh
// rate but with different timings to the other CRTC.
GetModesetControllerProps(commit_request, controller,
controller->origin(), mode, primary);
} else {
// Just get props to re-enable the controller re-using the current state.
GetEnableControllerProps(commit_request, controller, primary);
}
return;
}
// Either the mode or the location of the display changed, so exit mirror
// mode and configure the display independently. If the caller still wants
// mirror mode, subsequent calls configuring the other controllers will
// restore mirror mode.
if (controller->IsMirrored()) {
controllers_.push_back(std::make_unique<HardwareDisplayController>(
controller->RemoveCrtc(drm, crtc), controller->origin()));
it = controllers_.end() - 1;
controller = it->get();
}
GetModesetControllerProps(commit_request, controller, origin, mode, primary);
}
bool ScreenManager::SetDisableDisplayControllerForDisableAndGetProps(
CommitRequest* commit_request,
const scoped_refptr<DrmDevice>& drm,
uint32_t crtc) {
HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc);
if (it != controllers_.end()) {
HardwareDisplayController* controller = it->get();
if (controller->IsMirrored()) {
controllers_.push_back(std::make_unique<HardwareDisplayController>(
controller->RemoveCrtc(drm, crtc), controller->origin()));
controller = controllers_.back().get();
}
controller->GetDisableProps(commit_request);
return true;
}
LOG(ERROR) << "Failed to find display controller crtc=" << crtc;
return false;
}
void ScreenManager::UpdateControllerStateAfterModeset(
const ControllerConfigParams& config,
const CommitRequest& commit_request,
bool did_succeed) {
for (auto& crtc_request : commit_request) {
bool was_enabled = (crtc_request.should_enable());
HardwareDisplayControllers::iterator it =
FindDisplayController(config.drm, crtc_request.crtc_id());
if (it != controllers_.end()) {
it->get()->UpdateState(was_enabled, DrmOverlayPlane::GetPrimaryPlane(
crtc_request.overlays()));
// If the CRTC is mirrored, move it to the mirror controller.
if (did_succeed && was_enabled)
HandleMirrorIfExists(config, it);
}
}
}
void ScreenManager::HandleMirrorIfExists(
const ControllerConfigParams& config,
const HardwareDisplayControllers::iterator& controller) {
gfx::Rect modeset_bounds(config.origin, ModeSize(*config.mode));
HardwareDisplayControllers::iterator mirror =
FindActiveDisplayControllerByLocation(config.drm, modeset_bounds);
// TODO(dnicoara): This is hacky, instead the DrmDisplay and
// CrtcController should be merged and picking the mode should be done
// properly within HardwareDisplayController.
if (mirror != controllers_.end() && controller != mirror) {
// TODO(markyacoub): RemoveCrtc makes a blocking commit to
// DisableOverlayPlanes. This should be redesigned and included as part of
// the Modeset commit.
(*mirror)->AddCrtc((*controller)->RemoveCrtc(config.drm, config.crtc));
controllers_.erase(controller);
}
}
HardwareDisplayController* ScreenManager::GetDisplayController(
const gfx::Rect& bounds) {
HardwareDisplayControllers::iterator it =
FindActiveDisplayControllerByLocation(bounds);
if (it != controllers_.end())
return it->get();
return nullptr;
}
void ScreenManager::AddWindow(gfx::AcceleratedWidget widget,
std::unique_ptr<DrmWindow> window) {
std::pair<WidgetToWindowMap::iterator, bool> result =
window_map_.emplace(widget, std::move(window));
DCHECK(result.second) << "Window already added.";
UpdateControllerToWindowMapping();
}
std::unique_ptr<DrmWindow> ScreenManager::RemoveWindow(
gfx::AcceleratedWidget widget) {
std::unique_ptr<DrmWindow> window = std::move(window_map_[widget]);
window_map_.erase(widget);
DCHECK(window) << "Attempting to remove non-existing window for " << widget;
UpdateControllerToWindowMapping();
return window;
}
DrmWindow* ScreenManager::GetWindow(gfx::AcceleratedWidget widget) {
WidgetToWindowMap::iterator it = window_map_.find(widget);
if (it != window_map_.end())
return it->second.get();
return nullptr;
}
ScreenManager::HardwareDisplayControllers::iterator
ScreenManager::FindDisplayController(const scoped_refptr<DrmDevice>& drm,
uint32_t crtc) {
for (auto it = controllers_.begin(); it != controllers_.end(); ++it) {
if ((*it)->HasCrtc(drm, crtc))
return it;
}
return controllers_.end();
}
ScreenManager::HardwareDisplayControllers::iterator
ScreenManager::FindActiveDisplayControllerByLocation(const gfx::Rect& bounds) {
for (auto it = controllers_.begin(); it != controllers_.end(); ++it) {
gfx::Rect controller_bounds((*it)->origin(), (*it)->GetModeSize());
if (controller_bounds == bounds && (*it)->IsEnabled())
return it;
}
return controllers_.end();
}
ScreenManager::HardwareDisplayControllers::iterator
ScreenManager::FindActiveDisplayControllerByLocation(
const scoped_refptr<DrmDevice>& drm,
const gfx::Rect& bounds) {
for (auto it = controllers_.begin(); it != controllers_.end(); ++it) {
gfx::Rect controller_bounds((*it)->origin(), (*it)->GetModeSize());
if ((*it)->GetDrmDevice() == drm && controller_bounds == bounds &&
(*it)->IsEnabled())
return it;
}
return controllers_.end();
}
void ScreenManager::UpdateControllerToWindowMapping() {
std::map<DrmWindow*, HardwareDisplayController*> window_to_controller_map;
// First create a unique mapping between a window and a controller. Note, a
// controller may be associated with at most 1 window.
for (const auto& controller : controllers_) {
if (!controller->IsEnabled())
continue;
DrmWindow* window = FindWindowAt(
gfx::Rect(controller->origin(), controller->GetModeSize()));
if (!window)
continue;
window_to_controller_map[window] = controller.get();
}
// Apply the new mapping to all windows.
for (auto& pair : window_map_) {
auto it = window_to_controller_map.find(pair.second.get());
HardwareDisplayController* controller = nullptr;
if (it != window_to_controller_map.end())
controller = it->second;
bool should_enable = controller && pair.second->GetController() &&
pair.second->GetController() != controller;
pair.second->SetController(controller);
// If we're moving windows between controllers modeset the controller
// otherwise the controller may be waiting for a page flip while the window
// tries to schedule another buffer.
if (should_enable) {
DrmOverlayPlane primary_plane = GetModesetBuffer(
controller,
gfx::Rect(controller->origin(), controller->GetModeSize()),
GetModifiersForPrimaryFormat(controller), /*is_testing=*/false);
DCHECK(primary_plane.buffer);
CommitRequest commit_request;
GetEnableControllerProps(&commit_request, controller, primary_plane);
controller->GetDrmDevice()->plane_manager()->Commit(
std::move(commit_request), DRM_MODE_ATOMIC_ALLOW_MODESET);
}
}
}
DrmOverlayPlane ScreenManager::GetModesetBuffer(
HardwareDisplayController* controller,
const gfx::Rect& bounds,
const std::vector<uint64_t>& modifiers,
bool is_testing) {
scoped_refptr<DrmDevice> drm = controller->GetDrmDevice();
uint32_t fourcc_format = ui::GetFourCCFormatForOpaqueFramebuffer(
display::DisplaySnapshot::PrimaryFormat());
// Get the buffer that best reflects what the next Page Flip will look like,
// which is using the preferred modifiers from the controllers.
std::unique_ptr<GbmBuffer> buffer =
drm->gbm_device()->CreateBufferWithModifiers(
fourcc_format, bounds.size(), GBM_BO_USE_SCANOUT, modifiers);
if (!buffer) {
LOG(ERROR) << "Failed to create scanout buffer";
return DrmOverlayPlane::Error();
}
// If the current primary plane matches what we need for the next page flip,
// we can clone it.
DrmWindow* window = FindWindowAt(bounds);
if (window) {
const DrmOverlayPlane* primary = window->GetLastModesetBuffer();
const DrmDevice* drm = controller->GetDrmDevice().get();
if (primary && primary->buffer->size() == bounds.size() &&
primary->buffer->drm_device() == drm) {
if (primary->buffer->format_modifier() == buffer->GetFormatModifier())
return primary->Clone();
}
}
scoped_refptr<DrmFramebuffer> framebuffer = DrmFramebuffer::AddFramebuffer(
drm, buffer.get(), buffer->GetSize(), modifiers);
if (!framebuffer) {
LOG(ERROR) << "Failed to add framebuffer for scanout buffer";
return DrmOverlayPlane::Error();
}
if (!is_testing) {
sk_sp<SkSurface> surface = buffer->GetSurface();
if (!surface) {
VLOG(2) << "Can't get a SkSurface from the modeset gbm buffer.";
} else if (!FillModesetBuffer(drm, controller, surface.get(),
buffer->GetFormat())) {
// If we fail to fill the modeset buffer, clear it black to avoid
// displaying an uninitialized framebuffer.
surface->getCanvas()->clear(SK_ColorBLACK);
}
}
return DrmOverlayPlane(framebuffer, nullptr);
}
void ScreenManager::GetEnableControllerProps(
CommitRequest* commit_request,
HardwareDisplayController* controller,
const DrmOverlayPlane& primary) {
DCHECK(!controller->crtc_controllers().empty());
controller->GetEnableProps(commit_request, primary);
}
void ScreenManager::GetModesetControllerProps(
CommitRequest* commit_request,
HardwareDisplayController* controller,
const gfx::Point& origin,
const drmModeModeInfo& mode,
const DrmOverlayPlane& primary) {
DCHECK(!controller->crtc_controllers().empty());
controller->set_origin(origin);
controller->GetModesetProps(commit_request, primary, mode);
}
DrmWindow* ScreenManager::FindWindowAt(const gfx::Rect& bounds) const {
for (auto& pair : window_map_) {
if (pair.second->bounds() == bounds)
return pair.second.get();
}
return nullptr;
}
} // namespace ui