|  | // 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 "third_party/abseil-cpp/absl/types/optional.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, true /* should_register_frame_sink_id */); | 
|  |  | 
|  | 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(true /* is_resize */)); | 
|  | } | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | // 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()); | 
|  | 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_); | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | std::vector<viz::SurfaceId> | 
|  | BrowserCompositorIOS::CollectSurfaceIdsForEviction() { | 
|  | return client_->CollectSurfaceIdsForEviction(); | 
|  | } | 
|  |  | 
|  | bool BrowserCompositorIOS::ShouldShowStaleContentOnEviction() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | 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_); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace content |