SF: set active config state machine

1) Set the desired active config bit
2) On the next vsync, ask HWC to update config.
3) Send a refresh to HWC
4) When the fence comes back, update SF to the new config/refresh rate
   and trigger HWC vsync.

see go/surface-flinger-scheduler for more info

Test: Systrace.
Change-Id: I4ed649668cc6cc23cd3f428b3c92ab1c199c8aa4
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 2f581d2..cb83c62 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -69,7 +69,7 @@
     mEventControlThread = std::make_unique<impl::EventControlThread>(function);
 
     char value[PROPERTY_VALUE_MAX];
-    property_get("debug.sf.set_idle_timer_ms", value, "30");
+    property_get("debug.sf.set_idle_timer_ms", value, "0");
     mSetIdleTimerMs = atoi(value);
 
     if (mSetIdleTimerMs > 0) {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5f8de83..23eeb36 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -822,14 +822,12 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getDisplayConfigs(const sp<IBinder>& displayToken,
-                                           Vector<DisplayInfo>* configs) {
+status_t SurfaceFlinger::getDisplayConfigsLocked(const sp<IBinder>& displayToken,
+                                                 Vector<DisplayInfo>* configs) {
     if (!displayToken || !configs) {
         return BAD_VALUE;
     }
 
-    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-
     const auto displayId = getPhysicalDisplayIdLocked(displayToken);
     if (!displayId) {
         return NAME_NOT_FOUND;
@@ -957,22 +955,19 @@
     return display->getActiveConfig();
 }
 
-status_t SurfaceFlinger::setActiveConfig(const sp<IBinder>& displayToken, int mode) {
-    ATRACE_NAME("setActiveConfigSync");
-    postMessageSync(new LambdaMessage(
-            [&]() NO_THREAD_SAFETY_ANALYSIS { setActiveConfigInternal(displayToken, mode); }));
-    return NO_ERROR;
-}
+void SurfaceFlinger::setDesiredActiveConfig(const sp<IBinder>& displayToken, int mode) {
+    ATRACE_CALL();
 
-void SurfaceFlinger::setActiveConfigInternal(const sp<IBinder>& displayToken, int mode) {
     Vector<DisplayInfo> configs;
-    getDisplayConfigs(displayToken, &configs);
+    // Lock is acquired by setRefreshRateTo.
+    getDisplayConfigsLocked(displayToken, &configs);
     if (mode < 0 || mode >= static_cast<int>(configs.size())) {
         ALOGE("Attempt to set active config %d for display with %zu configs", mode, configs.size());
         return;
     }
 
-    const auto display = getDisplayDevice(displayToken);
+    // Lock is acquired by setRefreshRateTo.
+    const auto display = getDisplayDeviceLocked(displayToken);
     if (!display) {
         ALOGE("Attempt to set active config %d for invalid display token %p", mode,
               displayToken.get());
@@ -988,23 +983,95 @@
         return;
     }
 
-    int currentMode = display->getActiveConfig();
-    if (mode == currentMode) {
-        // Don't update config if we are already running in the desired mode.
+    // Don't check against the current mode yet. Worst case we set the desired
+    // config twice.
+    {
+        std::lock_guard<std::mutex> lock(mActiveConfigLock);
+        mDesiredActiveConfig = ActiveConfigInfo{mode, displayToken};
+    }
+    // This will trigger HWC refresh without resetting the idle timer.
+    repaintEverythingForHWC();
+}
+
+status_t SurfaceFlinger::setActiveConfig(const sp<IBinder>& displayToken, int mode) {
+    ATRACE_CALL();
+    postMessageSync(new LambdaMessage(
+            [&]() NO_THREAD_SAFETY_ANALYSIS { setDesiredActiveConfig(displayToken, mode); }));
+    return NO_ERROR;
+}
+
+void SurfaceFlinger::setActiveConfigInHWC() {
+    ATRACE_CALL();
+
+    const auto display = getDisplayDevice(mUpcomingActiveConfig.displayToken);
+    if (!display) {
         return;
     }
-    if (mUseScheduler) {
-        mRefreshRateStats->setConfigMode(mode);
-    }
-
     const auto displayId = display->getId();
     LOG_ALWAYS_FATAL_IF(!displayId);
 
-    display->setActiveConfig(mode);
-    getHwComposer().setActiveConfig(*displayId, mode);
+    ATRACE_INT("ActiveConfigModeHWC", mUpcomingActiveConfig.configId);
+    getHwComposer().setActiveConfig(*displayId, mUpcomingActiveConfig.configId);
+    mSetActiveConfigState = SetActiveConfigState::NOTIFIED_HWC;
+    ATRACE_INT("SetActiveConfigState", mSetActiveConfigState);
+}
 
-    ATRACE_INT("ActiveConfigMode", mode);
+void SurfaceFlinger::setActiveConfigInternal() {
+    ATRACE_CALL();
+
+    std::lock_guard<std::mutex> lock(mActiveConfigLock);
+    if (mUseScheduler) {
+        mRefreshRateStats->setConfigMode(mUpcomingActiveConfig.configId);
+    }
+
+    const auto display = getDisplayDeviceLocked(mUpcomingActiveConfig.displayToken);
+    display->setActiveConfig(mUpcomingActiveConfig.configId);
+
+    mSetActiveConfigState = SetActiveConfigState::NONE;
+    ATRACE_INT("SetActiveConfigState", mSetActiveConfigState);
+
     resyncToHardwareVsync(true, getVsyncPeriod());
+    ATRACE_INT("ActiveConfigMode", mUpcomingActiveConfig.configId);
+}
+
+bool SurfaceFlinger::updateSetActiveConfigStateMachine() NO_THREAD_SAFETY_ANALYSIS {
+    // Store the local variable to release the lock.
+    ActiveConfigInfo desiredActiveConfig;
+    {
+        std::lock_guard<std::mutex> lock(mActiveConfigLock);
+        desiredActiveConfig = mDesiredActiveConfig;
+    }
+
+    const auto display = getDisplayDevice(desiredActiveConfig.displayToken);
+    if (display) {
+        if (mSetActiveConfigState == SetActiveConfigState::NONE &&
+            display->getActiveConfig() != desiredActiveConfig.configId) {
+            // Step 1) Desired active config was set, it is different than the
+            // config currently in use. Notify HWC.
+            mUpcomingActiveConfig = desiredActiveConfig;
+            setActiveConfigInHWC();
+        } else if (mSetActiveConfigState == SetActiveConfigState::NOTIFIED_HWC) {
+            // Step 2) HWC was notified and we received refresh from it.
+            mSetActiveConfigState = SetActiveConfigState::REFRESH_RECEIVED;
+            ATRACE_INT("SetActiveConfigState", mSetActiveConfigState);
+            repaintEverythingForHWC();
+            // We do not want to give another frame to HWC while we are transitioning.
+            return false;
+        } else if (mSetActiveConfigState == SetActiveConfigState::REFRESH_RECEIVED &&
+                   !(mPreviousPresentFence != Fence::NO_FENCE &&
+                     (mPreviousPresentFence->getStatus() == Fence::Status::Unsignaled))) {
+            // Step 3) We received the present fence from the HWC, so we assume it
+            // successfully updated the config, hence we update SF.
+            setActiveConfigInternal();
+            // If the config changed again while we were transitioning, restart the
+            // process.
+            if (desiredActiveConfig != mUpcomingActiveConfig) {
+                mUpcomingActiveConfig = desiredActiveConfig;
+                setActiveConfigInHWC(); // sets the state to NOTIFY_HWC
+            }
+        }
+    }
+    return true;
 }
 
 status_t SurfaceFlinger::getDisplayColorModes(const sp<IBinder>& displayToken,
@@ -1482,8 +1549,7 @@
         // TODO(b/113612090): There should be a better way at determining which config
         // has the right refresh rate.
         if (std::abs(fps - newFps) <= 1) {
-            setActiveConfigInternal(getInternalDisplayTokenLocked(), i);
-            ATRACE_INT("FPS", newFps);
+            setDesiredActiveConfig(getInternalDisplayTokenLocked(), i);
         }
     }
 }
@@ -1627,14 +1693,16 @@
     ATRACE_CALL();
     switch (what) {
         case MessageQueue::INVALIDATE: {
+            if (!updateSetActiveConfigStateMachine()) {
+                break;
+            }
+
             if (mUseScheduler) {
                 // This call is made each time SF wakes up and creates a new frame.
                 mScheduler->incrementFrameCounter();
             }
-            bool frameMissed = !mHadClientComposition &&
-                    mPreviousPresentFence != Fence::NO_FENCE &&
-                    (mPreviousPresentFence->getSignalTime() ==
-                            Fence::SIGNAL_TIME_PENDING);
+            bool frameMissed = !mHadClientComposition && mPreviousPresentFence != Fence::NO_FENCE &&
+                    (mPreviousPresentFence->getStatus() == Fence::Status::Unsignaled);
             mFrameMissedCount += frameMissed;
             ATRACE_INT("FrameMissed", static_cast<int>(frameMissed));
             if (frameMissed) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6546973..a748bc5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -437,7 +437,12 @@
                            const Rect& sourceCrop, float frameScale, bool childrenOnly) override;
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats) override;
     status_t getDisplayConfigs(const sp<IBinder>& displayToken,
-                               Vector<DisplayInfo>* configs) override;
+                               Vector<DisplayInfo>* configs) override {
+        Mutex::Autolock _l(mStateLock);
+        return getDisplayConfigsLocked(displayToken, configs);
+    }
+    status_t getDisplayConfigsLocked(const sp<IBinder>& displayToken, Vector<DisplayInfo>* configs)
+            REQUIRES(mStateLock);
     int getActiveConfig(const sp<IBinder>& displayToken) override;
     status_t getDisplayColorModes(const sp<IBinder>& displayToken,
                                   Vector<ui::ColorMode>* configs) override;
@@ -503,8 +508,18 @@
 
     // called on the main thread in response to initializeDisplays()
     void onInitializeDisplays() REQUIRES(mStateLock);
-    // called on the main thread in response to setActiveConfig()
-    void setActiveConfigInternal(const sp<IBinder>& displayToken, int mode) REQUIRES(mStateLock);
+    // Sets the desired active config bit. It obtains the lock, and sets mDesiredActiveConfig.
+    void setDesiredActiveConfig(const sp<IBinder>& displayToken, int mode) REQUIRES(mStateLock);
+    // Calls setActiveConfig in HWC.
+    void setActiveConfigInHWC();
+    // Once HWC has returned the present fence, this sets the active config and a new refresh
+    // rate in SF. It also triggers HWC vsync.
+    void setActiveConfigInternal() REQUIRES(mStateLock);
+    // Active config is updated on INVALIDATE call in a state machine-like manner. When the
+    // desired config was set, HWC needs to update the pannel on the next refresh, and when
+    // we receive the fence back, we know that the process was complete. It returns whether
+    // the invalidate process should continue.
+    bool updateSetActiveConfigStateMachine();
     // called on the main thread in response to setPowerMode()
     void setPowerModeInternal(const sp<DisplayDevice>& display, int mode) REQUIRES(mStateLock);
 
@@ -1084,6 +1099,34 @@
     sp<Scheduler::ConnectionHandle> mSfConnectionHandle;
     std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
 
+    // The following structs are used for configuring active config state at a desired time,
+    // which is once per vsync at invalidate time.
+    enum SetActiveConfigState {
+        NONE,            // not in progress
+        NOTIFIED_HWC,    // call to HWC has been made
+        REFRESH_RECEIVED // onRefresh was received from HWC
+    };
+    std::atomic<SetActiveConfigState> mSetActiveConfigState = SetActiveConfigState::NONE;
+
+    struct ActiveConfigInfo {
+        int configId;
+        sp<IBinder> displayToken;
+
+        bool operator!=(const ActiveConfigInfo& other) const {
+            if (configId != other.configId) {
+                return true;
+            }
+            return (displayToken != other.displayToken);
+        }
+    };
+    std::mutex mActiveConfigLock;
+    // This bit is set once we start setting the config. We read from this bit during the
+    // process. If at the end, this bit is different than mDesiredActiveConfig, we restart
+    // the process.
+    ActiveConfigInfo mUpcomingActiveConfig; // Always read and written on the main thread.
+    // This bit can be set at any point in time when the system wants the new config.
+    ActiveConfigInfo mDesiredActiveConfig GUARDED_BY(mActiveConfigLock);
+
     /* ------------------------------------------------------------------------ */
     sp<IInputFlinger> mInputFlinger;