| // 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_state_machine.h" |
| |
| #include "base/logging.h" |
| #include "base/stringprintf.h" |
| |
| namespace cc { |
| |
| SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) |
| : m_settings(settings) |
| , m_commitState(COMMIT_STATE_IDLE) |
| , m_currentFrameNumber(0) |
| , m_lastFrameNumberWhereDrawWasCalled(-1) |
| , m_lastFrameNumberWhereTreeActivationAttempted(-1) |
| , m_lastFrameNumberWhereCheckForCompletedTileUploadsCalled(-1) |
| , m_consecutiveFailedDraws(0) |
| , m_maximumNumberOfFailedDrawsBeforeDrawIsForced(3) |
| , m_needsRedraw(false) |
| , m_swapUsedIncompleteTile(false) |
| , m_needsForcedRedraw(false) |
| , m_needsForcedRedrawAfterNextCommit(false) |
| , m_needsCommit(false) |
| , m_needsForcedCommit(false) |
| , m_expectImmediateBeginFrame(false) |
| , m_mainThreadNeedsLayerTextures(false) |
| , m_insideVSync(false) |
| , m_visible(false) |
| , m_canBeginFrame(false) |
| , m_canDraw(false) |
| , m_hasPendingTree(false) |
| , m_drawIfPossibleFailed(false) |
| , m_textureState(LAYER_TEXTURE_STATE_UNLOCKED) |
| , m_outputSurfaceState(OUTPUT_SURFACE_ACTIVE) |
| { |
| } |
| |
| std::string SchedulerStateMachine::toString() |
| { |
| std::string str; |
| base::StringAppendF(&str, "m_settings.implSidePainting = %d; ", m_settings.implSidePainting); |
| base::StringAppendF(&str, "m_commitState = %d; ", m_commitState); |
| base::StringAppendF(&str, "m_currentFrameNumber = %d; ", m_currentFrameNumber); |
| base::StringAppendF(&str, "m_lastFrameNumberWhereDrawWasCalled = %d; ", m_lastFrameNumberWhereDrawWasCalled); |
| base::StringAppendF(&str, "m_lastFrameNumberWhereTreeActivationAttempted = %d; ", m_lastFrameNumberWhereTreeActivationAttempted); |
| base::StringAppendF(&str, "m_lastFrameNumberWhereCheckForCompletedTileUploadsCalled = %d; ", m_lastFrameNumberWhereCheckForCompletedTileUploadsCalled); |
| base::StringAppendF(&str, "m_consecutiveFailedDraws = %d; ", m_consecutiveFailedDraws); |
| base::StringAppendF(&str, "m_maximumNumberOfFailedDrawsBeforeDrawIsForced = %d; ", m_maximumNumberOfFailedDrawsBeforeDrawIsForced); |
| base::StringAppendF(&str, "m_needsRedraw = %d; ", m_needsRedraw); |
| base::StringAppendF(&str, "m_swapUsedIncompleteTile = %d; ", m_swapUsedIncompleteTile); |
| base::StringAppendF(&str, "m_needsForcedRedraw = %d; ", m_needsForcedRedraw); |
| base::StringAppendF(&str, "m_needsForcedRedrawAfterNextCommit = %d; ", m_needsForcedRedrawAfterNextCommit); |
| base::StringAppendF(&str, "m_needsCommit = %d; ", m_needsCommit); |
| base::StringAppendF(&str, "m_needsForcedCommit = %d; ", m_needsForcedCommit); |
| base::StringAppendF(&str, "m_expectImmediateBeginFrame = %d; ", m_expectImmediateBeginFrame); |
| base::StringAppendF(&str, "m_mainThreadNeedsLayerTextures = %d; ", m_mainThreadNeedsLayerTextures); |
| base::StringAppendF(&str, "m_insideVSync = %d; ", m_insideVSync); |
| base::StringAppendF(&str, "m_visible = %d; ", m_visible); |
| base::StringAppendF(&str, "m_canBeginFrame = %d; ", m_canBeginFrame); |
| base::StringAppendF(&str, "m_canDraw = %d; ", m_canDraw); |
| base::StringAppendF(&str, "m_drawIfPossibleFailed = %d; ", m_drawIfPossibleFailed); |
| base::StringAppendF(&str, "m_hasPendingTree = %d; ", m_hasPendingTree); |
| base::StringAppendF(&str, "m_textureState = %d; ", m_textureState); |
| base::StringAppendF(&str, "m_outputSurfaceState = %d; ", m_outputSurfaceState); |
| return str; |
| } |
| |
| bool SchedulerStateMachine::hasDrawnThisFrame() const |
| { |
| return m_currentFrameNumber == m_lastFrameNumberWhereDrawWasCalled; |
| } |
| |
| bool SchedulerStateMachine::hasAttemptedTreeActivationThisFrame() const |
| { |
| return m_currentFrameNumber == m_lastFrameNumberWhereTreeActivationAttempted; |
| } |
| |
| bool SchedulerStateMachine::hasCheckedForCompletedTileUploadsThisFrame() const |
| { |
| return m_currentFrameNumber == |
| m_lastFrameNumberWhereCheckForCompletedTileUploadsCalled; |
| } |
| |
| bool SchedulerStateMachine::drawSuspendedUntilCommit() const |
| { |
| if (!m_canDraw) |
| return true; |
| if (!m_visible) |
| return true; |
| if (m_textureState == LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD) |
| return true; |
| return false; |
| } |
| |
| bool SchedulerStateMachine::scheduledToDraw() const |
| { |
| if (!m_needsRedraw) |
| return false; |
| if (drawSuspendedUntilCommit()) |
| return false; |
| return true; |
| } |
| |
| bool SchedulerStateMachine::shouldDraw() const |
| { |
| if (m_needsForcedRedraw) |
| return true; |
| |
| if (!scheduledToDraw()) |
| return false; |
| if (!m_insideVSync) |
| return false; |
| if (hasDrawnThisFrame()) |
| return false; |
| if (m_outputSurfaceState != OUTPUT_SURFACE_ACTIVE) |
| return false; |
| return true; |
| } |
| |
| bool SchedulerStateMachine::shouldAttemptTreeActivation() const |
| { |
| return m_hasPendingTree && m_insideVSync && !hasAttemptedTreeActivationThisFrame(); |
| } |
| |
| bool SchedulerStateMachine::shouldCheckForCompletedTileUploads() const |
| { |
| if (!m_settings.implSidePainting) |
| return false; |
| if (hasCheckedForCompletedTileUploadsThisFrame()) |
| return false; |
| |
| return shouldAttemptTreeActivation() || |
| shouldDraw() || |
| m_swapUsedIncompleteTile; |
| } |
| |
| bool SchedulerStateMachine::shouldAcquireLayerTexturesForMainThread() const |
| { |
| if (!m_mainThreadNeedsLayerTextures) |
| return false; |
| if (m_textureState == LAYER_TEXTURE_STATE_UNLOCKED) |
| return true; |
| DCHECK(m_textureState == LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD); |
| // Transfer the lock from impl thread to main thread immediately if the |
| // impl thread is not even scheduled to draw. Guards against deadlocking. |
| if (!scheduledToDraw()) |
| return true; |
| if (!vsyncCallbackNeeded()) |
| return true; |
| return false; |
| } |
| |
| SchedulerStateMachine::Action SchedulerStateMachine::nextAction() const |
| { |
| if (shouldAcquireLayerTexturesForMainThread()) |
| return ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD; |
| |
| switch (m_commitState) { |
| case COMMIT_STATE_IDLE: |
| if (m_outputSurfaceState != OUTPUT_SURFACE_ACTIVE && m_needsForcedRedraw) |
| return ACTION_DRAW_FORCED; |
| if (m_outputSurfaceState != OUTPUT_SURFACE_ACTIVE && m_needsForcedCommit) |
| // TODO(enne): Should probably drop the active tree on force commit |
| return m_hasPendingTree ? ACTION_NONE : ACTION_BEGIN_FRAME; |
| if (m_outputSurfaceState == OUTPUT_SURFACE_LOST) |
| return ACTION_BEGIN_OUTPUT_SURFACE_RECREATION; |
| if (m_outputSurfaceState == OUTPUT_SURFACE_RECREATING) |
| return ACTION_NONE; |
| if (shouldCheckForCompletedTileUploads()) |
| return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS; |
| if (shouldAttemptTreeActivation()) |
| return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED; |
| if (shouldDraw()) |
| return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; |
| if (m_needsCommit && ((m_visible && m_canBeginFrame) || m_needsForcedCommit)) |
| // TODO(enne): Should probably drop the active tree on force commit |
| return m_hasPendingTree ? ACTION_NONE : ACTION_BEGIN_FRAME; |
| return ACTION_NONE; |
| |
| case COMMIT_STATE_FRAME_IN_PROGRESS: |
| if (shouldCheckForCompletedTileUploads()) |
| return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS; |
| if (shouldAttemptTreeActivation()) |
| return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED; |
| if (shouldDraw()) |
| return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; |
| return ACTION_NONE; |
| |
| case COMMIT_STATE_READY_TO_COMMIT: |
| return ACTION_COMMIT; |
| |
| case COMMIT_STATE_WAITING_FOR_FIRST_DRAW: { |
| if (shouldCheckForCompletedTileUploads()) |
| return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS; |
| if (shouldAttemptTreeActivation()) |
| return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED; |
| if (shouldDraw() || m_outputSurfaceState == OUTPUT_SURFACE_LOST) |
| return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; |
| // COMMIT_STATE_WAITING_FOR_FIRST_DRAW wants to enforce a draw. If m_canDraw is false |
| // or textures are not available, proceed to the next step (similar as in COMMIT_STATE_IDLE). |
| bool canCommit = m_visible || m_needsForcedCommit; |
| if (m_needsCommit && canCommit && drawSuspendedUntilCommit()) |
| return m_hasPendingTree ? ACTION_NONE : ACTION_BEGIN_FRAME; |
| return ACTION_NONE; |
| } |
| |
| case COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW: |
| if (shouldCheckForCompletedTileUploads()) |
| return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS; |
| if (shouldAttemptTreeActivation()) |
| return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED; |
| if (m_needsForcedRedraw) |
| return ACTION_DRAW_FORCED; |
| return ACTION_NONE; |
| } |
| NOTREACHED(); |
| return ACTION_NONE; |
| } |
| |
| void SchedulerStateMachine::updateState(Action action) |
| { |
| switch (action) { |
| case ACTION_NONE: |
| return; |
| |
| case ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS: |
| m_lastFrameNumberWhereCheckForCompletedTileUploadsCalled = m_currentFrameNumber; |
| return; |
| |
| case ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED: |
| m_lastFrameNumberWhereTreeActivationAttempted = m_currentFrameNumber; |
| return; |
| |
| case ACTION_BEGIN_FRAME: |
| DCHECK(!m_hasPendingTree); |
| DCHECK(m_visible || m_needsForcedCommit); |
| m_commitState = COMMIT_STATE_FRAME_IN_PROGRESS; |
| m_needsCommit = false; |
| m_needsForcedCommit = false; |
| return; |
| |
| case ACTION_COMMIT: |
| if (m_expectImmediateBeginFrame) |
| m_commitState = COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW; |
| else |
| m_commitState = COMMIT_STATE_WAITING_FOR_FIRST_DRAW; |
| // When impl-side painting, we draw on activation instead of on commit. |
| if (!m_settings.implSidePainting) |
| m_needsRedraw = true; |
| if (m_drawIfPossibleFailed) |
| m_lastFrameNumberWhereDrawWasCalled = -1; |
| |
| if (m_needsForcedRedrawAfterNextCommit) { |
| m_needsForcedRedrawAfterNextCommit = false; |
| m_needsForcedRedraw = true; |
| } |
| |
| m_textureState = LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD; |
| return; |
| |
| case ACTION_DRAW_FORCED: |
| case ACTION_DRAW_IF_POSSIBLE: |
| m_needsRedraw = false; |
| m_needsForcedRedraw = false; |
| m_drawIfPossibleFailed = false; |
| m_swapUsedIncompleteTile = false; |
| if (m_insideVSync) |
| m_lastFrameNumberWhereDrawWasCalled = m_currentFrameNumber; |
| if (m_commitState == COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW) { |
| DCHECK(m_expectImmediateBeginFrame); |
| m_commitState = COMMIT_STATE_FRAME_IN_PROGRESS; |
| m_expectImmediateBeginFrame = false; |
| } else if (m_commitState == COMMIT_STATE_WAITING_FOR_FIRST_DRAW) |
| m_commitState = COMMIT_STATE_IDLE; |
| if (m_textureState == LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD) |
| m_textureState = LAYER_TEXTURE_STATE_UNLOCKED; |
| return; |
| |
| case ACTION_BEGIN_OUTPUT_SURFACE_RECREATION: |
| DCHECK(m_commitState == COMMIT_STATE_IDLE); |
| DCHECK(m_outputSurfaceState == OUTPUT_SURFACE_LOST); |
| m_outputSurfaceState = OUTPUT_SURFACE_RECREATING; |
| return; |
| |
| case ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD: |
| m_textureState = LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD; |
| m_mainThreadNeedsLayerTextures = false; |
| if (m_commitState != COMMIT_STATE_FRAME_IN_PROGRESS) |
| m_needsCommit = true; |
| return; |
| } |
| } |
| |
| void SchedulerStateMachine::setMainThreadNeedsLayerTextures() |
| { |
| DCHECK(!m_mainThreadNeedsLayerTextures); |
| DCHECK(m_textureState != LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD); |
| m_mainThreadNeedsLayerTextures = true; |
| } |
| |
| bool SchedulerStateMachine::vsyncCallbackNeeded() const |
| { |
| // If we have a pending tree, need to keep getting notifications until |
| // the tree is ready to be swapped. |
| if (m_hasPendingTree) |
| return true; |
| |
| // If we can't draw, don't tick until we are notified that we can draw again. |
| if (!m_canDraw) |
| return false; |
| |
| if (m_needsForcedRedraw) |
| return true; |
| |
| if (m_visible && m_swapUsedIncompleteTile) |
| return true; |
| |
| return m_needsRedraw && m_visible && m_outputSurfaceState == OUTPUT_SURFACE_ACTIVE; |
| } |
| |
| void SchedulerStateMachine::didEnterVSync() |
| { |
| m_insideVSync = true; |
| } |
| |
| void SchedulerStateMachine::didLeaveVSync() |
| { |
| m_currentFrameNumber++; |
| m_insideVSync = false; |
| } |
| |
| void SchedulerStateMachine::setVisible(bool visible) |
| { |
| m_visible = visible; |
| } |
| |
| void SchedulerStateMachine::setNeedsRedraw() |
| { |
| m_needsRedraw = true; |
| } |
| |
| void SchedulerStateMachine::didSwapUseIncompleteTile() |
| { |
| m_swapUsedIncompleteTile = true; |
| } |
| |
| void SchedulerStateMachine::setNeedsForcedRedraw() |
| { |
| m_needsForcedRedraw = true; |
| } |
| |
| void SchedulerStateMachine::didDrawIfPossibleCompleted(bool success) |
| { |
| m_drawIfPossibleFailed = !success; |
| if (m_drawIfPossibleFailed) { |
| m_needsRedraw = true; |
| m_needsCommit = true; |
| m_consecutiveFailedDraws++; |
| if (m_consecutiveFailedDraws >= m_maximumNumberOfFailedDrawsBeforeDrawIsForced) { |
| m_consecutiveFailedDraws = 0; |
| // We need to force a draw, but it doesn't make sense to do this until |
| // we've committed and have new textures. |
| m_needsForcedRedrawAfterNextCommit = true; |
| } |
| } else |
| m_consecutiveFailedDraws = 0; |
| } |
| |
| void SchedulerStateMachine::setNeedsCommit() |
| { |
| m_needsCommit = true; |
| } |
| |
| void SchedulerStateMachine::setNeedsForcedCommit() |
| { |
| m_needsForcedCommit = true; |
| m_expectImmediateBeginFrame = true; |
| } |
| |
| void SchedulerStateMachine::beginFrameComplete() |
| { |
| DCHECK(m_commitState == COMMIT_STATE_FRAME_IN_PROGRESS || |
| (m_expectImmediateBeginFrame && m_commitState != COMMIT_STATE_IDLE)) << toString(); |
| m_commitState = COMMIT_STATE_READY_TO_COMMIT; |
| } |
| |
| void SchedulerStateMachine::beginFrameAborted() |
| { |
| DCHECK(m_commitState == COMMIT_STATE_FRAME_IN_PROGRESS); |
| if (m_expectImmediateBeginFrame) |
| m_expectImmediateBeginFrame = false; |
| else { |
| m_commitState = COMMIT_STATE_IDLE; |
| setNeedsCommit(); |
| } |
| } |
| |
| void SchedulerStateMachine::didLoseOutputSurface() |
| { |
| if (m_outputSurfaceState == OUTPUT_SURFACE_LOST || m_outputSurfaceState == OUTPUT_SURFACE_RECREATING) |
| return; |
| m_outputSurfaceState = OUTPUT_SURFACE_LOST; |
| } |
| |
| void SchedulerStateMachine::setHasPendingTree(bool hasPendingTree) |
| { |
| m_hasPendingTree = hasPendingTree; |
| } |
| |
| void SchedulerStateMachine::setCanDraw(bool can) |
| { |
| m_canDraw = can; |
| } |
| |
| void SchedulerStateMachine::didRecreateOutputSurface() |
| { |
| DCHECK(m_outputSurfaceState == OUTPUT_SURFACE_RECREATING); |
| m_outputSurfaceState = OUTPUT_SURFACE_ACTIVE; |
| setNeedsCommit(); |
| } |
| |
| void SchedulerStateMachine::setMaximumNumberOfFailedDrawsBeforeDrawIsForced(int numDraws) |
| { |
| m_maximumNumberOfFailedDrawsBeforeDrawIsForced = numDraws; |
| } |
| |
| } // namespace cc |