blob: 21d3b34cc13f3471dcef456ec92ad7c1c4a012f4 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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_view_mac.h"
#import <Cocoa/Cocoa.h>
#include <stdint.h>
#include <utility>
#include "base/command_line.h"
#include "base/containers/circular_deque.h"
#include "base/lazy_instance.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/local_surface_id_allocation.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/renderer_host/display_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/context_factory.h"
#include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/layout.h"
#include "ui/compositor/recyclable_compositor_mac.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/dip_util.h"
namespace content {
namespace {
// Weak pointers to all BrowserCompositorMac instances, used to
// - Determine if a spare RecyclableCompositorMac should be kept around (one
// should be only if there exists at least one BrowserCompositorMac).
// - Force all ui::Compositors to be destroyed at shut-down (because the NSView
// signals to shut down will come in very late, long after things that the
// ui::Compositor depend on have been destroyed).
// https://crbug.com/805726
base::LazyInstance<std::set<BrowserCompositorMac*>>::Leaky
g_browser_compositors;
} // namespace
////////////////////////////////////////////////////////////////////////////////
// BrowserCompositorMac
BrowserCompositorMac::BrowserCompositorMac(
ui::AcceleratedWidgetMacNSView* accelerated_widget_mac_ns_view,
BrowserCompositorMacClient* client,
bool render_widget_host_is_hidden,
const display::Display& initial_display,
const viz::FrameSinkId& frame_sink_id)
: client_(client),
accelerated_widget_mac_ns_view_(accelerated_widget_mac_ns_view),
dfh_display_(initial_display),
weak_factory_(this) {
g_browser_compositors.Get().insert(this);
root_layer_.reset(new 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_ColorTRANSPARENT);
delegated_frame_host_.reset(new DelegatedFrameHost(
frame_sink_id, this, true /* should_register_frame_sink_id */));
SetRenderWidgetHostIsHidden(render_widget_host_is_hidden);
}
BrowserCompositorMac::~BrowserCompositorMac() {
// 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();
size_t num_erased = g_browser_compositors.Get().erase(this);
DCHECK_EQ(1u, num_erased);
}
DelegatedFrameHost* BrowserCompositorMac::GetDelegatedFrameHost() {
DCHECK(delegated_frame_host_);
return delegated_frame_host_.get();
}
bool BrowserCompositorMac::ForceNewSurfaceId() {
dfh_local_surface_id_allocator_.GenerateId();
delegated_frame_host_->EmbedSurface(
dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
.local_surface_id(),
dfh_size_dip_, cc::DeadlinePolicy::UseExistingDeadline());
return client_->OnBrowserCompositorSurfaceIdChanged();
}
const gfx::CALayerParams* BrowserCompositorMac::GetLastCALayerParams() const {
if (!recyclable_compositor_)
return nullptr;
return recyclable_compositor_->widget()->GetCALayerParams();
}
viz::FrameSinkId BrowserCompositorMac::GetRootFrameSinkId() {
if (parent_ui_layer_)
return parent_ui_layer_->GetCompositor()->frame_sink_id();
if (recyclable_compositor_)
return recyclable_compositor_->compositor()->frame_sink_id();
return viz::FrameSinkId();
}
void BrowserCompositorMac::DidCreateNewRendererCompositorFrameSink(
viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) {
renderer_compositor_frame_sink_ = renderer_compositor_frame_sink;
delegated_frame_host_->DidCreateNewRendererCompositorFrameSink(
renderer_compositor_frame_sink_);
}
void BrowserCompositorMac::OnDidNotProduceFrame(const viz::BeginFrameAck& ack) {
delegated_frame_host_->DidNotProduceFrame(ack);
}
void BrowserCompositorMac::SetBackgroundColor(SkColor background_color) {
background_color_ = background_color;
if (recyclable_compositor_)
recyclable_compositor_->compositor()->SetBackgroundColor(background_color_);
}
bool BrowserCompositorMac::UpdateSurfaceFromNSView(
const gfx::Size& new_size_dip,
const display::Display& new_display) {
if (new_size_dip == dfh_size_dip_ && new_display == dfh_display_)
return false;
bool is_resize = !dfh_size_dip_.IsEmpty() && new_size_dip != dfh_size_dip_;
bool needs_new_surface_id =
new_size_dip != dfh_size_dip_ ||
new_display.device_scale_factor() != dfh_display_.device_scale_factor();
dfh_display_ = new_display;
dfh_size_dip_ = new_size_dip;
dfh_size_pixels_ = gfx::ConvertSizeToPixel(dfh_display_.device_scale_factor(),
dfh_size_dip_);
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_.GetCurrentLocalSurfaceIdAllocation()
.local_surface_id(),
dfh_size_dip_, GetDeadlinePolicy(is_resize));
}
if (recyclable_compositor_) {
recyclable_compositor_->compositor()->SetDisplayColorSpace(
dfh_display_.color_space());
recyclable_compositor_->UpdateSurface(dfh_size_pixels_,
dfh_display_.device_scale_factor());
}
return true;
}
void BrowserCompositorMac::UpdateSurfaceFromChild(
float new_device_scale_factor,
const gfx::Size& new_size_in_pixels,
const viz::LocalSurfaceIdAllocation& child_local_surface_id_allocation) {
if (dfh_local_surface_id_allocator_.UpdateFromChild(
child_local_surface_id_allocation)) {
dfh_display_.set_device_scale_factor(new_device_scale_factor);
dfh_size_dip_ = gfx::ConvertSizeToDIP(dfh_display_.device_scale_factor(),
new_size_in_pixels);
dfh_size_pixels_ = new_size_in_pixels;
root_layer_->SetBounds(gfx::Rect(dfh_size_dip_));
if (recyclable_compositor_) {
recyclable_compositor_->UpdateSurface(dfh_size_pixels_,
dfh_display_.device_scale_factor());
}
delegated_frame_host_->EmbedSurface(
dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
.local_surface_id(),
dfh_size_dip_, GetDeadlinePolicy(true /* is_resize */));
}
client_->OnBrowserCompositorSurfaceIdChanged();
}
void BrowserCompositorMac::UpdateVSyncParameters(
const base::TimeTicks& timebase,
const base::TimeDelta& interval) {
ui::Compositor* compositor = nullptr;
if (recyclable_compositor_)
compositor = recyclable_compositor_->compositor();
// 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 BrowserCompositorMac::SetRenderWidgetHostIsHidden(bool hidden) {
render_widget_host_is_hidden_ = hidden;
UpdateState();
}
void BrowserCompositorMac::SetViewVisible(bool visible) {
root_layer_->SetVisible(visible);
}
void BrowserCompositorMac::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 BrowserCompositorMac::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) {
recyclable_compositor_->widget()->ResetNSView();
recyclable_compositor_->compositor()->SetRootLayer(nullptr);
recyclable_compositor_->InvalidateSurface();
ui::RecyclableCompositorMacFactory::Get()->RecycleCompositor(
std::move(recyclable_compositor_));
}
// 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();
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) {
recyclable_compositor_ =
ui::RecyclableCompositorMacFactory::Get()->CreateCompositor(
content::GetContextFactory(), content::GetContextFactoryPrivate());
recyclable_compositor_->UpdateSurface(dfh_size_pixels_,
dfh_display_.device_scale_factor());
recyclable_compositor_->compositor()->SetRootLayer(root_layer_.get());
recyclable_compositor_->compositor()->SetBackgroundColor(background_color_);
recyclable_compositor_->compositor()->SetDisplayColorSpace(
dfh_display_.color_space());
recyclable_compositor_->widget()->SetNSView(
accelerated_widget_mac_ns_view_);
recyclable_compositor_->Unsuspend();
state_ = HasOwnCompositor;
}
DCHECK_EQ(state_, new_state);
delegated_frame_host_->AttachToCompositor(GetCompositor());
delegated_frame_host_->WasShown(
GetRendererLocalSurfaceIdAllocation().local_surface_id(), dfh_size_dip_,
false /* record_presentation_time */);
}
// static
void BrowserCompositorMac::DisableRecyclingForShutdown() {
// Ensure that the client has destroyed its BrowserCompositorViewMac before
// it dependencies are destroyed.
// https://crbug.com/805726
while (!g_browser_compositors.Get().empty()) {
BrowserCompositorMac* browser_compositor =
*g_browser_compositors.Get().begin();
browser_compositor->client_->DestroyCompositorForShutdown();
}
ui::RecyclableCompositorMacFactory::Get()->DisableRecyclingForShutdown();
}
void BrowserCompositorMac::SetNeedsBeginFrames(bool needs_begin_frames) {
delegated_frame_host_->SetNeedsBeginFrames(needs_begin_frames);
}
void BrowserCompositorMac::SetWantsAnimateOnlyBeginFrames() {
delegated_frame_host_->SetWantsAnimateOnlyBeginFrames();
}
void BrowserCompositorMac::TakeFallbackContentFrom(
BrowserCompositorMac* other) {
delegated_frame_host_->TakeFallbackContentFrom(
other->delegated_frame_host_.get());
}
////////////////////////////////////////////////////////////////////////////////
// DelegatedFrameHost, public:
ui::Layer* BrowserCompositorMac::DelegatedFrameHostGetLayer() const {
return root_layer_.get();
}
bool BrowserCompositorMac::DelegatedFrameHostIsVisible() const {
return state_ != HasNoCompositor;
}
SkColor BrowserCompositorMac::DelegatedFrameHostGetGutterColor() const {
return client_->BrowserCompositorMacGetGutterColor();
}
void BrowserCompositorMac::OnBeginFrame(base::TimeTicks frame_time) {
client_->BrowserCompositorMacOnBeginFrame(frame_time);
}
void BrowserCompositorMac::OnFrameTokenChanged(uint32_t frame_token) {
client_->OnFrameTokenChanged(frame_token);
}
float BrowserCompositorMac::GetDeviceScaleFactor() const {
return dfh_display_.device_scale_factor();
}
void BrowserCompositorMac::InvalidateLocalSurfaceIdOnEviction() {
dfh_local_surface_id_allocator_.Invalidate();
}
std::vector<viz::SurfaceId>
BrowserCompositorMac::CollectSurfaceIdsForEviction() {
return client_->CollectSurfaceIdsForEviction();
}
void BrowserCompositorMac::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_.GetCurrentLocalSurfaceIdAllocation()
.local_surface_id(),
dfh_size_dip_, cc::DeadlinePolicy::UseExistingDeadline());
client_->OnBrowserCompositorSurfaceIdChanged();
}
delegated_frame_host_->DidNavigate();
is_first_navigation_ = false;
}
void BrowserCompositorMac::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_);
}
bool BrowserCompositorMac::ForceNewSurfaceForTesting() {
display::Display new_display(dfh_display_);
new_display.set_device_scale_factor(new_display.device_scale_factor() * 2.0f);
return UpdateSurfaceFromNSView(dfh_size_dip_, new_display);
}
void BrowserCompositorMac::GetRendererScreenInfo(
ScreenInfo* screen_info) const {
DisplayUtil::DisplayToScreenInfo(screen_info, dfh_display_);
}
viz::ScopedSurfaceIdAllocator
BrowserCompositorMac::GetScopedRendererSurfaceIdAllocator(
base::OnceCallback<void()> allocation_task) {
return viz::ScopedSurfaceIdAllocator(&dfh_local_surface_id_allocator_,
std::move(allocation_task));
}
const viz::LocalSurfaceIdAllocation&
BrowserCompositorMac::GetRendererLocalSurfaceIdAllocation() {
if (!dfh_local_surface_id_allocator_.HasValidLocalSurfaceIdAllocation())
dfh_local_surface_id_allocator_.GenerateId();
return dfh_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
}
void BrowserCompositorMac::TransformPointToRootSurface(gfx::PointF* point) {
gfx::Transform transform_to_root;
if (parent_ui_layer_)
parent_ui_layer_->GetTargetTransformRelativeTo(nullptr, &transform_to_root);
transform_to_root.TransformPoint(point);
}
void BrowserCompositorMac::LayerDestroyed(ui::Layer* layer) {
DCHECK_EQ(layer, parent_ui_layer_);
SetParentUiLayer(nullptr);
}
ui::Compositor* BrowserCompositorMac::GetCompositor() const {
if (parent_ui_layer_)
return parent_ui_layer_->GetCompositor();
if (recyclable_compositor_)
return recyclable_compositor_->compositor();
return nullptr;
}
cc::DeadlinePolicy BrowserCompositorMac::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 (recyclable_compositor_)
frames_to_wait = 0;
return cc::DeadlinePolicy::UseSpecifiedDeadline(frames_to_wait);
}
} // namespace content