blob: f4126b75d98bc8880fb02f4557b0a65f76959120 [file] [log] [blame]
// 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_ozone.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "build/chromecast_buildflags.h"
#include "components/viz/common/buildflags.h"
#include "components/viz/common/features.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "components/viz/service/display/overlay_strategy_fullscreen.h"
#include "components/viz/service/display/overlay_strategy_single_on_top.h"
#include "components/viz/service/display/overlay_strategy_underlay.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "gpu/config/gpu_finch_features.h"
#include "ui/display/display_features.h"
#include "ui/gl/gl_switches.h"
#endif
#if BUILDFLAG(ENABLE_CAST_OVERLAY_STRATEGY)
#include "components/viz/service/display/overlay_strategy_underlay_cast.h"
#endif
namespace viz {
namespace {
gfx::ColorSpace GetColorSpaceForOzone(SharedImageFormat format,
const gfx::ColorSpace& orig_color_space) {
#if BUILDFLAG(IS_CHROMEOS)
// The goal here is to ensure that the hw overlay path and the compositing
// path produce visually identical results. Otherwise, the user would perceive
// flickering when a particular resource (like a video) goes in and out of hw
// overlays repeatedly (for whatever reason).
//
// The Vulkan path is able to treat YUV 4:2:0 BT.601 and BT.709 resources
// correctly (see gpu::CreateVulkanYcbcrConversionInfo()). The GL path
// currently doesn't and just treats YUV 4:2:0 BT.709 resources as if they
// were BT.601 (but see the TODO below). Therefore, when Vulkan is enabled,
// we allow the overlay path to also distinguish between the two YUV
// encodings. Otherwise, we always use BT.601. This should ensure visual
// consistency between the compositing and the hw overlays path in both GL
// and Vulkan ChromeOS devices.
//
// TODO(b/233667677): the only reason the GL path treats YUV 4:2:0 BT.709
// resources as BT.601 is because of https://crrev.com/c/2336347. If we can
// be confident that hw overlays on all platforms can deal with BT.601 and
// BT.709 correctly, then we don't need GetColorSpaceForOzone() at all and we
// can just the pass the color space as-is to Ozone (and we would also need to
// change the GL path to distinguish between BT.601 and BT.709, see
// NativePixmapEGLBinding::InitializeFromNativePixmap()).
if ((format == MultiPlaneFormat::kNV12) &&
(!base::FeatureList::IsEnabled(features::kVulkan) ||
!base::FeatureList::IsEnabled(features::kDefaultANGLEVulkan) ||
!base::FeatureList::IsEnabled(features::kVulkanFromANGLE))) {
return orig_color_space.GetWithMatrixAndRange(
gfx::ColorSpace::MatrixID::SMPTE170M, orig_color_space.GetRangeID());
}
#endif
return orig_color_space;
}
void ConvertToOzoneOverlaySurface(
const OverlayCandidate& overlay_candidate,
ui::OverlaySurfaceCandidate* ozone_candidate) {
ozone_candidate->transform = overlay_candidate.transform;
ozone_candidate->format = overlay_candidate.format;
ozone_candidate->color_space =
GetColorSpaceForOzone(ozone_candidate->format,
/*orig_color_space=*/overlay_candidate.color_space);
ozone_candidate->display_rect = overlay_candidate.display_rect;
ozone_candidate->crop_rect = overlay_candidate.uv_rect;
ozone_candidate->clip_rect = overlay_candidate.clip_rect;
ozone_candidate->is_opaque = overlay_candidate.is_opaque;
ozone_candidate->opacity = overlay_candidate.opacity;
ozone_candidate->plane_z_order = overlay_candidate.plane_z_order;
ozone_candidate->buffer_size = overlay_candidate.resource_size_in_pixels;
ozone_candidate->requires_overlay = overlay_candidate.requires_overlay;
ozone_candidate->priority_hint = overlay_candidate.priority_hint;
ozone_candidate->rounded_corners = overlay_candidate.rounded_corners;
ozone_candidate->overlay_type = overlay_candidate.overlay_type;
// TODO(crbug.com/40219248): OverlaySurfaceCandidate to SkColor4f
// That can be a solid color quad.
if (!overlay_candidate.is_solid_color && ozone_candidate->background_color &&
overlay_candidate.color) {
ozone_candidate->background_color = overlay_candidate.color->toSkColor();
}
}
void ConvertToTiledOzoneOverlaySurface(
const OverlayCandidate& overlay_candidate,
ui::OverlaySurfaceCandidate* ozone_candidate) {
ozone_candidate->transform = gfx::OVERLAY_TRANSFORM_NONE;
ozone_candidate->format = SinglePlaneFormat::kRGBA_8888;
ozone_candidate->color_space =
GetColorSpaceForOzone(ozone_candidate->format,
/*orig_color_space=*/overlay_candidate.color_space);
ozone_candidate->display_rect = overlay_candidate.display_rect;
ozone_candidate->crop_rect = gfx::RectF(1.0, 1.0);
ozone_candidate->clip_rect = std::nullopt;
ozone_candidate->is_opaque = overlay_candidate.is_opaque;
ozone_candidate->opacity = overlay_candidate.opacity;
ozone_candidate->plane_z_order = overlay_candidate.plane_z_order;
ozone_candidate->buffer_size =
gfx::Size(static_cast<int>(overlay_candidate.display_rect.width()),
static_cast<int>(overlay_candidate.display_rect.height()));
ozone_candidate->requires_overlay = true;
ozone_candidate->priority_hint = overlay_candidate.priority_hint;
ozone_candidate->rounded_corners = overlay_candidate.rounded_corners;
ozone_candidate->native_pixmap = nullptr;
ozone_candidate->overlay_type = overlay_candidate.overlay_type;
}
#if BUILDFLAG(IS_CHROMEOS)
bool IsYUVColorSpace(const gfx::ColorSpace& color_space) {
SkYUVColorSpace yuv_color_space;
return color_space.ToSkYUVColorSpace(&yuv_color_space);
}
bool AllowColorSpaceCombination(
SharedImageFormat source_format,
const gfx::ColorSpace& source_color_space,
const gfx::ColorSpace& destination_color_space) {
// Allow invalid source color spaces because the assumption is that the
// compositor won't do a color space conversion in this case anyway, so it
// should be consistent with the overlay path.
if (!source_color_space.IsValid())
return true;
// Since https://crrev.com/c/2336347, we force BT.601/narrow for the
// COLOR_ENCODING and COLOR_RANGE DRM/KMS properties. On the other hand, the
// compositor is able to handle different YUV encodings and ranges. Therefore,
// in theory, if we don't want to see a difference between overlays and
// compositing, we should not promote video frames to overlays unless they
// actually use BT.601/narrow.
//
// In practice, however, we expect to see lots of BT.709 video frames, and we
// don't want to reject all of them for overlays because the visual difference
// between BT.601/narrow and BT.709/narrow is not expected to be much.
// Therefore, in being consistent with the values we provide for
// EGL_YUV_COLOR_SPACE_HINT_EXT/EGL_SAMPLE_RANGE_HINT_EXT, we'll only allow
// frames that use non-BT.2020 with non-full range. In those cases, the
// compositor and the display controller are expected to render the frames
// equally (and decently - with the understanding that the final result may
// not be fully correct).
//
// TODO(b/233667677): Remove this when we've plumbed the YUV encoding and
// range to DRM/KMS. At that point, we need to ensure that
// EGL_YUV_COLOR_SPACE_HINT_EXT/EGL_SAMPLE_RANGE_HINT_EXT would also get the
// same values as DRM/KMS.
if ((source_format == MultiPlaneFormat::kNV12 ||
source_format == MultiPlaneFormat::kYV12 ||
source_format == MultiPlaneFormat::kP010) &&
(source_color_space.GetMatrixID() ==
gfx::ColorSpace::MatrixID::BT2020_NCL ||
source_color_space.GetRangeID() == gfx::ColorSpace::RangeID::FULL)) {
DCHECK(IsYUVColorSpace(source_color_space));
return false;
}
// Do not promote SDR content to overlay if the screen is in HDR10 mode.
// TODO(b/367739334): Fix color conversion layer in skia_renderer.cc
if (base::FeatureList::IsEnabled(
display::features::kEnableExternalDisplayHDR10Mode) &&
destination_color_space.GetContentColorUsage() ==
gfx::ContentColorUsage::kHDR) {
return source_color_space.GetContentColorUsage() ==
destination_color_space.GetContentColorUsage();
}
// Allow color space mismatches as long as either a) the source color space is
// SRGB; or b) both the source and destination color spaces have the same
// color usage. It is possible that case (a) still allows for visible color
// inconsistency between overlays and composition, but we'll address that case
// if it comes up.
return source_color_space.GetContentColorUsage() ==
gfx::ContentColorUsage::kSRGB ||
source_color_space.GetContentColorUsage() ==
destination_color_space.GetContentColorUsage();
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
// |overlay_candidates| is an object used to answer questions about possible
// overlays configurations.
// |available_strategies| is a list of overlay strategies that should be
// initialized by InitializeStrategies.
OverlayProcessorOzone::OverlayProcessorOzone(
std::unique_ptr<ui::OverlayCandidatesOzone> overlay_candidates,
std::vector<OverlayStrategy> available_strategies,
std::unique_ptr<PixmapProvider> pixmap_provider)
: overlay_candidates_(std::move(overlay_candidates)),
available_strategies_(std::move(available_strategies)),
pixmap_provider_(std::move(pixmap_provider)) {
for (OverlayStrategy strategy : available_strategies_) {
switch (strategy) {
case OverlayStrategy::kFullscreen:
strategies_.push_back(
std::make_unique<OverlayStrategyFullscreen>(this));
break;
case OverlayStrategy::kSingleOnTop:
strategies_.push_back(
std::make_unique<OverlayStrategySingleOnTop>(this));
break;
case OverlayStrategy::kUnderlay:
strategies_.push_back(std::make_unique<OverlayStrategyUnderlay>(this));
break;
#if BUILDFLAG(ENABLE_CAST_OVERLAY_STRATEGY)
case OverlayStrategy::kUnderlayCast:
strategies_.push_back(
std::make_unique<OverlayStrategyUnderlayCast>(this));
break;
#endif
default:
NOTREACHED();
}
}
}
OverlayProcessorOzone::~OverlayProcessorOzone() = default;
bool OverlayProcessorOzone::IsOverlaySupported() const {
return true;
}
bool OverlayProcessorOzone::NeedsSurfaceDamageRectList() const {
return true;
}
bool OverlayProcessorOzone::SupportsFlipRotateTransform() const {
// TODO(petermcneeley): Test and enable for ChromeOS.
#if BUILDFLAG(IS_CASTOS)
return false;
#else
return false;
#endif
}
void OverlayProcessorOzone::NotifyOverlayPromotion(
DisplayResourceProvider* display_resource_provider,
const OverlayCandidateList& candidate_list,
const QuadList& quad_list) {
if (!overlay_candidates_) {
return;
}
std::vector<gfx::OverlayType> promoted_overlay_types;
promoted_overlay_types.reserve(candidate_list.size());
for (const auto& candidate : candidate_list) {
promoted_overlay_types.emplace_back(candidate.overlay_type);
}
overlay_candidates_->NotifyOverlayPromotion(
std::move(promoted_overlay_types));
}
void OverlayProcessorOzone::CheckOverlaySupportImpl(
const std::optional<OverlayCandidate>& primary_plane,
OverlayCandidateList* surfaces) {
MaybeObserveHardwareCapabilities();
auto full_size = surfaces->size();
if (primary_plane)
full_size += 1;
ui::OverlayCandidatesOzone::OverlaySurfaceCandidateList ozone_surface_list(
full_size);
// Convert OverlayCandidateList to OzoneSurfaceCandidateList.
{
auto ozone_surface_iterator = ozone_surface_list.begin();
// For ozone-cast, there will not be a primary_plane.
if (primary_plane) {
ConvertToOzoneOverlaySurface(*primary_plane, &(*ozone_surface_iterator));
// TODO(crbug.com/40153057): Fuchsia claims support for presenting primary
// plane as overlay, but does not provide a mailbox. Handle this case.
#if !BUILDFLAG(IS_FUCHSIA)
if (pixmap_provider_) {
bool result = SetNativePixmapForCandidate(&(*ozone_surface_iterator),
primary_plane->mailbox,
/*is_primary=*/true);
#if BUILDFLAG(IS_CHROMEOS)
if (!result) {
// For ChromeOS HW protected content, there's a race condition that
// can occur here where the mailbox for the native pixmap isn't
// registered yet so we will fail to promote to overlay due to this
// check. Allow us to proceed even w/out the native pixmap in that
// case as it will still succeed and would otherwise cause black
// flashing between frames while the race condition is completing.
// We don't know if we have a required overlay yet, so we need to
// go through all the candidates to see if one is present.
for (auto surface_iterator = surfaces->cbegin();
surface_iterator < surfaces->cend(); surface_iterator++) {
if (surface_iterator->requires_overlay) {
DLOG(WARNING)
<< "Allowing required overlay with missing primary pixmap";
result = true;
break;
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
// We cannot validate an overlay configuration without the buffer for
// primary plane present.
if (!result) {
for (auto& candidate : *surfaces) {
candidate.overlay_handled = false;
}
return;
}
}
#endif
ozone_surface_iterator++;
}
auto surface_iterator = surfaces->cbegin();
for (; ozone_surface_iterator < ozone_surface_list.end() &&
surface_iterator < surfaces->cend();
ozone_surface_iterator++, surface_iterator++) {
if (surface_iterator->needs_detiling) {
ConvertToTiledOzoneOverlaySurface(*surface_iterator,
&(*ozone_surface_iterator));
continue;
}
ConvertToOzoneOverlaySurface(*surface_iterator,
&(*ozone_surface_iterator));
#if BUILDFLAG(IS_CHROMEOS)
// On Chrome OS, skip the candidate if we think a color space combination
// might cause visible color differences between compositing and overlays.
// The reason is that on Chrome OS, we don't yet have an API to set up
// color space conversion per plane. Note however that we should only do
// this if the candidate does not require an overlay (e.g., for protected
// content, it's better to display it with an incorrect color space than
// to not display it at all).
// TODO(b/181974042): plumb the color space all the way to the ozone DRM
// backend when we get an API for per-plane color management.
if (!surface_iterator->requires_overlay &&
!AllowColorSpaceCombination(
/*source_format=*/surface_iterator->format,
/*source_color_space=*/surface_iterator->color_space,
/*destination_color_space=*/primary_plane_color_space_)) {
*ozone_surface_iterator = ui::OverlaySurfaceCandidate();
ozone_surface_iterator->plane_z_order = surface_iterator->plane_z_order;
continue;
}
#endif // BUILDFLAG(IS_CHROMEOS)
if (pixmap_provider_) {
bool result = SetNativePixmapForCandidate(&(*ozone_surface_iterator),
surface_iterator->mailbox,
/*is_primary=*/false);
#if BUILDFLAG(IS_CHROMEOS)
if (!result && surface_iterator->requires_overlay) {
// For ChromeOS HW protected content, same condition as above
// regarding missing pixmaps.
result = true;
DLOG(WARNING) << "Allowing required overlay with missing pixmap";
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Skip the candidate if the corresponding NativePixmap is not found.
if (!result) {
*ozone_surface_iterator = ui::OverlaySurfaceCandidate();
ozone_surface_iterator->plane_z_order =
surface_iterator->plane_z_order;
}
}
}
#if BUILDFLAG(IS_CHROMEOS)
// Some platforms (e.g. AMD) do not provide a dedicated cursor plane, and
// the display hardware will need to blit the cursor to the topmost plane.
// If the topmost plane is scaled/translated, the cursor will then be
// transformed along with it. Thus, we need to reject the topmost candidate
// if the buffer size is transformed at all.
if (!has_independent_cursor_plane_) {
auto highest_zindex_surface =
std::max_element(ozone_surface_list.begin(), ozone_surface_list.end(),
[](const auto& a, const auto& b) {
return a.plane_z_order < b.plane_z_order;
});
if (highest_zindex_surface != ozone_surface_list.end()) {
gfx::RectF display_rect = highest_zindex_surface->display_rect;
gfx::Size buffer_size = highest_zindex_surface->buffer_size;
if (!display_rect.origin().IsOrigin() ||
buffer_size != gfx::ToFlooredSize(display_rect.size())) {
int zindex = highest_zindex_surface->plane_z_order;
*highest_zindex_surface = ui::OverlaySurfaceCandidate();
highest_zindex_surface->plane_z_order = zindex;
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
}
overlay_candidates_->CheckOverlaySupport(&ozone_surface_list);
// Copy information from OzoneSurfaceCandidatelist back to
// OverlayCandidateList.
{
DCHECK_EQ(full_size, ozone_surface_list.size());
auto ozone_surface_iterator = ozone_surface_list.cbegin();
// The primary plane is always handled, and don't need to copy information.
if (primary_plane)
ozone_surface_iterator++;
auto surface_iterator = surfaces->begin();
for (; surface_iterator < surfaces->end() &&
ozone_surface_iterator < ozone_surface_list.cend();
surface_iterator++, ozone_surface_iterator++) {
surface_iterator->overlay_handled =
ozone_surface_iterator->overlay_handled;
surface_iterator->display_rect = ozone_surface_iterator->display_rect;
}
}
}
void OverlayProcessorOzone::MaybeObserveHardwareCapabilities() {
if (tried_observing_hardware_capabilities_) {
return;
}
tried_observing_hardware_capabilities_ = true;
if (overlay_candidates_) {
overlay_candidates_->ObserveHardwareCapabilities(
base::BindRepeating(&OverlayProcessorOzone::ReceiveHardwareCapabilities,
weak_ptr_factory_.GetWeakPtr()));
}
}
void OverlayProcessorOzone::ReceiveHardwareCapabilities(
ui::HardwareCapabilities hardware_capabilities) {
if (hardware_capabilities.is_valid) {
// Subtract 1 because one of these overlay capable planes will be needed for
// the primary plane.
int max_overlays_supported =
hardware_capabilities.num_overlay_capable_planes - 1;
max_overlays_considered_ =
std::min(max_overlays_supported, max_overlays_config_);
has_independent_cursor_plane_ =
hardware_capabilities.has_independent_cursor_plane;
UMA_HISTOGRAM_COUNTS_100(
"Compositing.Display.OverlayProcessorOzone.MaxPlanesSupported",
hardware_capabilities.num_overlay_capable_planes);
DCHECK(overlay_candidates_);
overlay_candidates_->SetSupportedBufferFormats(
std::move(hardware_capabilities.supported_buffer_formats));
} else {
// Default to attempting 1 overlay if we get an invalid response.
max_overlays_considered_ = 1;
}
// Different hardware capabilities may mean a different result for a specific
// combination of overlays, so clear this cache.
ClearOverlayCombinationCache();
}
gfx::Rect OverlayProcessorOzone::GetOverlayDamageRectForOutputSurface(
const OverlayCandidate& overlay) const {
return ToEnclosedRect(overlay.display_rect);
}
void OverlayProcessorOzone::RegisterOverlayRequirement(bool requires_overlay) {
// This can be null in unit tests.
if (overlay_candidates_)
overlay_candidates_->RegisterOverlayRequirement(requires_overlay);
}
void OverlayProcessorOzone::OnSwapBuffersComplete(gfx::SwapResult swap_result) {
if (overlay_candidates_) {
overlay_candidates_->OnSwapBuffersComplete(swap_result);
}
}
bool OverlayProcessorOzone::SetNativePixmapForCandidate(
ui::OverlaySurfaceCandidate* candidate,
const gpu::Mailbox& mailbox,
bool is_primary) {
DCHECK(pixmap_provider_);
scoped_refptr<gfx::NativePixmap> native_pixmap =
pixmap_provider_->GetNativePixmap(mailbox);
if (!native_pixmap) {
// SharedImage creation and destruction happens on a different
// thread so there is no guarantee that we can always look them up
// successfully. If a SharedImage doesn't exist, ignore the
// candidate. We will try again next frame.
DLOG(ERROR) << "Unable to find the NativePixmap corresponding to the "
"overlay candidate";
return false;
}
if (is_primary &&
(candidate->buffer_size != native_pixmap->GetBufferSize() ||
candidate->format !=
GetSharedImageFormat(native_pixmap->GetBufferFormat()))) {
// If |mailbox| corresponds to the last submitted primary plane, its
// parameters may not match those of the current candidate due to a
// reshape. If the size and format don't match, skip this candidate for
// now, and try again next frame.
return false;
}
candidate->native_pixmap = std::move(native_pixmap);
candidate->native_pixmap_unique_id = mailbox.ToU32();
return true;
}
OverlayProcessorOzone::PixmapProvider::~PixmapProvider() = default;
} // namespace viz