// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/browser_compositor_ios.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/command_line.h"
#include "base/containers/circular_deque.h"
#include "base/lazy_instance.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/local_surface_id.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/context_factory.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/size_conversions.h"
namespace content {
////////////////////////////////////////////////////////////////////////////////
// BrowserCompositorIOS
BrowserCompositorIOS::BrowserCompositorIOS(
gfx::AcceleratedWidget accelerated_widget,
BrowserCompositorIOSClient* client,
bool render_widget_host_is_hidden,
const viz::FrameSinkId& frame_sink_id)
: client_(client),
accelerated_widget_(accelerated_widget),
weak_factory_(this) {
root_layer_ = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
// Ensure that this layer draws nothing when it does not not have delegated
// content (otherwise this solid color will be flashed during navigation).
root_layer_->SetColor(SK_ColorRED);
delegated_frame_host_ = std::make_unique<DelegatedFrameHost>(
frame_sink_id, this, /*should_register_frame_sink_id=*/true);
SetRenderWidgetHostIsHidden(render_widget_host_is_hidden);
}
BrowserCompositorIOS::~BrowserCompositorIOS() {
// Ensure that copy callbacks completed or cancelled during further tear-down
// do not call back into this.
weak_factory_.InvalidateWeakPtrs();
TransitionToState(HasNoCompositor);
delegated_frame_host_.reset();
root_layer_.reset();
}
DelegatedFrameHost* BrowserCompositorIOS::GetDelegatedFrameHost() {
DCHECK(delegated_frame_host_);
return delegated_frame_host_.get();
}
bool BrowserCompositorIOS::ForceNewSurfaceId() {
dfh_local_surface_id_allocator_.GenerateId();
delegated_frame_host_->EmbedSurface(
dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceId(), dfh_size_dip_,
cc::DeadlinePolicy::UseExistingDeadline());
return client_->OnBrowserCompositorSurfaceIdChanged();
}
viz::FrameSinkId BrowserCompositorIOS::GetRootFrameSinkId() {
if (parent_ui_layer_) {
return parent_ui_layer_->GetCompositor()->frame_sink_id();
}
if (compositor_) {
return compositor_->frame_sink_id();
}
return viz::FrameSinkId();
}
void BrowserCompositorIOS::SetBackgroundColor(SkColor background_color) {
// background_color_ = background_color;
if (compositor_) {
compositor_->SetBackgroundColor(background_color_);
}
}
void BrowserCompositorIOS::UpdateSurfaceFromUIView(
const gfx::Size& new_size_dip) {
display::ScreenInfo current = client_->GetCurrentScreenInfo();
bool is_resize = !dfh_size_dip_.IsEmpty() && new_size_dip != dfh_size_dip_;
bool needs_new_surface_id =
new_size_dip != dfh_size_dip_ ||
current.device_scale_factor != dfh_device_scale_factor_;
dfh_size_dip_ = new_size_dip;
dfh_device_scale_factor_ = current.device_scale_factor;
// The device scale factor is always an integer, so the result here is also
// an integer.
dfh_size_pixels_ = gfx::ToRoundedSize(
gfx::ConvertSizeToPixels(dfh_size_dip_, current.device_scale_factor));
root_layer_->SetBounds(gfx::Rect(dfh_size_dip_));
if (needs_new_surface_id) {
dfh_local_surface_id_allocator_.GenerateId();
delegated_frame_host_->EmbedSurface(
dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
dfh_size_dip_, GetDeadlinePolicy(is_resize));
}
if (compositor_) {
UpdateSurface(dfh_size_pixels_, current.device_scale_factor,
current.display_color_spaces);
}
}
void BrowserCompositorIOS::UpdateSurfaceFromChild(
bool auto_resize_enabled,
float new_device_scale_factor,
const gfx::Size& new_size_in_pixels,
const viz::LocalSurfaceId& child_local_surface_id) {
if (dfh_local_surface_id_allocator_.UpdateFromChild(child_local_surface_id)) {
if (auto_resize_enabled) {
client_->SetCurrentDeviceScaleFactor(new_device_scale_factor);
display::ScreenInfo current = client_->GetCurrentScreenInfo();
// TODO(danakj): We should avoid lossy conversions to integer DIPs.
dfh_size_dip_ = gfx::ToFlooredSize(gfx::ConvertSizeToDips(
new_size_in_pixels, current.device_scale_factor));
dfh_size_pixels_ = new_size_in_pixels;
dfh_device_scale_factor_ = new_device_scale_factor;
root_layer_->SetBounds(gfx::Rect(dfh_size_dip_));
if (compositor_) {
UpdateSurface(dfh_size_pixels_, current.device_scale_factor,
current.display_color_spaces);
}
}
delegated_frame_host_->EmbedSurface(
dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
dfh_size_dip_, GetDeadlinePolicy(/*is_resize=*/true));
}
client_->OnBrowserCompositorSurfaceIdChanged();
}
void BrowserCompositorIOS::UpdateVSyncParameters(
const base::TimeTicks& timebase,
const base::TimeDelta& interval) {
ui::Compositor* compositor = nullptr;
if (compositor_) {
compositor = compositor_.get();
}
// TODO(ccameron): VSync parameters for a ui::Compositor should be tracked
// with the owner of that ui::Compositor (which, in the case of MacViews, is
// BridgedNativeView). For the moment, push the VSync parameters from here to
// the BridgedNativeView's ui::Compositor because that is a small change and
// is easy to merge.
// https://crbug.com/869129
if (parent_ui_layer_) {
compositor = parent_ui_layer_->GetCompositor();
}
if (compositor) {
compositor->SetDisplayVSyncParameters(timebase, interval);
}
}
void BrowserCompositorIOS::SetRenderWidgetHostIsHidden(bool hidden) {
render_widget_host_is_hidden_ = hidden;
UpdateState();
if (state_ == UseParentLayerCompositor) {
// UpdateState might not call WasShown when showing a frame using the same
// ParentLayerCompositor, since it returns early on a no-op state
// transition.
delegated_frame_host_->WasShown(GetRendererLocalSurfaceId(), dfh_size_dip_,
/*record_tab_switch_time_request=*/{});
}
}
void BrowserCompositorIOS::SetViewVisible(bool visible) {
root_layer_->SetVisible(visible);
}
void BrowserCompositorIOS::UpdateState() {
// Always use the parent ui::Layer's ui::Compositor if available.
if (parent_ui_layer_) {
TransitionToState(UseParentLayerCompositor);
return;
}
// If the host is visible and a compositor is required then create one.
if (!render_widget_host_is_hidden_) {
TransitionToState(HasOwnCompositor);
return;
}
// Otherwise put the compositor up for recycling.
TransitionToState(HasNoCompositor);
}
void BrowserCompositorIOS::TransitionToState(State new_state) {
// Skip if there is no change to make.
bool is_no_op = false;
if (state_ == new_state) {
if (state_ == UseParentLayerCompositor) {
is_no_op = parent_ui_layer_ == root_layer_->parent();
} else {
is_no_op = true;
}
}
if (is_no_op) {
return;
}
// First, detach from the current compositor, if there is one.
delegated_frame_host_->DetachFromCompositor();
if (state_ == UseParentLayerCompositor) {
DCHECK(root_layer_->parent());
state_ = HasNoCompositor;
root_layer_->parent()->RemoveObserver(this);
root_layer_->parent()->Remove(root_layer_.get());
}
if (state_ == HasOwnCompositor) {
compositor_->SetRootLayer(nullptr);
compositor_.reset();
InvalidateSurface();
}
// The compositor is now detached. If this is the target state, we're done.
state_ = HasNoCompositor;
if (new_state == HasNoCompositor) {
// Don't transiently hide the DelegatedFrameHost because that can cause the
// current frame to be inappropriately evicted.
// https://crbug.com/897156
delegated_frame_host_->WasHidden(DelegatedFrameHost::HiddenCause::kOther);
return;
}
// Attach to the new compositor.
if (new_state == UseParentLayerCompositor) {
DCHECK(parent_ui_layer_);
parent_ui_layer_->Add(root_layer_.get());
parent_ui_layer_->AddObserver(this);
state_ = UseParentLayerCompositor;
}
if (new_state == HasOwnCompositor) {
ui::ContextFactory* context_factory = GetContextFactory();
compositor_ = std::make_unique<ui::Compositor>(
context_factory->AllocateFrameSinkId(), context_factory,
base::SingleThreadTaskRunner::GetCurrentDefault(),
ui::IsPixelCanvasRecordingEnabled());
Suspend();
display::ScreenInfo current = client_->GetCurrentScreenInfo();
UpdateSurface(dfh_size_pixels_, current.device_scale_factor,
current.display_color_spaces);
compositor_->SetRootLayer(root_layer_.get());
compositor_->SetBackgroundColor(background_color_);
compositor_->SetAcceleratedWidget(accelerated_widget_);
Unsuspend();
state_ = HasOwnCompositor;
}
DCHECK_EQ(state_, new_state);
delegated_frame_host_->AttachToCompositor(GetCompositor());
delegated_frame_host_->WasShown(GetRendererLocalSurfaceId(), dfh_size_dip_,
/*record_tab_switch_time_request=*/{});
}
void BrowserCompositorIOS::TakeFallbackContentFrom(
BrowserCompositorIOS* other) {
delegated_frame_host_->TakeFallbackContentFrom(
other->delegated_frame_host_.get());
}
////////////////////////////////////////////////////////////////////////////////
// DelegatedFrameHost, public:
ui::Layer* BrowserCompositorIOS::DelegatedFrameHostGetLayer() const {
return root_layer_.get();
}
bool BrowserCompositorIOS::DelegatedFrameHostIsVisible() const {
return state_ != HasNoCompositor;
}
SkColor BrowserCompositorIOS::DelegatedFrameHostGetGutterColor() const {
return client_->BrowserCompositorIOSGetGutterColor();
}
void BrowserCompositorIOS::OnFrameTokenChanged(
uint32_t frame_token,
base::TimeTicks activation_time) {
client_->OnFrameTokenChanged(frame_token, activation_time);
}
float BrowserCompositorIOS::GetDeviceScaleFactor() const {
return dfh_device_scale_factor_;
}
void BrowserCompositorIOS::InvalidateLocalSurfaceIdOnEviction() {
dfh_local_surface_id_allocator_.Invalidate();
}
viz::FrameEvictorClient::EvictIds
BrowserCompositorIOS::CollectSurfaceIdsForEviction() {
viz::FrameEvictorClient::EvictIds ids;
ids.embedded_ids = client_->CollectSurfaceIdsForEviction();
return ids;
}
bool BrowserCompositorIOS::ShouldShowStaleContentOnEviction() {
return false;
}
void BrowserCompositorIOS::DidNavigateMainFramePreCommit() {
delegated_frame_host_->DidNavigateMainFramePreCommit();
}
void BrowserCompositorIOS::DidEnterBackForwardCache() {
dfh_local_surface_id_allocator_.GenerateId();
delegated_frame_host_->DidEnterBackForwardCache();
}
void BrowserCompositorIOS::DidNavigate() {
if (render_widget_host_is_hidden_) {
// Navigating while hidden should not allocate a new LocalSurfaceID. Once
// sizes are ready, or we begin to Show, we can then allocate the new
// LocalSurfaceId.
dfh_local_surface_id_allocator_.Invalidate();
} else {
// The first navigation does not need a new LocalSurfaceID. The renderer can
// use the ID that was already provided.
if (!is_first_navigation_) {
dfh_local_surface_id_allocator_.GenerateId();
}
delegated_frame_host_->EmbedSurface(
dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
dfh_size_dip_, cc::DeadlinePolicy::UseExistingDeadline());
client_->OnBrowserCompositorSurfaceIdChanged();
}
delegated_frame_host_->DidNavigate();
is_first_navigation_ = false;
}
void BrowserCompositorIOS::SetParentUiLayer(ui::Layer* new_parent_ui_layer) {
if (new_parent_ui_layer) {
DCHECK(new_parent_ui_layer->GetCompositor());
}
// Set |parent_ui_layer_| to the new value, which potentially not match the
// value of |root_layer_->parent()|. The call to UpdateState will re-parent
// |root_layer_|.
DCHECK_EQ(root_layer_->parent(), parent_ui_layer_);
parent_ui_layer_ = new_parent_ui_layer;
UpdateState();
DCHECK_EQ(root_layer_->parent(), parent_ui_layer_);
}
void BrowserCompositorIOS::ForceNewSurfaceForTesting() {
float current_device_scale_factor =
client_->GetCurrentScreenInfo().device_scale_factor;
client_->SetCurrentDeviceScaleFactor(current_device_scale_factor * 2.0f);
UpdateSurfaceFromUIView(dfh_size_dip_);
}
viz::ScopedSurfaceIdAllocator
BrowserCompositorIOS::GetScopedRendererSurfaceIdAllocator(
base::OnceCallback<void()> allocation_task) {
return viz::ScopedSurfaceIdAllocator(&dfh_local_surface_id_allocator_,
std::move(allocation_task));
}
const viz::LocalSurfaceId& BrowserCompositorIOS::GetRendererLocalSurfaceId() {
if (!dfh_local_surface_id_allocator_.HasValidLocalSurfaceId()) {
dfh_local_surface_id_allocator_.GenerateId();
}
return dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceId();
}
void BrowserCompositorIOS::TransformPointToRootSurface(gfx::PointF* point) {
gfx::Transform transform_to_root;
if (parent_ui_layer_) {
parent_ui_layer_->GetTargetTransformRelativeTo(nullptr, &transform_to_root);
}
*point = transform_to_root.MapPoint(*point);
}
void BrowserCompositorIOS::LayerDestroyed(ui::Layer* layer) {
DCHECK_EQ(layer, parent_ui_layer_);
SetParentUiLayer(nullptr);
}
ui::Compositor* BrowserCompositorIOS::GetCompositor() const {
if (parent_ui_layer_) {
return parent_ui_layer_->GetCompositor();
}
return compositor_.get();
}
void BrowserCompositorIOS::InvalidateSurfaceAllocationGroup() {
local_surface_id_allocator_.Invalidate(
/*also_invalidate_allocation_group=*/true);
}
cc::DeadlinePolicy BrowserCompositorIOS::GetDeadlinePolicy(
bool is_resize) const {
// Determined empirically for smoothness. Don't wait for non-resize frames,
// as it can cause jank at new tab creation.
// https://crbug.com/855364
uint32_t frames_to_wait = is_resize ? 8 : 0;
// When using the RecyclableCompositor, never wait for frames to arrive
// (surface sync is managed by the Suspend/Unsuspend lock).
if (compositor_) {
frames_to_wait = 0;
}
return cc::DeadlinePolicy::UseSpecifiedDeadline(frames_to_wait);
}
void BrowserCompositorIOS::UpdateSurface(
const gfx::Size& size_pixels,
float scale_factor,
const gfx::DisplayColorSpaces& display_color_spaces) {
if (size_pixels != size_pixels_ || scale_factor != scale_factor_) {
size_pixels_ = size_pixels;
scale_factor_ = scale_factor;
local_surface_id_allocator_.GenerateId();
viz::LocalSurfaceId local_surface_id =
local_surface_id_allocator_.GetCurrentLocalSurfaceId();
compositor_->SetScaleAndSize(scale_factor_, size_pixels_, local_surface_id);
}
if (display_color_spaces != display_color_spaces_) {
display_color_spaces_ = display_color_spaces;
compositor_->SetDisplayColorSpaces(display_color_spaces_);
}
}
void BrowserCompositorIOS::InvalidateSurface() {
size_pixels_ = gfx::Size();
scale_factor_ = 1.f;
local_surface_id_allocator_.Invalidate(
/*also_invalidate_allocation_group=*/true);
}
void BrowserCompositorIOS::Suspend() {
DCHECK(compositor_);
// Requests a compositor lock without a timeout.
compositor_suspended_lock_ =
compositor_->GetCompositorLock(nullptr, base::TimeDelta());
}
void BrowserCompositorIOS::Unsuspend() {
compositor_suspended_lock_ = nullptr;
}
} // namespace content