From 0e4c6a648c0639001bf0e33fe02160a748cddf2f Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Mon, 14 Nov 2011 17:47:21 +0000 Subject: [PATCH] Mac: use regions to improve painting performance (issue #360). git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@377 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- libcef/browser_webview_mac.mm | 36 +++++--- libcef/webview_host_mac.mm | 1 - libcef/webwidget_host.cc | 6 +- libcef/webwidget_host.h | 10 ++- libcef/webwidget_host_gtk.cc | 4 +- libcef/webwidget_host_mac.mm | 164 ++++++++++++++++------------------ libcef/webwidget_host_win.cc | 6 +- 7 files changed, 116 insertions(+), 111 deletions(-) diff --git a/libcef/browser_webview_mac.mm b/libcef/browser_webview_mac.mm index f90655696..d406592c0 100644 --- a/libcef/browser_webview_mac.mm +++ b/libcef/browser_webview_mac.mm @@ -2,21 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "browser_webview_mac.h" - #import -#include "browser_impl.h" -#include "cef_context.h" +#import "browser_webview_mac.h" +#import "browser_impl.h" +#import "cef_context.h" #import "web_drag_source_mac.h" #import "web_drop_target_mac.h" -#include "webwidget_host.h" +#import "webwidget_host.h" -#include "base/memory/scoped_ptr.h" +#import "base/memory/scoped_ptr.h" +#import "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#import "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #import "third_party/mozilla/NSPasteboard+Utils.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" -#include "ui/gfx/rect.h" +#import "third_party/skia/include/core/SkRegion.h" +#import "ui/gfx/rect.h" @implementation BrowserWebView @@ -56,9 +56,21 @@ #endif if (browser_ && browser_->UIT_GetWebView()) { - gfx::Rect dirty_rect = gfx::Rect(NSRectToCGRect(rect)); - dirty_rect.set_y(NSHeight([self bounds]) - dirty_rect.bottom()); - browser_->UIT_GetWebViewHost()->Paint(dirty_rect); + NSInteger count; + const NSRect *rects; + [self getRectsBeingDrawn:&rects count:&count]; + + SkRegion update_rgn; + for (int i = 0; i < count; i++) { + const NSRect r = rects[i]; + const float min_x = NSMinX(r); + const float max_x = NSMaxX(r); + const float min_y = NSHeight([self bounds]) - NSMaxY(r); + const float max_y = NSHeight([self bounds]) - NSMinY(r); + update_rgn.op(min_x, min_y, max_x, max_y, SkRegion::kUnion_Op); + } + + browser_->UIT_GetWebViewHost()->Paint(update_rgn); } } diff --git a/libcef/webview_host_mac.mm b/libcef/webview_host_mac.mm index 725d5eeb9..a0ccbe8d3 100644 --- a/libcef/webview_host_mac.mm +++ b/libcef/webview_host_mac.mm @@ -36,7 +36,6 @@ WebViewHost* WebViewHost::Create(NSView* parent_view, NSRect content_rect = {{rect.x(), rect.y()}, {rect.width(), rect.height()}}; host->view_ = [[BrowserWebView alloc] initWithFrame:content_rect]; - // make the height and width track the window size. [host->view_ setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; [parent_view addSubview:host->view_]; [host->view_ release]; diff --git a/libcef/webwidget_host.cc b/libcef/webwidget_host.cc index 50111eabf..490c4b3c2 100644 --- a/libcef/webwidget_host.cc +++ b/libcef/webwidget_host.cc @@ -21,7 +21,7 @@ void WebWidgetHost::ScheduleAnimation() { } void WebWidgetHost::UpdatePaintRect(const gfx::Rect& rect) { -#if defined(OS_WIN) +#if defined(OS_WIN) || defined(OS_MACOSX) paint_rgn_.op(rect.x(), rect.y(), rect.right(), rect.bottom(), SkRegion::kUnion_Op); #else @@ -96,8 +96,8 @@ void WebWidgetHost::DoPaint() { #if defined(OS_WIN) if (MessageLoop::current()->IsIdle()) { has_update_task_ = false; - // Paint to the delegate. The rect argument is unused. - Paint(gfx::Rect()); + // Paint to the delegate. + Paint(); } else { // Try again later. CefThread::PostTask(CefThread::UI, FROM_HERE, diff --git a/libcef/webwidget_host.h b/libcef/webwidget_host.h index 2c8aa5c71..8d02495c7 100644 --- a/libcef/webwidget_host.h +++ b/libcef/webwidget_host.h @@ -87,7 +87,11 @@ class WebWidgetHost { // is called. This is only used when window rendering is disabled. void UpdateRedrawRect(const gfx::Rect& rect); - void Paint(const gfx::Rect& dirty_rect); +#if defined(OS_MACOSX) + void Paint(SkRegion& update_rgn); +#else + void Paint(); +#endif void InvalidateRect(const gfx::Rect& rect); bool GetImage(int width, int height, void* buffer); @@ -211,8 +215,8 @@ class WebWidgetHost { bool popup_; // Specifies the portion of the webwidget that needs painting. - // TODO: Update all ports to use regions instead of rectangles. -#if defined(OS_WIN) + // TODO(cef): Update the Linux port to use regions instead of rectangles. +#if defined(OS_WIN) || defined(OS_MACOSX) SkRegion paint_rgn_; #else gfx::Rect paint_rect_; diff --git a/libcef/webwidget_host_gtk.cc b/libcef/webwidget_host_gtk.cc index 85dc14151..97a2d845f 100644 --- a/libcef/webwidget_host_gtk.cc +++ b/libcef/webwidget_host_gtk.cc @@ -149,7 +149,7 @@ class WebWidgetHostGtkWidget { g_handling_expose = true; gfx::Rect rect(expose->area); host->UpdatePaintRect(rect); - host->Paint(rect); + host->Paint(); g_handling_expose = false; return FALSE; } @@ -342,7 +342,7 @@ void WebWidgetHost::Resize(const gfx::Size &newsize) { SetSize(newsize.width(), newsize.height()); } -void WebWidgetHost::Paint(const gfx::Rect& dirty_rect) { +void WebWidgetHost::Paint() { int width = logical_size_.width(); int height = logical_size_.height(); gfx::Rect client_rect(width, height); diff --git a/libcef/webwidget_host_mac.mm b/libcef/webwidget_host_mac.mm index 3b5ec0d9f..45b98b0cc 100644 --- a/libcef/webwidget_host_mac.mm +++ b/libcef/webwidget_host_mac.mm @@ -12,6 +12,7 @@ #import "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h" #import "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" #import "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" +#import "third_party/skia/include/core/SkRegion.h" #import "ui/gfx/rect.h" #import "ui/gfx/size.h" #import "webkit/glue/webkit_glue.h" @@ -27,6 +28,20 @@ using WebKit::WebScreenInfoFactory; using WebKit::WebSize; using WebKit::WebWidgetClient; +namespace { + +inline SkIRect convertToSkiaRect(const gfx::Rect& r) +{ + return SkIRect::MakeLTRB(r.x(), r.y(), r.right(), r.bottom()); +} + +inline gfx::Rect convertFromSkiaRect(const SkIRect& r) +{ + return gfx::Rect(r.x(), r.y(), r.width(), r.height()); +} + +} // namespace + /*static*/ WebWidgetHost* WebWidgetHost::Create(NSView* parent_view, WebWidgetClient* client, @@ -44,15 +59,30 @@ WebWidgetHost* WebWidgetHost::Create(NSView* parent_view, return host; } +WebWidgetHost::WebWidgetHost() + : view_(NULL), + paint_delegate_(NULL), + webwidget_(NULL), + canvas_w_(0), + canvas_h_(0), + popup_(false), + has_update_task_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { + set_painting(false); +} + +WebWidgetHost::~WebWidgetHost() { +} + void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) { const gfx::Rect client_rect(NSRectToCGRect([view_ bounds])); const gfx::Rect damaged_rect_in_client = client_rect.Intersect(damaged_rect); if (!damaged_rect_in_client.IsEmpty()) { - paint_rect_ = paint_rect_.Union(damaged_rect_in_client); - NSRect r = NSRectFromCGRect(damaged_rect_in_client.ToCGRect()); - r.origin.y = NSHeight([view_ bounds]) - NSMaxY(r); - [view_ setNeedsDisplayInRect:r]; + UpdatePaintRect(damaged_rect_in_client); + NSRect cocoa_rect = NSRectFromCGRect(damaged_rect_in_client.ToCGRect()); + cocoa_rect.origin.y = client_rect.height() - NSMaxY(cocoa_rect); + [view_ setNeedsDisplayInRect:cocoa_rect]; } } @@ -85,14 +115,7 @@ void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { // The scrolling rect must not scroll outside the clip rect because that will // clobber the scrollbars. As a result we shorten the rectangle a bit from the // leading side of the scroll (could be either horizontally or vertically). - if (dx > 0) - rect = gfx::Rect(x, y, w - dx, h); - else if (dx < 0) - rect = gfx::Rect(x - dx, y, w + dx, h); - else if (dy > 0) - rect = gfx::Rect(x, y, w, h - dy); - else if (dy < 0) - rect = gfx::Rect(x, y - dy, w, h + dy); + rect = gfx::Rect(dx>=0? x: x + Dx, dy>=0? y: y + Dy, w - Dx, h - Dy); // Convert the scroll rectangle to the view's coordinate system and perform // the scroll directly without invalidating the view. In theory this could @@ -101,73 +124,28 @@ void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { // since just copying the pixels within the window is much faster than // redrawing them. NSRect cocoa_rect = NSRectFromCGRect(rect.ToCGRect()); - cocoa_rect.origin.y = NSHeight([view_ bounds]) - NSMaxY(cocoa_rect); + cocoa_rect.origin.y = client_rect.height() - NSMaxY(cocoa_rect); [view_ scrollRect:cocoa_rect by:NSMakeSize(dx, -dy)]; - const gfx::Rect saved_paint_rect = paint_rect_; - if (![view_ lockFocusIfCanDraw]) - return; - // Repaint the rectangle that was revealed when scrolling the given rectangle. - // We don't want to invalidate this rectangle because that would cause the - // invalidated area to be L-shaped due to the combination of this narrow area - // and the scrollbar area that may also be invalidated. The union of these two - // rectangles is pretty much the entire view area and we would not save any - // work by doing the scrollRect: call above. - if (dx > 0) - paint_rect_ = gfx::Rect(x, y, dx, h); - else if (dx < 0) - paint_rect_ = gfx::Rect(r + dx, y, -dx, h); - else if (dy > 0) - paint_rect_ = gfx::Rect(x, y, w, dy); - else if (dy < 0) - paint_rect_ = gfx::Rect(x, b + dy, w, -dy); - - paint_rect_ = paint_rect_.Intersect(client_rect); - if (!paint_rect_.IsEmpty()) - Paint(paint_rect_); + // The invalidated area will be painted before the view contents are flushed + // to the screen buffer. + rect = gfx::Rect(dx>=0? x: r - Dx, dy>=0? y: b - Dy, + dx>0? Dx: w, dy>0? Dy: h); + DidInvalidateRect(rect); // If any part of the scrolled rect was marked as dirty make sure to redraw // it in the new scrolled-to location. Otherwise we can end up with artifacts - // for overlapping elements. - gfx::Rect moved_paint_rect = saved_paint_rect.Intersect(clip_rect); - if (!moved_paint_rect.IsEmpty()) { - moved_paint_rect.Offset(dx, dy); - paint_rect_ = moved_paint_rect; - paint_rect_ = paint_rect_.Intersect(client_rect); - if (!paint_rect_.IsEmpty()) - Paint(paint_rect_); - } - - [view_ unlockFocus]; - paint_rect_ = saved_paint_rect; + // for overlapping elements. If there are multiple dirty regions in the scroll + // rect the rectangle union of those regions will be redrawn. + SkRegion moved_paint_rgn(paint_rgn_); + moved_paint_rgn.translate(dx, dy); + moved_paint_rgn.op(convertToSkiaRect(client_rect), SkRegion::kIntersect_Op); + DidInvalidateRect(convertFromSkiaRect(moved_paint_rgn.getBounds())); } -void WebWidgetHost::ScheduleComposite() { - if (!webwidget_) - return; - const WebSize size = webwidget_->size(); - [view_ setNeedsDisplayInRect:NSMakeRect(0, 0, size.width, size.height)]; -} - -WebWidgetHost::WebWidgetHost() - : view_(NULL), - paint_delegate_(NULL), - webwidget_(NULL), - canvas_w_(0), - canvas_h_(0), - popup_(false), - has_update_task_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { - set_painting(false); -} - -WebWidgetHost::~WebWidgetHost() { -} - -void WebWidgetHost::Paint(const gfx::Rect& dirty_rect) { +void WebWidgetHost::Paint(SkRegion& update_rgn) { gfx::Rect client_rect(NSRectToCGRect([view_ bounds])); - gfx::Rect copy_rect = dirty_rect; // Union the rectangle that WebKit think needs repainting with the rectangle // of the view that must be painted now. In most situations this will not @@ -176,7 +154,7 @@ void WebWidgetHost::Paint(const gfx::Rect& dirty_rect) { // itself. However, if we don't do this we can get artifacts when scrolling // because contents of the canvas are no longer correct after scrolling only // in the view. - paint_rect_ = paint_rect_.Union(dirty_rect); + paint_rgn_.op(update_rgn, SkRegion::kUnion_Op); // When we are not using accelerated compositing the canvas area is allowed // to differ in size from the client by a certain number of pixels (128 in @@ -196,6 +174,7 @@ void WebWidgetHost::Paint(const gfx::Rect& dirty_rect) { canvas_w_ = client_rect.width() + extra_w; canvas_h_ = client_rect.height() + extra_h; canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true)); + paint_rgn_.setRect(convertToSkiaRect(client_rect)); } // Animate the view and layout any views that have not been laid out yet. The @@ -216,29 +195,40 @@ void WebWidgetHost::Paint(const gfx::Rect& dirty_rect) { [NSGraphicsContext setCurrentContext:paint_context]; // Paint the canvas if necessary. The painting operation can cause additional - // rectangles to be invalidated because some elements are laid out only the - // first time they are painted. - paint_rect_ = client_rect.Intersect(paint_rect_); - const gfx::Rect paint_rect = paint_rect_; - paint_rect_ = gfx::Rect(); - PaintRect(paint_rect); + // regions to be invalidated because some elements are laid out the first time + // they are painted. + while (!paint_rgn_.isEmpty()) { + SkRegion draw_rgn; + draw_rgn.swap(paint_rgn_); - if (!paint_rect_.IsEmpty()) { - copy_rect = copy_rect.Union(paint_rect_); - const gfx::Rect paint_rect = paint_rect_; - paint_rect_ = gfx::Rect(); - PaintRect(paint_rect); + SkRegion::Cliperator iterator(draw_rgn, convertToSkiaRect(client_rect)); + for (; !iterator.done(); iterator.next()) + PaintRect(convertFromSkiaRect(iterator.rect())); + + // If any more rectangles were made dirty during the paint operation, make + // sure they are copied to the window buffer, by including the paint region. + // If nothing needs additional painting, this is a no-op. + update_rgn.op(paint_rgn_, SkRegion::kUnion_Op); } // Set the context back to our view and copy the bitmap that we just painted - // into to the view. Only the region that was updated is copied. + // into to the view. Only the regions that were updated are copied. [NSGraphicsContext restoreGraphicsState]; NSGraphicsContext* view_context = [NSGraphicsContext currentContext]; CGContextRef context = static_cast([view_context graphicsPort]); - CGRect bitmap_rect = { { copy_rect.x(), copy_rect.y() }, - { copy_rect.width(), copy_rect.height() } }; - skia::DrawToNativeContext(canvas_.get(), context, copy_rect.x(), - client_rect.height() - copy_rect.bottom(), &bitmap_rect); + + SkRegion::Cliperator iterator(update_rgn, convertToSkiaRect(client_rect)); + for (; !iterator.done(); iterator.next()) { + const SkIRect& r = iterator.rect(); + CGRect copy_rect = { { r.x(), r.y() }, { r.width(), r.height() } }; + const float x = r.x(); + const float y = client_rect.height() - r.bottom(); + skia::DrawToNativeContext(canvas_.get(), context, x, y, ©_rect); + } +} + +void WebWidgetHost::ScheduleComposite() { + [view_ setNeedsDisplay:YES]; } void WebWidgetHost::SetTooltipText(const CefString& tooltip_text) diff --git a/libcef/webwidget_host_win.cc b/libcef/webwidget_host_win.cc index 17bed0d8f..6a841e3aa 100644 --- a/libcef/webwidget_host_win.cc +++ b/libcef/webwidget_host_win.cc @@ -123,8 +123,8 @@ LRESULT CALLBACK WebWidgetHost::WndProc(HWND hwnd, UINT message, WPARAM wparam, if (host && !host->WndProc(message, wparam, lparam)) { switch (message) { case WM_PAINT: { - // Paint to the window. The rect argument is unused. - host->Paint(gfx::Rect()); + // Paint to the window. + host->Paint(); return 0; } @@ -362,7 +362,7 @@ bool WebWidgetHost::WndProc(UINT message, WPARAM wparam, LPARAM lparam) { return false; } -void WebWidgetHost::Paint(const gfx::Rect& /*dirty_rect*/) { +void WebWidgetHost::Paint() { int width, height; GetSize(width, height); gfx::Rect client_rect(width, height);