chromium/ui/display/manager/configure_displays_task.cc

// 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 "ui/display/manager/configure_displays_task.h"

#include <cstddef>
#include <string>

#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "ui/display/manager/util/display_manager_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 {

// The epsilon by which a refresh rate value may drift. For example:
// 239.76Hz --> 240Hz. This value was chosen with the consideration of the
// refresh rate value drifts presented in the "Video Formats—Video ID Code and
// Aspect Ratios" table on p.40 of the CTA-861-G standard.
constexpr float kRefreshRateEpsilon = 0.5f;

// 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;

// Consolidates the UMA name prefix creation to one location, since it is used
// in many different call-sites.
const std::string GetUmaNamePrefixForRequest(
    const DisplayConfigureRequest& request) {
  return request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL
             ? std::string("ConfigureDisplays.Internal.Modeset.")
             : std::string("ConfigureDisplays.External.Modeset.");
}

// Find the next best mode that is smaller than |request->mode|. The next best
// mode is found by comparing resolutions, and if those are similar, comparing
// refresh rates. If no mode is found, return nullptr.
const DisplayMode* FindNextMode(const DisplayConfigureRequest& request) {
  DCHECK(request.mode);

  // Internal displays are restricted to their native mode. We do not
  // attempt to downgrade their modes upon failure.
  if (request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
    return nullptr;

  if (request.display->modes().size() <= 1)
    return nullptr;

  const DisplayMode* best_mode = nullptr;
  for (const auto& mode : request.display->modes()) {
    if (*mode < *request.mode && (!best_mode || *mode > *best_mode))
      best_mode = mode.get();
  }

  return best_mode;
}

void LogIfInvalidRequestForInternalDisplay(
    const DisplayConfigureRequest& request) {
  if (request.display->type() != DISPLAY_CONNECTION_TYPE_INTERNAL)
    return;

  if (request.mode == nullptr)
    return;

  if (request.display->native_mode() &&
      *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};

void UpdateResolutionUma(const DisplayConfigureRequest& request,
                         const std::string& uma_name) {
  // Display is powered off.
  if (!request.mode)
    return;

  // First, compute the index of the enum DisplayResolution.
  // The index has to match the definition of the enum in enums.xml.
  const uint32_t samples_list_size = std::size(kDisplayResolutionSamples);
  const gfx::Size size = request.mode->size();
  uint32_t width_idx = 0;
  uint32_t height_idx = 0;
  for (; width_idx < samples_list_size; width_idx++) {
    if (size.width() <= kDisplayResolutionSamples[width_idx])
      break;
  }
  for (; height_idx < samples_list_size; height_idx++) {
    if (size.height() <= kDisplayResolutionSamples[height_idx])
      break;
  }

  int display_resolution_index = 0;
  if (width_idx == samples_list_size || height_idx == samples_list_size) {
    // Check if we are in the overflow bucket.
    display_resolution_index = samples_list_size * samples_list_size + 1;
  } else {
    // Compute the index of DisplayResolution, starting from 1, since 0 is used
    // when powering off the display.
    display_resolution_index = width_idx * samples_list_size + height_idx + 1;
  }

  base::UmaHistogramExactLinear(uma_name, display_resolution_index,
                                samples_list_size * samples_list_size + 2);
}

// A list of common refresh rates that are used to help fit approximate refresh
// rate values into one of the common refresh rate bins.
constexpr float kCommonDisplayRefreshRates[] = {
    24.0, 25.0,  30.0,  45.0,  48.0,  50.0,  60.0, 75.0,
    90.0, 100.0, 120.0, 144.0, 165.0, 200.0, 240.0};

void UpdateRefreshRateUma(const DisplayConfigureRequest& request,
                          const std::string& uma_name) {
  // Display is powered off.
  if (!request.mode)
    return;

  base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
      uma_name, base::HistogramBase::kUmaTargetedHistogramFlag);

  // Check if the refresh value is within an epsilon from one of the common
  // refresh rate values.
  for (float common_rate : kCommonDisplayRefreshRates) {
    const bool is_within_epsilon = std::abs(request.mode->refresh_rate() -
                                            common_rate) < kRefreshRateEpsilon;
    if (is_within_epsilon) {
      histogram->Add(common_rate);
      return;
    }
  }

  // Since this is not a common refresh rate value, report it as is.
  histogram->Add(request.mode->refresh_rate());
}

void UpdateAttemptSucceededUma(
    const std::vector<DisplayConfigureRequest>& requests,
    bool display_success) {
  for (const auto& request : requests) {
    const std::string uma_name_prefix = GetUmaNamePrefixForRequest(request);
    base::UmaHistogramBoolean(uma_name_prefix + "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")
            << " enable_vrr=" << request.enable_vrr;
  }
}

void UpdateFinalStatusUma(
    const std::vector<RequestAndStatusList>& requests_and_statuses,
    ConfigureDisplaysTask::Status status) {
  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).
    const bool sst_display = request->display->base_connector_id() &&
                             request->display->path_topology().empty();
    if (!sst_display)
      mst_external_displays++;

    if (request->display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
      total_external_displays--;

    const std::string uma_name_prefix = GetUmaNamePrefixForRequest(*request);
    if (request_and_status.second) {
      UpdateResolutionUma(*request, uma_name_prefix + "Success.Resolution");
      UpdateRefreshRateUma(*request, uma_name_prefix + "Success.RefreshRate");
    }
    base::UmaHistogramBoolean(uma_name_prefix + "FinalStatus",
                              request_and_status.second);
  }

  base::UmaHistogramEnumeration("ConfigureDisplays.Modeset.FinalTaskStatus",
                                status);

  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);
  }
}

