From 9fa941f1d79522e2977f37b8b88b957a1bdc3d4f Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Mon, 3 Oct 2011 15:48:32 +0000 Subject: [PATCH] - Improve redraw and scrolling performance (issue #360). - Don't show magenta background when redrawing in release build (issue #300). git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@299 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- libcef/browser_webview_mac.mm | 7 +- libcef/webwidget_host.cc | 7 -- libcef/webwidget_host.h | 3 +- libcef/webwidget_host_gtk.cc | 18 ++++- libcef/webwidget_host_mac.mm | 141 +++++++++++++++++++--------------- libcef/webwidget_host_win.cc | 38 +++++++-- 6 files changed, 131 insertions(+), 83 deletions(-) diff --git a/libcef/browser_webview_mac.mm b/libcef/browser_webview_mac.mm index eb8a0ffe0..a4c4c82a0 100644 --- a/libcef/browser_webview_mac.mm +++ b/libcef/browser_webview_mac.mm @@ -48,6 +48,7 @@ } - (void)drawRect:(NSRect)rect { +#ifndef NDEBUG CGContextRef context = reinterpret_cast([[NSGraphicsContext currentContext] graphicsPort]); @@ -55,13 +56,11 @@ // start by filling the rect with magenta, so that we can see what's drawn CGContextSetRGBFillColor (context, 1, 0, 1, 1); CGContextFillRect(context, NSRectToCGRect(rect)); +#endif if (browser_ && browser_->UIT_GetWebView()) { gfx::Rect client_rect(NSRectToCGRect(rect)); - // flip from cocoa coordinates - client_rect.set_y([self frame].size.height - - client_rect.height() - client_rect.y()); - + client_rect.set_y(NSHeight([self bounds]) - client_rect.bottom()); browser_->UIT_GetWebViewHost()->UpdatePaintRect(client_rect); browser_->UIT_GetWebViewHost()->Paint(); } diff --git a/libcef/webwidget_host.cc b/libcef/webwidget_host.cc index 82207c152..354e2031f 100644 --- a/libcef/webwidget_host.cc +++ b/libcef/webwidget_host.cc @@ -18,18 +18,11 @@ void WebWidgetHost::ScheduleAnimation() { factory_.NewRunnableMethod(&WebWidgetHost::ScheduleComposite), 10); } -void WebWidgetHost::DiscardBackingStore() { - canvas_.reset(); -} - void WebWidgetHost::UpdatePaintRect(const gfx::Rect& rect) { paint_rect_ = paint_rect_.Union(rect); } void WebWidgetHost::SetSize(int width, int height) { - // Force an entire re-paint. TODO(darin): Maybe reuse this memory buffer. - DiscardBackingStore(); - webwidget_->resize(WebSize(width, height)); EnsureTooltip(); } diff --git a/libcef/webwidget_host.h b/libcef/webwidget_host.h index 35801174c..f2f094950 100644 --- a/libcef/webwidget_host.h +++ b/libcef/webwidget_host.h @@ -72,7 +72,6 @@ class WebWidgetHost { void SetCursor(HCURSOR cursor); #endif - void DiscardBackingStore(); // Allow clients to update the paint rect. For example, if we get a gdk // expose or WM_PAINT event, we need to update the paint rect. void UpdatePaintRect(const gfx::Rect& rect); @@ -192,6 +191,8 @@ class WebWidgetHost { WebKit::WebWidget* webwidget_; scoped_ptr canvas_; + int canvas_w_; + int canvas_h_; // True if this widget is a popup widget. bool popup_; diff --git a/libcef/webwidget_host_gtk.cc b/libcef/webwidget_host_gtk.cc index d142cdc85..03e409585 100644 --- a/libcef/webwidget_host_gtk.cc +++ b/libcef/webwidget_host_gtk.cc @@ -330,6 +330,8 @@ WebWidgetHost::WebWidgetHost() : view_(NULL), paint_delegate_(NULL), webwidget_(NULL), + canvas_w_(0), + canvas_h_(0), popup_(false), scroll_dx_(0), scroll_dy_(0), @@ -357,11 +359,21 @@ void WebWidgetHost::Paint() { int height = logical_size_.height(); gfx::Rect client_rect(width, height); - // Allocate a canvas if necessary - if (!canvas_.get()) { + // Number of pixels that the canvas is allowed to differ from the client area. + const int kCanvasGrowSize = 128; + + if (!canvas_.get() || + canvas_w_ < client_rect.width() || + canvas_h_ < client_rect.height() || + canvas_w_ > client_rect.width() + kCanvasGrowSize * 2 || + canvas_h_ > client_rect.height() + kCanvasGrowSize * 2) { ResetScrollRect(); paint_rect_ = client_rect; - canvas_.reset(new skia::PlatformCanvas(width, height, true)); + + // Resize the canvas to be within a reasonable size of the client area. + canvas_w_ = client_rect.width() + kCanvasGrowSize; + canvas_h_ = client_rect.height() + kCanvasGrowSize; + canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true)); if (!canvas_.get()) { // memory allocation failed, we can't paint. LOG(ERROR) << "Failed to allocate memory for " << width << "x" << height; diff --git a/libcef/webwidget_host_mac.mm b/libcef/webwidget_host_mac.mm index ada8793c8..fce32de99 100644 --- a/libcef/webwidget_host_mac.mm +++ b/libcef/webwidget_host_mac.mm @@ -35,7 +35,7 @@ WebWidgetHost* WebWidgetHost::Create(NSView* parent_view, PaintDelegate* paint_delegate) { WebWidgetHost* host = new WebWidgetHost(); - NSRect content_rect = [parent_view frame]; + NSRect content_rect = [parent_view bounds]; host->view_ = [[NSView alloc] initWithFrame:content_rect]; [parent_view addSubview:host->view_]; @@ -50,40 +50,62 @@ void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) { DLOG_IF(WARNING, painting_) << "unexpected invalidation while painting"; #endif - // If this invalidate overlaps with a pending scroll, then we have to - // downgrade to invalidating the scroll rect. - if (damaged_rect.Intersects(scroll_rect_)) { - paint_rect_ = paint_rect_.Union(scroll_rect_); - ResetScrollRect(); - } paint_rect_ = paint_rect_.Union(damaged_rect); + // Convert scroll rectangle to the view's coordinate system. NSRect r = NSRectFromCGRect(damaged_rect.ToCGRect()); - // flip to cocoa coordinates - r.origin.y = [view_ frame].size.height - r.size.height - r.origin.y; + r.origin.y = NSHeight([view_ frame]) - NSMaxY(r); [view_ setNeedsDisplayInRect:r]; } void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { DCHECK(dx || dy); - // If we already have a pending scroll operation or if this scroll operation - // intersects the existing paint region, then just failover to invalidating. - if (!scroll_rect_.IsEmpty() || paint_rect_.Intersects(clip_rect)) { - paint_rect_ = paint_rect_.Union(scroll_rect_); - ResetScrollRect(); - paint_rect_ = paint_rect_.Union(clip_rect); - } + gfx::Rect client_rect(NSRectToCGRect([view_ bounds])); + gfx::Rect rect = clip_rect.Intersect(client_rect); + + // Convert scroll rectangle to the view's coordinate system, and perform the + // scroll directly, without invalidating the view. In theory this could cause + // some kind of performance issue, since we're not coalescing redraw events, + // but in practice we get much smoother scrolling of big views, since just + // copying the pixels within the window is much faster than redrawing them. + NSRect r = NSRectFromCGRect(rect.ToCGRect()); + r.origin.y = NSHeight([view_ bounds]) - NSMaxY(r); + [view_ scrollRect:r by:NSMakeSize(dx, -dy)]; - // We will perform scrolling lazily, when requested to actually paint. - scroll_rect_ = clip_rect; - scroll_dx_ = dx; - scroll_dy_ = dy; + const gfx::Rect saved_paint_rect = paint_rect_; - NSRect r = NSRectFromCGRect(clip_rect.ToCGRect()); - // flip to cocoa coordinates - r.origin.y = [view_ frame].size.height - r.size.height - r.origin.y; - [view_ setNeedsDisplayInRect:r]; + // Repaint the rectangle that was revealed when scrolling the given rectangle. + // We don't want to invalidate the rectangle, because that would cause the + // invalidated area to be L-shaped, because of this narrow area, and the + // scrollbar area that also could be invalidated, and the union of those 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(rect.x(), rect.y(), dx, rect.height()); + else if (dx < 0) + paint_rect_ = gfx::Rect(rect.right() + dx, rect.y(), -dx, rect.height()); + else if (dy > 0) + paint_rect_ = gfx::Rect(rect.x(), rect.y(), rect.width(), dy); + else if (dy < 0) + paint_rect_ = gfx::Rect(rect.x(), rect.bottom() + dy, rect.width(), -dy); + Paint(); + + // Also make sure we repaint the area that the rectangle was pushed over. This + // is required to make sure we don't clobber the scrollbar on that side of the + // view. Also do the repaint immediately here, so that we don't get into the + // L-shaped issue described above. + if (dx > 0) + paint_rect_ = gfx::Rect(rect.right(), rect.y(), dx, rect.height()); + else if (dx < 0) + paint_rect_ = gfx::Rect(rect.x() + dx, rect.y(), -dx, rect.height()); + else if (dy > 0) + paint_rect_ = gfx::Rect(rect.x(), rect.bottom(), rect.width(), dx); + else if (dy < 0) + paint_rect_ = gfx::Rect(rect.x(), rect.y() + dy, rect.width(), -dx); + Paint(); + + paint_rect_ = saved_paint_rect; } void WebWidgetHost::ScheduleComposite() { @@ -98,9 +120,9 @@ WebWidgetHost::WebWidgetHost() : view_(NULL), paint_delegate_(NULL), webwidget_(NULL), + canvas_w_(0), + canvas_h_(0), popup_(false), - scroll_dx_(0), - scroll_dy_(0), update_task_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { set_painting(false); @@ -110,24 +132,34 @@ WebWidgetHost::~WebWidgetHost() { } void WebWidgetHost::Paint() { - gfx::Rect client_rect(NSRectToCGRect([view_ frame])); - NSGraphicsContext* view_context = [NSGraphicsContext currentContext]; - CGContextRef context = static_cast([view_context graphicsPort]); - - // Allocate a canvas if necessary - if (!canvas_.get()) { - ResetScrollRect(); + gfx::Rect client_rect(NSRectToCGRect([view_ bounds])); + gfx::Rect update_rect; + + // Number of pixels that the canvas is allowed to differ from the client area. + const int kCanvasGrowSize = 128; + + if (!canvas_.get() || + canvas_w_ < client_rect.width() || + canvas_h_ < client_rect.height() || + canvas_w_ > client_rect.width() + kCanvasGrowSize * 2 || + canvas_h_ > client_rect.height() + kCanvasGrowSize * 2) { paint_rect_ = client_rect; - canvas_.reset(new skia::PlatformCanvas( - paint_rect_.width(), paint_rect_.height(), true)); + + // Resize the canvas to be within a reasonable size of the client area. + canvas_w_ = client_rect.width() + kCanvasGrowSize; + canvas_h_ = client_rect.height() + kCanvasGrowSize; + canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true)); } - // make sure webkit draws into our bitmap, not the window + // Draw into the graphics context of the canvas instead of the view's context. + // The view's context is pushed onto the context stack, to be restored below. CGContextRef bitmap_context = skia::GetBitmapContext(skia::GetTopDevice(*canvas_)); - [NSGraphicsContext setCurrentContext: + NSGraphicsContext* paint_context = [NSGraphicsContext graphicsContextWithGraphicsPort:bitmap_context - flipped:YES]]; + flipped:YES]; + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:paint_context]; #ifdef WEBWIDGET_HAS_ANIMATE_CHANGES webwidget_->animate(0.0); @@ -138,14 +170,6 @@ void WebWidgetHost::Paint() { // This may result in more invalidation webwidget_->layout(); - // Scroll the canvas if necessary - scroll_rect_ = client_rect.Intersect(scroll_rect_); - if (!scroll_rect_.IsEmpty()) { - // add to invalidate rect, since there's no equivalent of ScrollDC. - paint_rect_ = paint_rect_.Union(scroll_rect_); - } - ResetScrollRect(); - // Paint the canvas if necessary. Allow painting to generate extra rects the // first time we call it. This is necessary because some WebCore rendering // objects update their layout only when painted. @@ -153,26 +177,27 @@ void WebWidgetHost::Paint() { paint_rect_ = client_rect.Intersect(paint_rect_); if (!paint_rect_.IsEmpty()) { gfx::Rect rect(paint_rect_); + update_rect = (i == 0? rect: update_rect.Union(rect)); paint_rect_ = gfx::Rect(); -// DLOG_IF(WARNING, i == 1) << "painting caused additional invalidations"; +// DLOG_IF(WARNING, i == 1) << "painting caused additional invalidations"; PaintRect(rect); } } DCHECK(paint_rect_.IsEmpty()); // set the context back to our window - [NSGraphicsContext setCurrentContext: view_context]; + [NSGraphicsContext restoreGraphicsState]; // Paint to the screen if ([view_ lockFocusIfCanDraw]) { - int bitmap_height = CGBitmapContextGetHeight(bitmap_context); - int bitmap_width = CGBitmapContextGetWidth(bitmap_context); - CGRect bitmap_rect = { { 0, 0 }, - { bitmap_width, bitmap_height } }; - skia::DrawToNativeContext(canvas_.get(), context, 0, - client_rect.height() - bitmap_height, &bitmap_rect); - + NSGraphicsContext* view_context = [NSGraphicsContext currentContext]; + CGContextRef context = + static_cast([view_context graphicsPort]); + CGRect bitmap_rect = { { update_rect.x(), update_rect.y() }, + { update_rect.width(), update_rect.height() } }; + skia::DrawToNativeContext(canvas_.get(), context, update_rect.x(), + client_rect.height() - update_rect.bottom(), &bitmap_rect); [view_ unlockFocus]; } } @@ -235,12 +260,6 @@ void WebWidgetHost::SetFocus(bool enable) { webwidget_->setFocus(enable); } -void WebWidgetHost::ResetScrollRect() { - scroll_rect_ = gfx::Rect(); - scroll_dx_ = 0; - scroll_dy_ = 0; -} - void WebWidgetHost::PaintRect(const gfx::Rect& rect) { #ifndef NDEBUG DCHECK(!painting_); diff --git a/libcef/webwidget_host_win.cc b/libcef/webwidget_host_win.cc index fa33304b9..c287fc9c2 100644 --- a/libcef/webwidget_host_win.cc +++ b/libcef/webwidget_host_win.cc @@ -314,16 +314,18 @@ WebWidgetHost::WebWidgetHost() : view_(NULL), paint_delegate_(NULL), webwidget_(NULL), + canvas_w_(0), + canvas_h_(0), popup_(false), track_mouse_leave_(false), - ime_notification_(false), - input_method_is_active_(false), - text_input_type_(WebKit::WebTextInputTypeNone), scroll_dx_(0), scroll_dy_(0), update_task_(NULL), tooltip_view_(NULL), tooltip_showing_(false), + ime_notification_(false), + input_method_is_active_(false), + text_input_type_(WebKit::WebTextInputTypeNone), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { set_painting(false); } @@ -358,12 +360,34 @@ void WebWidgetHost::Paint() { gfx::Rect client_rect(width, height); gfx::Rect damaged_rect; - // Allocate a canvas if necessary - if (!canvas_.get()) { + if (view_) { + // Number of pixels that the canvas is allowed to differ from the client + // area. + const int kCanvasGrowSize = 128; + + if (!canvas_.get() || + canvas_w_ < client_rect.width() || + canvas_h_ < client_rect.height() || + canvas_w_ > client_rect.width() + kCanvasGrowSize * 2 || + canvas_h_ > client_rect.height() + kCanvasGrowSize * 2) { + ResetScrollRect(); + paint_rect_ = client_rect; + + // Resize the canvas to be within a reasonable size of the client area. + canvas_w_ = client_rect.width() + kCanvasGrowSize; + canvas_h_ = client_rect.height() + kCanvasGrowSize; + canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true)); + } + } else if(!canvas_.get() || canvas_w_ != client_rect.width() || + canvas_h_ != client_rect.height()) { ResetScrollRect(); paint_rect_ = client_rect; - canvas_.reset(new skia::PlatformCanvas( - paint_rect_.width(), paint_rect_.height(), true)); + + // For non-window-based rendering the canvas must be the exact size of the + // client area. + canvas_w_ = client_rect.width(); + canvas_h_ = client_rect.height(); + canvas_.reset(new skia::PlatformCanvas(canvas_w_, canvas_h_, true)); } #ifdef WEBWIDGET_HAS_ANIMATE_CHANGES