// Copyright 2011 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 "cc/scheduler/scheduler_state_machine.h" #include "base/format_macros.h" #include "base/logging.h" #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event_argument.h" #include "base/values.h" namespace cc { namespace { // Surfaces and CompositorTimingHistory don't support more than 1 pending swap. const int kMaxPendingSwaps = 1; } // namespace SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) : settings_(settings), output_surface_state_(OUTPUT_SURFACE_NONE), begin_impl_frame_state_(BEGIN_IMPL_FRAME_STATE_IDLE), begin_main_frame_state_(BEGIN_MAIN_FRAME_STATE_IDLE), forced_redraw_state_(FORCED_REDRAW_STATE_IDLE), commit_count_(0), current_frame_number_(0), last_frame_number_swap_performed_(-1), last_frame_number_draw_performed_(-1), last_frame_number_begin_main_frame_sent_(-1), last_frame_number_invalidate_output_surface_performed_(-1), draw_funnel_(false), send_begin_main_frame_funnel_(true), invalidate_output_surface_funnel_(false), prepare_tiles_funnel_(0), consecutive_checkerboard_animations_(0), pending_swaps_(0), swaps_with_current_output_surface_(0), needs_redraw_(false), needs_prepare_tiles_(false), needs_begin_main_frame_(false), needs_one_begin_impl_frame_(false), visible_(false), begin_frame_source_paused_(false), resourceless_draw_(false), can_draw_(false), has_pending_tree_(false), pending_tree_is_ready_for_activation_(false), active_tree_needs_first_draw_(false), did_create_and_initialize_first_output_surface_(false), tree_priority_(NEW_CONTENT_TAKES_PRIORITY), scroll_handler_state_( ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER), critical_begin_main_frame_to_activate_is_fast_(true), main_thread_missed_last_deadline_(false), skip_next_begin_main_frame_to_reduce_latency_(false), children_need_begin_frames_(false), defer_commits_(false), video_needs_begin_frames_(false), last_commit_had_no_updates_(false), wait_for_ready_to_draw_(false), did_draw_in_last_frame_(false), did_swap_in_last_frame_(false) {} const char* SchedulerStateMachine::OutputSurfaceStateToString( OutputSurfaceState state) { switch (state) { case OUTPUT_SURFACE_NONE: return "OUTPUT_SURFACE_NONE"; case OUTPUT_SURFACE_ACTIVE: return "OUTPUT_SURFACE_ACTIVE"; case OUTPUT_SURFACE_CREATING: return "OUTPUT_SURFACE_CREATING"; case OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT: return "OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT"; case OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION: return "OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION"; } NOTREACHED(); return "???"; } const char* SchedulerStateMachine::BeginImplFrameStateToString( BeginImplFrameState state) { switch (state) { case BEGIN_IMPL_FRAME_STATE_IDLE: return "BEGIN_IMPL_FRAME_STATE_IDLE"; case BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING: return "BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING"; case BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME: return "BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME"; case BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE: return "BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE"; } NOTREACHED(); 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"; case BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED_ON_READY_TO_DRAW: return "BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED_ON_READY_TO_DRAW"; } NOTREACHED(); return "???"; } const char* SchedulerStateMachine::BeginMainFrameStateToString( BeginMainFrameState state) { switch (state) { case BEGIN_MAIN_FRAME_STATE_IDLE: return "BEGIN_MAIN_FRAME_STATE_IDLE"; case BEGIN_MAIN_FRAME_STATE_SENT: return "BEGIN_MAIN_FRAME_STATE_SENT"; case BEGIN_MAIN_FRAME_STATE_STARTED: return "BEGIN_MAIN_FRAME_STATE_STARTED"; case BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT: return "BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT"; case BEGIN_MAIN_FRAME_STATE_WAITING_FOR_ACTIVATION: return "BEGIN_MAIN_FRAME_STATE_WAITING_FOR_ACTIVATION"; case BEGIN_MAIN_FRAME_STATE_WAITING_FOR_DRAW: return "BEGIN_MAIN_FRAME_STATE_WAITING_FOR_DRAW"; } NOTREACHED(); return "???"; } const char* SchedulerStateMachine::ForcedRedrawOnTimeoutStateToString( ForcedRedrawOnTimeoutState state) { switch (state) { case FORCED_REDRAW_STATE_IDLE: return "FORCED_REDRAW_STATE_IDLE"; case FORCED_REDRAW_STATE_WAITING_FOR_COMMIT: return "FORCED_REDRAW_STATE_WAITING_FOR_COMMIT"; case FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION: return "FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION"; case FORCED_REDRAW_STATE_WAITING_FOR_DRAW: return "FORCED_REDRAW_STATE_WAITING_FOR_DRAW"; } NOTREACHED(); return "???"; } const char* ScrollHandlerStateToString(ScrollHandlerState state) { switch (state) { case ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER: return "SCROLL_AFFECTS_SCROLL_HANDLER"; case ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER: return "SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER"; } NOTREACHED(); return "???"; } const char* SchedulerStateMachine::ActionToString(Action action) { switch (action) { case ACTION_NONE: return "ACTION_NONE"; case ACTION_SEND_BEGIN_MAIN_FRAME: return "ACTION_SEND_BEGIN_MAIN_FRAME"; case ACTION_COMMIT: return "ACTION_COMMIT"; case ACTION_ACTIVATE_SYNC_TREE: return "ACTION_ACTIVATE_SYNC_TREE"; case ACTION_DRAW_AND_SWAP_IF_POSSIBLE: return "ACTION_DRAW_AND_SWAP_IF_POSSIBLE"; case ACTION_DRAW_AND_SWAP_FORCED: return "ACTION_DRAW_AND_SWAP_FORCED"; case ACTION_DRAW_AND_SWAP_ABORT: return "ACTION_DRAW_AND_SWAP_ABORT"; case ACTION_BEGIN_OUTPUT_SURFACE_CREATION: 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 "???"; } scoped_ptr SchedulerStateMachine::AsValue() const { scoped_ptr state( new base::trace_event::TracedValue()); AsValueInto(state.get()); return std::move(state); } void SchedulerStateMachine::AsValueInto( base::trace_event::TracedValue* state) const { state->BeginDictionary("major_state"); state->SetString("next_action", ActionToString(NextAction())); state->SetString("begin_impl_frame_state", BeginImplFrameStateToString(begin_impl_frame_state_)); state->SetString("begin_main_frame_state", BeginMainFrameStateToString(begin_main_frame_state_)); state->SetString("output_surface_state_", OutputSurfaceStateToString(output_surface_state_)); state->SetString("forced_redraw_state", ForcedRedrawOnTimeoutStateToString(forced_redraw_state_)); state->EndDictionary(); state->BeginDictionary("minor_state"); state->SetInteger("commit_count", commit_count_); state->SetInteger("current_frame_number", current_frame_number_); state->SetInteger("last_frame_number_swap_performed", last_frame_number_swap_performed_); state->SetInteger("last_frame_number_draw_performed", last_frame_number_draw_performed_); state->SetInteger("last_frame_number_begin_main_frame_sent", last_frame_number_begin_main_frame_sent_); state->SetBoolean("funnel: draw_funnel", draw_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("pending_swaps_", pending_swaps_); state->SetInteger("swaps_with_current_output_surface", swaps_with_current_output_surface_); state->SetBoolean("needs_redraw", needs_redraw_); state->SetBoolean("needs_prepare_tiles", needs_prepare_tiles_); state->SetBoolean("needs_begin_main_frame", needs_begin_main_frame_); state->SetBoolean("needs_one_begin_impl_frame", needs_one_begin_impl_frame_); state->SetBoolean("visible", visible_); state->SetBoolean("begin_frame_source_paused", begin_frame_source_paused_); state->SetBoolean("can_draw", can_draw_); state->SetBoolean("resourceless_draw", resourceless_draw_); state->SetBoolean("has_pending_tree", has_pending_tree_); state->SetBoolean("pending_tree_is_ready_for_activation", pending_tree_is_ready_for_activation_); state->SetBoolean("active_tree_needs_first_draw", active_tree_needs_first_draw_); state->SetBoolean("wait_for_ready_to_draw", wait_for_ready_to_draw_); state->SetBoolean("did_create_and_initialize_first_output_surface", did_create_and_initialize_first_output_surface_); state->SetString("tree_priority", TreePriorityToString(tree_priority_)); state->SetString("scroll_handler_state", ScrollHandlerStateToString(scroll_handler_state_)); state->SetBoolean("critical_begin_main_frame_to_activate_is_fast_", critical_begin_main_frame_to_activate_is_fast_); state->SetBoolean("main_thread_missed_last_deadline", main_thread_missed_last_deadline_); state->SetBoolean("skip_next_begin_main_frame_to_reduce_latency", skip_next_begin_main_frame_to_reduce_latency_); state->SetBoolean("children_need_begin_frames", children_need_begin_frames_); state->SetBoolean("video_needs_begin_frames", video_needs_begin_frames_); state->SetBoolean("defer_commits", defer_commits_); state->SetBoolean("last_commit_had_no_updates", last_commit_had_no_updates_); state->SetBoolean("did_draw_in_last_frame", did_draw_in_last_frame_); state->SetBoolean("did_swap_in_last_frame", did_swap_in_last_frame_); state->EndDictionary(); } bool SchedulerStateMachine::PendingDrawsShouldBeAborted() const { // Normally when |visible_| is false or |begin_frame_source_paused_| is true, // pending activations will be forced and draws will be aborted. However, // when the embedder is Android WebView, software draws could be scheduled by // the Android OS at any time and draws should not be aborted in this case. bool is_output_surface_lost = (output_surface_state_ == OUTPUT_SURFACE_NONE); if (resourceless_draw_) return is_output_surface_lost || !can_draw_; // 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, // we might deadlock with the main thread. // This should be a superset of PendingActivationsShouldBeForced() since // activation of the pending tree is blocked by drawing of the active tree and // the main thread might be blocked on activation of the most recent commit. return is_output_surface_lost || !can_draw_ || !visible_ || begin_frame_source_paused_; } bool SchedulerStateMachine::PendingActivationsShouldBeForced() const { // There is no output surface to trigger our activations. // If we do not force activations to make forward progress, we might deadlock // with the main thread. if (output_surface_state_ == OUTPUT_SURFACE_NONE) return true; // If we're not visible, we should force activation. // Since we set RequiresHighResToDraw when becoming visible, we ensure that we // don't checkerboard until all visible resources are done. Furthermore, if we // do keep the pending tree around, when becoming visible we might activate // prematurely causing RequiresHighResToDraw flag to be reset. In all cases, // we can simply activate on becoming invisible since we don't need to draw // the active tree when we're in this state. if (!visible_) return true; // Force pending activations when BeginFrameSource is paused to avoid // deadlocking the main thread. if (begin_frame_source_paused_) return true; return false; } bool SchedulerStateMachine::ShouldBeginOutputSurfaceCreation() const { if (!visible_) return false; // We only want to start output surface initialization after the // previous commit is complete. // We make an exception if the embedder explicitly allows beginning output // surface creation while the previous commit has not been aborted. This // assumes that any state passed from the client during the commit will not be // tied to the output surface. if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE && settings_.abort_commit_before_output_surface_creation) { return false; } // Make sure the BeginImplFrame from any previous OutputSurfaces // are complete before creating the new OutputSurface. if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_IDLE) return false; // We want to clear the pipeline of any pending draws and activations // before starting output surface initialization. This allows us to avoid // weird corner cases where we abort draws or force activation while we // are initializing the output surface. if (active_tree_needs_first_draw_ || has_pending_tree_) return false; // We need to create the output surface if we don't have one and we haven't // started creating one yet. return output_surface_state_ == OUTPUT_SURFACE_NONE; } bool SchedulerStateMachine::ShouldDraw() const { // If we need to abort draws, we should do so ASAP since the draw could // be blocking other important actions (like output surface initialization), // from occurring. If we are waiting for the first draw, then perform the // aborted draw to keep things moving. If we are not waiting for the first // draw however, we don't want to abort for no reason. if (PendingDrawsShouldBeAborted()) return active_tree_needs_first_draw_; // Do not draw too many times in a single frame. It's okay that we don't check // this before checking for aborted draws because aborted draws do not request // a swap. if (draw_funnel_) return false; // Don't draw if we are waiting on the first commit after a surface. if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE) return false; // Do not queue too many swaps. if (SwapThrottled()) return false; // Except for the cases above, do not draw outside of the BeginImplFrame // deadline. if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) return false; // Only handle forced redraws due to timeouts on the regular deadline. if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) return true; return needs_redraw_; } bool SchedulerStateMachine::ShouldActivatePendingTree() const { // There is nothing to activate. if (!has_pending_tree_) return false; // We should not activate a second tree before drawing the first one. // Even if we need to force activation of the pending tree, we should abort // drawing the active tree first. if (active_tree_needs_first_draw_) return false; // If we want to force activation, do so ASAP. if (PendingActivationsShouldBeForced()) return true; // At this point, only activate if we are ready to activate. return pending_tree_is_ready_for_activation_; } bool SchedulerStateMachine::CouldSendBeginMainFrame() const { if (!needs_begin_main_frame_) return false; // We can not perform commits if we are not visible. if (!visible_) return false; // There are no BeginImplFrames while BeginFrameSource is paused, // so should also stop BeginMainFrames. if (begin_frame_source_paused_) return false; // Do not make a new commits when it is deferred. if (defer_commits_) return false; return true; } bool SchedulerStateMachine::SendingBeginMainFrameMightCauseDeadlock() const { // NPAPI is the only case where the UI thread makes synchronous calls to the // Renderer main thread. During that synchronous call, we may not get a // SwapAck for the UI thread, which may prevent BeginMainFrame's from // completing if there's enough back pressure. If the BeginMainFrame can't // make progress, the Renderer can't service the UI thread's synchronous call // and we have deadlock. // This returns true if there's too much backpressure to finish a commit // if we were to initiate a BeginMainFrame. return has_pending_tree_ && active_tree_needs_first_draw_ && SwapThrottled(); } bool SchedulerStateMachine::ShouldSendBeginMainFrame() const { if (!CouldSendBeginMainFrame()) return false; // Do not send begin main frame too many times in a single frame or before // the first BeginFrame. if (send_begin_main_frame_funnel_) return false; // Only send BeginMainFrame when there isn't another commit pending already. // Other parts of the state machine indirectly defer the BeginMainFrame // by transitioning to WAITING commit states rather than going // immediately to IDLE. if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) return false; // Don't send BeginMainFrame early if we are prioritizing the active tree // because of ImplLatencyTakesPriority. if (ImplLatencyTakesPriority() && (has_pending_tree_ || active_tree_needs_first_draw_)) { return false; } // We should not send BeginMainFrame while we are in the idle state since we // might have new user input arriving soon. It's okay to send BeginMainFrame // for the synchronous compositor because the main thread is always high // latency in that case. // TODO(brianderson): Allow sending BeginMainFrame while idle when the main // thread isn't consuming user input for non-synchronous compositor. if (!settings_.using_synchronous_renderer_compositor && begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE) { return false; } // We need a new commit for the forced redraw. This honors the // single commit per interval because the result will be swapped to screen. // TODO(brianderson): Remove this or move it below the // SendingBeginMainFrameMightCauseDeadlock check since we want to avoid // ever returning true from this method if we might cause deadlock. if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) return true; // We shouldn't normally accept commits if there isn't an OutputSurface. if (!HasInitializedOutputSurface()) return false; // Make sure the BeginMainFrame can finish eventually if we start it. if (SendingBeginMainFrameMightCauseDeadlock()) return false; if (!settings_.main_frame_while_swap_throttled_enabled) { // SwapAck throttle the BeginMainFrames unless we just swapped to // potentially improve impl-thread latency over main-thread throughput. // TODO(brianderson): Remove this restriction to improve throughput or // make it conditional on ImplLatencyTakesPriority. bool just_swapped_in_deadline = begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE && did_swap_in_last_frame_; if (SwapThrottled() && !just_swapped_in_deadline) return false; } if (skip_next_begin_main_frame_to_reduce_latency_) return false; return true; } bool SchedulerStateMachine::ShouldCommit() const { if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT) return false; // We must not finish the commit until the pending tree is free. if (has_pending_tree_) { DCHECK(settings_.main_frame_before_activation_enabled); return false; } // If we only have an active tree, it is incorrect to replace it // before we've drawn it. DCHECK(!settings_.commit_to_active_tree || !active_tree_needs_first_draw_); return true; } bool SchedulerStateMachine::ShouldPrepareTiles() const { // PrepareTiles only really needs to be called immediately after commit // and then periodically after that. Use a funnel to make sure we average // one PrepareTiles per BeginImplFrame in the long run. if (prepare_tiles_funnel_ > 0) return false; // Limiting to once per-frame is not enough, since we only want to // 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; // TODO(sunnyps): needs_prepare_tiles_ is needed here because PrepareTiles is // called only inside the deadline / draw phase. We could remove this if we // allowed PrepareTiles to happen in OnBeginImplFrame. return needs_redraw_ || needs_prepare_tiles_; } SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { if (ShouldActivatePendingTree()) return ACTION_ACTIVATE_SYNC_TREE; if (ShouldCommit()) return ACTION_COMMIT; if (ShouldDraw()) { if (PendingDrawsShouldBeAborted()) return ACTION_DRAW_AND_SWAP_ABORT; else if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) return ACTION_DRAW_AND_SWAP_FORCED; else return ACTION_DRAW_AND_SWAP_IF_POSSIBLE; } if (ShouldPrepareTiles()) 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; } void SchedulerStateMachine::WillSendBeginMainFrame() { DCHECK(!has_pending_tree_ || settings_.main_frame_before_activation_enabled); DCHECK(visible_); DCHECK(!begin_frame_source_paused_); DCHECK(!send_begin_main_frame_funnel_); begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_SENT; needs_begin_main_frame_ = false; send_begin_main_frame_funnel_ = true; last_frame_number_begin_main_frame_sent_ = current_frame_number_; } void SchedulerStateMachine::WillCommit(bool commit_has_no_updates) { commit_count_++; if (commit_has_no_updates || settings_.main_frame_before_activation_enabled) { begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE; } else { begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_WAITING_FOR_ACTIVATION; } // If the commit was aborted, then there is no pending tree. has_pending_tree_ = !commit_has_no_updates; wait_for_ready_to_draw_ = !commit_has_no_updates && settings_.commit_to_active_tree; // Update state related to forced draws. if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) { forced_redraw_state_ = has_pending_tree_ ? FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION : FORCED_REDRAW_STATE_WAITING_FOR_DRAW; } // Update the output surface state. DCHECK_NE(output_surface_state_, OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION); if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT) { if (has_pending_tree_) { output_surface_state_ = OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION; } else { output_surface_state_ = OUTPUT_SURFACE_ACTIVE; } } // Update state if there's no updates heading for the active tree, but we need // to do a forced draw. if (commit_has_no_updates && forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) { DCHECK(!has_pending_tree_); needs_redraw_ = true; } // This post-commit work is common to both completed and aborted commits. pending_tree_is_ready_for_activation_ = false; last_commit_had_no_updates_ = commit_has_no_updates; } void SchedulerStateMachine::WillActivate() { if (begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_WAITING_FOR_ACTIVATION) { begin_main_frame_state_ = settings_.commit_to_active_tree ? BEGIN_MAIN_FRAME_STATE_WAITING_FOR_DRAW : BEGIN_MAIN_FRAME_STATE_IDLE; } if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION) output_surface_state_ = OUTPUT_SURFACE_ACTIVE; if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION) forced_redraw_state_ = FORCED_REDRAW_STATE_WAITING_FOR_DRAW; has_pending_tree_ = false; pending_tree_is_ready_for_activation_ = false; active_tree_needs_first_draw_ = true; needs_redraw_ = true; } void SchedulerStateMachine::WillDrawInternal() { // If a new active tree is pending after the one we are about to draw, // the main thread is in a high latency mode. // main_thread_missed_last_deadline_ is here in addition to // OnBeginImplFrameIdle for cases where the scheduler aborts draws outside // of the deadline. main_thread_missed_last_deadline_ = CommitPending() || has_pending_tree_; // We need to reset needs_redraw_ before we draw since the // draw itself might request another draw. needs_redraw_ = false; draw_funnel_ = true; active_tree_needs_first_draw_ = false; did_draw_in_last_frame_ = true; last_frame_number_draw_performed_ = current_frame_number_; if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; if (begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_WAITING_FOR_DRAW) begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE; } void SchedulerStateMachine::DidDrawInternal(DrawResult draw_result) { switch (draw_result) { case INVALID_RESULT: case DRAW_ABORTED_CANT_DRAW: case DRAW_ABORTED_CONTEXT_LOST: NOTREACHED() << "Invalid return DrawResult:" << draw_result; break; case DRAW_ABORTED_DRAINING_PIPELINE: case DRAW_SUCCESS: consecutive_checkerboard_animations_ = 0; forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; break; case DRAW_ABORTED_CHECKERBOARD_ANIMATIONS: DCHECK(!did_swap_in_last_frame_); needs_begin_main_frame_ = true; needs_redraw_ = true; consecutive_checkerboard_animations_++; if (consecutive_checkerboard_animations_ >= settings_.maximum_number_of_failed_draws_before_draw_is_forced && forced_redraw_state_ == FORCED_REDRAW_STATE_IDLE && settings_.timeout_and_draw_when_animation_checkerboards) { // We need to force a draw, but it doesn't make sense to do this until // we've committed and have new textures. forced_redraw_state_ = FORCED_REDRAW_STATE_WAITING_FOR_COMMIT; } break; case DRAW_ABORTED_MISSING_HIGH_RES_CONTENT: DCHECK(!did_swap_in_last_frame_); // It's not clear whether this missing content is because of missing // pictures (which requires a commit) or because of memory pressure // removing textures (which might not). To be safe, request a commit // anyway. needs_begin_main_frame_ = true; break; } } void SchedulerStateMachine::WillDraw() { DCHECK(!draw_funnel_); WillDrawInternal(); } void SchedulerStateMachine::DidDraw(DrawResult draw_result) { DidDrawInternal(draw_result); } void SchedulerStateMachine::AbortDrawAndSwap() { // Pretend like the draw was successful. // Note: We may abort at any time and cannot DCHECK that // we haven't drawn in or swapped in the last frame here. WillDrawInternal(); DidDrawInternal(DRAW_ABORTED_DRAINING_PIPELINE); } void SchedulerStateMachine::WillPrepareTiles() { needs_prepare_tiles_ = false; } void SchedulerStateMachine::WillBeginOutputSurfaceCreation() { DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_NONE); 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. // We allow output surface creation while the previous commit has not been // aborted if the embedder explicitly allows it. DCHECK(!settings_.abort_commit_before_output_surface_creation || begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_IDLE); DCHECK(!has_pending_tree_); DCHECK(!active_tree_needs_first_draw_); } void SchedulerStateMachine::WillInvalidateOutputSurface() { DCHECK(!invalidate_output_surface_funnel_); invalidate_output_surface_funnel_ = true; last_frame_number_invalidate_output_surface_performed_ = current_frame_number_; // The synchronous compositor makes no guarantees about a draw coming in after // an invalidate so clear any flags that would cause the compositor's pipeline // to stall. active_tree_needs_first_draw_ = false; // blocks commit if true } void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency() { TRACE_EVENT_INSTANT0("cc", "Scheduler: SkipNextBeginMainFrameToReduceLatency", TRACE_EVENT_SCOPE_THREAD); skip_next_begin_main_frame_to_reduce_latency_ = true; } bool SchedulerStateMachine::BeginFrameRequiredForChildren() const { return children_need_begin_frames_; } bool SchedulerStateMachine::BeginFrameNeededForVideo() const { return video_needs_begin_frames_; } bool SchedulerStateMachine::BeginFrameNeeded() const { // We can't handle BeginFrames when output surface isn't initialized. // TODO(brianderson): Support output surface creation inside a BeginFrame. if (!HasInitializedOutputSurface()) return false; // If we are not visible, we don't need BeginFrame messages. if (!visible_) return false; return (BeginFrameRequiredForAction() || BeginFrameRequiredForChildren() || BeginFrameNeededForVideo() || ProactiveBeginFrameWanted()); } void SchedulerStateMachine::SetChildrenNeedBeginFrames( bool children_need_begin_frames) { children_need_begin_frames_ = children_need_begin_frames; } void SchedulerStateMachine::SetVideoNeedsBeginFrames( bool video_needs_begin_frames) { video_needs_begin_frames_ = video_needs_begin_frames; } void SchedulerStateMachine::SetDeferCommits(bool defer_commits) { defer_commits_ = defer_commits; } // These are the cases where we require a BeginFrame message to make progress // on requested actions. bool SchedulerStateMachine::BeginFrameRequiredForAction() const { // The forced draw respects our normal draw scheduling, so we need to // request a BeginImplFrame for it. if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) return true; return needs_redraw_ || needs_one_begin_impl_frame_ || (needs_begin_main_frame_ && !defer_commits_); } // These are cases where we are very likely want a BeginFrame message in the // near future. Proactively requesting the BeginImplFrame helps hide the round // trip latency of the SetNeedsBeginFrame request that has to go to the // Browser. // This includes things like drawing soon, but might not actually have a new // frame to draw when we receive the next BeginImplFrame. bool SchedulerStateMachine::ProactiveBeginFrameWanted() const { // Do not be proactive when invisible. if (!visible_) return false; // We should proactively request a BeginImplFrame if a commit is pending // because we will want to draw if the commit completes quickly. Do not // request frames when commits are disabled, because the frame requests will // not provide the needed commit (and will wake up the process when it could // stay idle). if ((begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) && !defer_commits_) return true; // If the pending tree activates quickly, we'll want a BeginImplFrame soon // to draw the new active tree. if (has_pending_tree_) return true; // Changing priorities may allow us to activate (given the new priorities), // which may result in a new frame. if (needs_prepare_tiles_) return true; // If we just tried to DrawAndSwap, it's likely that we are going to produce // another frame soon. This helps avoid negative glitches in our // SetNeedsBeginFrame requests, which may propagate to the BeginImplFrame // provider and get sampled at an inopportune time, delaying the next // BeginImplFrame. if (did_draw_in_last_frame_) return true; // If the last commit was aborted because of early out (no updates), we should // still want a begin frame in case there is a commit coming again. if (last_commit_had_no_updates_) return true; return false; } void SchedulerStateMachine::OnBeginImplFrame() { begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING; current_frame_number_++; last_commit_had_no_updates_ = false; did_draw_in_last_frame_ = false; did_swap_in_last_frame_ = false; needs_one_begin_impl_frame_ = false; // Clear funnels for any actions we perform during the frame. send_begin_main_frame_funnel_ = false; invalidate_output_surface_funnel_ = false; // "Drain" the PrepareTiles funnel. if (prepare_tiles_funnel_ > 0) prepare_tiles_funnel_--; } void SchedulerStateMachine::OnBeginImplFrameDeadlinePending() { begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME; } void SchedulerStateMachine::OnBeginImplFrameDeadline() { begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE; // Clear funnels for any actions we perform during the deadline. draw_funnel_ = false; // Allow one PrepareTiles per draw for synchronous compositor. if (settings_.using_synchronous_renderer_compositor) { if (prepare_tiles_funnel_ > 0) prepare_tiles_funnel_--; } } void SchedulerStateMachine::OnBeginImplFrameIdle() { begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_IDLE; skip_next_begin_main_frame_to_reduce_latency_ = false; // If a new or undrawn active tree is pending after the deadline, // then the main thread is in a high latency mode. main_thread_missed_last_deadline_ = CommitPending() || has_pending_tree_ || active_tree_needs_first_draw_; // If we're entering a state where we won't get BeginFrames set all the // funnels so that we don't perform any actions that we shouldn't. if (!BeginFrameNeeded()) send_begin_main_frame_funnel_ = true; } SchedulerStateMachine::BeginImplFrameDeadlineMode SchedulerStateMachine::CurrentBeginImplFrameDeadlineMode() const { 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 (wait_for_ready_to_draw_) { // When we are waiting for ready to draw signal, we do not wait to post a // deadline yet. return BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED_ON_READY_TO_DRAW; } else if (needs_redraw_) { // We have an animation or fast input path on the impl thread that wants // to draw, so don't wait too long for a new active tree. return BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR; } else { // The impl thread doesn't have anything it wants to draw and we are just // waiting for a new active tree. In short we are blocked. return BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE; } } bool SchedulerStateMachine::ShouldTriggerBeginImplFrameDeadlineImmediately() const { // If we just forced activation, we should end the deadline right now. if (PendingActivationsShouldBeForced() && !has_pending_tree_) return true; // Do not trigger deadline immediately if we're waiting for READY_TO_DRAW // unless it's one of the forced cases. if (wait_for_ready_to_draw_) return false; // SwapAck throttle the deadline since we wont draw and swap anyway. if (SwapThrottled()) return false; if (active_tree_needs_first_draw_) return true; if (!needs_redraw_) return false; // This is used to prioritize impl-thread draws when the main thread isn't // producing anything, e.g., after an aborted commit. We also check that we // don't have a pending tree -- otherwise we should give it a chance to // activate. // TODO(skyostil): Revisit this when we have more accurate deadline estimates. if (begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_IDLE && !has_pending_tree_) return true; // Prioritize impl-thread draws in ImplLatencyTakesPriority mode. if (ImplLatencyTakesPriority()) return true; return false; } bool SchedulerStateMachine::SwapThrottled() const { return pending_swaps_ >= kMaxPendingSwaps; } void SchedulerStateMachine::SetVisible(bool visible) { if (visible_ == visible) return; visible_ = visible; if (visible) main_thread_missed_last_deadline_ = false; // TODO(sunnyps): Change the funnel to a bool to avoid hacks like this. prepare_tiles_funnel_ = 0; wait_for_ready_to_draw_ = false; } void SchedulerStateMachine::SetBeginFrameSourcePaused(bool paused) { begin_frame_source_paused_ = paused; } void SchedulerStateMachine::SetResourcelessSoftareDraw(bool resourceless_draw) { resourceless_draw_ = resourceless_draw; } void SchedulerStateMachine::SetCanDraw(bool can_draw) { can_draw_ = can_draw; } void SchedulerStateMachine::SetNeedsRedraw() { needs_redraw_ = true; } bool SchedulerStateMachine::OnlyImplSideUpdatesExpected() const { bool has_impl_updates = needs_redraw_ || needs_one_begin_impl_frame_; bool main_updates_expected = needs_begin_main_frame_ || begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE || has_pending_tree_; return has_impl_updates && !main_updates_expected; } void SchedulerStateMachine::SetNeedsPrepareTiles() { if (!needs_prepare_tiles_) { TRACE_EVENT0("cc", "SchedulerStateMachine::SetNeedsPrepareTiles"); needs_prepare_tiles_ = true; } } void SchedulerStateMachine::DidSwapBuffers() { TRACE_EVENT_ASYNC_BEGIN1("cc", "Scheduler:pending_swaps", this, "pending_frames", pending_swaps_); DCHECK_LT(pending_swaps_, kMaxPendingSwaps); pending_swaps_++; swaps_with_current_output_surface_++; did_swap_in_last_frame_ = true; last_frame_number_swap_performed_ = current_frame_number_; } void SchedulerStateMachine::DidSwapBuffersComplete() { TRACE_EVENT_ASYNC_END1("cc", "Scheduler:pending_swaps", this, "pending_frames", pending_swaps_); pending_swaps_--; } void SchedulerStateMachine::SetTreePrioritiesAndScrollState( TreePriority tree_priority, ScrollHandlerState scroll_handler_state) { tree_priority_ = tree_priority; scroll_handler_state_ = scroll_handler_state; } void SchedulerStateMachine::SetCriticalBeginMainFrameToActivateIsFast( bool is_fast) { critical_begin_main_frame_to_activate_is_fast_ = is_fast; } bool SchedulerStateMachine::ImplLatencyTakesPriority() const { // Attempt to synchronize with the main thread if it has a scroll listener // and is fast. if (ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER == scroll_handler_state_ && critical_begin_main_frame_to_activate_is_fast_) return false; // Don't wait for the main thread if we are prioritizing smoothness. if (SMOOTHNESS_TAKES_PRIORITY == tree_priority_) return true; return false; } void SchedulerStateMachine::SetNeedsBeginMainFrame() { needs_begin_main_frame_ = true; } void SchedulerStateMachine::SetNeedsOneBeginImplFrame() { needs_one_begin_impl_frame_ = true; } void SchedulerStateMachine::NotifyReadyToCommit() { DCHECK_EQ(begin_main_frame_state_, BEGIN_MAIN_FRAME_STATE_STARTED) << AsValue()->ToString(); begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT; // In commit_to_active_tree mode, commit should happen right after // BeginFrame, meaning when this function is called, next action should be // commit. if (settings_.commit_to_active_tree) DCHECK(ShouldCommit()); } void SchedulerStateMachine::BeginMainFrameAborted(CommitEarlyOutReason reason) { DCHECK_EQ(begin_main_frame_state_, BEGIN_MAIN_FRAME_STATE_STARTED); // If the main thread aborted, it doesn't matter if the main thread missed // the last deadline since it didn't have an update anyway. main_thread_missed_last_deadline_ = false; switch (reason) { case CommitEarlyOutReason::ABORTED_OUTPUT_SURFACE_LOST: case CommitEarlyOutReason::ABORTED_NOT_VISIBLE: case CommitEarlyOutReason::ABORTED_DEFERRED_COMMIT: begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE; SetNeedsBeginMainFrame(); return; case CommitEarlyOutReason::FINISHED_NO_UPDATES: bool commit_has_no_updates = true; WillCommit(commit_has_no_updates); return; } } void SchedulerStateMachine::DidPrepareTiles() { needs_prepare_tiles_ = false; // "Fill" the PrepareTiles funnel. prepare_tiles_funnel_++; } void SchedulerStateMachine::DidLoseOutputSurface() { if (output_surface_state_ == OUTPUT_SURFACE_NONE || output_surface_state_ == OUTPUT_SURFACE_CREATING) return; output_surface_state_ = OUTPUT_SURFACE_NONE; needs_redraw_ = false; wait_for_ready_to_draw_ = false; } void SchedulerStateMachine::NotifyReadyToActivate() { if (has_pending_tree_) pending_tree_is_ready_for_activation_ = true; } void SchedulerStateMachine::NotifyReadyToDraw() { wait_for_ready_to_draw_ = false; } void SchedulerStateMachine::DidCreateAndInitializeOutputSurface() { DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_CREATING); output_surface_state_ = OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT; if (did_create_and_initialize_first_output_surface_) { // TODO(boliu): See if we can remove this when impl-side painting is always // on. Does anything on the main thread need to update after recreate? needs_begin_main_frame_ = true; } did_create_and_initialize_first_output_surface_ = true; pending_swaps_ = 0; swaps_with_current_output_surface_ = 0; main_thread_missed_last_deadline_ = false; } void SchedulerStateMachine::NotifyBeginMainFrameStarted() { DCHECK_EQ(begin_main_frame_state_, BEGIN_MAIN_FRAME_STATE_SENT); begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_STARTED; } bool SchedulerStateMachine::HasInitializedOutputSurface() const { switch (output_surface_state_) { case OUTPUT_SURFACE_NONE: case OUTPUT_SURFACE_CREATING: return false; case OUTPUT_SURFACE_ACTIVE: case OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT: case OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION: return true; } NOTREACHED(); return false; } } // namespace cc