// Updates properties of the |display| according to the given |request| after a
// successful configuration. The display ids of |display| and |request| must
// match.
void UpdateSnapshotAfterConfiguration(
    DisplaySnapshot* display,
    const DisplayConfigurationParams& request) {
  CHECK_EQ(display->display_id(), request.id);
  display->set_current_mode(request.mode.get());
  display->set_origin(request.origin);
  if (display->IsVrrCapable()) {
    display->set_variable_refresh_rate_state(
        request.enable_vrr ? VariableRefreshRateState::kVrrEnabled
                           : VariableRefreshRateState::kVrrDisabled);
  }
}

}  // namespace

DisplayConfigureRequest::DisplayConfigureRequest(DisplaySnapshot* display,
                                                 const DisplayMode* mode,
                                                 const gfx::Point& origin,
                                                 bool enable_vrr)
    : display(display),
      mode(mode ? mode->Clone() : nullptr),
      origin(origin),
      enable_vrr(enable_vrr) {}

DisplayConfigureRequest::DisplayConfigureRequest(DisplaySnapshot* display,
                                                 const DisplayMode* mode,
                                                 const gfx::Point& origin)
    : DisplayConfigureRequest(display,
                              mode,
                              origin,
                              /*enable_vrr=*/false) {}

DisplayConfigureRequest::DisplayConfigureRequest(
    const DisplayConfigureRequest& other)
    : DisplayConfigureRequest(other.display,
                              other.mode.get(),
                              other.origin,
                              other.enable_vrr) {}

DisplayConfigureRequest::~DisplayConfigureRequest() = default;

ConfigureDisplaysTask::ConfigureDisplaysTask(
    NativeDisplayDelegate* delegate,
    const std::vector<DisplayConfigureRequest>& requests,
    ResponseCallback callback,
    ConfigurationType configuration_type)
    : delegate_(delegate),
      requests_(requests),
      configuration_type_(configuration_type),
      callback_(std::move(callback)),
      task_status_(SUCCESS) {
  delegate_->AddObserver(this);
}

