// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/browser/gfx/browser_view_renderer.h"
#include <memory>
#include <utility>
#include "android_webview/browser/gfx/browser_view_renderer_client.h"
#include "android_webview/browser/gfx/compositor_frame_consumer.h"
#include "android_webview/browser/gfx/root_frame_sink.h"
#include "android_webview/browser/gfx/root_frame_sink_proxy.h"
#include "android_webview/common/aw_features.h"
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/supports_user_data.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/traced_value.h"
#include "cc/base/math_util.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace android_webview {
namespace {
const double kEpsilon = 1e-8;
// Used to calculate memory allocation. Determined experimentally.
const size_t kMemoryMultiplier = 20;
const size_t kBytesPerPixel = 4;
const size_t kMemoryAllocationStep = 5 * 1024 * 1024;
uint64_t g_memory_override_in_bytes = 0u;
const void* const kBrowserViewRendererUserDataKey =
&kBrowserViewRendererUserDataKey;
class BrowserViewRendererUserData : public base::SupportsUserData::Data {
public:
explicit BrowserViewRendererUserData(BrowserViewRenderer* ptr) : bvr_(ptr) {}
static BrowserViewRenderer* GetBrowserViewRenderer(
content::WebContents* web_contents) {
if (!web_contents)
return NULL;
BrowserViewRendererUserData* data =
static_cast<BrowserViewRendererUserData*>(
web_contents->GetUserData(kBrowserViewRendererUserDataKey));
return data ? data->bvr_.get() : NULL;
}
private:
raw_ptr<BrowserViewRenderer> bvr_;
};
} // namespace
// static
void BrowserViewRenderer::CalculateTileMemoryPolicy() {
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
// If the value was overridden on the command line, use the specified value.
bool client_hard_limit_bytes_overridden =
cl->HasSwitch(switches::kForceGpuMemAvailableMb);
if (client_hard_limit_bytes_overridden) {
base::StringToUint64(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kForceGpuMemAvailableMb),
&g_memory_override_in_bytes);
g_memory_override_in_bytes *= 1024 * 1024;
}
}
// static
BrowserViewRenderer* BrowserViewRenderer::FromWebContents(
content::WebContents* web_contents) {
return BrowserViewRendererUserData::GetBrowserViewRenderer(web_contents);
}
BrowserViewRenderer::BrowserViewRenderer(
BrowserViewRendererClient* client,
const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
: client_(client),
ui_task_runner_(ui_task_runner),
current_compositor_frame_consumer_(nullptr),
compositor_(nullptr),
is_paused_(false),
view_visible_(false),
window_visible_(false),
attached_to_window_(false),
was_attached_(false),
hardware_enabled_(false),
dip_scale_(0.f),
page_scale_factor_(1.f),
min_page_scale_factor_(0.f),
max_page_scale_factor_(0.f),
on_new_picture_enable_(false),
clear_view_(false),
offscreen_pre_raster_(false) {
begin_frame_source_ = std::make_unique<BeginFrameSourceWebView>();
root_frame_sink_proxy_ = std::make_unique<RootFrameSinkProxy>(
ui_task_runner_, this, begin_frame_source_.get());
UpdateBeginFrameSource();
base::OnceCallback<base::PlatformThreadId()> compute_current_thread_id =
base::BindOnce([]() { return base::PlatformThread::CurrentId(); });
io_task_runner->PostTask(
FROM_HERE, std::move(compute_current_thread_id)
.Then(base::BindPostTaskToCurrentDefault(base::BindOnce(
&BrowserViewRenderer::SetBrowserIOThreadId,
weak_ptr_factory_.GetWeakPtr()))));
}
BrowserViewRenderer::~BrowserViewRenderer() {
DCHECK(compositor_map_.empty());
DCHECK(!current_compositor_frame_consumer_);
if (foreground_for_gpu_resources_) {
// Cannot leave a dangling foreground compositor. Just detach from
// destructor.
OnDetachedFromWindow();
}
// We need to destroy |root_frame_sink_proxy_| before |begin_frame_source_|;
root_frame_sink_proxy_.reset();
}
base::WeakPtr<CompositorFrameProducer> BrowserViewRenderer::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void BrowserViewRenderer::SetCurrentCompositorFrameConsumer(
CompositorFrameConsumer* compositor_frame_consumer) {
if (compositor_frame_consumer == current_compositor_frame_consumer_) {
return;
}
current_compositor_frame_consumer_ = compositor_frame_consumer;
if (current_compositor_frame_consumer_) {
// Previous renderer will evict CompositorFrame, compositor needs to submit
// next frames with new local surface id.
if (compositor_)
compositor_->WasEvicted();
RootFrameSinkGetter root_sink_getter;
if (root_frame_sink_proxy_)
root_sink_getter = root_frame_sink_proxy_->GetRootFrameSinkCallback();
current_compositor_frame_consumer_->SetCompositorFrameProducer(
this, std::move(root_sink_getter));
OnParentDrawDataUpdated(current_compositor_frame_consumer_);
}
}
void BrowserViewRenderer::RegisterWithWebContents(
content::WebContents* web_contents) {
web_contents->SetUserData(
kBrowserViewRendererUserDataKey,
std::make_unique<BrowserViewRendererUserData>(this));
}
void BrowserViewRenderer::TrimMemory() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
TRACE_EVENT0("android_webview", "BrowserViewRenderer::TrimMemory");
// Trimming memory might destroy HardwareRenderer which will evict
// CompositorFrame, compositor needs to submit next frames with new local
// surface id.
if (compositor_)
compositor_->WasEvicted();
// Just set the memory limit to 0 and drop all tiles. This will be reset to
// normal levels in the next DrawGL call.
if (!offscreen_pre_raster_)
ReleaseHardware();
}
gfx::Rect BrowserViewRenderer::ComputeTileRectAndUpdateMemoryPolicy() {
if (!compositor_) {
return gfx::Rect();
}
if (!hardware_enabled_) {
compositor_->SetMemoryPolicy(0u);
return gfx::Rect();
}
gfx::Transform transform_for_tile_priority =
external_draw_constraints_.transform;
gfx::Rect viewport_rect_for_tile_priority_in_view_space;
gfx::Transform screen_to_view;
if (transform_for_tile_priority.GetInverse(&screen_to_view)) {
// Convert from screen space to view space.
viewport_rect_for_tile_priority_in_view_space =
cc::MathUtil::ProjectEnclosingClippedRect(
screen_to_view,
gfx::Rect(external_draw_constraints_.viewport_size));
}
viewport_rect_for_tile_priority_in_view_space.Intersect(gfx::Rect(size_));
size_t bytes_limit = 0u;
if (g_memory_override_in_bytes) {
bytes_limit = static_cast<size_t>(g_memory_override_in_bytes);
} else {
// Note we are using |last_on_draw_global_visible_rect_| rather than
// |external_draw_constraints_.viewport_size|. This is to reduce budget
// for a webview that's much smaller than the surface it's rendering.
gfx::Rect interest_rect;
if (offscreen_pre_raster_) {
interest_rect = gfx::Rect(size_);
} else {
// Re-compute screen-space rect for computing tile budget, since tile is
// rastered in screen space.
gfx::Rect viewport_rect_for_tile_priority_in_screen_space =
cc::MathUtil::ProjectEnclosingClippedRect(
transform_for_tile_priority,
viewport_rect_for_tile_priority_in_view_space);
// Intersect by viewport size again, in case axis-aligning operations made
// the rect bigger than necessary.
viewport_rect_for_tile_priority_in_screen_space.Intersect(
gfx::Rect(external_draw_constraints_.viewport_size));
interest_rect = viewport_rect_for_tile_priority_in_screen_space.IsEmpty()
? last_on_draw_global_visible_rect_
: viewport_rect_for_tile_priority_in_screen_space;
}
size_t width = interest_rect.width();
size_t height = interest_rect.height();
bytes_limit = kMemoryMultiplier * kBytesPerPixel * width * height;
// Round up to a multiple of kMemoryAllocationStep.
bytes_limit =
(bytes_limit / kMemoryAllocationStep + 1) * kMemoryAllocationStep;
}
compositor_->SetMemoryPolicy(bytes_limit);
return viewport_rect_for_tile_priority_in_view_space;
}
content::SynchronousCompositor* BrowserViewRenderer::FindCompositor(
const viz::FrameSinkId& frame_sink_id) const {
const auto& compositor_iterator = compositor_map_.find(frame_sink_id);
if (compositor_iterator == compositor_map_.end())
return nullptr;
return compositor_iterator->second;
}
void BrowserViewRenderer::PrepareToDraw(const gfx::Point& scroll,
const gfx::Rect& global_visible_rect) {
last_on_draw_scroll_offset_ = scroll;
last_on_draw_global_visible_rect_ = global_visible_rect;
}
bool BrowserViewRenderer::CanOnDraw() {
if (!compositor_) {
TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_NoCompositor",
TRACE_EVENT_SCOPE_THREAD);
return false;
}
if (clear_view_) {
TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_ClearView",
TRACE_EVENT_SCOPE_THREAD);
return false;
}
return true;
}
bool BrowserViewRenderer::OnDrawHardware() {
DCHECK(current_compositor_frame_consumer_);
TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnDrawHardware");
const bool did_invalidate = did_invalidate_since_last_draw_;
did_invalidate_since_last_draw_ = false;
if (!CanOnDraw()) {
return false;
}
current_compositor_frame_consumer_->SetScrollOffsetOnUI(
last_on_draw_scroll_offset_);
hardware_enabled_ = true;
DoUpdateParentDrawData();
// Do not override (ie leave empty) for offscreen raster.
gfx::Size viewport_size_for_tile_priority =
offscreen_pre_raster_ ? gfx::Size()
: external_draw_constraints_.viewport_size;
gfx::Rect viewport_rect_for_tile_priority_in_view_space =
ComputeTileRectAndUpdateMemoryPolicy();
// Explanation for the various viewports and transforms. There are:
// * "default" viewport" (and identity transform) that's normally used by
// compositor. This is |size_| in this file.
// * "draw" viewport and transform. Compositor applies them at the root at
// draw time. This is contained in SkCanvas for a software draw
// * "tile" viewport and transform. These are set in hardware draw to
// correctly prioritize and raster tiles.
// The draw viewport was added to support software draw's ability to change
// the viewport and transform at draw time to anything the embedding app
// desires. However the tile system was not expecting its viewport to jump
// around, and only move incrementally due to user input. This required adding
// the tile viewport and transform. Tile and default are separate to reduce
// memory in the case when only a small portion of webview (ie the default
// viewport) is actually visible.
// We intersect the tile viewport with the default viewport above so that the
// tile viewport can only shrink and not grow from the default viewport. This
// is because webview can also be small in relation to the surface size, so
// and growing the tile viewport can cause more tiles to be rastered than
// necessary.
scoped_refptr<content::SynchronousCompositor::FrameFuture> future =
compositor_->DemandDrawHwAsync(
size_, viewport_rect_for_tile_priority_in_view_space,
external_draw_constraints_.transform);
CopyOutputRequestQueue requests;
copy_requests_.swap(requests);
for (auto& copy_request_ptr : requests) {
if (!copy_request_ptr->has_result_task_runner())
copy_request_ptr->set_result_task_runner(ui_task_runner_);
}
std::unique_ptr<ChildFrame> child_frame = std::make_unique<ChildFrame>(
std::move(future), frame_sink_id_, viewport_size_for_tile_priority,
external_draw_constraints_.transform, offscreen_pre_raster_, dip_scale_,
std::move(requests), did_invalidate,
begin_frame_source_->LastDispatchedBeginFrameArgs(), renderer_thread_ids_,
browser_io_thread_id_);
ReturnUnusedResource(
current_compositor_frame_consumer_->SetFrameOnUI(std::move(child_frame)));
return true;
}
void BrowserViewRenderer::OnParentDrawDataUpdated(
CompositorFrameConsumer* compositor_frame_consumer) {
DCHECK(compositor_frame_consumer);
if (compositor_frame_consumer != current_compositor_frame_consumer_)
return;
if (!DoUpdateParentDrawData())
return;
PostInvalidate(compositor_);
ComputeTileRectAndUpdateMemoryPolicy();
}
bool BrowserViewRenderer::DoUpdateParentDrawData() {
ParentCompositorDrawConstraints new_constraints;
viz::FrameTimingDetailsMap new_timing_details;
viz::FrameSinkId id;
uint32_t frame_token = 0u;
base::TimeDelta preferred_frame_interval;
current_compositor_frame_consumer_->TakeParentDrawDataOnUI(
&new_constraints, &id, &new_timing_details, &frame_token,
&preferred_frame_interval);
content::SynchronousCompositor* compositor = FindCompositor(id);
if (compositor) {
compositor->DidPresentCompositorFrames(std::move(new_timing_details),
frame_token);
}
client_->SetPreferredFrameInterval(preferred_frame_interval);
if (external_draw_constraints_ == new_constraints)
return false;
external_draw_constraints_ = new_constraints;
return true;
}
void BrowserViewRenderer::OnViewTreeForceDarkStateChanged(
bool view_tree_force_dark_state) {
client_->OnViewTreeForceDarkStateChanged(view_tree_force_dark_state);
}
void BrowserViewRenderer::ChildSurfaceWasEvicted() {
if (compositor_)
compositor_->WasEvicted();
}
void BrowserViewRenderer::RemoveCompositorFrameConsumer(
CompositorFrameConsumer* consumer) {
ReturnUncommittedFrames(consumer->PassUncommittedFrameOnUI());
if (current_compositor_frame_consumer_ == consumer)
SetCurrentCompositorFrameConsumer(nullptr);
}
void BrowserViewRenderer::ReturnUncommittedFrames(
ChildFrameQueue child_frames) {
for (auto& child_frame : child_frames)
ReturnUnusedResource(std::move(child_frame));
}
void BrowserViewRenderer::ReturnUnusedResource(
std::unique_ptr<ChildFrame> child_frame) {
if (!child_frame.get() || !child_frame->frame.get())
return;
std::vector<viz::ReturnedResource> resources =
viz::TransferableResource::ReturnResources(
child_frame->frame->resource_list);
content::SynchronousCompositor* compositor =
FindCompositor(child_frame->frame_sink_id);
if (compositor && !resources.empty())
compositor->ReturnResources(child_frame->layer_tree_frame_sink_id,
std::move(resources));
}
void BrowserViewRenderer::ReturnUsedResources(
std::vector<viz::ReturnedResource> resources,
const viz::FrameSinkId& frame_sink_id,
uint32_t layer_tree_frame_sink_id) {
content::SynchronousCompositor* compositor = FindCompositor(frame_sink_id);
if (compositor && !resources.empty())
compositor->ReturnResources(layer_tree_frame_sink_id, std::move(resources));
has_rendered_frame_ = true;
}
bool BrowserViewRenderer::OnDrawSoftware(SkCanvas* canvas) {
did_invalidate_since_last_draw_ = false;
return CanOnDraw() && CompositeSW(canvas, /*software_canvas=*/true);
}
float BrowserViewRenderer::GetVelocityInPixelsPerSecond() {
if (!compositor_) {
return 0.f;
}
return compositor_->GetVelocityInPixelsPerSecond();
}
bool BrowserViewRenderer::NeedToDrawBackgroundColor() {
return !has_rendered_frame_;
}
sk_sp<SkPicture> BrowserViewRenderer::CapturePicture(int width,
int height) {
TRACE_EVENT0("android_webview", "BrowserViewRenderer::CapturePicture");
// Return empty Picture objects for empty SkPictures.
if (width <= 0 || height <= 0) {
SkPictureRecorder emptyRecorder;
emptyRecorder.beginRecording(0, 0);
return emptyRecorder.finishRecordingAsPicture();
}
SkPictureRecorder recorder;
SkCanvas* rec_canvas = recorder.beginRecording(width, height);
if (compositor_) {
{
// Reset scroll back to the origin, will go back to the old
// value when scroll_reset is out of scope.
base::AutoReset<gfx::PointF> scroll_reset(&scroll_offset_unscaled_,
gfx::PointF());
compositor_->DidChangeRootLayerScrollOffset(scroll_offset_unscaled_);
CompositeSW(rec_canvas, /*software_canvas=*/false);
}
compositor_->DidChangeRootLayerScrollOffset(scroll_offset_unscaled_);
}
return recorder.finishRecordingAsPicture();
}
void BrowserViewRenderer::EnableOnNewPicture(bool enabled) {
on_new_picture_enable_ = enabled;
}
void BrowserViewRenderer::ClearView() {
TRACE_EVENT_INSTANT0("android_webview",
"BrowserViewRenderer::ClearView",
TRACE_EVENT_SCOPE_THREAD);
if (clear_view_)
return;
clear_view_ = true;
// Always invalidate ignoring the compositor to actually clear the webview.
PostInvalidate(compositor_);
}
void BrowserViewRenderer::SetOffscreenPreRaster(bool enable) {
if (offscreen_pre_raster_ != enable) {
offscreen_pre_raster_ = enable;
ComputeTileRectAndUpdateMemoryPolicy();
}
}
void BrowserViewRenderer::SetIsPaused(bool paused) {
TRACE_EVENT_INSTANT1("android_webview",
"BrowserViewRenderer::SetIsPaused",
TRACE_EVENT_SCOPE_THREAD,
"paused",
paused);
is_paused_ = paused;
UpdateBeginFrameSource();
}
void BrowserViewRenderer::SetViewVisibility(bool view_visible) {
TRACE_EVENT_INSTANT1("android_webview",
"BrowserViewRenderer::SetViewVisibility",
TRACE_EVENT_SCOPE_THREAD,
"view_visible",
view_visible);
view_visible_ = view_visible;
}
void BrowserViewRenderer::SetWindowVisibility(bool window_visible) {
TRACE_EVENT_INSTANT1("android_webview",
"BrowserViewRenderer::SetWindowVisibility",
TRACE_EVENT_SCOPE_THREAD,
"window_visible",
window_visible);
window_visible_ = window_visible;
UpdateBeginFrameSource();
UpdateForegroundForGpuResources();
}
void BrowserViewRenderer::OnSizeChanged(int width, int height) {
TRACE_EVENT_INSTANT2("android_webview",
"BrowserViewRenderer::OnSizeChanged",
TRACE_EVENT_SCOPE_THREAD,
"width",
width,
"height",
height);
size_.SetSize(width, height);
if (offscreen_pre_raster_)
ComputeTileRectAndUpdateMemoryPolicy();
}
void BrowserViewRenderer::OnAttachedToWindow(int width, int height) {
TRACE_EVENT2("android_webview",
"BrowserViewRenderer::OnAttachedToWindow",
"width",
width,
"height",
height);
attached_to_window_ = true;
was_attached_ = true;
size_.SetSize(width, height);
if (offscreen_pre_raster_)
ComputeTileRectAndUpdateMemoryPolicy();
UpdateBeginFrameSource();
UpdateForegroundForGpuResources();
}
void BrowserViewRenderer::OnDetachedFromWindow() {
TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnDetachedFromWindow");
attached_to_window_ = false;
ReleaseHardware();
UpdateBeginFrameSource();
UpdateForegroundForGpuResources();
}
void BrowserViewRenderer::ZoomBy(float delta) {
if (!compositor_)
return;
compositor_->SynchronouslyZoomBy(
delta, gfx::Point(size_.width() / 2, size_.height() / 2));
}
void BrowserViewRenderer::OnComputeScroll(base::TimeTicks animation_time) {
if (!compositor_)
return;
TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnComputeScroll");
compositor_->OnComputeScroll(animation_time);
}
void BrowserViewRenderer::ReleaseHardware() {
if (current_compositor_frame_consumer_) {
ReturnUncommittedFrames(
current_compositor_frame_consumer_->PassUncommittedFrameOnUI());
}
hardware_enabled_ = false;
has_rendered_frame_ = false;
ComputeTileRectAndUpdateMemoryPolicy();
}
bool BrowserViewRenderer::IsVisible() const {
// Ignore |window_visible_| if |attached_to_window_| is false.
return view_visible_ && (!attached_to_window_ || window_visible_);
}
bool BrowserViewRenderer::IsClientVisible() const {
// When WebView is not paused, we declare it visible even before it is
// attached to window to allow for background operations. If it ever gets
// attached though, the WebView is visible as long as it is attached
// to a window and the window is visible.
return is_paused_
? false
: !was_attached_ || (attached_to_window_ && window_visible_);
}
void BrowserViewRenderer::UpdateBeginFrameSource() {
if (IsClientVisible()) {
begin_frame_source_->SetParentSource(
RootBeginFrameSourceWebView::GetInstance());
} else {
begin_frame_source_->SetParentSource(nullptr);
}
}
void BrowserViewRenderer::UpdateForegroundForGpuResources() {
bool foreground = attached_to_window_ && window_visible_;
if (foreground != foreground_for_gpu_resources_) {
foreground_for_gpu_resources_ = foreground;
if (!compositor_) {
return;
}
if (foreground_for_gpu_resources_) {
compositor_->OnCompositorVisible();
} else {
compositor_->OnCompositorHidden();
}
}
}
gfx::Rect BrowserViewRenderer::GetScreenRect() const {
return gfx::Rect(client_->GetLocationOnScreen(), size_);
}
void BrowserViewRenderer::DidInitializeCompositor(
content::SynchronousCompositor* compositor,
const viz::FrameSinkId& frame_sink_id) {
TRACE_EVENT_INSTANT0("android_webview",
"BrowserViewRenderer::DidInitializeCompositor",
TRACE_EVENT_SCOPE_THREAD);
DCHECK(compositor);
// This assumes that a RenderViewHost has at most 1 synchronous compositor
// througout its lifetime.
DCHECK(compositor_map_.count(frame_sink_id) == 0);
compositor_map_[frame_sink_id] = compositor;
if (root_frame_sink_proxy_)
root_frame_sink_proxy_->AddChildFrameSinkId(frame_sink_id);
compositor->SetBeginFrameSource(begin_frame_source_.get());
// At this point, the RVHChanged event for the new RVH that contains the
// |compositor| might have been fired already, in which case just set the
// current compositor with the new compositor.
if (frame_sink_id == frame_sink_id_)
SetActiveCompositor(compositor);
}
void BrowserViewRenderer::DidDestroyCompositor(
content::SynchronousCompositor* compositor,
const viz::FrameSinkId& frame_sink_id) {
TRACE_EVENT_INSTANT0("android_webview",
"BrowserViewRenderer::DidDestroyCompositor",
TRACE_EVENT_SCOPE_THREAD);
DCHECK(compositor_map_.count(frame_sink_id));
if (compositor_ == compositor) {
if (compositor_ && foreground_for_gpu_resources_) {
compositor_->OnCompositorHidden();
}
compositor_ = nullptr;
copy_requests_.clear();
}
if (root_frame_sink_proxy_)
root_frame_sink_proxy_->RemoveChildFrameSinkId(frame_sink_id);
compositor_map_.erase(frame_sink_id);
}
void BrowserViewRenderer::SetActiveFrameSinkId(
const viz::FrameSinkId& frame_sink_id) {
frame_sink_id_ = frame_sink_id;
SetActiveCompositor(FindCompositor(frame_sink_id));
}
void BrowserViewRenderer::SetActiveCompositor(
content::SynchronousCompositor* compositor) {
if (compositor_ == compositor)
return;
content::SynchronousCompositor* existing_compositor = compositor_;
if (existing_compositor) {
existing_compositor->SetMemoryPolicy(0u);
}
compositor_ = compositor;
copy_requests_.clear();
if (compositor_) {
ComputeTileRectAndUpdateMemoryPolicy();
compositor_->DidBecomeActive();
}
if (foreground_for_gpu_resources_) {
if (compositor_) {
compositor_->OnCompositorVisible();
}
if (existing_compositor) {
existing_compositor->OnCompositorHidden();
}
}
}
void BrowserViewRenderer::SetDipScale(float dip_scale) {
dip_scale_ = dip_scale;
CHECK_GT(dip_scale_, 0.f);
}
gfx::Point BrowserViewRenderer::max_scroll_offset() const {
DCHECK_GT(dip_scale_, 0.f);
return gfx::ToCeiledPoint(
gfx::ScalePoint(max_scroll_offset_unscaled_, page_scale_factor_));
}
void BrowserViewRenderer::ScrollTo(const gfx::Point& scroll_offset) {
gfx::Point max_offset = max_scroll_offset();
gfx::PointF scroll_offset_unscaled;
// To preserve the invariant that scrolling to the maximum physical pixel
// value also scrolls to the maximum dip pixel value we transform the physical
// offset into the dip offset by using a proportion (instead of dividing by
// dip_scale * page_scale_factor).
if (max_offset.x()) {
scroll_offset_unscaled.set_x(
(scroll_offset.x() * max_scroll_offset_unscaled_.x()) / max_offset.x());
}
if (max_offset.y()) {
scroll_offset_unscaled.set_y(
(scroll_offset.y() * max_scroll_offset_unscaled_.y()) / max_offset.y());
}
DCHECK_LE(0.f, scroll_offset_unscaled.x());
DCHECK_LE(0.f, scroll_offset_unscaled.y());
DCHECK(scroll_offset_unscaled.x() < max_scroll_offset_unscaled_.x() ||
scroll_offset_unscaled.x() - max_scroll_offset_unscaled_.x() <
kEpsilon)
<< scroll_offset_unscaled.x() << " " << max_scroll_offset_unscaled_.x();
DCHECK(scroll_offset_unscaled.y() < max_scroll_offset_unscaled_.y() ||
scroll_offset_unscaled.y() - max_scroll_offset_unscaled_.y() <
kEpsilon)
<< scroll_offset_unscaled.y() << " " << max_scroll_offset_unscaled_.y();
if (scroll_offset_unscaled_ == scroll_offset_unscaled)
return;
scroll_offset_unscaled_ = scroll_offset_unscaled;
TRACE_EVENT_INSTANT2("android_webview", "BrowserViewRenderer::ScrollTo",
TRACE_EVENT_SCOPE_THREAD, "x",
scroll_offset_unscaled.x(), "y",
scroll_offset_unscaled.y());
if (compositor_)
compositor_->DidChangeRootLayerScrollOffset(scroll_offset_unscaled);
}
void BrowserViewRenderer::RestoreScrollAfterTransition(
const gfx::Point& scroll_offset) {
// Determine if the clipped scroll offset.
gfx::Point clipped_offset = scroll_offset;
clipped_offset.SetToMin(max_scroll_offset());
// If the scroll will be clipped due to the max scroll then we haven't
// received a ScrollStateUpdate with the revised max scroll values. This
// situation occurs due to a race in exiting fullscreen mode request, we need
// to wait for the max scroll values to be updated before applying the
// restored scroll values.
if (clipped_offset != scroll_offset) {
scroll_on_scroll_state_update_ = scroll_offset;
} else {
ScrollTo(scroll_offset);
}
}
void BrowserViewRenderer::DidUpdateContent(
content::SynchronousCompositor* compositor) {
TRACE_EVENT_INSTANT0("android_webview",
"BrowserViewRenderer::DidUpdateContent",
TRACE_EVENT_SCOPE_THREAD);
if (compositor != compositor_)
return;
clear_view_ = false;
if (on_new_picture_enable_)
client_->OnNewPicture();
}
void BrowserViewRenderer::SetTotalRootLayerScrollOffset(
const gfx::PointF& scroll_offset_unscaled) {
if (scroll_offset_unscaled_ == scroll_offset_unscaled)
return;
scroll_offset_unscaled_ = scroll_offset_unscaled;
gfx::Point max_offset = max_scroll_offset();
gfx::Point scroll_offset;
// For an explanation as to why this is done this way see the comment in
// BrowserViewRenderer::ScrollTo.
if (max_scroll_offset_unscaled_.x()) {
scroll_offset.set_x(
base::ClampRound((scroll_offset_unscaled.x() * max_offset.x()) /
max_scroll_offset_unscaled_.x()));
}
if (max_scroll_offset_unscaled_.y()) {
scroll_offset.set_y(
base::ClampRound((scroll_offset_unscaled.y() * max_offset.y()) /
max_scroll_offset_unscaled_.y()));
}
DCHECK_LE(0, scroll_offset.x());
DCHECK_LE(0, scroll_offset.y());
DCHECK_LE(scroll_offset.x(), max_offset.x());
DCHECK_LE(scroll_offset.y(), max_offset.y());
client_->ScrollContainerViewTo(scroll_offset);
}
void BrowserViewRenderer::UpdateRootLayerState(
content::SynchronousCompositor* compositor,
const gfx::PointF& total_scroll_offset,
const gfx::PointF& total_max_scroll_offset,
const gfx::SizeF& scrollable_size,
float page_scale_factor,
float min_page_scale_factor,
float max_page_scale_factor) {
if (compositor != compositor_)
return;
gfx::SizeF scrollable_size_dip = scrollable_size;
scrollable_size_dip.Scale(1 / dip_scale_);
TRACE_EVENT_INSTANT1(
"android_webview", "BrowserViewRenderer::UpdateRootLayerState",
TRACE_EVENT_SCOPE_THREAD, "state",
RootLayerStateAsValue(total_scroll_offset, scrollable_size_dip));
DCHECK_GE(total_max_scroll_offset.x(), 0.f);
DCHECK_GE(total_max_scroll_offset.y(), 0.f);
DCHECK_GT(page_scale_factor, 0.f);
// SetDipScale should have been called at least once before this is called.
DCHECK_GT(dip_scale_, 0.f);
bool apply_scroll_to = false;
if (max_scroll_offset_unscaled_ != total_max_scroll_offset ||
scrollable_size_dip_ != scrollable_size_dip ||
page_scale_factor_ != page_scale_factor ||
min_page_scale_factor_ != min_page_scale_factor ||
max_page_scale_factor_ != max_page_scale_factor) {
max_scroll_offset_unscaled_ = total_max_scroll_offset;
scrollable_size_dip_ = scrollable_size_dip;
page_scale_factor_ = page_scale_factor;
min_page_scale_factor_ = min_page_scale_factor;
max_page_scale_factor_ = max_page_scale_factor;
client_->UpdateScrollState(max_scroll_offset(), scrollable_size_dip,
page_scale_factor, min_page_scale_factor,
max_page_scale_factor);
apply_scroll_to = scroll_on_scroll_state_update_.has_value();
}
SetTotalRootLayerScrollOffset(total_scroll_offset);
if (apply_scroll_to) {
ScrollTo(scroll_on_scroll_state_update_.value());
scroll_on_scroll_state_update_.reset();
}
}
std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
BrowserViewRenderer::RootLayerStateAsValue(
const gfx::PointF& total_scroll_offset,
const gfx::SizeF& scrollable_size_dip) {
std::unique_ptr<base::trace_event::TracedValue> state(
new base::trace_event::TracedValue());
state->SetDouble("total_scroll_offset.x", total_scroll_offset.x());
state->SetDouble("total_scroll_offset.y", total_scroll_offset.y());
state->SetDouble("max_scroll_offset_unscaled.x",
max_scroll_offset_unscaled_.x());
state->SetDouble("max_scroll_offset_unscaled.y",
max_scroll_offset_unscaled_.y());
state->SetDouble("scrollable_size_dip.width", scrollable_size_dip.width());
state->SetDouble("scrollable_size_dip.height", scrollable_size_dip.height());
state->SetDouble("page_scale_factor", page_scale_factor_);
return std::move(state);
}
void BrowserViewRenderer::DidOverscroll(
content::SynchronousCompositor* compositor,
const gfx::Vector2dF& accumulated_overscroll,
const gfx::Vector2dF& latest_overscroll_delta,
const gfx::Vector2dF& current_fling_velocity) {
if (compositor != compositor_)
return;
const float physical_pixel_scale = dip_scale_ * page_scale_factor_;
if (accumulated_overscroll == latest_overscroll_delta)
overscroll_rounding_error_ = gfx::Vector2dF();
gfx::Vector2dF scaled_overscroll_delta =
gfx::ScaleVector2d(latest_overscroll_delta, physical_pixel_scale);
gfx::Vector2d rounded_overscroll_delta = gfx::ToRoundedVector2d(
scaled_overscroll_delta + overscroll_rounding_error_);
overscroll_rounding_error_ =
scaled_overscroll_delta - rounded_overscroll_delta;
gfx::Vector2dF fling_velocity_pixels =
gfx::ScaleVector2d(current_fling_velocity, physical_pixel_scale);
client_->DidOverscroll(rounded_overscroll_delta, fling_velocity_pixels,
begin_frame_source_->inside_begin_frame());
}
ui::TouchHandleDrawable* BrowserViewRenderer::CreateDrawable() {
return client_->CreateDrawable();
}
void BrowserViewRenderer::CopyOutput(
content::SynchronousCompositor* compositor,
std::unique_ptr<viz::CopyOutputRequest> copy_request) {
if (compositor != compositor_ || !hardware_enabled_)
return;
copy_requests_.emplace_back(std::move(copy_request));
PostInvalidate(compositor_);
}
void BrowserViewRenderer::Invalidate() {
if (compositor_)
compositor_->DidInvalidate();
PostInvalidate(compositor_);
}
void BrowserViewRenderer::ReturnResourcesFromViz(
viz::FrameSinkId frame_sink_id,
uint32_t layer_tree_frame_sink_id,
std::vector<viz::ReturnedResource> resources) {
ReturnUsedResources(std::move(resources), frame_sink_id,
layer_tree_frame_sink_id);
}
void BrowserViewRenderer::OnCompositorFrameTransitionDirectiveProcessed(
viz::FrameSinkId frame_sink_id,
uint32_t layer_tree_frame_sink_id,
uint32_t sequence_id) {
content::SynchronousCompositor* compositor = FindCompositor(frame_sink_id);
if (compositor) {
compositor->OnCompositorFrameTransitionDirectiveProcessed(
layer_tree_frame_sink_id, sequence_id);
}
}
void BrowserViewRenderer::OnInputEvent() {
if (root_frame_sink_proxy_)
root_frame_sink_proxy_->OnInputEvent();
}
void BrowserViewRenderer::AddBeginFrameCompletionCallback(
base::OnceClosure callback) {
begin_frame_source_->AddBeginFrameCompletionCallback(std::move(callback));
}
void BrowserViewRenderer::SetThreadIds(const std::vector<int32_t>& thread_ids) {
renderer_thread_ids_ = thread_ids;
}
void BrowserViewRenderer::PostInvalidate(
content::SynchronousCompositor* compositor) {
TRACE_EVENT_INSTANT0("android_webview", "BrowserViewRenderer::PostInvalidate",
TRACE_EVENT_SCOPE_THREAD);
if (compositor != compositor_)
return;
did_invalidate_since_last_draw_ = true;
client_->PostInvalidate(
RootBeginFrameSourceWebView::GetInstance()->inside_begin_frame());
}
bool BrowserViewRenderer::CompositeSW(SkCanvas* canvas, bool software_canvas) {
DCHECK(compositor_);
return compositor_->DemandDrawSw(canvas, software_canvas);
}
std::string BrowserViewRenderer::ToString() const {
std::string str;
base::StringAppendF(&str, "is_paused: %d ", is_paused_);
base::StringAppendF(&str, "view_visible: %d ", view_visible_);
base::StringAppendF(&str, "window_visible: %d ", window_visible_);
base::StringAppendF(&str, "dip_scale: %f ", dip_scale_);
base::StringAppendF(&str, "page_scale_factor: %f ", page_scale_factor_);
base::StringAppendF(&str, "view size: %s ", size_.ToString().c_str());
base::StringAppendF(&str, "attached_to_window: %d ", attached_to_window_);
base::StringAppendF(&str,
"global visible rect: %s ",
last_on_draw_global_visible_rect_.ToString().c_str());
base::StringAppendF(&str, "scroll_offset_unscaled: %s ",
scroll_offset_unscaled_.ToString().c_str());
base::StringAppendF(&str,
"overscroll_rounding_error_: %s ",
overscroll_rounding_error_.ToString().c_str());
base::StringAppendF(
&str, "on_new_picture_enable: %d ", on_new_picture_enable_);
base::StringAppendF(&str, "clear_view: %d ", clear_view_);
return str;
}
void BrowserViewRenderer::SetBrowserIOThreadId(
base::PlatformThreadId thread_id) {
browser_io_thread_id_ = thread_id;
}
} // namespace android_webview