diff options
author | enne@chromium.org <enne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-24 04:44:50 +0000 |
---|---|---|
committer | enne@chromium.org <enne@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-24 04:44:50 +0000 |
commit | b535f65a24fbb16f0d1a93a1f2ac6ca8b1837d50 (patch) | |
tree | 118820d27365034891ffd4dd107dce00d393212f /cc | |
parent | 4204b9ffa857f6b95ccb05401995b0b4b9ef7690 (diff) | |
download | chromium_src-b535f65a24fbb16f0d1a93a1f2ac6ca8b1837d50.zip chromium_src-b535f65a24fbb16f0d1a93a1f2ac6ca8b1837d50.tar.gz chromium_src-b535f65a24fbb16f0d1a93a1f2ac6ca8b1837d50.tar.bz2 |
cc: Allow the main thread to cancel commits
Add a new SetNeedsUpdateLayers that triggers the commit flow, but is
abortable if update layers doesn't actually make any changes. This
allows the main thread to abort a begin frame. This happens in the case
of scroll updates from the compositor thread or invalidations.
There was previously an abort begin frame call for when a visibility
message and a begin frame message were posted simultaneously, but it
incorrectly applied the scrolls and scales without informing the
compositor thread that these had already been consumed. To fix this,
the abort message passes back a boolean about whether or not the
commit was aborted (and needed to be sent again) or was handled
(and the scrolls and scales processed).
To avoid a deluge of begin frames (in the commit sense) from the
scheduler, the scheduler has been adjusted to wait until the next begin
frame (in the vsync signal sense) so that these calls can be throttled.
Otherwise, the scheduler will just keep trying to begin frame.
R=brianderson@chromium.org, danakj@chromium.org
BUG=256381
Review URL: https://chromiumcodereview.appspot.com/19106007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@213338 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc')
34 files changed, 581 insertions, 114 deletions
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc index 3437d86..156e475 100644 --- a/cc/layers/layer.cc +++ b/cc/layers/layer.cc @@ -121,6 +121,11 @@ void Layer::SetLayerTreeHost(LayerTreeHost* host) { layer_tree_host_->set_needs_filter_context(); } +void Layer::SetNeedsUpdate() { + if (layer_tree_host_ && !ignore_set_needs_commit_) + layer_tree_host_->SetNeedsUpdateLayers(); +} + void Layer::SetNeedsCommit() { if (!layer_tree_host_) return; @@ -563,14 +568,17 @@ void Layer::SetScrollOffset(gfx::Vector2d scroll_offset) { void Layer::SetScrollOffsetFromImplSide(gfx::Vector2d scroll_offset) { DCHECK(IsPropertyChangeAllowed()); + // This function only gets called during a begin frame, so there + // is no need to call SetNeedsUpdate here. DCHECK(layer_tree_host_ && layer_tree_host_->CommitRequested()); if (scroll_offset_ == scroll_offset) return; scroll_offset_ = scroll_offset; + SetNeedsPushProperties(); if (!did_scroll_callback_.is_null()) did_scroll_callback_.Run(); - // Note: didScroll() could potentially change the layer structure. - // "this" may have been destroyed during the process. + // The callback could potentially change the layer structure: + // "this" may have been destroyed during the process. } void Layer::SetMaxScrollOffset(gfx::Vector2d max_scroll_offset) { @@ -663,14 +671,16 @@ void Layer::SetHideLayerAndSubtree(bool hide) { } void Layer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) { + if (!update_rect_.Contains(dirty_rect)) { + SetNeedsPushProperties(); + } + update_rect_.Union(dirty_rect); needs_display_ = true; - // Simply mark the contents as dirty. For non-root layers, the call to - // SetNeedsCommit will schedule a fresh compositing pass. - // For the root layer, SetNeedsCommit has no effect. - if (DrawsContent() && !update_rect_.IsEmpty()) - SetNeedsCommit(); + if (DrawsContent() && !update_rect_.IsEmpty()) { + SetNeedsUpdate(); + } } bool Layer::DescendantIsFixedToContainerLayer() const { diff --git a/cc/layers/layer.h b/cc/layers/layer.h index 76c4899..3f76a9b 100644 --- a/cc/layers/layer.h +++ b/cc/layers/layer.h @@ -393,7 +393,18 @@ class CC_EXPORT Layer : public base::RefCounted<Layer>, Layer(); + // These SetNeeds functions are in order of severity of update: + // + // Called when this layer has been modified in some way, but isn't sure + // that it needs a commit yet. It needs CalcDrawProperties and UpdateLayers + // before it knows whether or not a commit is required. + void SetNeedsUpdate(); + // Called when a property has been modified in a way that the layer + // knows immediately that a commit is required. This implies SetNeedsUpdate + // as well as SetNeedsPushProperties to push that property. void SetNeedsCommit(); + // Called when there's been a change in layer structure. Implies both + // SetNeedsUpdate and SetNeedsCommit, but not SetNeedsPushProperties. void SetNeedsFullTreeSync(); bool IsPropertyChangeAllowed() const; @@ -434,7 +445,7 @@ class CC_EXPORT Layer : public base::RefCounted<Layer>, int layer_id_; // When true, the layer is about to perform an update. Any commit requests - // will be handled implcitly after the update completes. + // will be handled implicitly after the update completes. bool ignore_set_needs_commit_; private: diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc index 4865fe2..33d5c04 100644 --- a/cc/layers/layer_impl.cc +++ b/cc/layers/layer_impl.cc @@ -270,6 +270,22 @@ gfx::Vector2dF LayerImpl::ScrollBy(gfx::Vector2dF scroll) { return unscrolled; } +void LayerImpl::ApplySentScrollDeltas() { + // Pending tree never has sent scroll deltas + DCHECK(layer_tree_impl()->IsActiveTree()); + + // Apply sent scroll deltas to scroll position / scroll delta as if the + // main thread had applied them and then committed those values. + scroll_offset_ += sent_scroll_delta_; + scroll_delta_ -= sent_scroll_delta_; + sent_scroll_delta_ = gfx::Vector2d(); + + DCHECK_GE(TotalScrollOffset().x(), 0); + DCHECK_GE(TotalScrollOffset().y(), 0); + DCHECK_LE(TotalScrollOffset().x(), max_scroll_offset_.x()); + DCHECK_LE(TotalScrollOffset().y(), max_scroll_offset_.y()); +} + InputHandler::ScrollStatus LayerImpl::TryScroll( gfx::PointF screen_space_point, InputHandler::ScrollInputType type) const { diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h index ff7e956..90da41a 100644 --- a/cc/layers/layer_impl.h +++ b/cc/layers/layer_impl.h @@ -330,6 +330,8 @@ class CC_EXPORT LayerImpl : LayerAnimationValueObserver { void SetScrollable(bool scrollable) { scrollable_ = scrollable; } bool scrollable() const { return scrollable_; } + void ApplySentScrollDeltas(); + void SetShouldScrollOnMainThread(bool should_scroll_on_main_thread) { should_scroll_on_main_thread_ = should_scroll_on_main_thread; } diff --git a/cc/layers/layer_unittest.cc b/cc/layers/layer_unittest.cc index fb3a752..b7fa166 100644 --- a/cc/layers/layer_unittest.cc +++ b/cc/layers/layer_unittest.cc @@ -44,6 +44,7 @@ class MockLayerTreeHost : public LayerTreeHost { } MOCK_METHOD0(SetNeedsCommit, void()); + MOCK_METHOD0(SetNeedsUpdateLayers, void()); MOCK_METHOD0(SetNeedsFullTreeSync, void()); }; @@ -446,7 +447,7 @@ TEST_F(LayerTest, GetRootLayerAfterTreeManipulations) { TEST_F(LayerTest, CheckSetNeedsDisplayCausesCorrectBehavior) { // The semantics for SetNeedsDisplay which are tested here: // 1. sets NeedsDisplay flag appropriately. - // 2. indirectly calls SetNeedsCommit, exactly once for each call to + // 2. indirectly calls SetNeedsUpdate, exactly once for each call to // SetNeedsDisplay. scoped_refptr<Layer> test_layer = Layer::Create(); @@ -476,7 +477,7 @@ TEST_F(LayerTest, CheckSetNeedsDisplayCausesCorrectBehavior) { // Case 1: Layer should accept dirty rects that go beyond its bounds. test_layer->ResetNeedsDisplayForTesting(); EXPECT_FALSE(test_layer->NeedsDisplayForTesting()); - EXPECT_SET_NEEDS_COMMIT( + EXPECT_SET_NEEDS_UPDATE( 1, test_layer->SetNeedsDisplayRect(out_of_bounds_dirty_rect)); EXPECT_TRUE(test_layer->NeedsDisplayForTesting()); test_layer->ResetNeedsDisplayForTesting(); @@ -484,7 +485,7 @@ TEST_F(LayerTest, CheckSetNeedsDisplayCausesCorrectBehavior) { // Case 2: SetNeedsDisplay() without the dirty rect arg. test_layer->ResetNeedsDisplayForTesting(); EXPECT_FALSE(test_layer->NeedsDisplayForTesting()); - EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetNeedsDisplay()); + EXPECT_SET_NEEDS_UPDATE(1, test_layer->SetNeedsDisplay()); EXPECT_TRUE(test_layer->NeedsDisplayForTesting()); test_layer->ResetNeedsDisplayForTesting(); @@ -492,7 +493,7 @@ TEST_F(LayerTest, CheckSetNeedsDisplayCausesCorrectBehavior) { EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(false)); test_layer->ResetNeedsDisplayForTesting(); EXPECT_FALSE(test_layer->NeedsDisplayForTesting()); - EXPECT_SET_NEEDS_COMMIT(0, test_layer->SetNeedsDisplayRect(dirty1)); + EXPECT_SET_NEEDS_UPDATE(0, test_layer->SetNeedsDisplayRect(dirty1)); EXPECT_TRUE(test_layer->NeedsDisplayForTesting()); } diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc index f4482ac..2d39dab 100644 --- a/cc/layers/texture_layer_unittest.cc +++ b/cc/layers/texture_layer_unittest.cc @@ -38,6 +38,7 @@ class MockLayerTreeHost : public LayerTreeHost { MOCK_METHOD0(AcquireLayerTextures, void()); MOCK_METHOD0(SetNeedsCommit, void()); + MOCK_METHOD0(SetNeedsUpdateLayers, void()); MOCK_METHOD1(StartRateLimiter, void(WebKit::WebGraphicsContext3D* context)); MOCK_METHOD1(StopRateLimiter, void(WebKit::WebGraphicsContext3D* context)); }; @@ -118,7 +119,7 @@ TEST_F(TextureLayerTest, SyncImplWhenDrawing) { Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); - EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1); + EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers()).Times(1); test_layer->SetNeedsDisplayRect(dirty_rect); Mock::VerifyAndClearExpectations(layer_tree_host_.get()); diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc index 5e397bd..010cc99 100644 --- a/cc/scheduler/scheduler.cc +++ b/cc/scheduler/scheduler.cc @@ -85,9 +85,9 @@ void Scheduler::FinishCommit() { ProcessScheduledActions(); } -void Scheduler::BeginFrameAbortedByMainThread() { +void Scheduler::BeginFrameAbortedByMainThread(bool did_handle) { TRACE_EVENT0("cc", "Scheduler::BeginFrameAbortedByMainThread"); - state_machine_.BeginFrameAbortedByMainThread(); + state_machine_.BeginFrameAbortedByMainThread(did_handle); ProcessScheduledActions(); } diff --git a/cc/scheduler/scheduler.h b/cc/scheduler/scheduler.h index 48a3bb2..230f20d 100644 --- a/cc/scheduler/scheduler.h +++ b/cc/scheduler/scheduler.h @@ -86,7 +86,7 @@ class CC_EXPORT Scheduler { void DidSwapUseIncompleteTile(); void FinishCommit(); - void BeginFrameAbortedByMainThread(); + void BeginFrameAbortedByMainThread(bool did_handle); void DidLoseOutputSurface(); void DidCreateAndInitializeOutputSurface(); diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc index eed9f15..94100b8 100644 --- a/cc/scheduler/scheduler_state_machine.cc +++ b/cc/scheduler/scheduler_state_machine.cc @@ -15,6 +15,7 @@ SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) commit_state_(COMMIT_STATE_IDLE), commit_count_(0), current_frame_number_(0), + last_frame_number_where_begin_frame_sent_to_main_thread_(-1), last_frame_number_where_draw_was_called_(-1), last_frame_number_where_tree_activation_attempted_(-1), last_frame_number_where_update_visible_tiles_was_called_(-1), @@ -24,6 +25,7 @@ SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) swap_used_incomplete_tile_(false), needs_forced_redraw_(false), needs_forced_redraw_after_next_commit_(false), + needs_redraw_after_next_commit_(false), needs_commit_(false), needs_forced_commit_(false), expect_immediate_begin_frame_for_main_thread_(false), @@ -116,6 +118,19 @@ bool SchedulerStateMachine::HasUpdatedVisibleTilesThisFrame() const { last_frame_number_where_update_visible_tiles_was_called_; } +void SchedulerStateMachine::SetPostCommitFlags() { + // This post-commit work is common to both completed and aborted commits. + if (needs_forced_redraw_after_next_commit_) { + needs_forced_redraw_after_next_commit_ = false; + needs_forced_redraw_ = true; + } + if (needs_redraw_after_next_commit_) { + needs_redraw_after_next_commit_ = false; + needs_redraw_ = true; + } + texture_state_ = LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD; +} + bool SchedulerStateMachine::DrawSuspendedUntilCommit() const { if (!can_draw_) return true; @@ -184,7 +199,7 @@ SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { return ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD; switch (commit_state_) { - case COMMIT_STATE_IDLE: + case COMMIT_STATE_IDLE: { if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE && needs_forced_redraw_) return ACTION_DRAW_FORCED; @@ -205,14 +220,18 @@ SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { return needs_forced_redraw_ ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; } - if (needs_commit_ && - ((visible_ && output_surface_state_ == OUTPUT_SURFACE_ACTIVE) - || needs_forced_commit_)) + bool can_commit_this_frame = + visible_ && + current_frame_number_ > + last_frame_number_where_begin_frame_sent_to_main_thread_; + if (needs_commit_ && ((can_commit_this_frame && + output_surface_state_ == OUTPUT_SURFACE_ACTIVE) || + needs_forced_commit_)) // TODO(enne): Should probably drop the active tree on force commit. return has_pending_tree_ ? ACTION_NONE : ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD; return ACTION_NONE; - + } case COMMIT_STATE_FRAME_IN_PROGRESS: if (ShouldUpdateVisibleTiles()) return ACTION_UPDATE_VISIBLE_TILES; @@ -239,7 +258,11 @@ SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { // COMMIT_STATE_WAITING_FOR_FIRST_DRAW wants to enforce a draw. If // can_draw_ is false or textures are not available, proceed to the next // step (similar as in COMMIT_STATE_IDLE). - bool can_commit = visible_ || needs_forced_commit_; + bool can_commit = + needs_forced_commit_ || + (visible_ && + current_frame_number_ > + last_frame_number_where_begin_frame_sent_to_main_thread_); if (needs_commit_ && can_commit && DrawSuspendedUntilCommit()) return has_pending_tree_ ? ACTION_NONE : ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD; @@ -276,10 +299,16 @@ void SchedulerStateMachine::UpdateState(Action action) { case ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD: DCHECK(!has_pending_tree_); - DCHECK(visible_ || needs_forced_commit_); + if (!needs_forced_commit_) { + DCHECK(visible_); + DCHECK_GT(current_frame_number_, + last_frame_number_where_begin_frame_sent_to_main_thread_); + } commit_state_ = COMMIT_STATE_FRAME_IN_PROGRESS; needs_commit_ = false; needs_forced_commit_ = false; + last_frame_number_where_begin_frame_sent_to_main_thread_ = + current_frame_number_; return; case ACTION_COMMIT: @@ -293,13 +322,7 @@ void SchedulerStateMachine::UpdateState(Action action) { needs_redraw_ = true; if (draw_if_possible_failed_) last_frame_number_where_draw_was_called_ = -1; - - if (needs_forced_redraw_after_next_commit_) { - needs_forced_redraw_after_next_commit_ = false; - needs_forced_redraw_ = true; - } - - texture_state_ = LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD; + SetPostCommitFlags(); return; case ACTION_DRAW_FORCED: @@ -367,12 +390,12 @@ bool SchedulerStateMachine::ProactiveBeginFrameWantedByImplThread() const { } void SchedulerStateMachine::DidEnterBeginFrame(const BeginFrameArgs& args) { + current_frame_number_++; inside_begin_frame_ = true; last_begin_frame_args_ = args; } void SchedulerStateMachine::DidLeaveBeginFrame() { - current_frame_number_++; inside_begin_frame_ = false; } @@ -422,10 +445,13 @@ void SchedulerStateMachine::FinishCommit() { commit_state_ = COMMIT_STATE_READY_TO_COMMIT; } -void SchedulerStateMachine::BeginFrameAbortedByMainThread() { +void SchedulerStateMachine::BeginFrameAbortedByMainThread(bool did_handle) { DCHECK_EQ(commit_state_, COMMIT_STATE_FRAME_IN_PROGRESS); if (expect_immediate_begin_frame_for_main_thread_) { expect_immediate_begin_frame_for_main_thread_ = false; + } else if (did_handle) { + commit_state_ = COMMIT_STATE_IDLE; + SetPostCommitFlags(); } else { commit_state_ = COMMIT_STATE_IDLE; SetNeedsCommit(); @@ -458,6 +484,7 @@ void SchedulerStateMachine::DidCreateAndInitializeOutputSurface() { // sort themselves out with a commit. needs_redraw_ = false; } + needs_redraw_after_next_commit_ = true; did_create_and_initialize_first_output_surface_ = true; } diff --git a/cc/scheduler/scheduler_state_machine.h b/cc/scheduler/scheduler_state_machine.h index 5a29830d..5c7d5b5 100644 --- a/cc/scheduler/scheduler_state_machine.h +++ b/cc/scheduler/scheduler_state_machine.h @@ -79,7 +79,7 @@ class CC_EXPORT SchedulerStateMachine { // Indicates that the system has entered and left a BeginFrame callback. // The scheduler will not draw more than once in a given BeginFrame - // callback. + // callback nor send more than one BeginFrame message. void DidEnterBeginFrame(const BeginFrameArgs& args); void DidLeaveBeginFrame(); bool inside_begin_frame() const { return inside_begin_frame_; } @@ -120,8 +120,9 @@ class CC_EXPORT SchedulerStateMachine { // Call this only in response to receiving an // ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD from NextAction if the client - // rejects the begin frame message. - void BeginFrameAbortedByMainThread(); + // rejects the begin frame message. If did_handle is false, then + // another commit will be retried soon. + void BeginFrameAbortedByMainThread(bool did_handle); // Request exclusive access to the textures that back single buffered // layers on behalf of the main thread. Upon acquisition, @@ -168,6 +169,7 @@ class CC_EXPORT SchedulerStateMachine { bool HasDrawnThisFrame() const; bool HasAttemptedTreeActivationThisFrame() const; bool HasUpdatedVisibleTilesThisFrame() const; + void SetPostCommitFlags(); const SchedulerSettings settings_; @@ -175,6 +177,7 @@ class CC_EXPORT SchedulerStateMachine { int commit_count_; int current_frame_number_; + int last_frame_number_where_begin_frame_sent_to_main_thread_; int last_frame_number_where_draw_was_called_; int last_frame_number_where_tree_activation_attempted_; int last_frame_number_where_update_visible_tiles_was_called_; @@ -184,6 +187,7 @@ class CC_EXPORT SchedulerStateMachine { bool swap_used_incomplete_tile_; bool needs_forced_redraw_; bool needs_forced_redraw_after_next_commit_; + bool needs_redraw_after_next_commit_; bool needs_commit_; bool needs_forced_commit_; bool expect_immediate_begin_frame_for_main_thread_; diff --git a/cc/scheduler/scheduler_state_machine_unittest.cc b/cc/scheduler/scheduler_state_machine_unittest.cc index 7036459..c7210eb 100644 --- a/cc/scheduler/scheduler_state_machine_unittest.cc +++ b/cc/scheduler/scheduler_state_machine_unittest.cc @@ -718,7 +718,7 @@ TEST(SchedulerStateMachineTest, TestGoesInvisibleBeforeFinishCommit) { // Become invisible and abort the main thread's begin frame. state.SetVisible(false); - state.BeginFrameAbortedByMainThread(); + state.BeginFrameAbortedByMainThread(false); // We should now be back in the idle state as if we didn't start a frame at // all. @@ -728,8 +728,14 @@ TEST(SchedulerStateMachineTest, TestGoesInvisibleBeforeFinishCommit) { // Become visible again. state.SetVisible(true); - // We should be beginning a frame now. + // Although we have aborted on this frame and haven't cancelled the commit + // (i.e. need another), don't send another begin frame yet. EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState()); + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + EXPECT_TRUE(state.NeedsCommit()); + + // Start a new frame. + state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting()); EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD, state.NextAction()); @@ -741,6 +747,52 @@ TEST(SchedulerStateMachineTest, TestGoesInvisibleBeforeFinishCommit) { state.CommitState()); } +TEST(SchedulerStateMachineTest, AbortBeginFrameAndCancelCommit) { + SchedulerSettings default_scheduler_settings; + StateMachine state(default_scheduler_settings); + state.SetCanStart(); + state.UpdateState(state.NextAction()); + state.DidCreateAndInitializeOutputSurface(); + state.SetVisible(true); + state.SetCanDraw(true); + + // Get into a begin frame / commit state. + state.SetNeedsCommit(); + EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD, + state.NextAction()); + state.UpdateState( + SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD); + EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, + state.CommitState()); + EXPECT_FALSE(state.NeedsCommit()); + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + + // Abort the commit, cancelling future commits. + state.BeginFrameAbortedByMainThread(true); + + // Verify that another commit doesn't start on the same frame. + EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState()); + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + EXPECT_FALSE(state.NeedsCommit()); + + // Start a new frame; draw because this is the first frame since output + // surface init'd. + state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting()); + EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction()); + state.DidLeaveBeginFrame(); + + // Verify another commit doesn't start on another frame either. + EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState()); + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + EXPECT_FALSE(state.NeedsCommit()); + + // Verify another commit can start if requested, though. + state.SetNeedsCommit(); + EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState()); + EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD, + state.NextAction()); +} + TEST(SchedulerStateMachineTest, TestFirstContextCreation) { SchedulerSettings default_scheduler_settings; StateMachine state(default_scheduler_settings); @@ -816,15 +868,23 @@ TEST(SchedulerStateMachineTest, // Recreate the context state.DidCreateAndInitializeOutputSurface(); + EXPECT_FALSE(state.RedrawPending()); // When the context is recreated, we should begin a commit EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD, state.NextAction()); state.UpdateState(state.NextAction()); + EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, + state.CommitState()); + state.FinishCommit(); + EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction()); + state.UpdateState(state.NextAction()); + // Finishing the first commit after initializing an output surface should + // automatically cause a redraw. + EXPECT_TRUE(state.RedrawPending()); // Once the context is recreated, whether we draw should be based on // SetCanDraw. - state.SetNeedsRedraw(true); state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting()); EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction()); state.SetCanDraw(false); @@ -1192,7 +1252,7 @@ TEST(SchedulerStateMachineTest, // Become invisible and abort the main thread's begin frame. state.SetVisible(false); - state.BeginFrameAbortedByMainThread(); + state.BeginFrameAbortedByMainThread(false); // Should be back in the idle state, but needing a commit. EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState()); @@ -1295,5 +1355,37 @@ TEST(SchedulerStateMachineTest, ReportIfNotDrawingFromAcquiredTextures) { EXPECT_FALSE(state.DrawSuspendedUntilCommit()); } +TEST(SchedulerStateMachineTest, AcquireTexturesWithAbort) { + SchedulerSettings default_scheduler_settings; + SchedulerStateMachine state(default_scheduler_settings); + state.SetCanStart(); + state.UpdateState(state.NextAction()); + state.DidCreateAndInitializeOutputSurface(); + state.SetCanDraw(true); + state.SetVisible(true); + + state.SetMainThreadNeedsLayerTextures(); + EXPECT_EQ( + SchedulerStateMachine::ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD, + state.NextAction()); + state.UpdateState(state.NextAction()); + EXPECT_TRUE(state.DrawSuspendedUntilCommit()); + + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + + state.SetNeedsCommit(); + EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD, + state.NextAction()); + state.UpdateState(state.NextAction()); + EXPECT_TRUE(state.DrawSuspendedUntilCommit()); + + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + + state.BeginFrameAbortedByMainThread(true); + + EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction()); + EXPECT_FALSE(state.DrawSuspendedUntilCommit()); +} + } // namespace } // namespace cc diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc index 0a3c250..930111c 100644 --- a/cc/scheduler/scheduler_unittest.cc +++ b/cc/scheduler/scheduler_unittest.cc @@ -378,12 +378,17 @@ TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition) { client); client.Reset(); + // Already sent a begin frame on this current frame, so wait. + scheduler->SetVisible(true); + EXPECT_EQ(0, client.num_actions_()); + client.Reset(); + // Regaining visibility with textures acquired by main thread while // compositor is waiting for first draw should result in a request // for a new frame in order to escape a deadlock. - scheduler->SetVisible(true); - EXPECT_SINGLE_ACTION("ScheduledActionSendBeginFrameToMainThread", client); - client.Reset(); + scheduler->BeginFrame(BeginFrameArgs::CreateForTesting()); + EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 0, 2); + EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2); } class SchedulerClientThatsetNeedsDrawInsideDraw : public FakeSchedulerClient { diff --git a/cc/test/fake_content_layer.cc b/cc/test/fake_content_layer.cc index 5dcc512..077831fd 100644 --- a/cc/test/fake_content_layer.cc +++ b/cc/test/fake_content_layer.cc @@ -12,7 +12,8 @@ namespace cc { FakeContentLayer::FakeContentLayer(ContentLayerClient* client) : ContentLayer(client), update_count_(0), - push_properties_count_(0) { + push_properties_count_(0), + always_update_resources_(false) { SetAnchorPoint(gfx::PointF(0.f, 0.f)); SetBounds(gfx::Size(1, 1)); SetIsDrawable(true); @@ -29,7 +30,7 @@ bool FakeContentLayer::Update(ResourceUpdateQueue* queue, const OcclusionTracker* occlusion) { bool updated = ContentLayer::Update(queue, occlusion); update_count_++; - return updated; + return updated || always_update_resources_; } void FakeContentLayer::PushPropertiesTo(LayerImpl* layer) { diff --git a/cc/test/fake_content_layer.h b/cc/test/fake_content_layer.h index 6b7a37d..191a634 100644 --- a/cc/test/fake_content_layer.h +++ b/cc/test/fake_content_layer.h @@ -29,6 +29,10 @@ class FakeContentLayer : public ContentLayer { ResourceUpdateQueue* queue, const OcclusionTracker* occlusion) OVERRIDE; + void set_always_update_resources(bool always_update_resources) { + always_update_resources_ = always_update_resources; + } + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; bool HaveBackingAt(int i, int j); @@ -39,6 +43,7 @@ class FakeContentLayer : public ContentLayer { size_t update_count_; size_t push_properties_count_; + bool always_update_resources_; }; } // namespace cc diff --git a/cc/test/fake_picture_layer.cc b/cc/test/fake_picture_layer.cc index 997197c..69292c3 100644 --- a/cc/test/fake_picture_layer.cc +++ b/cc/test/fake_picture_layer.cc @@ -11,7 +11,8 @@ namespace cc { FakePictureLayer::FakePictureLayer(ContentLayerClient* client) : PictureLayer(client), update_count_(0), - push_properties_count_(0) { + push_properties_count_(0), + always_update_resources_(false) { SetAnchorPoint(gfx::PointF(0.f, 0.f)); SetBounds(gfx::Size(1, 1)); SetIsDrawable(true); @@ -28,7 +29,7 @@ bool FakePictureLayer::Update(ResourceUpdateQueue* queue, const OcclusionTracker* occlusion) { bool updated = PictureLayer::Update(queue, occlusion); update_count_++; - return updated; + return updated || always_update_resources_; } void FakePictureLayer::PushPropertiesTo(LayerImpl* layer) { diff --git a/cc/test/fake_picture_layer.h b/cc/test/fake_picture_layer.h index b5b81de..824ac6c 100644 --- a/cc/test/fake_picture_layer.h +++ b/cc/test/fake_picture_layer.h @@ -26,9 +26,12 @@ class FakePictureLayer : public PictureLayer { size_t push_properties_count() const { return push_properties_count_; } void reset_push_properties_count() { push_properties_count_ = 0; } - virtual bool Update( - ResourceUpdateQueue* queue, - const OcclusionTracker* occlusion) OVERRIDE; + void set_always_update_resources(bool always_update_resources) { + always_update_resources_ = always_update_resources; + } + + virtual bool Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion) OVERRIDE; virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; @@ -38,6 +41,7 @@ class FakePictureLayer : public PictureLayer { size_t update_count_; size_t push_properties_count_; + bool always_update_resources_; }; } // namespace cc diff --git a/cc/test/fake_proxy.h b/cc/test/fake_proxy.h index 76de4b0..8cc3311 100644 --- a/cc/test/fake_proxy.h +++ b/cc/test/fake_proxy.h @@ -29,6 +29,7 @@ class FakeProxy : public Proxy { virtual void CreateAndInitializeOutputSurface() OVERRIDE; virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE; virtual void SetNeedsAnimate() OVERRIDE {} + virtual void SetNeedsUpdateLayers() OVERRIDE {} virtual void SetNeedsCommit() OVERRIDE {} virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE {} virtual void SetDeferCommits(bool defer_commits) OVERRIDE {} diff --git a/cc/test/layer_test_common.h b/cc/test/layer_test_common.h index 94e2f2a..0021398 100644 --- a/cc/test/layer_test_common.h +++ b/cc/test/layer_test_common.h @@ -5,10 +5,18 @@ #ifndef CC_TEST_LAYER_TEST_COMMON_H_ #define CC_TEST_LAYER_TEST_COMMON_H_ -#define EXPECT_SET_NEEDS_COMMIT(expect, code_to_test) do { \ - EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times((expect)); \ - code_to_test; \ - Mock::VerifyAndClearExpectations(layer_tree_host_.get()); \ +#define EXPECT_SET_NEEDS_COMMIT(expect, code_to_test) \ + do { \ + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times((expect)); \ + code_to_test; \ + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); \ + } while (false) + +#define EXPECT_SET_NEEDS_UPDATE(expect, code_to_test) \ + do { \ + EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers()).Times((expect)); \ + code_to_test; \ + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); \ } while (false) namespace gfx { class Rect; } diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc index ed97752..6193d84 100644 --- a/cc/test/layer_tree_test.cc +++ b/cc/test/layer_tree_test.cc @@ -233,9 +233,9 @@ class LayerTreeHostClientForTesting : public LayerTreeHostClient { } virtual ~LayerTreeHostClientForTesting() {} - virtual void WillBeginFrame() OVERRIDE {} + virtual void WillBeginFrame() OVERRIDE { test_hooks_->WillBeginFrame(); } - virtual void DidBeginFrame() OVERRIDE {} + virtual void DidBeginFrame() OVERRIDE { test_hooks_->DidBeginFrame(); } virtual void Animate(double monotonic_time) OVERRIDE { test_hooks_->Animate(base::TimeTicks::FromInternalValue( @@ -263,7 +263,7 @@ class LayerTreeHostClientForTesting : public LayerTreeHostClient { test_hooks_->DidFailToInitializeOutputSurface(); } - virtual void WillCommit() OVERRIDE {} + virtual void WillCommit() OVERRIDE { test_hooks_->WillCommit(); } virtual void DidCommit() OVERRIDE { test_hooks_->DidCommit(); diff --git a/cc/test/layer_tree_test.h b/cc/test/layer_tree_test.h index 556378b..627fff2 100644 --- a/cc/test/layer_tree_test.h +++ b/cc/test/layer_tree_test.h @@ -52,10 +52,13 @@ class TestHooks : public AnimationDelegate { virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale) {} virtual void Animate(base::TimeTicks monotonic_time) {} + virtual void WillBeginFrame() {} + virtual void DidBeginFrame() {} virtual void Layout() {} virtual void DidInitializeOutputSurface(bool succeeded) {} virtual void DidFailToInitializeOutputSurface() {} virtual void DidAddAnimation() {} + virtual void WillCommit() {} virtual void DidCommit() {} virtual void DidCommitAndDrawFrame() {} virtual void DidCompleteSwapBuffers() {} diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc index 0d90c59..308b519 100644 --- a/cc/trees/layer_tree_host.cc +++ b/cc/trees/layer_tree_host.cc @@ -469,6 +469,8 @@ void LayerTreeHost::SetNeedsAnimate() { proxy_->SetNeedsAnimate(); } +void LayerTreeHost::SetNeedsUpdateLayers() { proxy_->SetNeedsUpdateLayers(); } + void LayerTreeHost::SetNeedsCommit() { if (!prepaint_callback_.IsCancelled()) { TRACE_EVENT_INSTANT0("cc", @@ -581,6 +583,11 @@ void LayerTreeHost::SetOverdrawBottomHeight(float overdraw_bottom_height) { SetNeedsCommit(); } +void LayerTreeHost::ApplyPageScaleDeltaFromImplSide(float page_scale_delta) { + DCHECK(CommitRequested()); + page_scale_factor_ *= page_scale_delta; +} + void LayerTreeHost::SetPageScaleFactorAndLimits(float page_scale_factor, float min_page_scale_factor, float max_page_scale_factor) { @@ -964,8 +971,8 @@ void LayerTreeHost::ApplyScrollAndScale(const ScrollAndScaleSet& info) { if (!root_layer_.get()) return; - Layer* root_scroll_layer = FindFirstScrollableLayer(root_layer_.get()); gfx::Vector2d root_scroll_delta; + Layer* root_scroll_layer = FindFirstScrollableLayer(root_layer_.get()); for (size_t i = 0; i < info.scrolls.size(); ++i) { Layer* layer = @@ -980,8 +987,22 @@ void LayerTreeHost::ApplyScrollAndScale(const ScrollAndScaleSet& info) { info.scrolls[i].scroll_delta); } } - if (!root_scroll_delta.IsZero() || info.page_scale_delta != 1.f) + + if (!root_scroll_delta.IsZero() || info.page_scale_delta != 1.f) { + // SetScrollOffsetFromImplSide above could have destroyed the tree, + // so re-get this layer before doing anything to it. + root_scroll_layer = FindFirstScrollableLayer(root_layer_.get()); + + // Preemptively apply the scroll offset and scale delta here before sending + // it to the client. If the client comes back and sets it to the same + // value, then the layer can early out without needing a full commit. + if (root_scroll_layer) { + root_scroll_layer->SetScrollOffsetFromImplSide( + root_scroll_layer->scroll_offset() + root_scroll_delta); + } + ApplyPageScaleDeltaFromImplSide(info.page_scale_delta); client_->ApplyScrollAndScale(root_scroll_delta, info.page_scale_delta); + } } void LayerTreeHost::StartRateLimiter(WebKit::WebGraphicsContext3D* context3d) { diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h index 0fd3abb..6de24c4 100644 --- a/cc/trees/layer_tree_host.h +++ b/cc/trees/layer_tree_host.h @@ -165,6 +165,7 @@ class CC_EXPORT LayerTreeHost : NON_EXPORTED_BASE(public RateLimiterClient) { const RendererCapabilities& GetRendererCapabilities() const; void SetNeedsAnimate(); + virtual void SetNeedsUpdateLayers(); virtual void SetNeedsCommit(); virtual void SetNeedsFullTreeSync(); void SetNeedsRedraw(); @@ -189,6 +190,7 @@ class CC_EXPORT LayerTreeHost : NON_EXPORTED_BASE(public RateLimiterClient) { gfx::Size device_viewport_size() const { return device_viewport_size_; } float overdraw_bottom_height() const { return overdraw_bottom_height_; } + void ApplyPageScaleDeltaFromImplSide(float page_scale_delta); void SetPageScaleFactorAndLimits(float page_scale_factor, float min_page_scale_factor, float max_page_scale_factor); diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc index 629877f..f97eea0 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc @@ -1180,8 +1180,10 @@ void LayerTreeHostImpl::DrawLayers(FrameData* frame, TRACE_EVENT0("cc", "LayerTreeHostImpl::DrawLayers"); DCHECK(CanDraw()); - if (frame->has_no_damage) + if (frame->has_no_damage) { + TRACE_EVENT0("cc", "EarlyOut_NoDamage"); return; + } DCHECK(!frame->render_passes.empty()); diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc index 7c940bb..a6bdd90 100644 --- a/cc/trees/layer_tree_host_unittest.cc +++ b/cc/trees/layer_tree_host_unittest.cc @@ -1812,7 +1812,7 @@ class LayerTreeHostTestContinuousCommit : public LayerTreeHostTest { virtual void DidCommit() OVERRIDE { if (num_draw_layers_ == 2) return; - layer_tree_host()->root_layer()->SetNeedsDisplay(); + layer_tree_host()->SetNeedsCommit(); } virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE { @@ -3099,6 +3099,9 @@ class LayerTreeHostTestDeferredInitialize : public LayerTreeHostTest { virtual void SetupTree() OVERRIDE { layer_ = FakePictureLayer::Create(&client_); + // Force commits to not be aborted so new frames get drawn, otherwise + // the renderer gets deferred initialized but nothing new needs drawing. + layer_->set_always_update_resources(true); layer_tree_host()->SetRootLayer(layer_); LayerTreeHostTest::SetupTree(); } @@ -3395,9 +3398,11 @@ class LayerTreeHostTestLayersPushProperties : public LayerTreeHostTest { ++expected_push_properties_grandchild_; break; case 16: + // SetNeedsDisplay does not always set needs commit (so call it + // explicitly), but is a property change. child_->SetNeedsDisplay(); - // The modified layer needs commit ++expected_push_properties_child_; + layer_tree_host()->SetNeedsCommit(); break; case 17: EndTest(); diff --git a/cc/trees/layer_tree_host_unittest_context.cc b/cc/trees/layer_tree_host_unittest_context.cc index 94f15c2..fc779b0 100644 --- a/cc/trees/layer_tree_host_unittest_context.cc +++ b/cc/trees/layer_tree_host_unittest_context.cc @@ -286,6 +286,7 @@ class LayerTreeHostContextTestLostContextSucceeds virtual void InvalidateAndSetNeedsCommit() { // Cause damage so we try to draw. layer_tree_host()->root_layer()->SetNeedsDisplay(); + layer_tree_host()->SetNeedsCommit(); } bool NextTestCase() { diff --git a/cc/trees/layer_tree_host_unittest_damage.cc b/cc/trees/layer_tree_host_unittest_damage.cc index 0feabf7..69fa34d 100644 --- a/cc/trees/layer_tree_host_unittest_damage.cc +++ b/cc/trees/layer_tree_host_unittest_damage.cc @@ -87,6 +87,7 @@ class LayerTreeHostDamageTestNoDamageDoesNotSwap case 3: // Cause non-visible damage. content_->SetNeedsDisplayRect(gfx::Rect(1990, 1990, 10, 10)); + layer_tree_host()->SetNeedsCommit(); break; } } diff --git a/cc/trees/layer_tree_host_unittest_scroll.cc b/cc/trees/layer_tree_host_unittest_scroll.cc index e531194..5443cd3 100644 --- a/cc/trees/layer_tree_host_unittest_scroll.cc +++ b/cc/trees/layer_tree_host_unittest_scroll.cc @@ -70,10 +70,8 @@ class LayerTreeHostScrollTestScrollSimple : public LayerTreeHostScrollTest { } } - virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale) - OVERRIDE { - gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset(); - layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta); + virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, + float scale) OVERRIDE { num_scrolls_++; } @@ -150,10 +148,8 @@ class LayerTreeHostScrollTestScrollMultipleRedraw } } - virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale) - OVERRIDE { - gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset(); - layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta); + virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, + float scale) OVERRIDE { num_scrolls_++; } @@ -167,6 +163,182 @@ class LayerTreeHostScrollTestScrollMultipleRedraw MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollMultipleRedraw); +class LayerTreeHostScrollTestScrollAbortedCommit + : public LayerTreeHostScrollTest { + public: + LayerTreeHostScrollTestScrollAbortedCommit() + : initial_scroll_(50, 60), + impl_scroll_(-3, 2), + second_main_scroll_(14, -3), + impl_scale_(2.f), + num_will_begin_frames_(0), + num_did_begin_frames_(0), + num_will_commits_(0), + num_did_commits_(0), + num_impl_commits_(0), + num_impl_scrolls_(0) {} + + virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); } + + virtual void SetupTree() OVERRIDE { + LayerTreeHostScrollTest::SetupTree(); + scoped_refptr<Layer> root_scroll_layer = Layer::Create(); + root_scroll_layer->SetScrollable(true); + root_scroll_layer->SetScrollOffset(initial_scroll_); + root_scroll_layer->SetBounds(gfx::Size(200, 200)); + root_scroll_layer->SetMaxScrollOffset(gfx::Vector2d(100, 100)); + root_scroll_layer->SetIsDrawable(true); + layer_tree_host()->root_layer()->AddChild(root_scroll_layer); + + layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 0.01f, 100.f); + } + + virtual void WillBeginFrame() OVERRIDE { + num_will_begin_frames_++; + Layer* root_scroll_layer = layer_tree_host()->root_layer()->children()[0]; + switch (num_will_begin_frames_) { + case 1: + // This will not be aborted because of the initial prop changes. + EXPECT_EQ(0, num_impl_scrolls_); + EXPECT_EQ(0, layer_tree_host()->commit_number()); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), initial_scroll_); + EXPECT_EQ(1.f, layer_tree_host()->page_scale_factor()); + break; + case 2: + // This commit will be aborted, and another commit will be + // initiated from the redraw. + EXPECT_EQ(1, num_impl_scrolls_); + EXPECT_EQ(1, layer_tree_host()->commit_number()); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), + initial_scroll_ + impl_scroll_); + EXPECT_EQ(impl_scale_, layer_tree_host()->page_scale_factor()); + PostSetNeedsRedrawToMainThread(); + break; + case 3: + // This commit will not be aborted because of the scroll change. + EXPECT_EQ(2, num_impl_scrolls_); + EXPECT_EQ(1, layer_tree_host()->commit_number()); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), + initial_scroll_ + impl_scroll_ + impl_scroll_); + EXPECT_EQ(impl_scale_ * impl_scale_, + layer_tree_host()->page_scale_factor()); + root_scroll_layer->SetScrollOffset(root_scroll_layer->scroll_offset() + + second_main_scroll_); + break; + case 4: + // This commit will also be aborted. + EXPECT_EQ(3, num_impl_scrolls_); + EXPECT_EQ(2, layer_tree_host()->commit_number()); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), + initial_scroll_ + impl_scroll_ + impl_scroll_ + + impl_scroll_ + second_main_scroll_); + // End the test by drawing to verify this commit is also aborted. + PostSetNeedsRedrawToMainThread(); + break; + } + } + + virtual void DidBeginFrame() OVERRIDE { num_did_begin_frames_++; } + + virtual void WillCommit() OVERRIDE { num_will_commits_++; } + + virtual void DidCommit() OVERRIDE { num_did_commits_++; } + + virtual void BeginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE { + num_impl_commits_++; + } + + virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE { + LayerImpl* root_scroll_layer = + impl->active_tree()->root_layer()->children()[0]; + + if (impl->active_tree()->source_frame_number() == 0 && + impl->SourceAnimationFrameNumber() == 1) { + // First draw + EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d()); + root_scroll_layer->ScrollBy(impl_scroll_); + EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), initial_scroll_); + + EXPECT_EQ(1.f, impl->active_tree()->page_scale_delta()); + EXPECT_EQ(1.f, impl->active_tree()->total_page_scale_factor()); + impl->active_tree()->SetPageScaleDelta(impl_scale_); + EXPECT_EQ(impl_scale_, impl->active_tree()->page_scale_delta()); + EXPECT_EQ(impl_scale_, impl->active_tree()->total_page_scale_factor()); + + // To simplify the testing flow, don't redraw here, just commit. + impl->SetNeedsCommit(); + } else if (impl->active_tree()->source_frame_number() == 0 && + impl->SourceAnimationFrameNumber() == 2) { + // Test a second draw after an aborted commit. + // The scroll/scale values should be baked into the offset/scale factor + // since the main thread consumed but aborted the begin frame. + EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d()); + root_scroll_layer->ScrollBy(impl_scroll_); + EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), + initial_scroll_ + impl_scroll_); + + EXPECT_EQ(1.f, impl->active_tree()->page_scale_delta()); + EXPECT_EQ(impl_scale_, impl->active_tree()->total_page_scale_factor()); + impl->active_tree()->SetPageScaleDelta(impl_scale_); + EXPECT_EQ(impl_scale_, impl->active_tree()->page_scale_delta()); + EXPECT_EQ(impl_scale_ * impl_scale_, + impl->active_tree()->total_page_scale_factor()); + + impl->SetNeedsCommit(); + } else if (impl->active_tree()->source_frame_number() == 1 && + impl->SourceAnimationFrameNumber() == 3) { + // Third draw after the second full commit. + EXPECT_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d()); + root_scroll_layer->ScrollBy(impl_scroll_); + impl->SetNeedsCommit(); + EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_); + EXPECT_VECTOR_EQ( + root_scroll_layer->scroll_offset(), + initial_scroll_ + impl_scroll_ + impl_scroll_ + second_main_scroll_); + } else if (impl->active_tree()->source_frame_number() == 1 && + impl->SourceAnimationFrameNumber() == 4) { + // Final draw after the second aborted commit. + EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d()); + EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), + initial_scroll_ + impl_scroll_ + impl_scroll_ + + impl_scroll_ + second_main_scroll_); + EndTest(); + } + } + + virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, + float scale) OVERRIDE { + num_impl_scrolls_++; + } + + virtual void AfterTest() OVERRIDE { + EXPECT_EQ(3, num_impl_scrolls_); + // Verify that the embedder sees aborted commits as real commits. + EXPECT_EQ(4, num_will_begin_frames_); + EXPECT_EQ(4, num_did_begin_frames_); + EXPECT_EQ(4, num_will_commits_); + EXPECT_EQ(4, num_did_commits_); + // ...but the compositor thread only sees two real ones. + EXPECT_EQ(2, num_impl_commits_); + } + + private: + gfx::Vector2d initial_scroll_; + gfx::Vector2d impl_scroll_; + gfx::Vector2d second_main_scroll_; + float impl_scale_; + int num_will_begin_frames_; + int num_did_begin_frames_; + int num_will_commits_; + int num_did_commits_; + int num_impl_commits_; + int num_impl_scrolls_; +}; + +MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollAbortedCommit); + class LayerTreeHostScrollTestFractionalScroll : public LayerTreeHostScrollTest { public: LayerTreeHostScrollTestFractionalScroll() : scroll_amount_(1.75, 0) {} @@ -208,12 +380,6 @@ class LayerTreeHostScrollTestFractionalScroll : public LayerTreeHostScrollTest { root->ScrollBy(scroll_amount_); } - virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale) - OVERRIDE { - gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset(); - layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta); - } - virtual void AfterTest() OVERRIDE {} private: @@ -285,14 +451,20 @@ class LayerTreeHostScrollTestCaseWithChild : public LayerTreeHostScrollTest { virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); } + virtual void WillCommit() OVERRIDE { + // Keep the test committing (otherwise the early out for no update + // will stall the test). + if (layer_tree_host()->commit_number() < 2) { + layer_tree_host()->SetNeedsCommit(); + } + } + void DidScroll() { final_scroll_offset_ = expected_scroll_layer_->scroll_offset(); } - virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale) - OVERRIDE { - gfx::Vector2d offset = root_scroll_layer_->scroll_offset(); - root_scroll_layer_->SetScrollOffset(offset + scroll_delta); + virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, + float scale) OVERRIDE { num_scrolls_++; } @@ -642,10 +814,8 @@ class ImplSidePaintingScrollTestSimple : public ImplSidePaintingScrollTest { } } - virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale) - OVERRIDE { - gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset(); - layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta); + virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, + float scale) OVERRIDE { num_scrolls_++; } diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc index d69e7ff..ca768e3 100644 --- a/cc/trees/layer_tree_impl.cc +++ b/cc/trees/layer_tree_impl.cc @@ -231,6 +231,24 @@ void LayerTreeImpl::UpdateMaxScrollOffset() { root_scroll_layer_->SetMaxScrollOffset(gfx::ToFlooredVector2d(max_scroll)); } +static void ApplySentScrollDeltasOn(LayerImpl* layer) { + layer->ApplySentScrollDeltas(); +} + +void LayerTreeImpl::ApplySentScrollAndScaleDeltas() { + DCHECK(IsActiveTree()); + + page_scale_factor_ *= sent_page_scale_delta_; + page_scale_delta_ /= sent_page_scale_delta_; + sent_page_scale_delta_ = 1.f; + + if (!root_layer()) + return; + + LayerTreeHostCommon::CallFunctionForSubtree( + root_layer(), base::Bind(&ApplySentScrollDeltasOn)); +} + void LayerTreeImpl::UpdateSolidColorScrollbars() { DCHECK(settings().solid_color_scrollbars); diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h index aafaf2a..27aa6fa 100644 --- a/cc/trees/layer_tree_impl.h +++ b/cc/trees/layer_tree_impl.h @@ -109,6 +109,7 @@ class CC_EXPORT LayerTreeImpl { void FindRootScrollLayer(); void UpdateMaxScrollOffset(); + void ApplySentScrollAndScaleDeltas(); SkColor background_color() const { return background_color_; } void set_background_color(SkColor color) { background_color_ = color; } diff --git a/cc/trees/proxy.h b/cc/trees/proxy.h index e3b5f82..f4a1ef9 100644 --- a/cc/trees/proxy.h +++ b/cc/trees/proxy.h @@ -66,6 +66,7 @@ class CC_EXPORT Proxy { virtual const RendererCapabilities& GetRendererCapabilities() const = 0; virtual void SetNeedsAnimate() = 0; + virtual void SetNeedsUpdateLayers() = 0; virtual void SetNeedsCommit() = 0; virtual void SetNeedsRedraw(gfx::Rect damage_rect) = 0; diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc index 426b44f..2c4d3a8 100644 --- a/cc/trees/single_thread_proxy.cc +++ b/cc/trees/single_thread_proxy.cc @@ -171,6 +171,11 @@ void SingleThreadProxy::SetNeedsAnimate() { NOTREACHED(); } +void SingleThreadProxy::SetNeedsUpdateLayers() { + DCHECK(Proxy::IsMainThread()); + layer_tree_host_->ScheduleComposite(); +} + void SingleThreadProxy::DoCommit(scoped_ptr<ResourceUpdateQueue> queue) { DCHECK(Proxy::IsMainThread()); // Commit immediately. @@ -203,11 +208,12 @@ void SingleThreadProxy::DoCommit(scoped_ptr<ResourceUpdateQueue> queue) { layer_tree_host_impl_->CommitComplete(); #ifndef NDEBUG - // In the single-threaded case, the scroll deltas should never be + // In the single-threaded case, the scale and scroll deltas should never be // touched on the impl layer tree. scoped_ptr<ScrollAndScaleSet> scroll_info = layer_tree_host_impl_->ProcessScrollDeltas(); DCHECK(!scroll_info->scrolls.size()); + DCHECK_EQ(1.f, scroll_info->page_scale_delta); #endif base::TimeDelta duration = stats_instrumentation->EndRecording(start_time); diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h index b344cc3..9f7eed4 100644 --- a/cc/trees/single_thread_proxy.h +++ b/cc/trees/single_thread_proxy.h @@ -32,6 +32,7 @@ class SingleThreadProxy : public Proxy, LayerTreeHostImplClient { virtual void CreateAndInitializeOutputSurface() OVERRIDE; virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE; virtual void SetNeedsAnimate() OVERRIDE; + virtual void SetNeedsUpdateLayers() OVERRIDE; virtual void SetNeedsCommit() OVERRIDE; virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE; virtual void SetDeferCommits(bool defer_commits) OVERRIDE; diff --git a/cc/trees/thread_proxy.cc b/cc/trees/thread_proxy.cc index 2bf8247..775330e 100644 --- a/cc/trees/thread_proxy.cc +++ b/cc/trees/thread_proxy.cc @@ -90,6 +90,7 @@ ThreadProxy::ThreadProxy( using_synchronous_renderer_compositor_( layer_tree_host->settings().using_synchronous_renderer_compositor), inside_draw_(false), + can_cancel_commit_(true), defer_commits_(false), renew_tree_priority_on_impl_thread_pending_(false), draw_duration_history_(kDurationHistorySize), @@ -128,10 +129,15 @@ bool ThreadProxy::CompositeAndReadback(void* pixels, gfx::Rect rect) { &begin_frame_sent_to_main_thread_completion)); begin_frame_sent_to_main_thread_completion.Wait(); } + in_composite_and_readback_ = true; BeginFrameOnMainThread(scoped_ptr<BeginFrameAndCommitState>()); in_composite_and_readback_ = false; + // Composite and readback requires a second commit to undo any changes + // that it made. + can_cancel_commit_ = false; + // Perform a synchronous readback. ReadbackRequest request; request.rect = rect; @@ -301,6 +307,17 @@ void ThreadProxy::OnOutputSurfaceInitializeAttempted( } } +void ThreadProxy::SendCommitRequestToImplThreadIfNeeded() { + DCHECK(IsMainThread()); + if (commit_request_sent_to_impl_thread_) + return; + commit_request_sent_to_impl_thread_ = true; + Proxy::ImplThreadTaskRunner()->PostTask( + FROM_HERE, + base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread, + impl_thread_weak_ptr_)); +} + const RendererCapabilities& ThreadProxy::GetRendererCapabilities() const { DCHECK(IsMainThread()); DCHECK(!layer_tree_host_->output_surface_lost()); @@ -314,30 +331,26 @@ void ThreadProxy::SetNeedsAnimate() { TRACE_EVENT0("cc", "ThreadProxy::SetNeedsAnimate"); animate_requested_ = true; + can_cancel_commit_ = false; + SendCommitRequestToImplThreadIfNeeded(); +} - if (commit_request_sent_to_impl_thread_) - return; - commit_request_sent_to_impl_thread_ = true; - Proxy::ImplThreadTaskRunner()->PostTask( - FROM_HERE, - base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread, - impl_thread_weak_ptr_)); +void ThreadProxy::SetNeedsUpdateLayers() { + DCHECK(IsMainThread()); + SendCommitRequestToImplThreadIfNeeded(); } void ThreadProxy::SetNeedsCommit() { DCHECK(IsMainThread()); + // Unconditionally set here to handle SetNeedsCommit calls during a commit. + can_cancel_commit_ = false; + if (commit_requested_) return; TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommit"); commit_requested_ = true; - if (commit_request_sent_to_impl_thread_) - return; - commit_request_sent_to_impl_thread_ = true; - Proxy::ImplThreadTaskRunner()->PostTask( - FROM_HERE, - base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread, - impl_thread_weak_ptr_)); + SendCommitRequestToImplThreadIfNeeded(); } void ThreadProxy::DidLoseOutputSurfaceOnImplThread() { @@ -680,21 +693,23 @@ void ThreadProxy::BeginFrameOnMainThread( // callbacks will trigger another frame. animate_requested_ = false; - if (begin_frame_state) - layer_tree_host_->ApplyScrollAndScale(*begin_frame_state->scroll_info); - if (!in_composite_and_readback_ && !layer_tree_host_->visible()) { commit_requested_ = false; commit_request_sent_to_impl_thread_ = false; TRACE_EVENT0("cc", "EarlyOut_NotVisible"); + bool did_handle = false; Proxy::ImplThreadTaskRunner()->PostTask( FROM_HERE, base::Bind(&ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread, - impl_thread_weak_ptr_)); + impl_thread_weak_ptr_, + did_handle)); return; } + if (begin_frame_state) + layer_tree_host_->ApplyScrollAndScale(*begin_frame_state->scroll_info); + layer_tree_host_->WillBeginFrame(); if (begin_frame_state) { @@ -718,24 +733,44 @@ void ThreadProxy::BeginFrameOnMainThread( // UpdateLayers. commit_requested_ = false; commit_request_sent_to_impl_thread_ = false; + bool can_cancel_this_commit = + can_cancel_commit_ && !in_composite_and_readback_; + can_cancel_commit_ = true; scoped_ptr<ResourceUpdateQueue> queue = make_scoped_ptr(new ResourceUpdateQueue); - layer_tree_host_->UpdateLayers( + bool updated = layer_tree_host_->UpdateLayers( queue.get(), - begin_frame_state ? - begin_frame_state->memory_allocation_limit_bytes : 0u); + begin_frame_state ? begin_frame_state->memory_allocation_limit_bytes + : 0u); // Once single buffered layers are committed, they cannot be modified until // they are drawn by the impl thread. textures_acquired_ = false; layer_tree_host_->WillCommit(); - // Before applying scrolls and calling animate, we set animate_requested_ to - // false. If it is true now, it means SetNeedAnimate was called again, but - // during a state when commit_request_sent_to_impl_thread_ = true. We need to - // force that call to happen again now so that the commit request is sent to - // the impl thread. + + if (!updated && can_cancel_this_commit) { + TRACE_EVENT0("cc", "EarlyOut_NoUpdates"); + bool did_handle = true; + Proxy::ImplThreadTaskRunner()->PostTask( + FROM_HERE, + base::Bind(&ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread, + impl_thread_weak_ptr_, + did_handle)); + + // Although the commit is internally aborted, this is because it has been + // detected to be a no-op. From the perspective of an embedder, this commit + // went through, and input should no longer be throttled, etc. + layer_tree_host_->CommitComplete(); + layer_tree_host_->DidBeginFrame(); + return; + } + + // Before calling animate, we set animate_requested_ to false. If it is true + // now, it means SetNeedAnimate was called again, but during a state when + // commit_request_sent_to_impl_thread_ = true. We need to force that call to + // happen again now so that the commit request is sent to the impl thread. if (animate_requested_) { // Forces SetNeedsAnimate to consider posting a commit task. animate_requested_ = false; @@ -831,13 +866,18 @@ void ThreadProxy::StartCommitOnImplThread( scheduler_on_impl_thread_->AnticipatedDrawTime()); } -void ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread() { +void ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread(bool did_handle) { TRACE_EVENT0("cc", "ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread"); DCHECK(IsImplThread()); DCHECK(scheduler_on_impl_thread_); DCHECK(scheduler_on_impl_thread_->CommitPending()); - scheduler_on_impl_thread_->BeginFrameAbortedByMainThread(); + // If the begin frame data was handled, then scroll and scale set was applied + // by the main thread, so the active tree needs to be updated as if these sent + // values were applied and committed. + if (did_handle) + layer_tree_host_impl_->active_tree()->ApplySentScrollAndScaleDeltas(); + scheduler_on_impl_thread_->BeginFrameAbortedByMainThread(did_handle); } void ThreadProxy::ScheduledActionCommit() { @@ -905,7 +945,8 @@ void ThreadProxy::ScheduledActionBeginOutputSurfaceCreation() { ScheduledActionDrawAndSwapResult ThreadProxy::ScheduledActionDrawAndSwapInternal(bool forced_draw) { - TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionDrawAndSwap"); + TRACE_EVENT1( + "cc", "ThreadProxy::ScheduledActionDrawAndSwap", "forced", forced_draw); ScheduledActionDrawAndSwapResult result; result.did_draw = false; @@ -1066,6 +1107,7 @@ void ThreadProxy::AcquireLayerTextures() { completion.Wait(); textures_acquired_ = true; + can_cancel_commit_ = false; } void ThreadProxy::AcquireLayerTexturesForMainThreadOnImplThread( diff --git a/cc/trees/thread_proxy.h b/cc/trees/thread_proxy.h index eb2ef7e..c9d5b47 100644 --- a/cc/trees/thread_proxy.h +++ b/cc/trees/thread_proxy.h @@ -49,6 +49,7 @@ class ThreadProxy : public Proxy, virtual void CreateAndInitializeOutputSurface() OVERRIDE; virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE; virtual void SetNeedsAnimate() OVERRIDE; + virtual void SetNeedsUpdateLayers() OVERRIDE; virtual void SetNeedsCommit() OVERRIDE; virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE; virtual void SetDeferCommits(bool defer_commits) OVERRIDE; @@ -135,6 +136,7 @@ class ThreadProxy : public Proxy, void OnOutputSurfaceInitializeAttempted( bool success, const RendererCapabilities& capabilities); + void SendCommitRequestToImplThreadIfNeeded(); // Called on impl thread. struct ReadbackRequest; @@ -146,7 +148,7 @@ class ThreadProxy : public Proxy, CompletionEvent* completion, ResourceUpdateQueue* queue, scoped_refptr<cc::ContextProvider> offscreen_context_provider); - void BeginFrameAbortedByMainThreadOnImplThread(); + void BeginFrameAbortedByMainThreadOnImplThread(bool did_handle); void RequestReadbackOnImplThread(ReadbackRequest* request); void FinishAllRenderingOnImplThread(CompletionEvent* completion); void InitializeImplOnImplThread(CompletionEvent* completion); @@ -241,6 +243,8 @@ class ThreadProxy : public Proxy, bool inside_draw_; + bool can_cancel_commit_; + bool defer_commits_; scoped_ptr<BeginFrameAndCommitState> pending_deferred_commit_; |