ConfigureDisplaysTask::RequestToOriginalMode::RequestToOriginalMode(
    DisplayConfigureRequest* request,
    const DisplayMode* original_mode)
    : request(request),
      original_mode(original_mode ? original_mode->Clone() : nullptr) {}

ConfigureDisplaysTask::RequestToOriginalMode::RequestToOriginalMode(
    const RequestToOriginalMode& other)
    : RequestToOriginalMode(other.request, other.original_mode.get()) {}

ConfigureDisplaysTask::RequestToOriginalMode::~RequestToOriginalMode() =
    default;

ConfigureDisplaysTask::~ConfigureDisplaysTask() {
  delegate_->RemoveObserver(this);
}

void ConfigureDisplaysTask::Run() {
  DCHECK(!requests_.empty());

  const bool is_first_attempt = pending_display_group_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.get(), request.enable_vrr);

    if (is_first_attempt) {
      const std::string uma_name_prefix = GetUmaNamePrefixForRequest(request);
      UpdateResolutionUma(request,
                          uma_name_prefix + "OriginalRequest.Resolution");
    }
  }

  const auto& on_configured =
      is_first_attempt ? &ConfigureDisplaysTask::OnFirstAttemptConfigured
                       : &ConfigureDisplaysTask::OnRetryConfigured;

  display::ModesetFlags modeset_flags{display::ModesetFlag::kTestModeset};
  if (configuration_type_ == kConfigurationTypeSeamless)
    modeset_flags.Put(display::ModesetFlag::kSeamlessModeset);
  delegate_->Configure(
      config_requests,
      base::BindOnce(on_configured, weak_ptr_factory_.GetWeakPtr()),
      modeset_flags);
}

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(
    const std::vector<DisplayConfigurationParams>& request_results,
    bool config_success) {
  UpdateAttemptSucceededUma(requests_, config_success);

  if (!config_success) {
    // Partition |requests_| into smaller groups via
    // |pending_display_group_requests_|, 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());
    // Prep the first group
    for (const auto& pair : pending_display_group_requests_.front()) {
      pair.request->mode =
          pair.original_mode ? pair.original_mode->Clone() : nullptr;
    }
    task_status_ = PARTIAL_SUCCESS;
    Run();
    return;
  }

  // This code execute only when the first modeset attempt fully succeeds.
  // Submit the current |requests_| for modeset. Note that |requests_| is used
  // directly instead of |request_results|, since that is what was tested (ozone
  // sometimes alters the resulting requests to achieve better results during
  // mode matching).
  std::vector<display::DisplayConfigurationParams> config_requests;
  for (const auto& request : requests_) {
    final_requests_status_.emplace_back(&request, true);

    config_requests.emplace_back(request.display->display_id(), request.origin,
                                 request.mode.get(), request.enable_vrr);
  }

  display::ModesetFlags modeset_flags{display::ModesetFlag::kCommitModeset};
  if (configuration_type_ == kConfigurationTypeSeamless)
    modeset_flags.Put(display::ModesetFlag::kSeamlessModeset);
  delegate_->Configure(config_requests,
                       base::BindOnce(&ConfigureDisplaysTask::OnConfigured,
                                      weak_ptr_factory_.GetWeakPtr()),
                       modeset_flags);
}

