Chromium Code Reviews| Index: cc/scheduler/scheduler_state_machine.cc |
| diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc |
| index c512987bd23f501abdaf1fceab783945f8dc1ae4..9a0caf506cb0948de57ac94f3e78cc221771074a 100644 |
| --- a/cc/scheduler/scheduler_state_machine.cc |
| +++ b/cc/scheduler/scheduler_state_machine.cc |
| @@ -26,10 +26,11 @@ SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) |
| last_frame_number_swap_performed_(-1), |
| last_frame_number_swap_requested_(-1), |
| last_frame_number_begin_main_frame_sent_(-1), |
| + last_frame_number_invalidate_output_surface_performed_(-1), |
| animate_funnel_(false), |
| - perform_swap_funnel_(false), |
| request_swap_funnel_(false), |
| send_begin_main_frame_funnel_(false), |
| + invalidate_output_surface_funnel_(false), |
| prepare_tiles_funnel_(0), |
| consecutive_checkerboard_animations_(0), |
| max_pending_swaps_(1), |
| @@ -38,7 +39,6 @@ SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) |
| needs_animate_(false), |
| needs_prepare_tiles_(false), |
| needs_commit_(false), |
| - inside_poll_for_anticipated_draw_triggers_(false), |
| visible_(false), |
| can_start_(false), |
| can_draw_(false), |
| @@ -52,7 +52,9 @@ SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) |
| continuous_painting_(false), |
| children_need_begin_frames_(false), |
| defer_commits_(false), |
| - last_commit_had_no_updates_(false) { |
| + last_commit_had_no_updates_(false), |
| + did_request_swap_in_last_frame_(false), |
| + did_perform_swap_in_last_draw_(false) { |
| } |
| const char* SchedulerStateMachine::OutputSurfaceStateToString( |
| @@ -89,6 +91,22 @@ const char* SchedulerStateMachine::BeginImplFrameStateToString( |
| return "???"; |
| } |
| +const char* SchedulerStateMachine::BeginImplFrameDeadlineModeToString( |
| + BeginImplFrameDeadlineMode mode) { |
| + switch (mode) { |
| + case BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE: |
| + return "BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE"; |
| + case BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE: |
| + return "BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE"; |
| + case BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR: |
| + return "BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR"; |
| + case BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE: |
| + return "BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE"; |
| + } |
| + NOTREACHED(); |
| + return "???"; |
| +} |
| + |
| const char* SchedulerStateMachine::CommitStateToString(CommitState state) { |
| switch (state) { |
| case COMMIT_STATE_IDLE: |
| @@ -146,6 +164,8 @@ const char* SchedulerStateMachine::ActionToString(Action action) { |
| return "ACTION_BEGIN_OUTPUT_SURFACE_CREATION"; |
| case ACTION_PREPARE_TILES: |
| return "ACTION_PREPARE_TILES"; |
| + case ACTION_INVALIDATE_OUTPUT_SURFACE: |
| + return "ACTION_INVALIDATE_OUTPUT_SURFACE"; |
| } |
| NOTREACHED(); |
| return "???"; |
| @@ -184,11 +204,12 @@ void SchedulerStateMachine::AsValueInto( |
| state->SetInteger("last_frame_number_begin_main_frame_sent", |
| last_frame_number_begin_main_frame_sent_); |
| state->SetBoolean("funnel: animate_funnel", animate_funnel_); |
| - state->SetBoolean("funnel: perform_swap_funnel", perform_swap_funnel_); |
| state->SetBoolean("funnel: request_swap_funnel", request_swap_funnel_); |
| state->SetBoolean("funnel: send_begin_main_frame_funnel", |
| send_begin_main_frame_funnel_); |
| state->SetInteger("funnel: prepare_tiles_funnel", prepare_tiles_funnel_); |
| + state->SetBoolean("funnel: invalidate_output_surface_funnel", |
| + invalidate_output_surface_funnel_); |
| state->SetInteger("consecutive_checkerboard_animations", |
| consecutive_checkerboard_animations_); |
| state->SetInteger("max_pending_swaps_", max_pending_swaps_); |
| @@ -218,26 +239,14 @@ void SchedulerStateMachine::AsValueInto( |
| state->SetBoolean("continuous_painting", continuous_painting_); |
| state->SetBoolean("children_need_begin_frames", children_need_begin_frames_); |
| state->SetBoolean("defer_commits", defer_commits_); |
| + state->SetBoolean("last_commit_had_no_updates", last_commit_had_no_updates_); |
| + state->SetBoolean("did_request_swap_in_last_frame", |
| + did_request_swap_in_last_frame_); |
| + state->SetBoolean("did_perform_swap_in_last_draw", |
| + did_perform_swap_in_last_draw_); |
| state->EndDictionary(); |
| } |
| -void SchedulerStateMachine::AdvanceCurrentFrameNumber() { |
| - current_frame_number_++; |
| - |
| - animate_funnel_ = false; |
| - perform_swap_funnel_ = false; |
| - request_swap_funnel_ = false; |
| - send_begin_main_frame_funnel_ = false; |
| - |
| - // "Drain" the PrepareTiles funnel. |
| - if (prepare_tiles_funnel_ > 0) |
| - prepare_tiles_funnel_--; |
| - |
| - skip_begin_main_frame_to_reduce_latency_ = |
| - skip_next_begin_main_frame_to_reduce_latency_; |
| - skip_next_begin_main_frame_to_reduce_latency_ = false; |
| -} |
| - |
| bool SchedulerStateMachine::PendingDrawsShouldBeAborted() const { |
| // These are all the cases where we normally cannot or do not want to draw |
| // but, if needs_redraw_ is true and we do not draw to make forward progress, |
| @@ -427,7 +436,7 @@ bool SchedulerStateMachine::ShouldSendBeginMainFrame() const { |
| // TODO(brianderson): Remove this restriction to improve throughput. |
| bool just_swapped_in_deadline = |
| begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE && |
| - perform_swap_funnel_; |
| + did_perform_swap_in_last_draw_; |
| if (pending_swaps_ >= max_pending_swaps_ && !just_swapped_in_deadline) |
| return false; |
| @@ -462,14 +471,29 @@ bool SchedulerStateMachine::ShouldPrepareTiles() const { |
| return false; |
| // Limiting to once per-frame is not enough, since we only want to |
| - // prepare tiles _after_ draws. Polling for draw triggers and |
| - // begin-frame are mutually exclusive, so we limit to these two cases. |
| - if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE && |
| - !inside_poll_for_anticipated_draw_triggers_) |
| + // prepare tiles _after_ draws. |
| + if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) |
| return false; |
| + |
| return needs_prepare_tiles_; |
| } |
| +bool SchedulerStateMachine::ShouldInvalidateOutputSurface() const { |
| + // Do not invalidate too many times in a frame. |
| + if (invalidate_output_surface_funnel_) |
| + return false; |
| + |
| + // Only the synchronous compositor requires invalidations. |
| + if (!settings_.using_synchronous_renderer_compositor) |
| + return false; |
| + |
| + // Invalidations are only performed inside a BeginFrame. |
| + if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING) |
| + return false; |
| + |
| + return needs_redraw_ || needs_prepare_tiles_; |
|
brianderson
2015/04/01 00:23:38
Can you add a TODO explaining what would need to h
sunnyps
2015/04/01 19:53:01
Done.
|
| +} |
| + |
| SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { |
| if (ShouldActivatePendingTree()) |
| return ACTION_ACTIVATE_SYNC_TREE; |
| @@ -489,6 +513,8 @@ SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { |
| return ACTION_PREPARE_TILES; |
| if (ShouldSendBeginMainFrame()) |
| return ACTION_SEND_BEGIN_MAIN_FRAME; |
| + if (ShouldInvalidateOutputSurface()) |
| + return ACTION_INVALIDATE_OUTPUT_SURFACE; |
| if (ShouldBeginOutputSurfaceCreation()) |
| return ACTION_BEGIN_OUTPUT_SURFACE_CREATION; |
| return ACTION_NONE; |
| @@ -504,25 +530,11 @@ void SchedulerStateMachine::UpdateState(Action action) { |
| return; |
| case ACTION_ANIMATE: |
| - DCHECK(!animate_funnel_); |
| - last_frame_number_animate_performed_ = current_frame_number_; |
| - animate_funnel_ = true; |
| - needs_animate_ = false; |
| - // TODO(skyostil): Instead of assuming this, require the client to tell |
| - // us. |
| - SetNeedsRedraw(); |
| + UpdateStateOnAnimate(); |
| return; |
| case ACTION_SEND_BEGIN_MAIN_FRAME: |
| - DCHECK(!has_pending_tree_ || |
| - settings_.main_frame_before_activation_enabled); |
| - DCHECK(visible_); |
| - DCHECK(!send_begin_main_frame_funnel_); |
| - commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT; |
| - needs_commit_ = false; |
| - send_begin_main_frame_funnel_ = true; |
| - last_frame_number_begin_main_frame_sent_ = |
| - current_frame_number_; |
| + UpdateStateOnSendBeginMainFrame(); |
| return; |
| case ACTION_COMMIT: { |
| @@ -545,26 +557,42 @@ void SchedulerStateMachine::UpdateState(Action action) { |
| } |
| case ACTION_BEGIN_OUTPUT_SURFACE_CREATION: |
| - DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_LOST); |
| - output_surface_state_ = OUTPUT_SURFACE_CREATING; |
| - |
| - // The following DCHECKs make sure we are in the proper quiescent state. |
| - // The pipeline should be flushed entirely before we start output |
| - // surface creation to avoid complicated corner cases. |
| - DCHECK_EQ(commit_state_, COMMIT_STATE_IDLE); |
| - DCHECK(!has_pending_tree_); |
| - DCHECK(!active_tree_needs_first_draw_); |
| + UpdateStateOnBeginOutputSurfaceCreation(); |
| return; |
| case ACTION_PREPARE_TILES: |
| UpdateStateOnPrepareTiles(); |
| return; |
| + |
| + case ACTION_INVALIDATE_OUTPUT_SURFACE: |
| + UpdateStateOnInvalidateOutputSurface(); |
| + return; |
| } |
| } |
| +void SchedulerStateMachine::UpdateStateOnAnimate() { |
| + DCHECK(!animate_funnel_); |
| + last_frame_number_animate_performed_ = current_frame_number_; |
| + animate_funnel_ = true; |
| + needs_animate_ = false; |
| + // TODO(skyostil): Instead of assuming this, require the client to tell us. |
| + SetNeedsRedraw(); |
| +} |
| + |
| +void SchedulerStateMachine::UpdateStateOnSendBeginMainFrame() { |
| + DCHECK(!has_pending_tree_ || settings_.main_frame_before_activation_enabled); |
| + DCHECK(visible_); |
| + DCHECK(!send_begin_main_frame_funnel_); |
| + commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT; |
| + needs_commit_ = false; |
| + send_begin_main_frame_funnel_ = true; |
| + last_frame_number_begin_main_frame_sent_ = current_frame_number_; |
| +} |
| + |
| void SchedulerStateMachine::UpdateStateOnCommit(bool commit_has_no_updates) { |
| commit_count_++; |
| + // Animate after commit even if we've already animated. |
| if (!commit_has_no_updates) |
| animate_funnel_ = false; |
| @@ -649,6 +677,7 @@ void SchedulerStateMachine::UpdateStateOnDraw(bool did_request_swap) { |
| if (did_request_swap) { |
| DCHECK(!request_swap_funnel_); |
| request_swap_funnel_ = true; |
| + did_request_swap_in_last_frame_ = true; |
| last_frame_number_swap_requested_ = current_frame_number_; |
| } |
| } |
| @@ -657,6 +686,25 @@ void SchedulerStateMachine::UpdateStateOnPrepareTiles() { |
| needs_prepare_tiles_ = false; |
| } |
| +void SchedulerStateMachine::UpdateStateOnBeginOutputSurfaceCreation() { |
| + DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_LOST); |
| + output_surface_state_ = OUTPUT_SURFACE_CREATING; |
| + |
| + // The following DCHECKs make sure we are in the proper quiescent state. |
| + // The pipeline should be flushed entirely before we start output |
| + // surface creation to avoid complicated corner cases. |
| + DCHECK_EQ(commit_state_, COMMIT_STATE_IDLE); |
| + DCHECK(!has_pending_tree_); |
| + DCHECK(!active_tree_needs_first_draw_); |
| +} |
| + |
| +void SchedulerStateMachine::UpdateStateOnInvalidateOutputSurface() { |
| + DCHECK(!invalidate_output_surface_funnel_); |
| + invalidate_output_surface_funnel_ = true; |
| + last_frame_number_invalidate_output_surface_performed_ = |
| + current_frame_number_; |
| +} |
| + |
| void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency() { |
| TRACE_EVENT_INSTANT0("cc", |
| "Scheduler: SkipNextBeginMainFrameToReduceLatency", |
| @@ -665,10 +713,7 @@ void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency() { |
| } |
| bool SchedulerStateMachine::BeginFrameNeededForChildren() const { |
| - if (HasInitializedOutputSurface()) |
| - return children_need_begin_frames_; |
| - |
| - return false; |
| + return children_need_begin_frames_; |
| } |
| bool SchedulerStateMachine::BeginFrameNeeded() const { |
| @@ -676,44 +721,8 @@ bool SchedulerStateMachine::BeginFrameNeeded() const { |
| // TODO(brianderson): Support output surface creation inside a BeginFrame. |
| if (!HasInitializedOutputSurface()) |
| return false; |
| - |
| - if (SupportsProactiveBeginFrame()) { |
| - return (BeginFrameNeededToAnimateOrDraw() || |
| - BeginFrameNeededForChildren() || |
| - ProactiveBeginFrameWanted()); |
| - } |
| - |
| - // Proactive BeginFrames are bad for the synchronous compositor because we |
| - // have to draw when we get the BeginFrame and could end up drawing many |
| - // duplicate frames if our new frame isn't ready in time. |
| - // To poll for state with the synchronous compositor without having to draw, |
| - // we rely on ShouldPollForAnticipatedDrawTriggers instead. |
| - // Synchronous compositor doesn't have a browser. |
| - DCHECK(!children_need_begin_frames_); |
| - return BeginFrameNeededToAnimateOrDraw(); |
| -} |
| - |
| -bool SchedulerStateMachine::ShouldPollForAnticipatedDrawTriggers() const { |
| - // ShouldPollForAnticipatedDrawTriggers is what we use in place of |
| - // ProactiveBeginFrameWanted when we are using the synchronous |
| - // compositor. |
| - if (!SupportsProactiveBeginFrame()) { |
| - return !BeginFrameNeededToAnimateOrDraw() && ProactiveBeginFrameWanted(); |
| - } |
| - |
| - // Non synchronous compositors should rely on |
| - // ProactiveBeginFrameWanted to poll for state instead. |
| - return false; |
| -} |
| - |
| -// Note: If SupportsProactiveBeginFrame is false, the scheduler should poll |
| -// for changes in it's draw state so it can request a BeginFrame when it's |
| -// actually ready. |
| -bool SchedulerStateMachine::SupportsProactiveBeginFrame() const { |
| - // It is undesirable to proactively request BeginFrames if we are |
| - // using a synchronous compositor because we *must* draw for every |
| - // BeginFrame, which could cause duplicate draws. |
| - return !settings_.using_synchronous_renderer_compositor; |
| + return (BeginFrameNeededToAnimateOrDraw() || BeginFrameNeededForChildren() || |
| + ProactiveBeginFrameWanted()); |
| } |
| void SchedulerStateMachine::SetChildrenNeedBeginFrames( |
| @@ -765,7 +774,7 @@ bool SchedulerStateMachine::ProactiveBeginFrameWanted() const { |
| // SetNeedsBeginFrame requests, which may propagate to the BeginImplFrame |
| // provider and get sampled at an inopportune time, delaying the next |
| // BeginImplFrame. |
| - if (request_swap_funnel_) |
| + if (did_request_swap_in_last_frame_) |
| return true; |
| // If the last commit was aborted because of early out (no updates), we should |
| @@ -777,35 +786,49 @@ bool SchedulerStateMachine::ProactiveBeginFrameWanted() const { |
| } |
| void SchedulerStateMachine::OnBeginImplFrame() { |
| - AdvanceCurrentFrameNumber(); |
| - DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_IDLE) |
| - << AsValue()->ToString(); |
| begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING; |
| + current_frame_number_++; |
| + |
| last_commit_had_no_updates_ = false; |
| + did_request_swap_in_last_frame_ = false; |
| + |
| + // Clear funnels for any actions we perform during the frame. |
| + animate_funnel_ = false; |
| + send_begin_main_frame_funnel_ = false; |
| + invalidate_output_surface_funnel_ = false; |
| + |
| + // "Drain" the PrepareTiles funnel. |
| + if (prepare_tiles_funnel_ > 0) |
| + prepare_tiles_funnel_--; |
| + |
| + skip_begin_main_frame_to_reduce_latency_ = |
| + skip_next_begin_main_frame_to_reduce_latency_; |
| + skip_next_begin_main_frame_to_reduce_latency_ = false; |
| } |
| void SchedulerStateMachine::OnBeginImplFrameDeadlinePending() { |
| - DCHECK_EQ(begin_impl_frame_state_, |
| - BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING) |
| - << AsValue()->ToString(); |
| begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME; |
| } |
| void SchedulerStateMachine::OnBeginImplFrameDeadline() { |
| - DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME) |
| - << AsValue()->ToString(); |
| begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE; |
| + |
| + did_perform_swap_in_last_draw_ = false; |
| + |
| + // Clear funnels for any actions we perform during the deadline. |
| + request_swap_funnel_ = false; |
| } |
| void SchedulerStateMachine::OnBeginImplFrameIdle() { |
| - DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) |
| - << AsValue()->ToString(); |
| begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_IDLE; |
| } |
| SchedulerStateMachine::BeginImplFrameDeadlineMode |
| SchedulerStateMachine::CurrentBeginImplFrameDeadlineMode() const { |
| - if (ShouldTriggerBeginImplFrameDeadlineImmediately()) { |
| + if (settings_.using_synchronous_renderer_compositor) { |
| + // No deadline for synchronous compositor. |
| + return BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE; |
| + } else if (ShouldTriggerBeginImplFrameDeadlineImmediately()) { |
| return BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE; |
| } else if (needs_redraw_ && pending_swaps_ < max_pending_swaps_) { |
| // We have an animation or fast input path on the impl thread that wants |
| @@ -823,7 +846,6 @@ SchedulerStateMachine::CurrentBeginImplFrameDeadlineMode() const { |
| bool SchedulerStateMachine::ShouldTriggerBeginImplFrameDeadlineImmediately() |
| const { |
| // TODO(brianderson): This should take into account multiple commit sources. |
| - |
| if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME) |
| return false; |
| @@ -889,7 +911,7 @@ bool SchedulerStateMachine::MainThreadIsInHighLatencyMode() const { |
| // Even if there's a new active tree to draw at the deadline or we've just |
| // swapped it, it may have been triggered by a previous BeginImplFrame, in |
| // which case the main thread is in a high latency mode. |
| - return (active_tree_needs_first_draw_ || perform_swap_funnel_) && |
| + return (active_tree_needs_first_draw_ || did_perform_swap_in_last_draw_) && |
| !send_begin_main_frame_funnel_; |
| } |
| @@ -898,15 +920,6 @@ bool SchedulerStateMachine::MainThreadIsInHighLatencyMode() const { |
| return active_tree_needs_first_draw_; |
| } |
| -void SchedulerStateMachine::DidEnterPollForAnticipatedDrawTriggers() { |
| - AdvanceCurrentFrameNumber(); |
| - inside_poll_for_anticipated_draw_triggers_ = true; |
| -} |
| - |
| -void SchedulerStateMachine::DidLeavePollForAnticipatedDrawTriggers() { |
| - inside_poll_for_anticipated_draw_triggers_ = false; |
| -} |
| - |
| void SchedulerStateMachine::SetVisible(bool visible) { visible_ = visible; } |
| void SchedulerStateMachine::SetCanDraw(bool can_draw) { can_draw_ = can_draw; } |
| @@ -931,9 +944,8 @@ void SchedulerStateMachine::SetMaxSwapsPending(int max) { |
| void SchedulerStateMachine::DidSwapBuffers() { |
| pending_swaps_++; |
| DCHECK_LE(pending_swaps_, max_pending_swaps_); |
| - DCHECK(!perform_swap_funnel_); |
| - perform_swap_funnel_ = true; |
| + did_perform_swap_in_last_draw_ = true; |
| last_frame_number_swap_performed_ = current_frame_number_; |
| } |