diff options
author | darin@chromium.org <darin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-19 05:24:57 +0000 |
---|---|---|
committer | darin@chromium.org <darin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-19 05:24:57 +0000 |
commit | 552e600db9f51ac1efb0438bd749227226dffa64 (patch) | |
tree | 121e6a11a6f637e9b5cfd2d04244c94b1961420f /chrome/renderer | |
parent | 0dea18f231b425216aae0c956129bc80b7086770 (diff) | |
download | chromium_src-552e600db9f51ac1efb0438bd749227226dffa64.zip chromium_src-552e600db9f51ac1efb0438bd749227226dffa64.tar.gz chromium_src-552e600db9f51ac1efb0438bd749227226dffa64.tar.bz2 |
Refactors RenderWidget to extract a PaintAggregator class.
After this change, I plan on changing the PaintAggregator algorithm.
Some things to note:
1- Previously, it was possible to send overlapping ViewHostMsg_PaintRect and
ViewHostMsg_ScrollRect messages. This happened when scrolling a page since the
scrollbar would need to be repainted while the contents of the page are being
scrolled. With this CL, this overlapping behavior is a bit more explicit.
2- There was a TODO about view_size clipping that I've eliminated. I was able
to eliminate it because I realized that it is correct to clip the rects passed
by didInvalidateRect and didScrollRect to the size of the RenderWidget.
Apparently WebKit can sometimes invalidate regions outside the view.
R=brettw
BUG=25905
TEST=none
Review URL: http://codereview.chromium.org/403005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@32496 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/renderer')
-rw-r--r-- | chrome/renderer/paint_aggregator.cc | 105 | ||||
-rw-r--r-- | chrome/renderer/paint_aggregator.h | 49 | ||||
-rw-r--r-- | chrome/renderer/paint_aggregator_unittest.cc | 128 | ||||
-rw-r--r-- | chrome/renderer/render_widget.cc | 293 | ||||
-rw-r--r-- | chrome/renderer/render_widget.h | 18 |
5 files changed, 397 insertions, 196 deletions
diff --git a/chrome/renderer/paint_aggregator.cc b/chrome/renderer/paint_aggregator.cc new file mode 100644 index 0000000..ca4841b --- /dev/null +++ b/chrome/renderer/paint_aggregator.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2009 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 "chrome/renderer/paint_aggregator.h" + +#include "base/logging.h" + +// We implement a very simple algorithm: +// +// - Multiple repaints are unioned to form the smallest bounding box. +// - If a scroll intersects a repaint, then the scroll is downgraded +// to a repaint and then unioned with the existing repaint. +// +// This allows for a scroll to exist in parallel to a repaint provided the two +// do not intersect. + +gfx::Rect PaintAggregator::PendingUpdate::GetScrollDamage() const { + // Should only be scrolling in one direction at a time. + DCHECK(!(scroll_delta.x() && scroll_delta.y())); + + gfx::Rect damaged_rect; + + // Compute the region we will expose by scrolling, and paint that into a + // shared memory section. + if (scroll_delta.x()) { + int dx = scroll_delta.x(); + damaged_rect.set_y(scroll_rect.y()); + damaged_rect.set_height(scroll_rect.height()); + if (dx > 0) { + damaged_rect.set_x(scroll_rect.x()); + damaged_rect.set_width(dx); + } else { + damaged_rect.set_x(scroll_rect.right() + dx); + damaged_rect.set_width(-dx); + } + } else { + int dy = scroll_delta.y(); + damaged_rect.set_x(scroll_rect.x()); + damaged_rect.set_width(scroll_rect.width()); + if (dy > 0) { + damaged_rect.set_y(scroll_rect.y()); + damaged_rect.set_height(dy); + } else { + damaged_rect.set_y(scroll_rect.bottom() + dy); + damaged_rect.set_height(-dy); + } + } + + // In case the scroll offset exceeds the width/height of the scroll rect + return scroll_rect.Intersect(damaged_rect); +} + +bool PaintAggregator::HasPendingUpdate() const { + return !update_.scroll_rect.IsEmpty() || !update_.paint_rect.IsEmpty(); +} + +void PaintAggregator::ClearPendingUpdate() { + update_ = PendingUpdate(); +} + +void PaintAggregator::InvalidateRect(const gfx::Rect& rect) { + // If this invalidate overlaps with a pending scroll, then we have to + // downgrade to invalidating the scroll rect. + if (rect.Intersects(update_.scroll_rect)) { + update_.paint_rect = update_.paint_rect.Union(update_.scroll_rect); + update_.scroll_rect = gfx::Rect(); + update_.scroll_delta = gfx::Point(); + } + + update_.paint_rect = update_.paint_rect.Union(rect); +} + +void PaintAggregator::ScrollRect(int dx, int dy, const gfx::Rect& clip_rect) { + if (dx != 0 && dy != 0) { + // We only support scrolling along one axis at a time. + ScrollRect(0, dy, clip_rect); + dy = 0; + } + + bool intersects_with_painting = update_.paint_rect.Intersects(clip_rect); + + // If we already have a pending scroll operation or if this scroll operation + // intersects the existing paint region, then just failover to invalidating. + if (!update_.scroll_rect.IsEmpty() || intersects_with_painting) { + if (!intersects_with_painting && update_.scroll_rect == clip_rect) { + // OK, we can just update the scroll delta (requires same scrolling axis) + if (!dx && !update_.scroll_delta.x()) { + update_.scroll_delta.set_y(update_.scroll_delta.y() + dy); + return; + } + if (!dy && !update_.scroll_delta.y()) { + update_.scroll_delta.set_x(update_.scroll_delta.x() + dx); + return; + } + } + InvalidateRect(update_.scroll_rect); + DCHECK(update_.scroll_rect.IsEmpty()); + InvalidateRect(clip_rect); + return; + } + + update_.scroll_rect = clip_rect; + update_.scroll_delta = gfx::Point(dx, dy); +} diff --git a/chrome/renderer/paint_aggregator.h b/chrome/renderer/paint_aggregator.h new file mode 100644 index 0000000..b3ecf4d --- /dev/null +++ b/chrome/renderer/paint_aggregator.h @@ -0,0 +1,49 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_RENDERER_PAINT_AGGREGATOR_H_ +#define CHROME_RENDERER_PAINT_AGGREGATOR_H_ + +#include "base/gfx/rect.h" + +// This class is responsible for aggregating multiple invalidation and scroll +// commands to produce a single scroll and repaint. +class PaintAggregator { + public: + + // This structure describes an aggregation of InvalidateRect and ScrollRect + // calls. If |scroll_rect| is non-empty, then that rect should be scrolled + // by the amount specified by |scroll_delta|. If |paint_rect| is non-empty, + // then that rect should be repainted. If |scroll_rect| and |paint_rect| are + // non-empty, then scrolling should be performed before repainting. + // |scroll_delta| can only specify scrolling in one direction (i.e., the x + // and y members cannot both be non-zero). + struct PendingUpdate { + gfx::Point scroll_delta; + gfx::Rect scroll_rect; + gfx::Rect paint_rect; + + // Returns the rect damaged by scrolling within |scroll_rect| by + // |scroll_delta|. This rect must be repainted. + gfx::Rect GetScrollDamage() const; + }; + + // There is a PendingUpdate if InvalidateRect or ScrollRect were called and + // ClearPendingUpdate was not called. + bool HasPendingUpdate() const; + void ClearPendingUpdate(); + + const PendingUpdate& GetPendingUpdate() const { return update_; } + + // The given rect should be repainted. + void InvalidateRect(const gfx::Rect& rect); + + // The given rect should be scrolled by the given amounts. + void ScrollRect(int dx, int dy, const gfx::Rect& clip_rect); + + private: + PendingUpdate update_; +}; + +#endif // CHROME_RENDERER_PAINT_AGGREGATOR_H_ diff --git a/chrome/renderer/paint_aggregator_unittest.cc b/chrome/renderer/paint_aggregator_unittest.cc new file mode 100644 index 0000000..f490a2d --- /dev/null +++ b/chrome/renderer/paint_aggregator_unittest.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2009 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 "chrome/renderer/paint_aggregator.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PaintAggregator, InitialState) { + PaintAggregator greg; + EXPECT_FALSE(greg.HasPendingUpdate()); +} + +TEST(PaintAggregator, SingleInvalidation) { + PaintAggregator greg; + + gfx::Rect rect(2, 4, 10, 16); + greg.InvalidateRect(rect); + + EXPECT_TRUE(greg.HasPendingUpdate()); + EXPECT_TRUE(greg.GetPendingUpdate().scroll_rect.IsEmpty()); + EXPECT_FALSE(greg.GetPendingUpdate().paint_rect.IsEmpty()); + + EXPECT_EQ(rect.x(), greg.GetPendingUpdate().paint_rect.x()); + EXPECT_EQ(rect.y(), greg.GetPendingUpdate().paint_rect.y()); + EXPECT_EQ(rect.width(), greg.GetPendingUpdate().paint_rect.width()); + EXPECT_EQ(rect.height(), greg.GetPendingUpdate().paint_rect.height()); +} + +TEST(PaintAggregator, DoubleDisjointInvalidation) { + PaintAggregator greg; + + gfx::Rect r1(2, 4, 2, 4); + gfx::Rect r2(4, 2, 4, 2); + + greg.InvalidateRect(r1); + greg.InvalidateRect(r2); + + gfx::Rect expected = r1.Union(r2); + + EXPECT_TRUE(greg.HasPendingUpdate()); + EXPECT_TRUE(greg.GetPendingUpdate().scroll_rect.IsEmpty()); + EXPECT_FALSE(greg.GetPendingUpdate().paint_rect.IsEmpty()); + + EXPECT_EQ(expected.x(), greg.GetPendingUpdate().paint_rect.x()); + EXPECT_EQ(expected.y(), greg.GetPendingUpdate().paint_rect.y()); + EXPECT_EQ(expected.width(), greg.GetPendingUpdate().paint_rect.width()); + EXPECT_EQ(expected.height(), greg.GetPendingUpdate().paint_rect.height()); +} + +TEST(PaintAggregator, SingleScroll) { + PaintAggregator greg; + + gfx::Rect rect(1, 2, 3, 4); + gfx::Point delta(1, 0); + greg.ScrollRect(delta.x(), delta.y(), rect); + + EXPECT_TRUE(greg.HasPendingUpdate()); + EXPECT_TRUE(greg.GetPendingUpdate().paint_rect.IsEmpty()); + EXPECT_FALSE(greg.GetPendingUpdate().scroll_rect.IsEmpty()); + + EXPECT_EQ(rect.x(), greg.GetPendingUpdate().scroll_rect.x()); + EXPECT_EQ(rect.y(), greg.GetPendingUpdate().scroll_rect.y()); + EXPECT_EQ(rect.width(), greg.GetPendingUpdate().scroll_rect.width()); + EXPECT_EQ(rect.height(), greg.GetPendingUpdate().scroll_rect.height()); + + EXPECT_EQ(delta.x(), greg.GetPendingUpdate().scroll_delta.x()); + EXPECT_EQ(delta.y(), greg.GetPendingUpdate().scroll_delta.y()); + + gfx::Rect resulting_damage = greg.GetPendingUpdate().GetScrollDamage(); + gfx::Rect expected_damage(1, 2, 1, 4); + EXPECT_EQ(expected_damage.x(), resulting_damage.x()); + EXPECT_EQ(expected_damage.y(), resulting_damage.y()); + EXPECT_EQ(expected_damage.width(), resulting_damage.width()); + EXPECT_EQ(expected_damage.height(), resulting_damage.height()); +} + +TEST(PaintAggregator, DoubleOverlappingScroll) { + PaintAggregator greg; + + gfx::Rect rect(1, 2, 3, 4); + gfx::Point delta1(1, 0); + gfx::Point delta2(1, 0); + greg.ScrollRect(delta1.x(), delta1.y(), rect); + greg.ScrollRect(delta2.x(), delta2.y(), rect); + + EXPECT_TRUE(greg.HasPendingUpdate()); + EXPECT_TRUE(greg.GetPendingUpdate().paint_rect.IsEmpty()); + EXPECT_FALSE(greg.GetPendingUpdate().scroll_rect.IsEmpty()); + + EXPECT_EQ(rect.x(), greg.GetPendingUpdate().scroll_rect.x()); + EXPECT_EQ(rect.y(), greg.GetPendingUpdate().scroll_rect.y()); + EXPECT_EQ(rect.width(), greg.GetPendingUpdate().scroll_rect.width()); + EXPECT_EQ(rect.height(), greg.GetPendingUpdate().scroll_rect.height()); + + gfx::Point expected_delta(delta1.x() + delta2.x(), + delta1.y() + delta2.y()); + EXPECT_EQ(expected_delta.x(), greg.GetPendingUpdate().scroll_delta.x()); + EXPECT_EQ(expected_delta.y(), greg.GetPendingUpdate().scroll_delta.y()); + + gfx::Rect resulting_damage = greg.GetPendingUpdate().GetScrollDamage(); + gfx::Rect expected_damage(1, 2, 2, 4); + EXPECT_EQ(expected_damage.x(), resulting_damage.x()); + EXPECT_EQ(expected_damage.y(), resulting_damage.y()); + EXPECT_EQ(expected_damage.width(), resulting_damage.width()); + EXPECT_EQ(expected_damage.height(), resulting_damage.height()); +} + +TEST(PaintAggregator, DiagonalScroll) { + PaintAggregator greg; + + // We don't support optimized diagonal scrolling, so this should result in + // repainting. + + gfx::Rect rect(1, 2, 3, 4); + gfx::Point delta(1, 1); + greg.ScrollRect(delta.x(), delta.y(), rect); + + EXPECT_TRUE(greg.HasPendingUpdate()); + EXPECT_TRUE(greg.GetPendingUpdate().scroll_rect.IsEmpty()); + EXPECT_FALSE(greg.GetPendingUpdate().paint_rect.IsEmpty()); + + EXPECT_EQ(rect.x(), greg.GetPendingUpdate().paint_rect.x()); + EXPECT_EQ(rect.y(), greg.GetPendingUpdate().paint_rect.y()); + EXPECT_EQ(rect.width(), greg.GetPendingUpdate().paint_rect.width()); + EXPECT_EQ(rect.height(), greg.GetPendingUpdate().paint_rect.height()); +} + +// TODO(darin): Add tests for mixed scrolling and invalidation diff --git a/chrome/renderer/render_widget.cc b/chrome/renderer/render_widget.cc index 85ecf77..34dee52 100644 --- a/chrome/renderer/render_widget.cc +++ b/chrome/renderer/render_widget.cc @@ -215,17 +215,15 @@ void RenderWidget::OnResize(const gfx::Size& new_size, // We should not be sent a Resize message if we have not ACK'd the previous DCHECK(!next_paint_is_resize_ack()); + paint_aggregator_.ClearPendingUpdate(); + // When resizing, we want to wait to paint before ACK'ing the resize. This // ensures that we only resize as fast as we can paint. We only need to send // an ACK if we are resized to a non-empty rect. webwidget_->resize(new_size); if (!new_size.IsEmpty()) { - DCHECK(!paint_rect_.IsEmpty()); - - // This should have caused an invalidation of the entire view. The damaged - // rect could be larger than new_size if we are being made smaller. - DCHECK_GE(paint_rect_.width(), new_size.width()); - DCHECK_GE(paint_rect_.height(), new_size.height()); + // Resize should have caused an invalidation of the entire view. + DCHECK(paint_aggregator_.HasPendingUpdate()); // We will send the Resize_ACK flag once we paint again. set_next_paint_is_resize_ack(); @@ -271,7 +269,7 @@ void RenderWidget::OnPaintRectAck() { DidPaint(); // Continue painting if necessary... - CallDoDeferredPaint(); + CallDoDeferredUpdate(); } void RenderWidget::OnRequestMoveAck() { @@ -288,16 +286,7 @@ void RenderWidget::OnScrollRectAck() { } // Continue scrolling if necessary... - CallDoDeferredScroll(); -} - -void RenderWidget::CallDoDeferredScroll() { - DoDeferredScroll(); - - if (pending_input_event_ack_.get()) { - Send(pending_input_event_ack_.get()); - pending_input_event_ack_.release(); - } + CallDoDeferredUpdate(); } void RenderWidget::OnHandleInputEvent(const IPC::Message& message) { @@ -322,7 +311,7 @@ void RenderWidget::OnHandleInputEvent(const IPC::Message& message) { response->WriteBool(processed); if (input_event->type == WebInputEvent::MouseMove && - (!paint_rect_.IsEmpty() || !scroll_rect_.IsEmpty())) { + paint_aggregator_.HasPendingUpdate()) { // We want to rate limit the input events in this case, so we'll wait for // painting to finish before ACKing this message. pending_input_event_ack_.reset(response); @@ -387,8 +376,8 @@ void RenderWidget::PaintRect(const gfx::Rect& rect, canvas->getTopPlatformDevice().accessBitmap(false); } -void RenderWidget::CallDoDeferredPaint() { - DoDeferredPaint(); +void RenderWidget::CallDoDeferredUpdate() { + DoDeferredUpdate(); if (pending_input_event_ack_.get()) { Send(pending_input_event_ack_.get()); @@ -396,140 +385,96 @@ void RenderWidget::CallDoDeferredPaint() { } } -void RenderWidget::DoDeferredPaint() { - if (!webwidget_ || paint_reply_pending() || paint_rect_.IsEmpty()) +void RenderWidget::DoDeferredUpdate() { + if (!webwidget_ || !paint_aggregator_.HasPendingUpdate() || + paint_reply_pending() || scroll_reply_pending()) return; - // When we are hidden, we want to suppress painting, but we still need to - // mark this DoDeferredPaint as complete. + // Suppress updating when we are hidden. if (is_hidden_ || size_.IsEmpty()) { - paint_rect_ = gfx::Rect(); + paint_aggregator_.ClearPendingUpdate(); needs_repainting_on_restore_ = true; return; } - // Layout may generate more invalidation... + // Layout may generate more invalidation. webwidget_->layout(); - // OK, save the current paint_rect to a local since painting may cause more + // OK, save the pending update to a local since painting may cause more // invalidation. Some WebCore rendering objects only layout when painted. - gfx::Rect damaged_rect = paint_rect_; - paint_rect_ = gfx::Rect(); - - // Compute a buffer for painting and cache it. - skia::PlatformCanvas* canvas = - RenderProcess::current()->GetDrawingCanvas(¤t_paint_buf_, - damaged_rect); - if (!canvas) { - NOTREACHED(); - return; - } - - // We may get back a smaller canvas than we asked for. - damaged_rect.set_width(canvas->getDevice()->width()); - damaged_rect.set_height(canvas->getDevice()->height()); - - PaintRect(damaged_rect, canvas); + PaintAggregator::PendingUpdate update = paint_aggregator_.GetPendingUpdate(); + paint_aggregator_.ClearPendingUpdate(); + + if (!update.scroll_rect.IsEmpty()) { + // Optmized scrolling + + // Compute the region we will expose by scrolling, and paint that into a + // shared memory section. + gfx::Rect damaged_rect = update.GetScrollDamage(); + + scoped_ptr<skia::PlatformCanvas> canvas( + RenderProcess::current()->GetDrawingCanvas(¤t_scroll_buf_, + damaged_rect)); + if (!canvas.get()) { + NOTREACHED(); + return; + } - ViewHostMsg_PaintRect_Params params; - params.bitmap_rect = damaged_rect; - params.view_size = size_; - params.plugin_window_moves = plugin_window_moves_; - params.flags = next_paint_flags_; - params.bitmap = current_paint_buf_->id(); + // We may get back a smaller canvas than we asked for. + damaged_rect.set_width(canvas->getDevice()->width()); + damaged_rect.set_height(canvas->getDevice()->height()); - delete canvas; + // Set these parameters before calling Paint, since that could result in + // further invalidates (uncommon). + ViewHostMsg_ScrollRect_Params params; + params.bitmap_rect = damaged_rect; + params.dx = update.scroll_delta.x(); + params.dy = update.scroll_delta.y(); + params.clip_rect = update.scroll_rect; + params.view_size = size_; + params.plugin_window_moves = plugin_window_moves_; + params.bitmap = current_scroll_buf_->id(); - plugin_window_moves_.clear(); + plugin_window_moves_.clear(); - paint_reply_pending_ = true; - Send(new ViewHostMsg_PaintRect(routing_id_, params)); - next_paint_flags_ = 0; + PaintRect(damaged_rect, canvas.get()); + Send(new ViewHostMsg_ScrollRect(routing_id_, params)); + } - UpdateIME(); -} + if (!update.paint_rect.IsEmpty()) { + // Normal painting -void RenderWidget::DoDeferredScroll() { - if (!webwidget_ || scroll_reply_pending() || scroll_rect_.IsEmpty()) - return; + gfx::Rect damaged_rect = update.paint_rect; - // When we are hidden, we want to suppress scrolling, but we still need to - // mark this DoDeferredScroll as complete. - if (is_hidden_ || size_.IsEmpty()) { - scroll_rect_ = gfx::Rect(); - needs_repainting_on_restore_ = true; - return; - } + // Compute a buffer for painting and cache it. + scoped_ptr<skia::PlatformCanvas> canvas( + RenderProcess::current()->GetDrawingCanvas(¤t_paint_buf_, + damaged_rect)); + if (!canvas.get()) { + NOTREACHED(); + return; + } - // Layout may generate more invalidation, so we might have to bail on - // optimized scrolling... - webwidget_->layout(); + // We may get back a smaller canvas than we asked for. + damaged_rect.set_width(canvas->getDevice()->width()); + damaged_rect.set_height(canvas->getDevice()->height()); - if (scroll_rect_.IsEmpty()) - return; + PaintRect(damaged_rect, canvas.get()); - gfx::Rect damaged_rect; - - // Compute the region we will expose by scrolling, and paint that into a - // shared memory section. - if (scroll_delta_.x()) { - int dx = scroll_delta_.x(); - damaged_rect.set_y(scroll_rect_.y()); - damaged_rect.set_height(scroll_rect_.height()); - if (dx > 0) { - damaged_rect.set_x(scroll_rect_.x()); - damaged_rect.set_width(dx); - } else { - damaged_rect.set_x(scroll_rect_.right() + dx); - damaged_rect.set_width(-dx); - } - } else { - int dy = scroll_delta_.y(); - damaged_rect.set_x(scroll_rect_.x()); - damaged_rect.set_width(scroll_rect_.width()); - if (dy > 0) { - damaged_rect.set_y(scroll_rect_.y()); - damaged_rect.set_height(dy); - } else { - damaged_rect.set_y(scroll_rect_.bottom() + dy); - damaged_rect.set_height(-dy); - } - } + ViewHostMsg_PaintRect_Params params; + params.bitmap_rect = damaged_rect; + params.view_size = size_; + params.plugin_window_moves = plugin_window_moves_; + params.flags = next_paint_flags_; + params.bitmap = current_paint_buf_->id(); - // In case the scroll offset exceeds the width/height of the scroll rect - damaged_rect = scroll_rect_.Intersect(damaged_rect); + plugin_window_moves_.clear(); - skia::PlatformCanvas* canvas = - RenderProcess::current()->GetDrawingCanvas(¤t_scroll_buf_, - damaged_rect); - if (!canvas) { - NOTREACHED(); - return; + paint_reply_pending_ = true; + Send(new ViewHostMsg_PaintRect(routing_id_, params)); + next_paint_flags_ = 0; } - // We may get back a smaller canvas than we asked for. - damaged_rect.set_width(canvas->getDevice()->width()); - damaged_rect.set_height(canvas->getDevice()->height()); - - // Set these parameters before calling Paint, since that could result in - // further invalidates (uncommon). - ViewHostMsg_ScrollRect_Params params; - params.bitmap_rect = damaged_rect; - params.dx = scroll_delta_.x(); - params.dy = scroll_delta_.y(); - params.clip_rect = scroll_rect_; - params.view_size = size_; - params.plugin_window_moves = plugin_window_moves_; - params.bitmap = current_scroll_buf_->id(); - - plugin_window_moves_.clear(); - - // Mark the scroll operation as no longer pending. - scroll_rect_ = gfx::Rect(); - - PaintRect(damaged_rect, canvas); - Send(new ViewHostMsg_ScrollRect(routing_id_, params)); - delete canvas; UpdateIME(); } @@ -537,77 +482,61 @@ void RenderWidget::DoDeferredScroll() { // WebWidgetDelegate void RenderWidget::didInvalidateRect(const WebRect& rect) { - // We only want one pending DoDeferredPaint call at any time... - bool paint_pending = !paint_rect_.IsEmpty(); - - // If this invalidate overlaps with a pending scroll, then we have to - // downgrade to invalidating the scroll rect. - if (gfx::Rect(rect).Intersects(scroll_rect_)) { - paint_rect_ = paint_rect_.Union(scroll_rect_); - scroll_rect_ = gfx::Rect(); - } + // We only want one pending DoDeferredUpdate call at any time... + bool update_pending = paint_aggregator_.HasPendingUpdate(); + // The invalidated rect might be outside the bounds of the view. gfx::Rect view_rect(0, 0, size_.width(), size_.height()); - // TODO(iyengar) Investigate why we have painting issues when - // we ignore invalid regions outside the view. - // Ignore invalidates that occur outside the bounds of the view - // TODO(darin): maybe this should move into the paint code? - // paint_rect_ = view_rect.Intersect(paint_rect_.Union(rect)); - paint_rect_ = paint_rect_.Union(view_rect.Intersect(rect)); - - if (paint_rect_.IsEmpty() || paint_reply_pending() || paint_pending) + gfx::Rect damaged_rect = view_rect.Intersect(rect); + if (damaged_rect.IsEmpty()) return; - // Perform painting asynchronously. This serves two purposes: + paint_aggregator_.InvalidateRect(damaged_rect); + + // We may not need to schedule another call to DoDeferredUpdate. + if (update_pending) + return; + if (!paint_aggregator_.HasPendingUpdate()) + return; + if (paint_reply_pending() || scroll_reply_pending()) + return; + + // Perform updating asynchronously. This serves two purposes: // 1) Ensures that we call WebView::Paint without a bunch of other junk // on the call stack. // 2) Allows us to collect more damage rects before painting to help coalesce // the work that we will need to do. MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( - this, &RenderWidget::CallDoDeferredPaint)); + this, &RenderWidget::CallDoDeferredUpdate)); } void RenderWidget::didScrollRect(int dx, int dy, const WebRect& clip_rect) { - if (dx != 0 && dy != 0) { - // We only support scrolling along one axis at a time. - didScrollRect(0, dy, clip_rect); - dy = 0; - } + // We only want one pending DoDeferredUpdate call at any time... + bool update_pending = paint_aggregator_.HasPendingUpdate(); - bool intersects_with_painting = paint_rect_.Intersects(clip_rect); - - // 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() || intersects_with_painting) { - if (!intersects_with_painting && scroll_rect_ == gfx::Rect(clip_rect)) { - // OK, we can just update the scroll delta (requires same scrolling axis) - if (!dx && !scroll_delta_.x()) { - scroll_delta_.set_y(scroll_delta_.y() + dy); - return; - } - if (!dy && !scroll_delta_.y()) { - scroll_delta_.set_x(scroll_delta_.x() + dx); - return; - } - } - didInvalidateRect(scroll_rect_); - DCHECK(scroll_rect_.IsEmpty()); - didInvalidateRect(clip_rect); + // The scrolled rect might be outside the bounds of the view. + gfx::Rect view_rect(0, 0, size_.width(), size_.height()); + gfx::Rect damaged_rect = view_rect.Intersect(clip_rect); + if (damaged_rect.IsEmpty()) return; - } - - // We only want one pending DoDeferredScroll call at any time... - bool scroll_pending = !scroll_rect_.IsEmpty(); - scroll_rect_ = clip_rect; - scroll_delta_.SetPoint(dx, dy); + paint_aggregator_.ScrollRect(dx, dy, damaged_rect); - if (scroll_pending) + // We may not need to schedule another call to DoDeferredUpdate. + if (update_pending) + return; + if (!paint_aggregator_.HasPendingUpdate()) + return; + if (paint_reply_pending() || scroll_reply_pending()) return; - // Perform scrolling asynchronously since we need to call WebView::Paint + // Perform updating asynchronously. This serves two purposes: + // 1) Ensures that we call WebView::Paint without a bunch of other junk + // on the call stack. + // 2) Allows us to collect more damage rects before painting to help coalesce + // the work that we will need to do. MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( - this, &RenderWidget::CallDoDeferredScroll)); + this, &RenderWidget::CallDoDeferredUpdate)); } void RenderWidget::didChangeCursor(const WebCursorInfo& cursor_info) { diff --git a/chrome/renderer/render_widget.h b/chrome/renderer/render_widget.h index df17bd1..31eee61 100644 --- a/chrome/renderer/render_widget.h +++ b/chrome/renderer/render_widget.h @@ -14,6 +14,7 @@ #include "base/gfx/size.h" #include "base/ref_counted.h" #include "base/shared_memory.h" +#include "chrome/renderer/paint_aggregator.h" #include "chrome/renderer/render_process.h" #include "ipc/ipc_channel.h" #include "skia/ext/platform_canvas.h" @@ -120,10 +121,8 @@ class RenderWidget : public IPC::Channel::Listener, // must ensure that the given rect fits within the bounds of the WebWidget. void PaintRect(const gfx::Rect& rect, skia::PlatformCanvas* canvas); - void CallDoDeferredPaint(); - void DoDeferredPaint(); - void CallDoDeferredScroll(); - void DoDeferredScroll(); + void CallDoDeferredUpdate(); + void DoDeferredUpdate(); void DoDeferredClose(); void DoDeferredSetWindowRect(const WebKit::WebRect& pos); @@ -240,20 +239,11 @@ class RenderWidget : public IPC::Channel::Listener, TransportDIB* current_paint_buf_; TransportDIB* current_scroll_buf_; - // The smallest bounding rectangle that needs to be re-painted. This is non- - // empty if a paint event is pending. - gfx::Rect paint_rect_; - - // The clip rect for the pending scroll event. This is non-empty if a - // scroll event is pending. - gfx::Rect scroll_rect_; + PaintAggregator paint_aggregator_; // The area that must be reserved for drawing the resize corner. gfx::Rect resizer_rect_; - // The scroll delta for a pending scroll event. - gfx::Point scroll_delta_; - // Flags for the next ViewHostMsg_PaintRect message. int next_paint_flags_; |