diff options
author | leandrogracia@chromium.org <leandrogracia@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-15 15:03:20 +0000 |
---|---|---|
committer | leandrogracia@chromium.org <leandrogracia@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-15 15:03:20 +0000 |
commit | 6f9281cf8100cf1c58f2eefd49fb763d9466a3cf (patch) | |
tree | ab20e217083171761c8fd4055d1fb22cc7abaffc /android_webview/browser/browser_view_renderer_impl.cc | |
parent | b0ca19705bd130834030973216288f2b2cb80707 (diff) | |
download | chromium_src-6f9281cf8100cf1c58f2eefd49fb763d9466a3cf.zip chromium_src-6f9281cf8100cf1c58f2eefd49fb763d9466a3cf.tar.gz chromium_src-6f9281cf8100cf1c58f2eefd49fb763d9466a3cf.tar.bz2 |
[Android WebView] Migrate the rendering code to a separate set of classes.
It takes from https://codereview.chromium.org/11823027/
and assumes SW rendering and Capture Picture to be ready and enabled.
Most changes just move around code. The major structural changes are:
- Introduce a browser-layer view renderer interface and move the code to its implementation.
- Take out the rendering-related IPC to its own separate set of host/renderer classes.
- Change the way the view hierarchy and the compositor are initialized. Now they are created and set on BrowserViewRendererImpl construction.
- Content is now provided via a ContentViewCore object when initialized, updating the layer to use and the WebContents to observe.
- Remove/update the DEPS and gyp changes introduced to support rendering in the native layer.
BUG=167913,167908,161409
NOTRY=true
Review URL: https://chromiumcodereview.appspot.com/12041009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182710 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'android_webview/browser/browser_view_renderer_impl.cc')
-rw-r--r-- | android_webview/browser/browser_view_renderer_impl.cc | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/android_webview/browser/browser_view_renderer_impl.cc b/android_webview/browser/browser_view_renderer_impl.cc new file mode 100644 index 0000000..250cdb0 --- /dev/null +++ b/android_webview/browser/browser_view_renderer_impl.cc @@ -0,0 +1,596 @@ +// Copyright (c) 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/browser_view_renderer_impl.h" + +#include <android/bitmap.h> +#include <sys/system_properties.h> + +#include "android_webview/common/renderer_picture_map.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/debug/trace_event.h" +#include "base/logging.h" +#include "cc/layer.h" +#include "content/public/browser/android/content_view_core.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.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 "ui/gfx/size.h" +#include "ui/gfx/transform.h" +#include "ui/gl/gl_bindings.h" + +// TODO(leandrogracia): remove when crbug.com/164140 is closed. +// Borrowed from gl2ext.h. Cannot be included due to conflicts with +// gl_bindings.h and the EGL library methods (eglGetCurrentContext). +#ifndef GL_TEXTURE_EXTERNAL_OES +#define GL_TEXTURE_EXTERNAL_OES 0x8D65 +#endif + +#ifndef GL_TEXTURE_BINDING_EXTERNAL_OES +#define GL_TEXTURE_BINDING_EXTERNAL_OES 0x8D67 +#endif + +using base::android::AttachCurrentThread; +using base::android::JavaRef; +using base::android::ScopedJavaLocalRef; +using content::Compositor; +using content::ContentViewCore; + +namespace { + +// 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; + +typedef base::Callback<bool(SkCanvas*)> RenderMethod; + +static 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; +} + +} // namespace + +namespace android_webview { + +// static +BrowserViewRendererImpl* BrowserViewRendererImpl::Create( + BrowserViewRenderer::Client* client, + JavaHelper* java_helper) { + return new BrowserViewRendererImpl(client, java_helper); +} + +BrowserViewRendererImpl::BrowserViewRendererImpl( + BrowserViewRenderer::Client* client, + JavaHelper* java_helper) + : client_(client), + java_helper_(java_helper), + ALLOW_THIS_IN_INITIALIZER_LIST(compositor_(Compositor::Create(this))), + view_clip_layer_(cc::Layer::create()), + transform_layer_(cc::Layer::create()), + scissor_clip_layer_(cc::Layer::create()), + view_visible_(false), + compositor_visible_(false), + is_composite_pending_(false), + dpi_scale_(1.0f), + on_new_picture_mode_(kOnNewPictureDisabled), + last_frame_context_(NULL), + web_contents_(NULL) { + DCHECK(java_helper); + + // Define the view hierarchy. + transform_layer_->addChild(view_clip_layer_); + scissor_clip_layer_->addChild(transform_layer_); + compositor_->SetRootLayer(scissor_clip_layer_); + + RendererPictureMap::CreateInstance(); +} + +BrowserViewRendererImpl::~BrowserViewRendererImpl() { +} + +// static +void BrowserViewRendererImpl::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."; +} + +void BrowserViewRendererImpl::SetContents(ContentViewCore* content_view_core) { + dpi_scale_ = content_view_core->GetDpiScale(); + web_contents_ = content_view_core->GetWebContents(); + if (!view_renderer_host_) + view_renderer_host_.reset(new ViewRendererHost(web_contents_, this)); + else + view_renderer_host_->Observe(web_contents_); + + view_clip_layer_->removeAllChildren(); + view_clip_layer_->addChild(content_view_core->GetLayer()); + Invalidate(); +} + +void BrowserViewRendererImpl::DrawGL(AwDrawGLInfo* draw_info) { + TRACE_EVENT0("android_webview", "BrowserViewRendererImpl::DrawGL"); + + if (view_size_.IsEmpty() || !scissor_clip_layer_ || + draw_info->mode == AwDrawGLInfo::kModeProcess) + return; + + DCHECK_EQ(draw_info->mode, AwDrawGLInfo::kModeDraw); + + SetCompositorVisibility(view_visible_); + if (!compositor_visible_) + return; + + // TODO(leandrogracia): remove when crbug.com/164140 is closed. + // --------------------------------------------------------------------------- + GLint texture_external_oes_binding; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texture_external_oes_binding); + + GLint vertex_array_buffer_binding; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &vertex_array_buffer_binding); + + GLint index_array_buffer_binding; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &index_array_buffer_binding); + + GLint pack_alignment; + glGetIntegerv(GL_PACK_ALIGNMENT, &pack_alignment); + + GLint unpack_alignment; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_alignment); + + struct { + GLint enabled; + GLint size; + GLint type; + GLint normalized; + GLint stride; + GLvoid* pointer; + } vertex_attrib[3]; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(vertex_attrib); ++i) { + glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, + &vertex_attrib[i].enabled); + glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, + &vertex_attrib[i].size); + glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, + &vertex_attrib[i].type); + glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, + &vertex_attrib[i].normalized); + glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, + &vertex_attrib[i].stride); + glGetVertexAttribPointerv(i, GL_VERTEX_ATTRIB_ARRAY_POINTER, + &vertex_attrib[i].pointer); + } + + GLboolean depth_test; + glGetBooleanv(GL_DEPTH_TEST, &depth_test); + + GLboolean cull_face; + glGetBooleanv(GL_CULL_FACE, &cull_face); + + GLboolean color_mask[4]; + glGetBooleanv(GL_COLOR_WRITEMASK, color_mask); + + GLboolean blend_enabled; + glGetBooleanv(GL_BLEND, &blend_enabled); + + GLint blend_src_rgb; + glGetIntegerv(GL_BLEND_SRC_RGB, &blend_src_rgb); + + GLint blend_src_alpha; + glGetIntegerv(GL_BLEND_SRC_ALPHA, &blend_src_alpha); + + GLint blend_dest_rgb; + glGetIntegerv(GL_BLEND_DST_RGB, &blend_dest_rgb); + + GLint blend_dest_alpha; + glGetIntegerv(GL_BLEND_DST_ALPHA, &blend_dest_alpha); + + GLint active_texture; + glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture); + + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + GLboolean scissor_test; + glGetBooleanv(GL_SCISSOR_TEST, &scissor_test); + + GLint scissor_box[4]; + glGetIntegerv(GL_SCISSOR_BOX, scissor_box); + + GLint current_program; + glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program); + // --------------------------------------------------------------------------- + + // 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) { + LOG(WARNING) << "No current context attached. Skipping composite."; + return; + } + + if (last_frame_context_ != current_context) { + if (last_frame_context_) + ResetCompositor(); + last_frame_context_ = current_context; + } + + compositor_->SetWindowBounds(gfx::Size(draw_info->width, draw_info->height)); + + if (draw_info->is_layer) { + // When rendering into a separate layer no view clipping, transform, + // scissoring or background transparency need to be handled. + // The Android framework will composite us afterwards. + compositor_->SetHasTransparentBackground(false); + view_clip_layer_->setMasksToBounds(false); + transform_layer_->setTransform(gfx::Transform()); + scissor_clip_layer_->setMasksToBounds(false); + scissor_clip_layer_->setPosition(gfx::PointF()); + scissor_clip_layer_->setBounds(gfx::Size()); + scissor_clip_layer_->setSublayerTransform(gfx::Transform()); + + } else { + compositor_->SetHasTransparentBackground(true); + + 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); + + scissor_clip_layer_->setPosition(clip_rect.origin()); + scissor_clip_layer_->setBounds(clip_rect.size()); + scissor_clip_layer_->setMasksToBounds(true); + + // The compositor clipping architecture enforces us to have the clip layer + // as an ancestor of the area we want to clip, but this makes the transform + // become relative to the clip area rather than the full surface. The clip + // position offset needs to be undone before applying the transform. + gfx::Transform undo_clip_position; + undo_clip_position.Translate(-clip_rect.x(), -clip_rect.y()); + scissor_clip_layer_->setSublayerTransform(undo_clip_position); + + gfx::Transform transform; + transform.matrix().setColMajorf(draw_info->transform); + + // The scrolling values of the Android Framework affect the transformation + // matrix. This needs to be undone to let the compositor handle scrolling. + // TODO(leandrogracia): when scrolling becomes synchronous we should undo + // or override the translation in the compositor, not the one coming from + // the Android View System, as it could be out of bounds for overscrolling. + transform.Translate(hw_rendering_scroll_.x(), hw_rendering_scroll_.y()); + transform_layer_->setTransform(transform); + + view_clip_layer_->setMasksToBounds(true); + } + + compositor_->Composite(); + is_composite_pending_ = false; + + // TODO(leandrogracia): remove when crbug.com/164140 is closed. + // --------------------------------------------------------------------------- + char no_gl_restore_prop[PROP_VALUE_MAX]; + __system_property_get("webview.chromium_no_gl_restore", no_gl_restore_prop); + if (!strcmp(no_gl_restore_prop, "true")) { + LOG(WARNING) << "Android GL functor not restoring the previous GL state."; + } else { + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_external_oes_binding); + glBindBuffer(GL_ARRAY_BUFFER, vertex_array_buffer_binding); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_array_buffer_binding); + + glPixelStorei(GL_PACK_ALIGNMENT, pack_alignment); + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(vertex_attrib); ++i) { + glVertexAttribPointer(i, vertex_attrib[i].size, + vertex_attrib[i].type, vertex_attrib[i].normalized, + vertex_attrib[i].stride, vertex_attrib[i].pointer); + + if (vertex_attrib[i].enabled) + glEnableVertexAttribArray(i); + else + glDisableVertexAttribArray(i); + } + + if (depth_test) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + + if (cull_face) + glEnable(GL_CULL_FACE); + else + glDisable(GL_CULL_FACE); + + glColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]); + + if (blend_enabled) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + + glBlendFuncSeparate(blend_src_rgb, blend_dest_rgb, + blend_src_alpha, blend_dest_alpha); + + glActiveTexture(active_texture); + + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + + if (scissor_test) + glEnable(GL_SCISSOR_TEST); + else + glDisable(GL_SCISSOR_TEST); + + glScissor(scissor_box[0], scissor_box[1], scissor_box[2], + scissor_box[3]); + + glUseProgram(current_program); + } + // --------------------------------------------------------------------------- +} + +void BrowserViewRendererImpl::SetScrollForHWFrame(int x, int y) { + hw_rendering_scroll_ = gfx::Point(x, y); +} + +bool BrowserViewRendererImpl::DrawSW(jobject java_canvas, + const gfx::Rect& clip) { + TRACE_EVENT0("android_webview", "BrowserViewRendererImpl::DrawSW"); + + if (clip.IsEmpty()) + return true; + + AwPixelInfo* pixels; + JNIEnv* env = AttachCurrentThread(); + + // Render into an auxiliary bitmap if pixel info is not available. + if (!g_sw_draw_functions || + (pixels = g_sw_draw_functions->access_pixels(env, java_canvas)) == NULL) { + ScopedJavaLocalRef<jobject> jbitmap(java_helper_->CreateBitmap( + env, clip.width(), clip.height())); + if (!jbitmap.obj()) + return false; + + if (!RasterizeIntoBitmap(env, jbitmap, clip.x(), clip.y(), + base::Bind(&BrowserViewRendererImpl::RenderSW, + base::Unretained(this)))) { + return false; + } + + ScopedJavaLocalRef<jobject> jcanvas(env, java_canvas); + java_helper_->DrawBitmapIntoCanvas(env, jbitmap, jcanvas); + 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); + + SkRegion clip; + if (pixels->clip_region_size) { + size_t bytes_read = clip.readFromMemory(pixels->clip_region); + DCHECK_EQ(pixels->clip_region_size, bytes_read); + canvas.setClipRegion(clip); + } else { + clip.setRect(SkIRect::MakeWH(pixels->width, pixels->height)); + } + + succeeded = RenderSW(&canvas); + } + + g_sw_draw_functions->release_pixels(pixels); + return succeeded; +} + +ScopedJavaLocalRef<jobject> BrowserViewRendererImpl::CapturePicture() { + skia::RefPtr<SkPicture> picture = GetLastCapturedPicture(); + if (!picture || !g_sw_draw_functions) + return ScopedJavaLocalRef<jobject>(); + + JNIEnv* env = AttachCurrentThread(); + if (g_is_skia_version_compatible) { + return ScopedJavaLocalRef<jobject>(env, + g_sw_draw_functions->create_picture(env, picture->clone())); + } + + // If Skia versions are not compatible, workaround it by rasterizing the + // picture into a bitmap and drawing it into a new Java picture. + ScopedJavaLocalRef<jobject> jbitmap(java_helper_->CreateBitmap( + env, picture->width(), picture->height())); + if (!jbitmap.obj()) + return ScopedJavaLocalRef<jobject>(); + + if (!RasterizeIntoBitmap(env, jbitmap, 0, 0, + base::Bind(&BrowserViewRendererImpl::RenderPicture, + base::Unretained(this)))) { + return ScopedJavaLocalRef<jobject>(); + } + + return java_helper_->RecordBitmapIntoPicture(env, jbitmap); +} + +void BrowserViewRendererImpl::EnableOnNewPicture(OnNewPictureMode mode) { + on_new_picture_mode_ = mode; + + // TODO(leandrogracia): when SW rendering uses the compositor rather than + // picture rasterization, send update the renderer side with the correct + // listener state. (For now, we always leave render picture listener enabled). + // render_view_host_ext_->EnableCapturePictureCallback(enabled); + //DCHECK(view_renderer_host_); + //view_renderer_host_->EnableCapturePictureCallback( + // on_new_picture_mode_ == kOnNewPictureEnabled); +} + +void BrowserViewRendererImpl::OnVisibilityChanged(bool view_visible, + bool window_visible) { + view_visible_ = window_visible && view_visible; + Invalidate(); +} + +void BrowserViewRendererImpl::OnSizeChanged(int width, int height) { + view_size_ = gfx::Size(width, height); + view_clip_layer_->setBounds(view_size_); +} + +void BrowserViewRendererImpl::OnAttachedToWindow(int width, int height) { + view_size_ = gfx::Size(width, height); + view_clip_layer_->setBounds(view_size_); +} + +void BrowserViewRendererImpl::OnDetachedFromWindow() { + view_visible_ = false; + SetCompositorVisibility(false); +} + +void BrowserViewRendererImpl::ScheduleComposite() { + TRACE_EVENT0("android_webview", "BrowserViewRendererImpl::ScheduleComposite"); + + if (is_composite_pending_) + return; + + is_composite_pending_ = true; + Invalidate(); +} + +skia::RefPtr<SkPicture> BrowserViewRendererImpl::GetLastCapturedPicture() { + // Use the latest available picture if the listener callback is enabled. + skia::RefPtr<SkPicture> picture; + if (on_new_picture_mode_ == kOnNewPictureEnabled) + picture = RendererPictureMap::GetInstance()->GetRendererPicture( + web_contents_->GetRoutingID()); + + // If not available or not in listener mode get it synchronously. + if (!picture) { + DCHECK(view_renderer_host_); + view_renderer_host_->CapturePictureSync(); + picture = RendererPictureMap::GetInstance()->GetRendererPicture( + web_contents_->GetRoutingID()); + } + + return picture; +} + +void BrowserViewRendererImpl::OnPictureUpdated(int process_id, + int render_view_id) { + CHECK_EQ(web_contents_->GetRenderProcessHost()->GetID(), process_id); + if (render_view_id != web_contents_->GetRoutingID()) + return; + + // TODO(leandrogracia): this can be made unconditional once software rendering + // uses Ubercompositor. Until then this path is required for SW invalidations. + if (on_new_picture_mode_ == kOnNewPictureEnabled) + client_->OnNewPicture(CapturePicture()); + + // TODO(leandrogracia): delete when sw rendering uses Ubercompositor. + // Invalidation should be provided by the compositor only. + Invalidate(); +} + +void BrowserViewRendererImpl::SetCompositorVisibility(bool visible) { + if (compositor_visible_ != visible) { + compositor_visible_ = visible; + compositor_->SetVisible(compositor_visible_); + } +} + +void BrowserViewRendererImpl::ResetCompositor() { + compositor_.reset(content::Compositor::Create(this)); + compositor_->SetRootLayer(scissor_clip_layer_); +} + +void BrowserViewRendererImpl::Invalidate() { + if (view_visible_) + client_->Invalidate(); + + // When not in invalidation-only mode onNewPicture will be triggered + // from the OnPictureUpdated callback. + if (on_new_picture_mode_ == kOnNewPictureInvalidationOnly) + client_->OnNewPicture(ScopedJavaLocalRef<jobject>()); +} + +bool BrowserViewRendererImpl::RenderSW(SkCanvas* canvas) { + // TODO(leandrogracia): once Ubercompositor is ready and we support software + // rendering mode, we should avoid this as much as we can, ideally always. + // This includes finding a proper replacement for onDraw calls in hardware + // mode with software canvases. http://crbug.com/170086. + return RenderPicture(canvas); +} + +bool BrowserViewRendererImpl::RenderPicture(SkCanvas* canvas) { + skia::RefPtr<SkPicture> picture = GetLastCapturedPicture(); + if (!picture) + return false; + + // Correct device scale. + canvas->scale(dpi_scale_, dpi_scale_); + + picture->draw(canvas); + return true; +} + +} // namespace android_webview |