Add pending period to DispSync.
When the period is updated due to a config change, cache the period
and don't immediately update the model until we actually observe updated
vsyncs from the hardware.
To make this more reliable, force hardware vsync to be enabled when we
first initiate a refresh rate change.
Also, override offsets with custom offsets while the period is in flux,
so that we don't fall into bad offsets when ramping up to 90hz
Bug: 128848865
Bug: 128860504
Test: systrace
Change-Id: I6aa87ad29b3effce9067a1d54d444023c7362b22
diff --git a/services/surfaceflinger/Scheduler/DispSync.cpp b/services/surfaceflinger/Scheduler/DispSync.cpp
index f72aef1..0738c6d 100644
--- a/services/surfaceflinger/Scheduler/DispSync.cpp
+++ b/services/surfaceflinger/Scheduler/DispSync.cpp
@@ -210,8 +210,7 @@
const nsecs_t baseTime = now - mReferenceTime;
const nsecs_t numPeriodsSinceReference = baseTime / mPeriod;
const nsecs_t predictedReference = mReferenceTime + numPeriodsSinceReference * mPeriod;
- const nsecs_t phaseCorrection = mPhase + listener.mPhase;
- listener.mLastEventTime = predictedReference + phaseCorrection;
+ listener.mLastEventTime = predictedReference + mPhase + listener.mPhase;
// If we're very close in time to the predicted last event time,
// then we need to back up the last event time so that we can
// attempt to fire an event immediately.
@@ -279,7 +278,6 @@
return NO_ERROR;
}
}
-
return BAD_VALUE;
}
@@ -525,21 +523,40 @@
mNumResyncSamples = 0;
}
-bool DispSync::addResyncSample(nsecs_t timestamp) {
+bool DispSync::addResyncSample(nsecs_t timestamp, bool* periodChanged) {
Mutex::Autolock lock(mMutex);
ALOGV("[%s] addResyncSample(%" PRId64 ")", mName, ns2us(timestamp));
- size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
+ *periodChanged = false;
+ const size_t idx = (mFirstResyncSample + mNumResyncSamples) % MAX_RESYNC_SAMPLES;
mResyncSamples[idx] = timestamp;
if (mNumResyncSamples == 0) {
mPhase = 0;
- mReferenceTime = timestamp;
ALOGV("[%s] First resync sample: mPeriod = %" PRId64 ", mPhase = 0, "
"mReferenceTime = %" PRId64,
- mName, ns2us(mPeriod), ns2us(mReferenceTime));
- mThread->updateModel(mPeriod, mPhase, mReferenceTime);
+ mName, ns2us(mPeriod), ns2us(timestamp));
+ } else if (mPendingPeriod > 0) {
+ // mNumResyncSamples > 0, so priorIdx won't overflow
+ const size_t priorIdx = (mFirstResyncSample + mNumResyncSamples - 1) % MAX_RESYNC_SAMPLES;
+ const nsecs_t lastTimestamp = mResyncSamples[priorIdx];
+
+ const nsecs_t observedVsync = std::abs(timestamp - lastTimestamp);
+ if (std::abs(observedVsync - mPendingPeriod) < std::abs(observedVsync - mPeriod)) {
+ // Observed vsync is closer to the pending period, so reset the
+ // model and flush the pending period.
+ resetLocked();
+ mPeriod = mPendingPeriod;
+ mPendingPeriod = 0;
+ if (mTraceDetailedInfo) {
+ ATRACE_INT("DispSync:PendingPeriod", mPendingPeriod);
+ }
+ *periodChanged = true;
+ }
}
+ // Always update the reference time with the most recent timestamp.
+ mReferenceTime = timestamp;
+ mThread->updateModel(mPeriod, mPhase, mReferenceTime);
if (mNumResyncSamples < MAX_RESYNC_SAMPLES) {
mNumResyncSamples++;
@@ -562,7 +579,7 @@
// Check against kErrorThreshold / 2 to add some hysteresis before having to
// resync again
- bool modelLocked = mModelUpdated && mError < (kErrorThreshold / 2);
+ bool modelLocked = mModelUpdated && mError < (kErrorThreshold / 2) && mPendingPeriod == 0;
ALOGV("[%s] addResyncSample returning %s", mName, modelLocked ? "locked" : "unlocked");
return !modelLocked;
}
@@ -594,9 +611,10 @@
void DispSync::setPeriod(nsecs_t period) {
Mutex::Autolock lock(mMutex);
- mPeriod = period;
- mPhase = 0;
- mThread->updateModel(mPeriod, mPhase, mReferenceTime);
+ if (mTraceDetailedInfo) {
+ ATRACE_INT("DispSync:PendingPeriod", period);
+ }
+ mPendingPeriod = period;
}
nsecs_t DispSync::getPeriod() {
diff --git a/services/surfaceflinger/Scheduler/DispSync.h b/services/surfaceflinger/Scheduler/DispSync.h
index de2b874..8f8b8e7 100644
--- a/services/surfaceflinger/Scheduler/DispSync.h
+++ b/services/surfaceflinger/Scheduler/DispSync.h
@@ -49,7 +49,7 @@
virtual void reset() = 0;
virtual bool addPresentFence(const std::shared_ptr<FenceTime>&) = 0;
virtual void beginResync() = 0;
- virtual bool addResyncSample(nsecs_t timestamp) = 0;
+ virtual bool addResyncSample(nsecs_t timestamp, bool* periodChanged) = 0;
virtual void endResync() = 0;
virtual void setPeriod(nsecs_t period) = 0;
virtual nsecs_t getPeriod() = 0;
@@ -119,7 +119,13 @@
// addPresentFence returns true indicating that the model has drifted away
// from the hardware vsync events.
void beginResync() override;
- bool addResyncSample(nsecs_t timestamp) override;
+ // Adds a vsync sample to the dispsync model. The timestamp is the time
+ // of the vsync event that fired. periodChanged will return true if the
+ // vsync period was detected to have changed to mPendingPeriod.
+ //
+ // This method will return true if more vsync samples are needed to lock
+ // down the DispSync model, and false otherwise.
+ bool addResyncSample(nsecs_t timestamp, bool* periodChanged) override;
void endResync() override;
// The setPeriod method sets the vsync event model's period to a specific
@@ -199,6 +205,12 @@
// nanoseconds.
nsecs_t mPeriod;
+ // mPendingPeriod is the proposed period change in nanoseconds.
+ // If mPendingPeriod differs from mPeriod and is nonzero, it will
+ // be flushed to mPeriod when we detect that the hardware switched
+ // vsync frequency.
+ nsecs_t mPendingPeriod = 0;
+
// mPhase is the phase offset of the modeled vsync events. It is the
// number of nanoseconds from time 0 to the first vsync event.
nsecs_t mPhase;
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.h b/services/surfaceflinger/Scheduler/PhaseOffsets.h
index cbcaade..a85660a 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.h
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.h
@@ -68,8 +68,8 @@
void dump(std::string& result) const override;
private:
- Offsets getmDefaultRefreshRateOffsets() { return mDefaultRefreshRateOffsets; }
- Offsets getmHighRefreshRateOffsets() { return mHighRefreshRateOffsets; }
+ Offsets getDefaultRefreshRateOffsets() { return mDefaultRefreshRateOffsets; }
+ Offsets getHighRefreshRateOffsets() { return mHighRefreshRateOffsets; }
std::atomic<RefreshRateConfigs::RefreshRateType> mRefreshRateType =
RefreshRateConfigs::RefreshRateType::DEFAULT;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index b1c39ef..e641c9e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -255,7 +255,6 @@
void Scheduler::setVsyncPeriod(const nsecs_t period) {
std::lock_guard<std::mutex> lock(mHWVsyncLock);
- mPrimaryDispSync->reset();
mPrimaryDispSync->setPeriod(period);
if (!mPrimaryHWVsyncEnabled) {
@@ -265,12 +264,13 @@
}
}
-void Scheduler::addResyncSample(const nsecs_t timestamp) {
+void Scheduler::addResyncSample(const nsecs_t timestamp, bool* periodChanged) {
bool needsHwVsync = false;
+ *periodChanged = false;
{ // Scope for the lock
std::lock_guard<std::mutex> lock(mHWVsyncLock);
if (mPrimaryHWVsyncEnabled) {
- needsHwVsync = mPrimaryDispSync->addResyncSample(timestamp);
+ needsHwVsync = mPrimaryDispSync->addResyncSample(timestamp, periodChanged);
}
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 1318fbb..830d941 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -142,7 +142,9 @@
// Creates a callback for resyncing.
ResyncCallback makeResyncCallback(GetVsyncPeriod&& getVsyncPeriod);
void setRefreshSkipCount(int count);
- void addResyncSample(const nsecs_t timestamp);
+ // Passes a vsync sample to DispSync. periodChange will be true if DipSync
+ // detected that the vsync period changed, and false otherwise.
+ void addResyncSample(const nsecs_t timestamp, bool* periodChanged);
void addPresentFence(const std::shared_ptr<FenceTime>& fenceTime);
void setIgnorePresentFences(bool ignore);
nsecs_t expectedPresentTime();
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.h b/services/surfaceflinger/Scheduler/VSyncModulator.h
index dab2003..1a0de08 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.h
@@ -30,9 +30,10 @@
*/
class VSyncModulator {
private:
- // Number of frames we'll keep the early phase offsets once they are activated. This acts as a
- // low-pass filter in case the client isn't quick enough in sending new transactions.
- const int MIN_EARLY_FRAME_COUNT = 2;
+ // Number of frames we'll keep the early phase offsets once they are activated for a
+ // transaction. This acts as a low-pass filter in case the client isn't quick enough in
+ // sending new transactions.
+ const int MIN_EARLY_FRAME_COUNT_TRANSACTION = 2;
public:
struct Offsets {
@@ -85,7 +86,7 @@
void setTransactionStart(Scheduler::TransactionStart transactionStart) {
if (transactionStart == Scheduler::TransactionStart::EARLY) {
- mRemainingEarlyFrameCount = MIN_EARLY_FRAME_COUNT;
+ mRemainingEarlyFrameCount = MIN_EARLY_FRAME_COUNT_TRANSACTION;
}
// An early transaction stays an early transaction.
@@ -103,6 +104,26 @@
updateOffsets();
}
+ // Called when we send a refresh rate change to hardware composer, so that
+ // we can move into early offsets.
+ void onRefreshRateChangeInitiated() {
+ if (mRefreshRateChangePending) {
+ return;
+ }
+ mRefreshRateChangePending = true;
+ updateOffsets();
+ }
+
+ // Called when we detect from vsync signals that the refresh rate changed.
+ // This way we can move out of early offsets if no longer necessary.
+ void onRefreshRateChangeDetected() {
+ if (!mRefreshRateChangePending) {
+ return;
+ }
+ mRefreshRateChangePending = false;
+ updateOffsets();
+ }
+
void onRefreshed(bool usedRenderEngine) {
bool updateOffsetsNeeded = false;
if (mRemainingEarlyFrameCount > 0) {
@@ -147,8 +168,10 @@
}
Offsets getOffsets() {
+ // Early offsets are used if we're in the middle of a refresh rate
+ // change, or if we recently begin a transaction.
if (mTransactionStart == Scheduler::TransactionStart::EARLY ||
- mRemainingEarlyFrameCount > 0) {
+ mRemainingEarlyFrameCount > 0 || mRefreshRateChangePending) {
return mEarlyOffsets;
} else if (mLastFrameUsedRenderEngine) {
return mEarlyGlOffsets;
@@ -173,6 +196,7 @@
std::atomic<Scheduler::TransactionStart> mTransactionStart =
Scheduler::TransactionStart::NORMAL;
std::atomic<bool> mLastFrameUsedRenderEngine = false;
+ std::atomic<bool> mRefreshRateChangePending = false;
std::atomic<int> mRemainingEarlyFrameCount = 0;
};
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index bd3f156..8bdd439 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -927,6 +927,13 @@
// This will trigger HWC refresh without resetting the idle timer.
repaintEverythingForHWC();
+ // Start receiving vsync samples now, so that we can detect a period
+ // switch.
+ mScheduler->resyncToHardwareVsync(true, getVsyncPeriod());
+ mPhaseOffsets->setRefreshRateType(info.type);
+ const auto [early, gl, late] = mPhaseOffsets->getCurrentOffsets();
+ mVsyncModulator.onRefreshRateChangeInitiated();
+ mVsyncModulator.setPhaseOffsets(early, gl, late);
}
mDesiredActiveConfigChanged = true;
ATRACE_INT("DesiredActiveConfigChanged", mDesiredActiveConfigChanged);
@@ -959,6 +966,7 @@
display->setActiveConfig(mUpcomingActiveConfig.configId);
mScheduler->resyncToHardwareVsync(true, getVsyncPeriod());
+ mPhaseOffsets->setRefreshRateType(mUpcomingActiveConfig.type);
const auto [early, gl, late] = mPhaseOffsets->getCurrentOffsets();
mVsyncModulator.setPhaseOffsets(early, gl, late);
ATRACE_INT("ActiveConfigMode", mUpcomingActiveConfig.configId);
@@ -1403,7 +1411,11 @@
return;
}
- mScheduler->addResyncSample(timestamp);
+ bool periodChanged = false;
+ mScheduler->addResyncSample(timestamp, &periodChanged);
+ if (periodChanged) {
+ mVsyncModulator.onRefreshRateChangeDetected();
+ }
}
void SurfaceFlinger::getCompositorTiming(CompositorTiming* compositorTiming) {
@@ -1436,8 +1448,6 @@
return;
}
- mPhaseOffsets->setRefreshRateType(refreshRate);
-
if (desiredConfigId == display->getActiveConfig()) {
return;
}
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index d83f1bd..a13b888 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -2830,7 +2830,6 @@
struct DispSyncIsSupportedVariant {
static void setupBeginResyncCallExpectations(DisplayTransactionTest* test) {
- EXPECT_CALL(*test->mPrimaryDispSync, reset()).Times(1);
EXPECT_CALL(*test->mPrimaryDispSync, setPeriod(DEFAULT_REFRESH_RATE)).Times(1);
EXPECT_CALL(*test->mPrimaryDispSync, beginResync()).Times(1);
}
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDispSync.h b/services/surfaceflinger/tests/unittests/mock/MockDispSync.h
index afcda5b..12a349d 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockDispSync.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockDispSync.h
@@ -31,7 +31,7 @@
MOCK_METHOD0(reset, void());
MOCK_METHOD1(addPresentFence, bool(const std::shared_ptr<FenceTime>&));
MOCK_METHOD0(beginResync, void());
- MOCK_METHOD1(addResyncSample, bool(nsecs_t));
+ MOCK_METHOD2(addResyncSample, bool(nsecs_t, bool*));
MOCK_METHOD0(endResync, void());
MOCK_METHOD1(setPeriod, void(nsecs_t));
MOCK_METHOD0(getPeriod, nsecs_t());