blob: b679a30ad1b9111c5059ea662f93f90569b9f1a1 [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/display/manager/configure_displays_task.h"
#include <cstddef>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "ui/display/manager/display_util.h"
#include "ui/display/types/display_configuration_params.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/native_display_delegate.h"
namespace display {
namespace {
// Because we do not offer hardware mirroring, the maximal number of external
// displays that can be configured is limited by the number of available CRTCs,
// which is usually three. Since the lifetime of the UMA using this value is one
// year (exp. Nov. 2021), five buckets are more than enough for
// its histogram (between 0 to 4 external monitors).
constexpr int kMaxDisplaysCount = 5;
// Find the next best mode after |display_mode|. If none can be found return
// nullptr.
const DisplayMode* FindNextMode(const DisplaySnapshot& display_state,
const DisplayMode* display_mode) {
// Internal displays are restricted to their native mode. We do not attempt to
// downgrade their modes upon failure.
if (display_state.type() == DISPLAY_CONNECTION_TYPE_INTERNAL) {
return nullptr;
}
if (!display_mode)
return nullptr;
int best_mode_pixels = 0;
const DisplayMode* best_mode = nullptr;
int current_mode_pixels = display_mode->size().GetArea();
for (const std::unique_ptr<const DisplayMode>& mode : display_state.modes()) {
int pixel_count = mode->size().GetArea();
if (pixel_count < current_mode_pixels && pixel_count > best_mode_pixels) {
best_mode = mode.get();
best_mode_pixels = pixel_count;
}
}
return best_mode;
}
void LogIfInvalidRequestForInternalDisplay(
const DisplayConfigureRequest& request) {
if (request.display->type() != DISPLAY_CONNECTION_TYPE_INTERNAL)
return;
if (request.mode == nullptr)
return;
if (request.mode == request.display->native_mode())
return;
LOG(ERROR) << "A mode other than the preferred mode was requested for the "
"internal display: preferred="
<< request.display->native_mode()->ToString()
<< " vs. requested=" << request.mode->ToString()
<< ". Current mode="
<< (request.display->current_mode()
? request.display->current_mode()->ToString()
: "nullptr (disabled)")
<< ".";
}
// Samples used to define buckets used by DisplayResolution enum.
// The enum is used to record screen resolution statistics.
const int32_t kDisplayResolutionSamples[] = {1024, 1280, 1440, 1920,
2560, 3840, 5120, 7680};
// Computes the index of the enum DisplayResolution.
// The index has to match the definition of the enum in enums.xml
int ComputeDisplayResolutionEnum(const DisplayMode* mode) {
if (!mode)
return 0; // Display is powered off
const gfx::Size size = mode->size();
uint32_t width_idx = 0;
uint32_t height_idx = 0;
for (; width_idx < base::size(kDisplayResolutionSamples); width_idx++) {
if (size.width() <= kDisplayResolutionSamples[width_idx])
break;
}
for (; height_idx < base::size(kDisplayResolutionSamples); height_idx++) {
if (size.height() <= kDisplayResolutionSamples[height_idx])
break;
}
if (width_idx == base::size(kDisplayResolutionSamples) ||
height_idx == base::size(kDisplayResolutionSamples))
return base::size(kDisplayResolutionSamples) *
base::size(kDisplayResolutionSamples) +
1; // Overflow bucket
// Computes the index of DisplayResolution, starting from 1, since 0 is used
// when powering off the display.
return width_idx * base::size(kDisplayResolutionSamples) + height_idx + 1;
}
void UpdateResolutionAndRefreshRateUma(const DisplayConfigureRequest& request) {
const bool internal =
request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
base::UmaHistogramExactLinear(
internal ? "ConfigureDisplays.Internal.Modeset.Resolution"
: "ConfigureDisplays.External.Modeset.Resolution",
ComputeDisplayResolutionEnum(request.mode),
base::size(kDisplayResolutionSamples) *
base::size(kDisplayResolutionSamples) +
2);
base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
internal ? "ConfigureDisplays.Internal.Modeset.RefreshRate"
: "ConfigureDisplays.External.Modeset.RefreshRate",
1, 240, 18, base::HistogramBase::kUmaTargetedHistogramFlag);
histogram->Add(request.mode ? std::round(request.mode->refresh_rate()) : 0);
}
void UpdateAttemptSucceededUma(
const std::vector<DisplayConfigureRequest>& requests,
bool display_success) {
for (const auto& request : requests) {
const bool internal =
request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
base::UmaHistogramBoolean(
internal ? "ConfigureDisplays.Internal.Modeset.AttemptSucceeded"
: "ConfigureDisplays.External.Modeset.AttemptSucceeded",
display_success);
VLOG(2) << "Configured status=" << display_success
<< " display=" << request.display->display_id()
<< " origin=" << request.origin.ToString()
<< " mode=" << (request.mode ? request.mode->ToString() : "null");
}
}
void UpdateFinalStatusUma(
const std::vector<RequestAndStatusList>& requests_and_statuses) {
int mst_external_displays = 0;
size_t total_external_displays = requests_and_statuses.size();
for (auto& request_and_status : requests_and_statuses) {
const DisplayConfigureRequest& request = request_and_status.first;
// Is this display SST (single-stream vs. MST multi-stream).
bool sst_display = request.display->base_connector_id() &&
request.display->path_topology().empty();
if (!sst_display)
mst_external_displays++;
bool internal = request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL;
if (internal)
total_external_displays--;
base::UmaHistogramBoolean(
internal ? "ConfigureDisplays.Internal.Modeset.FinalStatus"
: "ConfigureDisplays.External.Modeset.FinalStatus",
request_and_status.second);
}
base::UmaHistogramExactLinear(
"ConfigureDisplays.Modeset.TotalExternalDisplaysCount",
base::checked_cast<int>(total_external_displays), kMaxDisplaysCount);
base::UmaHistogramExactLinear(
"ConfigureDisplays.Modeset.MstExternalDisplaysCount",
mst_external_displays, kMaxDisplaysCount);
if (total_external_displays > 0) {
const int mst_displays_percentage =
100.0 * mst_external_displays / total_external_displays;
UMA_HISTOGRAM_PERCENTAGE(
"ConfigureDisplays.Modeset.MstExternalDisplaysPercentage",
mst_displays_percentage);
}
}
} // namespace
DisplayConfigureRequest::DisplayConfigureRequest(DisplaySnapshot* display,
const DisplayMode* mode,
const gfx::Point& origin)
: display(display), mode(mode), origin(origin) {}
ConfigureDisplaysTask::ConfigureDisplaysTask(
NativeDisplayDelegate* delegate,
const std::vector<DisplayConfigureRequest>& requests,
ResponseCallback callback)
: delegate_(delegate),
requests_(requests),
callback_(std::move(callback)),
task_status_(SUCCESS) {
delegate_->AddObserver(this);
}
ConfigureDisplaysTask::~ConfigureDisplaysTask() {
delegate_->RemoveObserver(this);
}
void ConfigureDisplaysTask::Run() {
DCHECK(!requests_.empty());
std::vector<display::DisplayConfigurationParams> config_requests;
for (const auto& request : requests_) {
LogIfInvalidRequestForInternalDisplay(request);
config_requests.emplace_back(request.display->display_id(), request.origin,
request.mode);
UpdateResolutionAndRefreshRateUma(request);
}
const auto& on_configured =
pending_display_group_requests_.empty()
? &ConfigureDisplaysTask::OnFirstAttemptConfigured
: &ConfigureDisplaysTask::OnRetryConfigured;
delegate_->Configure(
config_requests,
base::BindOnce(on_configured, weak_ptr_factory_.GetWeakPtr()));
}
void ConfigureDisplaysTask::OnConfigurationChanged() {}
void ConfigureDisplaysTask::OnDisplaySnapshotsInvalidated() {
// From now on, don't access |requests_[index]->display|; they're invalid.
task_status_ = ERROR;
weak_ptr_factory_.InvalidateWeakPtrs();
std::move(callback_).Run(task_status_);
}
void ConfigureDisplaysTask::OnFirstAttemptConfigured(bool config_success) {
UpdateAttemptSucceededUma(requests_, config_success);
if (!config_success) {
// Partition |requests_| into smaller groups, update the task's state, and
// initiate the retry logic. The next time |delegate_|->Configure()
// terminates OnRetryConfigured() will be executed instead.
PartitionRequests();
DCHECK(!pending_display_group_requests_.empty());
requests_ = pending_display_group_requests_.front();
task_status_ = PARTIAL_SUCCESS;
Run();
return;
}
// This code execute only when the first modeset attempt fully succeeds.
// Update the displays' status and report success.
for (const auto& request : requests_) {
request.display->set_current_mode(request.mode);
request.display->set_origin(request.origin);
final_requests_status_.emplace_back(std::make_pair(request, true));
}
UpdateFinalStatusUma(final_requests_status_);
std::move(callback_).Run(task_status_);
}
void ConfigureDisplaysTask::OnRetryConfigured(bool config_success) {
UpdateAttemptSucceededUma(requests_, config_success);
if (!config_success) {
// If one of the largest display request can be downgraded, try again.
// Otherwise this configuration task is a failure.
if (DowngradeLargestRequestWithAlternativeModes()) {
Run();
return;
} else {
task_status_ = ERROR;
}
}
// This code executes only when this display group request fully succeeds or
// fails to modeset. Update the final status of this group.
for (const auto& request : requests_) {
final_requests_status_.emplace_back(
std::make_pair(request, config_success));
if (config_success) {
request.display->set_current_mode(request.mode);
request.display->set_origin(request.origin);
}
}
// Subsequent modeset attempts will be done on the next pending display group,
// if one exists.
pending_display_group_requests_.pop();
requests_.clear();
if (!pending_display_group_requests_.empty()) {
requests_ = pending_display_group_requests_.front();
Run();
return;
}
// No more display groups to retry.
UpdateFinalStatusUma(final_requests_status_);
std::move(callback_).Run(task_status_);
}
void ConfigureDisplaysTask::PartitionRequests() {
pending_display_group_requests_ = PartitionedRequestsQueue();
base::flat_set<uint64_t> handled_connectors;
for (size_t i = 0; i < requests_.size(); ++i) {
uint64_t connector_id = requests_[i].display->base_connector_id();
if (handled_connectors.find(connector_id) != handled_connectors.end())
continue;
std::vector<DisplayConfigureRequest> request_group;
for (size_t j = i; j < requests_.size(); ++j) {
if (connector_id == requests_[j].display->base_connector_id())
request_group.push_back(requests_[j]);
}
pending_display_group_requests_.push(request_group);
handled_connectors.insert(connector_id);
}
}
bool ConfigureDisplaysTask::DowngradeLargestRequestWithAlternativeModes() {
auto cmp = [](DisplayConfigureRequest* lhs, DisplayConfigureRequest* rhs) {
return *lhs->mode < *rhs->mode;
};
std::priority_queue<DisplayConfigureRequest*,
std::vector<DisplayConfigureRequest*>, decltype(cmp)>
sorted_requests(cmp);
for (auto& request : requests_) {
if (request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
continue;
if (!request.mode)
continue;
sorted_requests.push(&request);
}
// Fail if there are no viable candidates to downgrade
if (sorted_requests.empty())
return false;
while (!sorted_requests.empty()) {
DisplayConfigureRequest* next_request = sorted_requests.top();
sorted_requests.pop();
const DisplayMode* next_mode =
FindNextMode(*next_request->display, next_request->mode);
if (next_mode) {
next_request->mode = next_mode;
return true;
}
}
return false;
}
} // namespace display