// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display/overlay_processor_surface_control.h"
#include <memory>
#include <optional>
#include "base/android/build_info.h"
#include "base/feature_list.h"
#include "cc/base/math_util.h"
#include "components/viz/common/features.h"
#include "components/viz/service/display/overlay_strategy_single_on_top.h"
#include "components/viz/service/display/overlay_strategy_underlay.h"
#include "ui/gfx/android/android_surface_control_compat.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/overlay_transform_utils.h"
namespace viz {
namespace {
BASE_FEATURE(kAndroidSurfaceControlSingleOnTOp,
"AndroidSurfaceControlSingleOnTOp",
base::FEATURE_ENABLED_BY_DEFAULT);
gfx::RectF ClipFromOrigin(gfx::RectF input) {
if (input.x() < 0.f) {
input.set_width(input.width() + input.x());
input.set_x(0.f);
}
if (input.y() < 0) {
input.set_height(input.height() + input.y());
input.set_y(0.f);
}
return input;
}
} // namespace
OverlayProcessorSurfaceControl::OverlayProcessorSurfaceControl() {
// Android webview never sets |frame_sequence_number_| for the overlay
// processor. Android Chrome does set this variable because it does call draw.
// However, it also may not update this variable when displaying an overlay.
// Therefore, our damage tracking for overlays is incorrect and we must ignore
// the thresholding of prioritization.
// TODO(crbug.com/40236858): We should take issue into account when trying to
// find a replacement for number-of-scanouts.
prioritization_config_.changing_threshold = false;
prioritization_config_.damage_rate_threshold = false;
strategies_.push_back(std::make_unique<OverlayStrategyUnderlay>(
this, OverlayStrategyUnderlay::OpaqueMode::AllowTransparentCandidates));
if (base::FeatureList::IsEnabled(kAndroidSurfaceControlSingleOnTOp)) {
strategies_.push_back(std::make_unique<OverlayStrategySingleOnTop>(this));
// Prefer underlay strategy because it is more mature on Android. So turn
// off sorting and just attempt the strategies in insertion order.
prioritization_config_.power_gain_sort = false;
}
}
OverlayProcessorSurfaceControl::~OverlayProcessorSurfaceControl() {}
bool OverlayProcessorSurfaceControl::IsOverlaySupported() const {
return true;
}
bool OverlayProcessorSurfaceControl::NeedsSurfaceDamageRectList() const {
return true;
}
void OverlayProcessorSurfaceControl::CheckOverlaySupportImpl(
const OverlayProcessorInterface::OutputSurfaceOverlayPlane* primary_plane,
OverlayCandidateList* candidates) {
DCHECK(!candidates->empty());
for (auto& candidate : *candidates) {
if (auto override_color_space = GetOverrideColorSpace()) {
candidate.color_space = override_color_space.value();
candidate.hdr_metadata = gfx::HDRMetadata();
}
// Check if the ColorSpace is supported
if (!gfx::SurfaceControl::SupportsColorSpace(candidate.color_space)) {
candidate.overlay_handled = false;
return;
}
// Aggregator adds `display_transform_` to all quads, which is then added to
// `candidate.transform` here. `display_transform_` only applies to content
// on the main plane so it needs to be removed candidate it its own plane.
gfx::OverlayTransform candidate_overlay_transform = OverlayTransformsConcat(
absl::get<gfx::OverlayTransform>(candidate.transform),
InvertOverlayTransform(display_transform_));
// Note the transform below using `candidate_overlay_transform` to compute
// clipped and normalized `uv_rect` is only tested with NONE and
// FLIP_VERTICAL.
if (candidate_overlay_transform != gfx::OVERLAY_TRANSFORM_NONE &&
candidate_overlay_transform != gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL) {
candidate.overlay_handled = false;
return;
}
candidate.transform = candidate_overlay_transform;
gfx::RectF orig_display_rect = candidate.display_rect;
gfx::RectF display_rect = orig_display_rect;
if (candidate.clip_rect)
display_rect.Intersect(gfx::RectF(*candidate.clip_rect));
// The framework doesn't support display rects positioned at a negative
// offset.
display_rect = ClipFromOrigin(display_rect);
if (display_rect.IsEmpty()) {
candidate.overlay_handled = false;
return;
}
// The display rect above includes the |display_transform_| while the rects
// sent to the platform API need to be in the logical screen space.
const gfx::Transform display_inverse = gfx::OverlayTransformToTransform(
gfx::InvertOverlayTransform(display_transform_),
gfx::SizeF(viewport_size_));
orig_display_rect = display_inverse.MapRect(orig_display_rect);
display_rect = display_inverse.MapRect(display_rect);
candidate.unclipped_display_rect = orig_display_rect;
candidate.unclipped_uv_rect = candidate.uv_rect;
candidate.display_rect = gfx::RectF(gfx::ToEnclosingRect(display_rect));
// Transform `uv_rect` to display space, then clip, then transform back.
candidate.uv_rect = gfx::OverlayTransformToTransform(
candidate_overlay_transform, gfx::SizeF(1, 1))
.MapRect(candidate.uv_rect);
candidate.uv_rect = cc::MathUtil::ScaleRectProportional(
candidate.uv_rect, orig_display_rect, candidate.display_rect);
candidate.uv_rect =
gfx::OverlayTransformToTransform(
gfx::InvertOverlayTransform(candidate_overlay_transform),
gfx::SizeF(1, 1))
.MapRect(candidate.uv_rect);
candidate.overlay_handled = true;
}
}
void OverlayProcessorSurfaceControl::AdjustOutputSurfaceOverlay(
std::optional<OutputSurfaceOverlayPlane>* output_surface_plane) {
// For surface control, we should always have a valid |output_surface_plane|
// here.
DCHECK(output_surface_plane && output_surface_plane->has_value());
OutputSurfaceOverlayPlane& plane = output_surface_plane->value();
DCHECK(gfx::SurfaceControl::SupportsColorSpace(plane.color_space))
<< "The main overlay must only use color space supported by the "
"device";
DCHECK_EQ(plane.transform, gfx::OVERLAY_TRANSFORM_NONE);
DCHECK(plane.display_rect == ClipFromOrigin(plane.display_rect));
plane.transform = display_transform_;
const gfx::Transform display_inverse = gfx::OverlayTransformToTransform(
gfx::InvertOverlayTransform(display_transform_),
gfx::SizeF(viewport_size_));
plane.display_rect = display_inverse.MapRect(plane.display_rect);
plane.display_rect = gfx::RectF(gfx::ToEnclosingRect(plane.display_rect));
// Call the base class implementation.
OverlayProcessorUsingStrategy::AdjustOutputSurfaceOverlay(
output_surface_plane);
}
gfx::Rect OverlayProcessorSurfaceControl::GetOverlayDamageRectForOutputSurface(
const OverlayCandidate& candidate) const {
// Should only be called after ProcessForOverlays on handled candidates.
DCHECK(candidate.overlay_handled);
// We transform the candidate's display rect to the logical screen space (used
// by the ui when preparing the frame) that the SurfaceControl expects it to
// be in. So in order to provide a damage rect which maps to the
// OutputSurface's main plane, we need to undo that transformation. But only
// if the overlay is in handled state, since the modification above is only
// applied when we mark the overlay as handled.
gfx::Size viewport_size_pre_display_transform(viewport_size_.height(),
viewport_size_.width());
auto transform = gfx::OverlayTransformToTransform(
display_transform_, gfx::SizeF(viewport_size_pre_display_transform));
return transform.MapRect(gfx::ToEnclosingRect(candidate.display_rect));
}
bool OverlayProcessorSurfaceControl::SupportsFlipRotateTransform() const {
return true;
}
void OverlayProcessorSurfaceControl::SetDisplayTransformHint(
gfx::OverlayTransform transform) {
display_transform_ = transform;
}
void OverlayProcessorSurfaceControl::SetViewportSize(
const gfx::Size& viewport_size) {
viewport_size_ = viewport_size;
}
std::optional<gfx::ColorSpace>
OverlayProcessorSurfaceControl::GetOverrideColorSpace() {
// Historically, android media was hardcoding color space to srgb and it
// wasn't possible to overlay with arbitrary colorspace on pre-S devices, so
// we keep old behaviour there.
static bool is_older_than_s =
base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_S;
if (is_older_than_s) {
return gfx::ColorSpace::CreateSRGB();
}
return std::nullopt;
}
} // namespace viz