blob: 2b60bea7e39de5275359f49ba18a9bf938b72066 [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/scoped_app_gl_state_restore.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/command_line.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "content/public/browser/android/synchronous_compositor.h"
#include "content/public/browser/web_contents.h"
#include "skia/ext/refptr.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkDevice.h"
#include "third_party/skia/include/core/SkGraphics.h"
#include "third_party/skia/include/core/SkPicture.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;
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_;
};
typedef base::Callback<bool(SkCanvas*)> RenderMethod;
bool RasterizeIntoBitmap(JNIEnv* env,
const JavaRef<jobject>& jbitmap,
int scroll_x,
int scroll_y,
const 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);
SkDevice 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;
}
bool RenderPictureToCanvas(SkPicture* picture, SkCanvas* canvas) {
canvas->drawPicture(*picture);
return true;
}
// TODO(boliu): Remove this when hardware mode is ready.
bool HardwareEnabled() {
return CommandLine::ForCurrentProcess()->HasSwitch("testing-webview-gl-mode");
}
// Provides software rendering functions from the Android glue layer.
// Allows preventing extra copies of data when rendering.
AwDrawSWFunctionTable* g_sw_draw_functions = NULL;
// Tells if the Skia library versions in Android and Chromium are compatible.
// If they are then it's possible to pass Skia objects like SkPictures to the
// Android glue layer via the SW rendering functions.
// If they are not, then additional copies and rasterizations are required
// as a fallback mechanism, which will have an important performance impact.
bool g_is_skia_version_compatible = false;
const int64 kFallbackTickTimeoutInMilliseconds = 500;
} // namespace
// static
void BrowserViewRenderer::SetAwDrawSWFunctionTable(
AwDrawSWFunctionTable* table) {
g_sw_draw_functions = table;
g_is_skia_version_compatible =
g_sw_draw_functions->is_skia_version_compatible(&SkGraphics::GetVersion);
LOG_IF(WARNING, !g_is_skia_version_compatible)
<< "Skia versions are not compatible, rendering performance will suffer.";
}
// static
AwDrawSWFunctionTable* BrowserViewRenderer::GetAwDrawSWFunctionTable() {
return g_sw_draw_functions;
}
// static
bool BrowserViewRenderer::IsSkiaVersionCompatible() {
DCHECK(g_sw_draw_functions);
return g_is_skia_version_compatible;
}
InProcessViewRenderer::InProcessViewRenderer(
BrowserViewRenderer::Client* client,
JavaHelper* java_helper,
content::WebContents* web_contents)
: client_(client),
java_helper_(java_helper),
web_contents_(web_contents),
compositor_(NULL),
visible_(false),
dip_scale_(0.0),
page_scale_factor_(1.0),
on_new_picture_enable_(false),
continuous_invalidate_(false),
block_invalidates_(false),
width_(0),
height_(0),
attached_to_window_(false),
hardware_initialized_(false),
hardware_failed_(false),
last_egl_context_(NULL) {
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);
DCHECK(web_contents_ == NULL); // WebContentsGone should have been called.
}
// static
InProcessViewRenderer* InProcessViewRenderer::FromWebContents(
content::WebContents* contents) {
return UserData::GetInstance(contents);
}
void InProcessViewRenderer::WebContentsGone() {
web_contents_ = NULL;
compositor_ = NULL;
}
bool InProcessViewRenderer::OnDraw(jobject java_canvas,
bool is_hardware_canvas,
const gfx::Vector2d& scroll,
const gfx::Rect& clip) {
fallback_tick_.Cancel();
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
block_invalidates_ = true;
bool result = DrawSWInternal(java_canvas, clip);
block_invalidates_ = false;
EnsureContinuousInvalidation(NULL);
return result;
}
void InProcessViewRenderer::DrawGL(AwDrawGLInfo* draw_info) {
TRACE_EVENT0("android_webview", "InProcessViewRenderer::DrawGL");
DCHECK(visible_);
// 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);
if (attached_to_window_ && compositor_ && !hardware_initialized_) {
TRACE_EVENT0("android_webview", "InitializeHwDraw");
hardware_failed_ = !compositor_->InitializeHwDraw();
hardware_initialized_ = true;
last_egl_context_ = current_context;
if (hardware_failed_)
return;
}
if (draw_info->mode == AwDrawGLInfo::kModeProcess)
return;
// DrawGL may be called without OnDraw, so cancel |fallback_tick_| here as
// well just to be safe.
fallback_tick_.Cancel();
if (last_egl_context_ != current_context) {
// TODO(boliu): Handle context lost
TRACE_EVENT_INSTANT0(
"android_webview", "EGLContextChanged", TRACE_EVENT_SCOPE_THREAD);
}
last_egl_context_ = current_context;
if (!compositor_) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_NoCompositor", TRACE_EVENT_SCOPE_THREAD);
return;
}
gfx::Transform transform;
transform.matrix().setColMajorf(draw_info->transform);
transform.Translate(scroll_at_start_of_frame_.x(),
scroll_at_start_of_frame_.y());
// TODO(joth): Check return value.
block_invalidates_ = true;
compositor_->DemandDrawHw(
gfx::Size(draw_info->width, draw_info->height),
transform,
gfx::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));
block_invalidates_ = false;
EnsureContinuousInvalidation(draw_info);
}
bool InProcessViewRenderer::DrawSWInternal(jobject java_canvas,
const gfx::Rect& clip) {
TRACE_EVENT0("android_webview", "InProcessViewRenderer::DrawSW");
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;
}
JNIEnv* env = AttachCurrentThread();
AwDrawSWFunctionTable* sw_functions = GetAwDrawSWFunctionTable();
AwPixelInfo* pixels = sw_functions ?
sw_functions->access_pixels(env, java_canvas) : NULL;
// Render into an auxiliary bitmap if pixel info is not available.
ScopedJavaLocalRef<jobject> jcanvas(env, java_canvas);
if (pixels == NULL) {
TRACE_EVENT0("android_webview", "RenderToAuxBitmap");
ScopedJavaLocalRef<jobject> jbitmap(java_helper_->CreateBitmap(
env, clip.width(), clip.height(), jcanvas, web_contents_));
if (!jbitmap.obj()) {
TRACE_EVENT_INSTANT0("android_webview",
"EarlyOut_BitmapAllocFail",
TRACE_EVENT_SCOPE_THREAD);
return false;
}
if (!RasterizeIntoBitmap(env, jbitmap,
clip.x() - scroll_at_start_of_frame_.x(),
clip.y() - scroll_at_start_of_frame_.y(),
base::Bind(&InProcessViewRenderer::CompositeSW,
base::Unretained(this)))) {
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;
}
// Draw in a SkCanvas built over the pixel information.
bool succeeded = false;
{
SkBitmap bitmap;
bitmap.setConfig(static_cast<SkBitmap::Config>(pixels->config),
pixels->width,
pixels->height,
pixels->row_bytes);
bitmap.setPixels(pixels->pixels);
SkDevice device(bitmap);
SkCanvas canvas(&device);
SkMatrix matrix;
for (int i = 0; i < 9; i++)
matrix.set(i, pixels->matrix[i]);
canvas.setMatrix(matrix);
if (pixels->clip_region_size) {
SkRegion clip_region;
size_t bytes_read = clip_region.readFromMemory(pixels->clip_region);
DCHECK_EQ(pixels->clip_region_size, bytes_read);
canvas.setClipRegion(clip_region);
} else {
canvas.clipRect(gfx::RectToSkRect(clip));
}
canvas.translate(scroll_at_start_of_frame_.x(),
scroll_at_start_of_frame_.y());
succeeded = CompositeSW(&canvas);
}
sw_functions->release_pixels(pixels);
return succeeded;
}
base::android::ScopedJavaLocalRef<jobject>
InProcessViewRenderer::CapturePicture() {
if (!compositor_ || !GetAwDrawSWFunctionTable()) {
TRACE_EVENT_INSTANT0(
"android_webview", "EarlyOut_CapturePicture", TRACE_EVENT_SCOPE_THREAD);
return ScopedJavaLocalRef<jobject>();
}
gfx::Size record_size(width_, height_);
// Return empty Picture objects for empty SkPictures.
JNIEnv* env = AttachCurrentThread();
if (record_size.width() <= 0 || record_size.height() <= 0) {
return java_helper_->RecordBitmapIntoPicture(
env, ScopedJavaLocalRef<jobject>());
}
skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture);
SkCanvas* rec_canvas = picture->beginRecording(record_size.width(),
record_size.height(),
0);
if (!CompositeSW(rec_canvas))
return ScopedJavaLocalRef<jobject>();
picture->endRecording();
if (IsSkiaVersionCompatible()) {
// Add a reference that the create_picture() will take ownership of.
picture->ref();
return ScopedJavaLocalRef<jobject>(env,
GetAwDrawSWFunctionTable()->create_picture(env, picture.get()));
}
// If Skia versions are not compatible, workaround it by rasterizing the
// picture into a bitmap and drawing it into a new Java picture. Pass null
// for |canvas| as we don't have java canvas at this point (and it would be
// software anyway).
ScopedJavaLocalRef<jobject> jbitmap(java_helper_->CreateBitmap(
env, picture->width(), picture->height(), ScopedJavaLocalRef<jobject>(),
NULL));
if (!jbitmap.obj())
return ScopedJavaLocalRef<jobject>();
if (!RasterizeIntoBitmap(env, jbitmap, 0, 0,
base::Bind(&RenderPictureToCanvas,
base::Unretained(picture.get())))) {
return ScopedJavaLocalRef<jobject>();
}
return java_helper_->RecordBitmapIntoPicture(env, jbitmap);
}
void InProcessViewRenderer::EnableOnNewPicture(bool enabled) {
on_new_picture_enable_ = enabled;
}
void InProcessViewRenderer::OnVisibilityChanged(bool visible) {
TRACE_EVENT_INSTANT1("android_webview",
"InProcessViewRenderer::OnVisibilityChanged",
TRACE_EVENT_SCOPE_THREAD,
"visible",
visible);
visible_ = visible;
}
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");
if (hardware_initialized_) {
DCHECK(compositor_);
ScopedAppGLStateRestore state_restore(
ScopedAppGLStateRestore::MODE_DETACH_FROM_WINDOW);
compositor_->ReleaseHwDraw();
hardware_initialized_ = false;
}
attached_to_window_ = false;
}
bool InProcessViewRenderer::IsAttachedToWindow() {
return attached_to_window_;
}
bool InProcessViewRenderer::IsViewVisible() {
return 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 (continuous_invalidate_ == invalidate)
return;
TRACE_EVENT_INSTANT1("android_webview",
"InProcessViewRenderer::SetContinuousInvalidate",
TRACE_EVENT_SCOPE_THREAD,
"invalidate",
invalidate);
continuous_invalidate_ = invalidate;
EnsureContinuousInvalidation(NULL);
}
void InProcessViewRenderer::SetDipScale(float dip_scale) {
dip_scale_ = dip_scale;
CHECK(dip_scale_ > 0);
}
void InProcessViewRenderer::SetPageScaleFactor(float page_scale_factor) {
page_scale_factor_ = page_scale_factor;
CHECK(page_scale_factor_ > 0);
}
void InProcessViewRenderer::ScrollTo(gfx::Vector2d new_value) {
DCHECK(dip_scale_ > 0);
// In general we don't guarantee that the scroll offset transforms are
// symmetrical. That is if scrolling from JS to offset1 results in a native
// offset2 then scrolling from UI to offset2 results in JS being scrolled to
// offset1 again.
// The reason we explicitly do rounding here is that it seems to yeld the
// most stabile transformation.
gfx::Vector2dF new_value_css = gfx::ToRoundedVector2d(
gfx::ScaleVector2d(new_value, 1.0f / (dip_scale_ * page_scale_factor_)));
DCHECK(scroll_offset_css_ != new_value_css);
scroll_offset_css_ = new_value_css;
if (compositor_)
compositor_->DidChangeRootLayerScrollOffset();
}
void InProcessViewRenderer::DidUpdateContent() {
if (on_new_picture_enable_)
client_->OnNewPicture();
}
void InProcessViewRenderer::SetTotalRootLayerScrollOffset(
gfx::Vector2dF new_value_css) {
// 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_css_ == new_value_css)
return;
scroll_offset_css_ = new_value_css;
DCHECK(dip_scale_ > 0);
DCHECK(page_scale_factor_ > 0);
gfx::Vector2d scroll_offset = gfx::ToRoundedVector2d(
gfx::ScaleVector2d(new_value_css, dip_scale_ * page_scale_factor_));
client_->ScrollContainerViewTo(scroll_offset);
}
gfx::Vector2dF InProcessViewRenderer::GetTotalRootLayerScrollOffset() {
return scroll_offset_css_;
}
void InProcessViewRenderer::DidOverscroll(
gfx::Vector2dF latest_overscroll_delta,
gfx::Vector2dF current_fling_velocity) {
// TODO(mkosiba): Enable this once flinging is handled entirely Java-side.
// DCHECK(current_fling_velocity.IsZero());
const float physical_pixel_scale = dip_scale_ * page_scale_factor_;
gfx::Vector2dF scaled_overscroll_delta = gfx::ScaleVector2d(
latest_overscroll_delta + overscroll_rounding_error_,
physical_pixel_scale);
gfx::Vector2d rounded_overscroll_delta =
gfx::ToRoundedVector2d(scaled_overscroll_delta);
overscroll_rounding_error_ =
scaled_overscroll_delta - rounded_overscroll_delta;
client_->DidOverscroll(rounded_overscroll_delta);
}
void InProcessViewRenderer::EnsureContinuousInvalidation(
AwDrawGLInfo* draw_info) {
if (continuous_invalidate_ && !block_invalidates_) {
if (draw_info) {
draw_info->dirty_left = draw_info->clip_left;
draw_info->dirty_top = draw_info->clip_top;
draw_info->dirty_right = draw_info->clip_right;
draw_info->dirty_bottom = draw_info->clip_bottom;
draw_info->status_mask |= AwDrawGLInfo::kStatusMaskDraw;
} else {
client_->PostInvalidate();
}
// Unretained here is safe because the callback is cancelled when
// |fallback_tick_| is destroyed.
fallback_tick_.Reset(base::Bind(&InProcessViewRenderer::FallbackTickFired,
base::Unretained(this)));
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
fallback_tick_.callback(),
base::TimeDelta::FromMilliseconds(kFallbackTickTimeoutInMilliseconds));
block_invalidates_ = true;
}
}
void InProcessViewRenderer::FallbackTickFired() {
TRACE_EVENT1("android_webview",
"InProcessViewRenderer::FallbackTickFired",
"continuous_invalidate_",
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 (continuous_invalidate_ && compositor_) {
SkDevice device(SkBitmap::kARGB_8888_Config, 1, 1);
SkCanvas canvas(&device);
block_invalidates_ = true;
CompositeSW(&canvas);
}
block_invalidates_ = false;
EnsureContinuousInvalidation(NULL);
}
bool InProcessViewRenderer::CompositeSW(SkCanvas* canvas) {
DCHECK(compositor_);
return compositor_->DemandDrawSw(canvas);
}
std::string InProcessViewRenderer::ToString(AwDrawGLInfo* draw_info) const {
std::string str;
base::StringAppendF(&str, "visible: %d ", visible_);
base::StringAppendF(&str, "dip_scale: %f ", dip_scale_);
base::StringAppendF(&str, "page_scale_factor: %f ", page_scale_factor_);
base::StringAppendF(
&str, "continuous_invalidate: %d ", 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,
"scroll_at_start_of_frame: %s ",
scroll_at_start_of_frame_.ToString().c_str());
base::StringAppendF(
&str, "scroll_offset_css: %s ", scroll_offset_css_.ToString().c_str());
base::StringAppendF(&str,
"overscroll_rounding_error_: %s ",
overscroll_rounding_error_.ToString().c_str());
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