blob: 4496f2b1b310adcc6809a69d31702044a6e8ebbc [file] [log] [blame]
// Copyright 2013 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 "android_webview/browser/in_process_view_renderer.h"
#include <android/bitmap.h>
#include "android_webview/browser/aw_gl_surface.h"
#include "android_webview/browser/scoped_app_gl_state_restore.h"
#include "android_webview/common/aw_switches.h"
#include "android_webview/public/browser/draw_gl.h"
#include "android_webview/public/browser/draw_sw.h"
#include "base/android/jni_android.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "gpu/command_buffer/service/in_process_command_buffer.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkBitmapDevice.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkGraphics.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/utils/SkCanvasStateUtils.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/transform.h"
#include "ui/gfx/vector2d_conversions.h"
#include "ui/gfx/vector2d_f.h"
using base::android::AttachCurrentThread;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
using content::BrowserThread;
namespace android_webview {
namespace {
const void* kUserDataKey = &kUserDataKey;
class UserData : public content::WebContents::Data {
public:
UserData(InProcessViewRenderer* ptr) : instance_(ptr) {}
virtual ~UserData() {
instance_->WebContentsGone();
}
static InProcessViewRenderer* GetInstance(content::WebContents* contents) {
if (!contents)
return NULL;
UserData* data = reinterpret_cast<UserData*>(
contents->GetUserData(kUserDataKey));
return data ? data->instance_ : NULL;
}
private:
InProcessViewRenderer* instance_;
};
bool RasterizeIntoBitmap(JNIEnv* env,
const JavaRef<jobject>& jbitmap,
int scroll_x,
int scroll_y,
const InProcessViewRenderer::RenderMethod& renderer) {
DCHECK(jbitmap.obj());
AndroidBitmapInfo bitmap_info;
if (AndroidBitmap_getInfo(env, jbitmap.obj(), &bitmap_info) < 0) {
LOG(ERROR) << "Error getting java bitmap info.";
return false;
}
void* pixels = NULL;
if (AndroidBitmap_lockPixels(env, jbitmap.obj(), &pixels) < 0) {
LOG(ERROR) << "Error locking java bitmap pixels.";
return false;
}
bool succeeded;
{
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config,
bitmap_info.width,
bitmap_info.height,
bitmap_info.stride);
bitmap.setPixels(pixels);
SkBitmapDevice device(bitmap);
SkCanvas canvas(&device);
canvas.translate(-scroll_x, -scroll_y);
succeeded = renderer.Run(&canvas);
}
if (AndroidBitmap_unlockPixels(env, jbitmap.obj()) < 0) {
LOG(ERROR) << "Error unlocking java bitmap pixels.";
return false;
}
return succeeded;
}
class ScopedPixelAccess {
public:
ScopedPixelAccess(JNIEnv* env, jobject java_canvas) {
AwDrawSWFunctionTable* sw_functions =
BrowserViewRenderer::GetAwDrawSWFunctionTable();
pixels_ = sw_functions ?
sw_functions->access_pixels(env, java_canvas) : NULL;
}
~ScopedPixelAccess() {
if (pixels_)
BrowserViewRenderer::GetAwDrawSWFunctionTable()->release_pixels(pixels_);
}
AwPixelInfo* pixels() { return pixels_; }
private:
AwPixelInfo* pixels_;
DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedPixelAccess);
};
bool HardwareEnabled() {
static bool g_hw_enabled = !CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebViewGLMode);
return g_hw_enabled;
}
// Provides software rendering functions from the Android glue layer.
// Allows preventing extra copies of data when rendering.
AwDrawSWFunctionTable* g_sw_draw_functions = NULL;
const int64 kFallbackTickTimeoutInMilliseconds = 20;
// Used to calculate memory and resource allocation. Determined experimentally.
size_t g_memory_multiplier = 10;
size_t g_num_gralloc_limit = 150;
const size_t kBytesPerPixel = 4;
const size_t kMemoryAllocationStep = 5 * 1024 * 1024;
class ScopedAllowGL {
public:
ScopedAllowGL();
~ScopedAllowGL();
static bool IsAllowed() {
return BrowserThread::CurrentlyOn(BrowserThread::UI) && allow_gl;
}
private:
static bool allow_gl;
DISALLOW_COPY_AND_ASSIGN(ScopedAllowGL);
};
ScopedAllowGL::ScopedAllowGL() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!allow_gl);
allow_gl = true;
}
ScopedAllowGL::~ScopedAllowGL() {
allow_gl = false;
}
bool ScopedAllowGL::allow_gl = false;
base::LazyInstance<GLViewRendererManager>::Leaky g_view_renderer_manager;
void RequestProcessGLOnUIThread() {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE, base::Bind(&RequestProcessGLOnUIThread));
return;
}
InProcessViewRenderer* renderer = static_cast<InProcessViewRenderer*>(
g_view_renderer_manager.Get().GetMostRecentlyDrawn());
if (!renderer || !renderer->RequestProcessGL()) {
LOG(ERROR) << "Failed to request GL process. Deadlock likely: "
<< !!renderer;
}
}
} // namespace
// Called from different threads!
static void ScheduleGpuWork() {
if (ScopedAllowGL::IsAllowed()) {
gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread();
} else {
RequestProcessGLOnUIThread();
}
}
// static
void BrowserViewRenderer::SetAwDrawSWFunctionTable(
AwDrawSWFunctionTable* table) {
g_sw_draw_functions = table;
gpu::InProcessCommandBuffer::SetScheduleCallback(
base::Bind(&ScheduleGpuWork));
}
// static
AwDrawSWFunctionTable* BrowserViewRenderer::GetAwDrawSWFunctionTable() {
return g_sw_draw_functions;
}
InProcessViewRenderer::InProcessViewRenderer(
BrowserViewRenderer::Client* client,
JavaHelper* java_helper,
content::WebContents* web_contents)
: client_(client),
java_helper_(java_helper),
web_contents_(web_contents),
compositor_(NULL),
is_paused_(false),
view_visible_(false),
window_visible_(false),
attached_to_window_(false),
dip_scale_(0.0),
page_scale_factor_(1.0),
on_new_picture_enable_(false),
compositor_needs_continuous_invalidate_(false),
block_invalidates_(false),
width_(0),
height_(0),
hardware_initialized_(false),
hardware_failed_(false),
last_egl_context_(NULL),
manager_key_(g_view_renderer_manager.Get().NullKey()) {
CHECK(web_contents_);
web_contents_->SetUserData(kUserDataKey, new UserData(this));
content::SynchronousCompositor::SetClientForWebContents(web_contents_, this);
// Currently the logic in this class relies on |compositor_| remaining NULL
// until the DidInitializeCompositor() call, hence it is not set here.
}
InProcessViewRenderer::~InProcessViewRenderer() {
CHECK(web_contents_);
content::SynchronousCompositor::SetClientForWebContents(web_contents_, NULL);
web_contents_->SetUserData(kUserDataKey, NULL);
NoLongerExpectsDrawGL();
DCHECK(web_contents_ == NULL); // WebContentsGone should have been called.
}
void InProcessViewRenderer::NoLongerExpectsDrawGL() {
GLViewRendererManager& mru = g_view_renderer_manager.Get();
if (manager_key_ != mru.NullKey()) {
mru.NoLongerExpectsDrawGL(manager_key_);
manager_key_ = mru.NullKey();
}
}
// static
InProcessViewRenderer* InProcessViewRenderer::FromWebContents(
content::WebContents* contents) {
return UserData::GetInstance(contents);
}
void InProcessViewRenderer::WebContentsGone() {
web_contents_ = NULL;
compositor_ = NULL;
}
// static
void InProcessViewRenderer::CalculateTileMemoryPolicy() {
CommandLine* cl = CommandLine::ForCurrentProcess();
if (cl->HasSwitch(switches::kTileMemoryMultiplier)) {
std::string string_value =
cl->GetSwitchValueASCII(switches::kTileMemoryMultiplier);
int int_value = 0;
if (base::StringToInt(string_value, &int_value) &&
int_value >= 2 && int_value <= 50) {
g_memory_multiplier = int_value;
}
}
if (cl->HasSwitch(switches::kNumGrallocBuffersPerWebview)) {
std::string string_value =
cl->GetSwitchValueASCII(switches::kNumGrallocBuffersPerWebview);
int int_value = 0;
if (base::StringToInt(string_value, &int_value) &&
int_value >= 50 && int_value <= 500) {
g_num_gralloc_limit = int_value;
}
}
const char kDefaultTileSize[] = "384";
if (!cl->HasSwitch(switches::kDefaultTileWidth))
cl->AppendSwitchASCII(switches::kDefaultTileWidth, kDefaultTileSize);
if (!cl->HasSwitch(switches::kDefaultTileHeight))
cl->AppendSwitchASCII(switches::kDefaultTileHeight, kDefaultTileSize);
}
bool InProcessViewRenderer::RequestProcessGL() {
return client_->RequestDrawGL(NULL);
}
void InProcessViewRenderer::TrimMemory(int level) {
// Constants from Android ComponentCallbacks2.
enum {
TRIM_MEMORY_RUNNING_LOW = 10,
TRIM_MEMORY_UI_HIDDEN = 20,
TRIM_MEMORY_BACKGROUND = 40,
};
// Not urgent enough. TRIM_MEMORY_UI_HIDDEN is treated specially because
// it does not indicate memory pressure, but merely that the app is
// backgrounded.
if (level < TRIM_MEMORY_RUNNING_LOW || level == TRIM_MEMORY_UI_HIDDEN)
return;
// Nothing to drop.
if (!attached_to_window_ || !hardware_initialized_ || !compositor_)
return;
// Do not release resources on view we expect to get DrawGL soon.
if (level < TRIM_MEMORY_BACKGROUND) {
client_->UpdateGlobalVisibleRect();
if (view_visible_ && window_visible_ &&
!cached_global_visible_rect_.IsEmpty()) {
return;
}
}
if (!eglGetCurrentContext()) {
NOTREACHED();
return;
}
// Just set the memory limit to 0 and drop all tiles. This will be reset to
// normal levels in the next DrawGL call.
content::SynchronousCompositorMemoryPolicy policy;
policy.bytes_limit = 0;
policy.num_resources_limit = 0;
if (memory_policy_ == policy)
return;
TRACE_EVENT0("android_webview", "InProcessViewRenderer::TrimMemory");
ScopedAppGLStateRestore state_restore(
ScopedAppGLStateRestore::MODE_RESOURCE_MANAGEMENT);
gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread();
ScopedAllowGL allow_gl;
SetMemoryPolicy(policy);
ForceFakeCompositeSW();
}
void InProcessViewRenderer::SetMemoryPolicy(
content::SynchronousCompositorMemoryPolicy& new_policy) {
if (memory_policy_ == new_policy)
return;
memory_policy_ = new_policy;
compositor_->SetMemoryPolicy(memory_policy_);
}
void InProcessViewRenderer::UpdateCachedGlobalVisibleRect() {
client_->UpdateGlobalVisibleRect();
}
bool InProcessViewRenderer::OnDraw(jobject java_canvas,
bool is_hardware_canvas,
const gfx::Vector2d& scroll,
const gfx::Rect& clip) {
scroll_at_start_of_frame_ = scroll;
if (is_hardware_canvas && attached_to_window_ && HardwareEnabled()) {
// We should be performing a hardware draw here. If we don't have the
// comositor yet or if RequestDrawGL fails, it means we failed this draw and
// thus return false here to clear to background color for this draw.
return compositor_ && client_->RequestDrawGL(java_canvas);
}
// Perform a software draw
return DrawSWInternal(java_canvas, clip);
}
bool InProcessViewRenderer::InitializeHwDraw() {
TRACE_EVENT0("android_webview", "InitializeHwDraw");
DCHECK(!gl_surface_);
gl_surface_ = new AwGLSurface;
hardware_failed_ = !compositor_->InitializeHwDraw(gl_surface_);
hardware_initialized_ = true;
if (hardware_failed_)
gl_surface_ = NULL;
return !hardware_failed_;
}
void InProcessViewRenderer::DrawGL(AwDrawGLInfo* draw_info) {
TRACE_EVENT0("android_webview", "InProcessViewRenderer::DrawGL");
manager_key_ = g_view_renderer_manager.Get().DidDrawGL(manager_key_, this);
// We need to watch if the current Android context has changed and enforce
// a clean-up in the compositor.
EGLContext current_context = eglGetCurrentContext();
if (!current_context) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_NullEGLContext", TRACE_EVENT_SCOPE_THREAD);
return;
}
ScopedAppGLStateRestore state_restore(ScopedAppGLStateRestore::MODE_DRAW);
gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread();
ScopedAllowGL allow_gl;
if (!attached_to_window_) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_NotAttached", TRACE_EVENT_SCOPE_THREAD);
return;
}
if (draw_info->mode == AwDrawGLInfo::kModeProcess) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_ModeProcess", TRACE_EVENT_SCOPE_THREAD);
return;
}
if (compositor_ && !hardware_initialized_) {
if (InitializeHwDraw()) {
last_egl_context_ = current_context;
} else {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_HwInitFail", TRACE_EVENT_SCOPE_THREAD);
LOG(ERROR) << "WebView hardware initialization failed";
return;
}
}
UpdateCachedGlobalVisibleRect();
if (cached_global_visible_rect_.IsEmpty()) {
TRACE_EVENT_INSTANT0("android_webview",
"EarlyOut_EmptyVisibleRect",
TRACE_EVENT_SCOPE_THREAD);
return;
}
if (last_egl_context_ != current_context) {
// TODO(boliu): Handle context lost
TRACE_EVENT_INSTANT0(
"android_webview", "EGLContextChanged", TRACE_EVENT_SCOPE_THREAD);
}
if (!compositor_) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_NoCompositor", TRACE_EVENT_SCOPE_THREAD);
return;
}
// DrawGL may be called without OnDraw, so cancel |fallback_tick_| here as
// well just to be safe.
fallback_tick_.Cancel();
// Update memory budget. This will no-op in compositor if the policy has not
// changed since last draw.
content::SynchronousCompositorMemoryPolicy policy;
policy.bytes_limit = g_memory_multiplier * kBytesPerPixel *
cached_global_visible_rect_.width() *
cached_global_visible_rect_.height();
// Round up to a multiple of kMemoryAllocationStep.
policy.bytes_limit =
(policy.bytes_limit / kMemoryAllocationStep + 1) * kMemoryAllocationStep;
policy.num_resources_limit = g_num_gralloc_limit;
SetMemoryPolicy(policy);
DCHECK(gl_surface_);
gl_surface_->SetBackingFrameBufferObject(
state_restore.framebuffer_binding_ext());
gfx::Transform transform;
transform.matrix().setColMajorf(draw_info->transform);
transform.Translate(scroll_at_start_of_frame_.x(),
scroll_at_start_of_frame_.y());
gfx::Rect clip_rect(draw_info->clip_left,
draw_info->clip_top,
draw_info->clip_right - draw_info->clip_left,
draw_info->clip_bottom - draw_info->clip_top);
// Assume we always draw the full visible rect if we are drawing into a layer.
bool drew_full_visible_rect = true;
gfx::Rect viewport_rect;
if (!draw_info->is_layer) {
viewport_rect = cached_global_visible_rect_;
clip_rect.Intersect(viewport_rect);
drew_full_visible_rect = clip_rect.Contains(viewport_rect);
} else {
viewport_rect = clip_rect;
}
block_invalidates_ = true;
// TODO(joth): Check return value.
compositor_->DemandDrawHw(gfx::Size(draw_info->width, draw_info->height),
transform,
viewport_rect,
clip_rect,
state_restore.stencil_enabled());
block_invalidates_ = false;
gl_surface_->ResetBackingFrameBufferObject();
EnsureContinuousInvalidation(draw_info, !drew_full_visible_rect);
}
void InProcessViewRenderer::SetGlobalVisibleRect(
const gfx::Rect& visible_rect) {
cached_global_visible_rect_ = visible_rect;
}
bool InProcessViewRenderer::DrawSWInternal(jobject java_canvas,
const gfx::Rect& clip) {
if (clip.IsEmpty()) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_EmptyClip", TRACE_EVENT_SCOPE_THREAD);
return true;
}
if (!compositor_) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_NoCompositor", TRACE_EVENT_SCOPE_THREAD);
return false;
}
return RenderViaAuxilaryBitmapIfNeeded(
java_canvas,
java_helper_,
scroll_at_start_of_frame_,
clip,
base::Bind(&InProcessViewRenderer::CompositeSW,
base::Unretained(this)),
web_contents_);
}
// static
bool InProcessViewRenderer::RenderViaAuxilaryBitmapIfNeeded(
jobject java_canvas,
BrowserViewRenderer::JavaHelper* java_helper,
const gfx::Vector2d& scroll_correction,
const gfx::Rect& clip,
InProcessViewRenderer::RenderMethod render_source,
void* owner_key) {
TRACE_EVENT0("android_webview",
"InProcessViewRenderer::RenderViaAuxilaryBitmapIfNeeded");
JNIEnv* env = AttachCurrentThread();
ScopedPixelAccess auto_release_pixels(env, java_canvas);
AwPixelInfo* pixels = auto_release_pixels.pixels();
if (pixels && pixels->state) {
skia::RefPtr<SkCanvas> canvas = skia::AdoptRef(
SkCanvasStateUtils::CreateFromCanvasState(pixels->state));
// Workarounds for http://crbug.com/271096: SW draw only supports
// translate & scale transforms, and a simple rectangular clip.
if (canvas && (!canvas->getTotalClip().isRect() ||
(canvas->getTotalMatrix().getType() &
~(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)))) {
canvas.clear();
}
if (canvas) {
canvas->translate(scroll_correction.x(), scroll_correction.y());
return render_source.Run(canvas.get());
}
}
// Render into an auxiliary bitmap if pixel info is not available.
ScopedJavaLocalRef<jobject> jcanvas(env, java_canvas);
TRACE_EVENT0("android_webview", "RenderToAuxBitmap");
ScopedJavaLocalRef<jobject> jbitmap(java_helper->CreateBitmap(
env, clip.width(), clip.height(), jcanvas, owner_key));
if (!jbitmap.obj()) {
TRACE_EVENT_INSTANT0("android_webview",
"EarlyOut_BitmapAllocFail",
TRACE_EVENT_SCOPE_THREAD);
return false;
}
if (!RasterizeIntoBitmap(env, jbitmap,
clip.x() - scroll_correction.x(),
clip.y() - scroll_correction.y(),
render_source)) {
TRACE_EVENT_INSTANT0("android_webview",
"EarlyOut_RasterizeFail",
TRACE_EVENT_SCOPE_THREAD);
return false;
}
java_helper->DrawBitmapIntoCanvas(env, jbitmap, jcanvas,
clip.x(), clip.y());
return true;
}
skia::RefPtr<SkPicture> InProcessViewRenderer::CapturePicture(int width,
int height) {
TRACE_EVENT0("android_webview", "InProcessViewRenderer::CapturePicture");
// Return empty Picture objects for empty SkPictures.
skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture);
if (width <= 0 || height <= 0) {
return picture;
}
// Reset scroll back to the origin, will go back to the old
// value when scroll_reset is out of scope.
base::AutoReset<gfx::Vector2dF> scroll_reset(&scroll_offset_dip_,
gfx::Vector2d());
SkCanvas* rec_canvas = picture->beginRecording(width, height, 0);
if (compositor_)
CompositeSW(rec_canvas);
picture->endRecording();
return picture;
}
void InProcessViewRenderer::EnableOnNewPicture(bool enabled) {
on_new_picture_enable_ = enabled;
EnsureContinuousInvalidation(NULL, false);
}
void InProcessViewRenderer::SetIsPaused(bool paused) {
TRACE_EVENT_INSTANT1("android_webview",
"InProcessViewRenderer::SetIsPaused",
TRACE_EVENT_SCOPE_THREAD,
"paused",
paused);
is_paused_ = paused;
EnsureContinuousInvalidation(NULL, false);
}
void InProcessViewRenderer::SetViewVisibility(bool view_visible) {
TRACE_EVENT_INSTANT1("android_webview",
"InProcessViewRenderer::SetViewVisibility",
TRACE_EVENT_SCOPE_THREAD,
"view_visible",
view_visible);
view_visible_ = view_visible;
}
void InProcessViewRenderer::SetWindowVisibility(bool window_visible) {
TRACE_EVENT_INSTANT1("android_webview",
"InProcessViewRenderer::SetWindowVisibility",
TRACE_EVENT_SCOPE_THREAD,
"window_visible",
window_visible);
window_visible_ = window_visible;
EnsureContinuousInvalidation(NULL, false);
}
void InProcessViewRenderer::OnSizeChanged(int width, int height) {
TRACE_EVENT_INSTANT2("android_webview",
"InProcessViewRenderer::OnSizeChanged",
TRACE_EVENT_SCOPE_THREAD,
"width",
width,
"height",
height);
width_ = width;
height_ = height;
}
void InProcessViewRenderer::OnAttachedToWindow(int width, int height) {
TRACE_EVENT2("android_webview",
"InProcessViewRenderer::OnAttachedToWindow",
"width",
width,
"height",
height);
attached_to_window_ = true;
width_ = width;
height_ = height;
}
void InProcessViewRenderer::OnDetachedFromWindow() {
TRACE_EVENT0("android_webview",
"InProcessViewRenderer::OnDetachedFromWindow");
NoLongerExpectsDrawGL();
if (hardware_initialized_) {
DCHECK(compositor_);
ScopedAppGLStateRestore state_restore(
ScopedAppGLStateRestore::MODE_RESOURCE_MANAGEMENT);
gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread();
ScopedAllowGL allow_gl;
compositor_->ReleaseHwDraw();
hardware_initialized_ = false;
}
gl_surface_ = NULL;
attached_to_window_ = false;
}
bool InProcessViewRenderer::IsAttachedToWindow() {
return attached_to_window_;
}
bool InProcessViewRenderer::IsVisible() {
// Ignore |window_visible_| if |attached_to_window_| is false.
return view_visible_ && (!attached_to_window_ || window_visible_);
}
gfx::Rect InProcessViewRenderer::GetScreenRect() {
return gfx::Rect(client_->GetLocationOnScreen(), gfx::Size(width_, height_));
}
void InProcessViewRenderer::DidInitializeCompositor(
content::SynchronousCompositor* compositor) {
TRACE_EVENT0("android_webview",
"InProcessViewRenderer::DidInitializeCompositor");
DCHECK(compositor && compositor_ == NULL);
compositor_ = compositor;
hardware_initialized_ = false;
hardware_failed_ = false;
}
void InProcessViewRenderer::DidDestroyCompositor(
content::SynchronousCompositor* compositor) {
TRACE_EVENT0("android_webview",
"InProcessViewRenderer::DidDestroyCompositor");
DCHECK(compositor_ == compositor);
// This can fail if Apps call destroy while the webview is still attached
// to the view tree. This is an illegal operation that will lead to leaks.
// Log for now. Consider a proper fix if this becomes a problem.
LOG_IF(ERROR, hardware_initialized_)
<< "Destroy called before OnDetachedFromWindow. May Leak GL resources";
compositor_ = NULL;
}
void InProcessViewRenderer::SetContinuousInvalidate(bool invalidate) {
if (compositor_needs_continuous_invalidate_ == invalidate)
return;
TRACE_EVENT_INSTANT1("android_webview",
"InProcessViewRenderer::SetContinuousInvalidate",
TRACE_EVENT_SCOPE_THREAD,
"invalidate",
invalidate);
compositor_needs_continuous_invalidate_ = invalidate;
EnsureContinuousInvalidation(NULL, false);
}
void InProcessViewRenderer::SetDipScale(float dip_scale) {
dip_scale_ = dip_scale;
CHECK(dip_scale_ > 0);
}
gfx::Vector2d InProcessViewRenderer::max_scroll_offset() const {
DCHECK_GT(dip_scale_, 0);
return gfx::ToCeiledVector2d(gfx::ScaleVector2d(
max_scroll_offset_dip_, dip_scale_ * page_scale_factor_));
}
void InProcessViewRenderer::ScrollTo(gfx::Vector2d scroll_offset) {
gfx::Vector2d max_offset = max_scroll_offset();
gfx::Vector2dF scroll_offset_dip;
// 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_dip.set_x((scroll_offset.x() * max_scroll_offset_dip_.x()) /
max_offset.x());
}
if (max_offset.y()) {
scroll_offset_dip.set_y((scroll_offset.y() * max_scroll_offset_dip_.y()) /
max_offset.y());
}
DCHECK_LE(0, scroll_offset_dip.x());
DCHECK_LE(0, scroll_offset_dip.y());
DCHECK_LE(scroll_offset_dip.x(), max_scroll_offset_dip_.x());
DCHECK_LE(scroll_offset_dip.y(), max_scroll_offset_dip_.y());
if (scroll_offset_dip_ == scroll_offset_dip)
return;
scroll_offset_dip_ = scroll_offset_dip;
if (compositor_)
compositor_->DidChangeRootLayerScrollOffset();
}
void InProcessViewRenderer::DidUpdateContent() {
if (on_new_picture_enable_)
client_->OnNewPicture();
}
void InProcessViewRenderer::SetMaxRootLayerScrollOffset(
gfx::Vector2dF new_value_dip) {
DCHECK_GT(dip_scale_, 0);
max_scroll_offset_dip_ = new_value_dip;
DCHECK_LE(0, max_scroll_offset_dip_.x());
DCHECK_LE(0, max_scroll_offset_dip_.y());
client_->SetMaxContainerViewScrollOffset(max_scroll_offset());
}
void InProcessViewRenderer::SetTotalRootLayerScrollOffset(
gfx::Vector2dF scroll_offset_dip) {
// TOOD(mkosiba): Add a DCHECK to say that this does _not_ get called during
// DrawGl when http://crbug.com/249972 is fixed.
if (scroll_offset_dip_ == scroll_offset_dip)
return;
scroll_offset_dip_ = scroll_offset_dip;
gfx::Vector2d max_offset = max_scroll_offset();
gfx::Vector2d scroll_offset;
// For an explanation as to why this is done this way see the comment in
// InProcessViewRenderer::ScrollTo.
if (max_scroll_offset_dip_.x()) {
scroll_offset.set_x((scroll_offset_dip.x() * max_offset.x()) /
max_scroll_offset_dip_.x());
}
if (max_scroll_offset_dip_.y()) {
scroll_offset.set_y((scroll_offset_dip.y() * max_offset.y()) /
max_scroll_offset_dip_.y());
}
DCHECK(0 <= scroll_offset.x());
DCHECK(0 <= scroll_offset.y());
DCHECK(scroll_offset.x() <= max_offset.x());
DCHECK(scroll_offset.y() <= max_offset.y());
client_->ScrollContainerViewTo(scroll_offset);
}
gfx::Vector2dF InProcessViewRenderer::GetTotalRootLayerScrollOffset() {
return scroll_offset_dip_;
}
bool InProcessViewRenderer::IsExternalFlingActive() const {
return client_->IsFlingActive();
}
void InProcessViewRenderer::SetRootLayerPageScaleFactor(
float page_scale_factor) {
page_scale_factor_ = page_scale_factor;
DCHECK_GT(page_scale_factor_, 0);
client_->SetPageScaleFactor(page_scale_factor);
}
void InProcessViewRenderer::SetRootLayerScrollableSize(
gfx::SizeF scrollable_size) {
client_->SetContentsSize(scrollable_size);
}
void InProcessViewRenderer::DidOverscroll(
gfx::Vector2dF accumulated_overscroll,
gfx::Vector2dF latest_overscroll_delta,
gfx::Vector2dF current_fling_velocity) {
DCHECK(current_fling_velocity.IsZero());
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;
client_->DidOverscroll(rounded_overscroll_delta);
}
void InProcessViewRenderer::EnsureContinuousInvalidation(
AwDrawGLInfo* draw_info,
bool invalidate_ignore_compositor) {
// This method should be called again when any of these conditions change.
bool need_invalidate =
compositor_needs_continuous_invalidate_ || invalidate_ignore_compositor;
if (!need_invalidate || block_invalidates_)
return;
if (draw_info) {
draw_info->dirty_left = cached_global_visible_rect_.x();
draw_info->dirty_top = cached_global_visible_rect_.y();
draw_info->dirty_right = cached_global_visible_rect_.right();
draw_info->dirty_bottom = cached_global_visible_rect_.bottom();
draw_info->status_mask |= AwDrawGLInfo::kStatusMaskDraw;
} else {
client_->PostInvalidate();
}
bool throttle_fallback_tick = (is_paused_ && !on_new_picture_enable_) ||
(attached_to_window_ && !window_visible_);
if (throttle_fallback_tick)
return;
block_invalidates_ = compositor_needs_continuous_invalidate_;
// Unretained here is safe because the callback is cancelled when
// |fallback_tick_| is destroyed.
fallback_tick_.Reset(base::Bind(&InProcessViewRenderer::FallbackTickFired,
base::Unretained(this)));
// No need to reschedule fallback tick if compositor does not need to be
// ticked. This can happen if this is reached because
// invalidate_ignore_compositor is true.
if (compositor_needs_continuous_invalidate_) {
BrowserThread::PostDelayedTask(
BrowserThread::UI,
FROM_HERE,
fallback_tick_.callback(),
base::TimeDelta::FromMilliseconds(
kFallbackTickTimeoutInMilliseconds));
}
}
void InProcessViewRenderer::FallbackTickFired() {
TRACE_EVENT1("android_webview",
"InProcessViewRenderer::FallbackTickFired",
"compositor_needs_continuous_invalidate_",
compositor_needs_continuous_invalidate_);
// This should only be called if OnDraw or DrawGL did not come in time, which
// means block_invalidates_ must still be true.
DCHECK(block_invalidates_);
if (compositor_needs_continuous_invalidate_ && compositor_)
ForceFakeCompositeSW();
}
void InProcessViewRenderer::ForceFakeCompositeSW() {
DCHECK(compositor_);
SkBitmapDevice device(SkBitmap::kARGB_8888_Config, 1, 1);
SkCanvas canvas(&device);
CompositeSW(&canvas);
}
bool InProcessViewRenderer::CompositeSW(SkCanvas* canvas) {
DCHECK(compositor_);
fallback_tick_.Cancel();
block_invalidates_ = true;
bool result = compositor_->DemandDrawSw(canvas);
block_invalidates_ = false;
EnsureContinuousInvalidation(NULL, false);
return result;
}
std::string InProcessViewRenderer::ToString(AwDrawGLInfo* draw_info) 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,
"compositor_needs_continuous_invalidate: %d ",
compositor_needs_continuous_invalidate_);
base::StringAppendF(&str, "block_invalidates: %d ", block_invalidates_);
base::StringAppendF(&str, "view width height: [%d %d] ", width_, height_);
base::StringAppendF(&str, "attached_to_window: %d ", attached_to_window_);
base::StringAppendF(&str, "hardware_initialized: %d ", hardware_initialized_);
base::StringAppendF(&str, "hardware_failed: %d ", hardware_failed_);
base::StringAppendF(&str,
"global visible rect: %s ",
cached_global_visible_rect_.ToString().c_str());
base::StringAppendF(&str,
"scroll_at_start_of_frame: %s ",
scroll_at_start_of_frame_.ToString().c_str());
base::StringAppendF(
&str, "scroll_offset_dip: %s ", scroll_offset_dip_.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_);
if (draw_info) {
base::StringAppendF(&str,
"clip left top right bottom: [%d %d %d %d] ",
draw_info->clip_left,
draw_info->clip_top,
draw_info->clip_right,
draw_info->clip_bottom);
base::StringAppendF(&str,
"surface width height: [%d %d] ",
draw_info->width,
draw_info->height);
base::StringAppendF(&str, "is_layer: %d ", draw_info->is_layer);
}
return str;
}
} // namespace android_webview