void ConfigureDisplaysTask::OnRetryConfigured(
    const std::vector<DisplayConfigurationParams>& request_results,
    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 (DowngradeDisplayRequestGroup()) {
      Run();
      return;
    } else {
      // Disable all displays in the current group, since we failed to find an
      // alternative mode. Note that we skip modeset if the latest (or a
      // single) pending group fails. There is no point in disabling displays
      // that are already disabled from previous attempts and failed to change
      // mode.
      for (const auto& pair : pending_display_group_requests_.front()) {
        pair.request->mode.reset();
      }
      task_status_ = ERROR;
    }
  } else {
    // This configuration attempt passed test-modeset. Cache |requests_| so we
    // can use it to modeset the displays once we are done testing, or if no
    // other future attempts succeed. Note that |requests_| is used directly
    // instead of |request_results|, since that is what was tested (ozone
    // sometimes alters the resulting requests to achieve better results during
    // mode matching).
    last_successful_config_parameters_.clear();
    for (const auto& request : requests_) {
      last_successful_config_parameters_.emplace_back(
          request.display->display_id(), request.origin, request.mode.get(),
          request.enable_vrr);
    }
  }

  // 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& pair : pending_display_group_requests_.front())
    final_requests_status_.emplace_back(pair.request, config_success);

  // Subsequent modeset attempts will be done on the next pending display group,
  // if one exists.
  pending_display_group_requests_.pop();
  if (!pending_display_group_requests_.empty()) {
    // Prep the next group
    for (const auto& pair : pending_display_group_requests_.front()) {
      pair.request->mode =
          pair.original_mode ? pair.original_mode->Clone() : nullptr;
    }
    Run();
    return;
  }

  if (task_status_ == ERROR) {
    LOG(WARNING) << "One or more of the connected display groups failed to "
                    "pass test-modeset entirely and will be disabled.";

    if (last_successful_config_parameters_.empty()) {
      LOG(ERROR) << "Display configuration failed. No modeset was attempted.";

      UpdateFinalStatusUma(final_requests_status_, task_status_);
      std::move(callback_).Run(task_status_);
      return;
    }
  }

  // Configure the displays using the last successful configuration parameter
  // list.
  display::ModesetFlags modeset_flags{display::ModesetFlag::kCommitModeset};
  if (configuration_type_ == kConfigurationTypeSeamless)
    modeset_flags.Put(display::ModesetFlag::kSeamlessModeset);
  delegate_->Configure(last_successful_config_parameters_,
                       base::BindOnce(&ConfigureDisplaysTask::OnConfigured,
                                      weak_ptr_factory_.GetWeakPtr()),
                       modeset_flags);
}

void ConfigureDisplaysTask::OnConfigured(
    const std::vector<DisplayConfigurationParams>& request_results,
    bool config_success) {
  if (config_success) {
    base::flat_map<int64_t, DisplaySnapshot*> snapshot_map;
    for (const DisplayConfigureRequest& request : requests_) {
      snapshot_map.emplace(request.display->display_id(), request.display);
    }
    // Use |request_results| to update the snapshots.
    for (const DisplayConfigurationParams& request : request_results) {
      const auto it = snapshot_map.find(request.id);
      CHECK(it != snapshot_map.end());
      UpdateSnapshotAfterConfiguration(it->second, request);
    }
  }

  UpdateFinalStatusUma(final_requests_status_, task_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<ConfigureDisplaysTask::RequestToOriginalMode> request_group;
    for (size_t j = i; j < requests_.size(); ++j) {
      if (connector_id == requests_[j].display->base_connector_id()) {
        // Disable all requests in preparation increment connector retries after
        // mapping them to their original request.
        request_group.emplace_back(&requests_[j], requests_[j].mode.get());
        requests_[j].mode.reset();
      }
    }

    handled_connectors.insert(connector_id);
    pending_display_group_requests_.push(request_group);
  }
}

bool ConfigureDisplaysTask::DowngradeDisplayRequestGroup() {
  auto cmp = [](DisplayConfigureRequest* lhs, DisplayConfigureRequest* rhs) {
    return *lhs->mode < *rhs->mode;
  };
  std::priority_queue<DisplayConfigureRequest*,
                      std::vector<DisplayConfigureRequest*>, decltype(cmp)>
      sorted_requests(cmp);

  for (const auto& pair : pending_display_group_requests_.front()) {
    if (pair.request->display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
      continue;

    if (!pair.request->mode)
      continue;

    sorted_requests.push(pair.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);
    if (next_mode) {
      next_request->mode = next_mode->Clone();
      return true;
    }
  }

  return false;
}

}  // namespace display