Early wake-up for transitions (1/2)
On some devices it's very likely that we fall into GL comp during
app transitions. However, SF offsets are chosen in a way such that
the time to finish a frame is just too tight to be completely jank
free when hitting GL composition in SurfaceFlinger. Thus, we
introduce the concept of a separate early offset, and wakeup
SurfaceFlinger at that time if we think that hitting GL comp is
likely, or we already hit GL comp in the last frame.
Test: Open app, check vsync offsets in systrace
Test: Open many dialogs/apps to fall into GPU comp.
Bug: 75985430
Change-Id: Ie17e30c4575359fa11bb8912f68dcafe3e569ddb
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index bbf681e..63560c4 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -101,7 +101,8 @@
SurfaceComposerClient::Transaction::Transaction(const Transaction& other) :
mForceSynchronous(other.mForceSynchronous),
mTransactionNestCount(other.mTransactionNestCount),
- mAnimation(other.mAnimation) {
+ mAnimation(other.mAnimation),
+ mEarlyWakeup(other.mEarlyWakeup) {
mDisplayStates = other.mDisplayStates;
mComposerStates = other.mComposerStates;
}
@@ -157,9 +158,13 @@
if (mAnimation) {
flags |= ISurfaceComposer::eAnimation;
}
+ if (mEarlyWakeup) {
+ flags |= ISurfaceComposer::eEarlyWakeup;
+ }
mForceSynchronous = false;
mAnimation = false;
+ mEarlyWakeup = false;
sf->setTransactionState(composerStates, displayStates, flags);
mStatus = NO_ERROR;
@@ -185,6 +190,10 @@
mAnimation = true;
}
+void SurfaceComposerClient::Transaction::setEarlyWakeup() {
+ mEarlyWakeup = true;
+}
+
layer_state_t* SurfaceComposerClient::Transaction::getLayerState(const sp<SurfaceControl>& sc) {
if (mComposerStates.count(sc) == 0) {
// we don't have it, add an initialized layer_state to our list
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 3591090..e401572 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -61,6 +61,11 @@
enum {
eSynchronous = 0x01,
eAnimation = 0x02,
+
+ // Indicates that this transaction will likely result in a lot of layers being composed, and
+ // thus, SurfaceFlinger should wake-up earlier to avoid missing frame deadlines. In this
+ // case SurfaceFlinger will wake up at (sf vsync offset - debug.sf.early_phase_offset_ns)
+ eEarlyWakeup = 0x04
};
enum {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index ffc22f6..377fe68 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -154,6 +154,7 @@
uint32_t mForceSynchronous = 0;
uint32_t mTransactionNestCount = 0;
bool mAnimation = false;
+ bool mEarlyWakeup = false;
int mStatus = NO_ERROR;
@@ -273,6 +274,7 @@
const Rect& displayRect);
void setDisplaySize(const sp<IBinder>& token, uint32_t width, uint32_t height);
void setAnimationTransaction();
+ void setEarlyWakeup();
};
status_t destroySurface(const sp<IBinder>& id);
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 1cbd602..b53c73a 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -764,6 +764,7 @@
}
void BufferLayer::drawWithOpenGL(const RenderArea& renderArea, bool useIdentityTransform) const {
+ ATRACE_CALL();
const State& s(getDrawingState());
computeGeometry(renderArea, getBE().mMesh, useIdentityTransform);
diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp
index 6b4f5db..87333d0 100644
--- a/services/surfaceflinger/BufferLayerConsumer.cpp
+++ b/services/surfaceflinger/BufferLayerConsumer.cpp
@@ -356,6 +356,7 @@
}
status_t BufferLayerConsumer::bindTextureImageLocked() {
+ ATRACE_CALL();
mRE.checkErrors();
if (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT && mCurrentTextureImage == nullptr) {
diff --git a/services/surfaceflinger/DispSync.cpp b/services/surfaceflinger/DispSync.cpp
index 9e01fd0..7acbd11 100644
--- a/services/surfaceflinger/DispSync.cpp
+++ b/services/surfaceflinger/DispSync.cpp
@@ -209,6 +209,28 @@
return BAD_VALUE;
}
+ status_t changePhaseOffset(DispSync::Callback* callback, nsecs_t phase) {
+ if (kTraceDetailedInfo) ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+
+ for (size_t i = 0; i < mEventListeners.size(); i++) {
+ if (mEventListeners[i].mCallback == callback) {
+ EventListener& listener = mEventListeners.editItemAt(i);
+ const nsecs_t oldPhase = listener.mPhase;
+ listener.mPhase = phase;
+
+ // Pretend that the last time this event was handled at the same frame but with the
+ // new offset to allow for a seamless offset change without double-firing or
+ // skipping.
+ listener.mLastEventTime -= (oldPhase - phase);
+ mCond.signal();
+ return NO_ERROR;
+ }
+ }
+
+ return BAD_VALUE;
+ }
+
// This method is only here to handle the !SurfaceFlinger::hasSyncFramework
// case.
bool hasAnyEventListeners() {
@@ -487,6 +509,11 @@
return mThread->removeEventListener(callback);
}
+status_t DispSync::changePhaseOffset(Callback* callback, nsecs_t phase) {
+ Mutex::Autolock lock(mMutex);
+ return mThread->changePhaseOffset(callback, phase);
+}
+
void DispSync::setPeriod(nsecs_t period) {
Mutex::Autolock lock(mMutex);
mPeriod = period;
diff --git a/services/surfaceflinger/DispSync.h b/services/surfaceflinger/DispSync.h
index 9336f4d..077256a 100644
--- a/services/surfaceflinger/DispSync.h
+++ b/services/surfaceflinger/DispSync.h
@@ -113,6 +113,11 @@
// DispSync object.
status_t removeEventListener(Callback* callback);
+ // changePhaseOffset changes the phase offset of an already-registered event callback. The
+ // method will make sure that there is no skipping or double-firing on the listener per frame,
+ // even when changing the offsets multiple times.
+ status_t changePhaseOffset(Callback* callback, nsecs_t phase);
+
// computeNextRefresh computes when the next refresh is expected to begin.
// The periodOffset value can be used to move forward or backward; an
// offset of zero is the next refresh, -1 is the previous refresh, 1 is
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
index 1fc3100..0fb3d28 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
@@ -293,6 +293,7 @@
}
void GLES20RenderEngine::drawMesh(const Mesh& mesh) {
+ ATRACE_CALL();
if (mesh.getTexCoordsSize()) {
glEnableVertexAttribArray(Program::texCoords);
glVertexAttribPointer(Program::texCoords, mesh.getTexCoordsSize(), GL_FLOAT, GL_FALSE,
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 18b2f68..50495ec 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -296,6 +296,12 @@
auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize));
mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize;
+ property_get("debug.sf.early_phase_offset_ns", value, "0");
+ const int earlyWakeupOffsetOffsetNs = atoi(value);
+ ALOGI_IF(earlyWakeupOffsetOffsetNs != 0, "Enabling separate early offset");
+ mVsyncModulator.setPhaseOffsets(sfVsyncPhaseOffsetNs - earlyWakeupOffsetOffsetNs,
+ sfVsyncPhaseOffsetNs);
+
// We should be reading 'persist.sys.sf.color_saturation' here
// but since /data may be encrypted, we need to wait until after vold
// comes online to attempt to read the property. The property is
@@ -522,19 +528,10 @@
return;
}
- // Remove the listener with the old offset
- status_t err = mDispSync->removeEventListener(
- static_cast<DispSync::Callback*>(this));
+ status_t err = mDispSync->changePhaseOffset(static_cast<DispSync::Callback*>(this),
+ mPhaseOffset);
if (err != NO_ERROR) {
- ALOGE("error unregistering vsync callback: %s (%d)",
- strerror(-err), err);
- }
-
- // Add a listener with the new offset
- err = mDispSync->addEventListener(mName, mPhaseOffset,
- static_cast<DispSync::Callback*>(this));
- if (err != NO_ERROR) {
- ALOGE("error registering vsync callback: %s (%d)",
+ ALOGE("error changing vsync offset: %s (%d)",
strerror(-err), err);
}
}
@@ -623,6 +620,7 @@
mSFEventThread = std::make_unique<impl::EventThread>(mSfEventThreadSource.get(), *this, true,
"sfEventThread");
mEventQueue->setEventThread(mSFEventThread.get());
+ mVsyncModulator.setEventThread(mSFEventThread.get());
// Get a RenderEngine for the given display / config (can't fail)
getBE().mRenderEngine =
@@ -1498,6 +1496,7 @@
mHadClientComposition = mHadClientComposition ||
getBE().mHwc->hasClientComposition(displayDevice->getHwcDisplayId());
}
+ mVsyncModulator.setLastFrameUsedRenderEngine(mHadClientComposition);
mLayersWithQueuedFrames.clear();
}
@@ -2122,6 +2121,7 @@
// with mStateLock held to guarantee that mCurrentState won't change
// until the transaction is committed.
+ mVsyncModulator.setTransactionStart(VSyncModulator::TransactionStart::NORMAL);
transactionFlags = getTransactionFlags(eTransactionMask);
handleTransactionLocked(transactionFlags);
@@ -3070,7 +3070,13 @@
}
uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) {
+ return setTransactionFlags(flags, VSyncModulator::TransactionStart::NORMAL);
+}
+
+uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags,
+ VSyncModulator::TransactionStart transactionStart) {
uint32_t old = android_atomic_or(flags, &mTransactionFlags);
+ mVsyncModulator.setTransactionStart(transactionStart);
if ((old & flags)==0) { // wake the server up
signalTransaction();
}
@@ -3159,7 +3165,10 @@
}
// this triggers the transaction
- setTransactionFlags(transactionFlags);
+ const auto start = (flags & eEarlyWakeup)
+ ? VSyncModulator::TransactionStart::EARLY
+ : VSyncModulator::TransactionStart::NORMAL;
+ setTransactionFlags(transactionFlags, start);
// if this is a synchronous transaction, wait for it to take effect
// before returning.
@@ -4099,9 +4108,9 @@
colorizer.bold(result);
result.append("DispSync configuration: ");
colorizer.reset(result);
- result.appendFormat("app phase %" PRId64 " ns, sf phase %" PRId64 " ns, "
- "present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
- vsyncPhaseOffsetNs, sfVsyncPhaseOffsetNs,
+ result.appendFormat("app phase %" PRId64 " ns, sf phase %" PRId64 " ns, early sf phase %" PRId64
+ " ns, present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
+ vsyncPhaseOffsetNs, sfVsyncPhaseOffsetNs, mVsyncModulator.getEarlyPhaseOffset(),
dispSyncPresentTimeOffset, activeConfig->getVsyncPeriod());
result.append("\n");
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 477ace5..01f1ac3 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -33,6 +33,7 @@
#include <utils/RefBase.h>
#include <utils/SortedVector.h>
#include <utils/threads.h>
+#include <utils/Trace.h>
#include <ui/FenceTime.h>
#include <ui/PixelFormat.h>
@@ -55,6 +56,7 @@
#include "Barrier.h"
#include "DisplayDevice.h"
#include "DispSync.h"
+#include "EventThread.h"
#include "FrameTracker.h"
#include "LayerStats.h"
#include "LayerVector.h"
@@ -63,6 +65,7 @@
#include "SurfaceTracing.h"
#include "StartPropertySetThread.h"
#include "LayerBE.h"
+#include "VSyncModulator.h"
#include "DisplayHardware/HWC2.h"
#include "DisplayHardware/HWComposer.h"
@@ -494,6 +497,7 @@
uint32_t peekTransactionFlags();
// Can only be called from the main thread or with mStateLock held
uint32_t setTransactionFlags(uint32_t flags);
+ uint32_t setTransactionFlags(uint32_t flags, VSyncModulator::TransactionStart transactionStart);
void commitTransaction();
bool containsAnyInvalidClientState(const Vector<ComposerState>& states);
uint32_t setClientStateLocked(const ComposerState& composerState);
@@ -767,6 +771,8 @@
std::unique_ptr<EventControlThread> mEventControlThread;
sp<IBinder> mBuiltinDisplays[DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES];
+ VSyncModulator mVsyncModulator;
+
// Can only accessed from the main thread, these members
// don't need synchronization
State mDrawingState{LayerVector::StateSet::Drawing};
diff --git a/services/surfaceflinger/VSyncModulator.h b/services/surfaceflinger/VSyncModulator.h
new file mode 100644
index 0000000..3126deb
--- /dev/null
+++ b/services/surfaceflinger/VSyncModulator.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/Errors.h>
+
+#include <mutex>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+/*
+ * Modulates the vsync-offsets depending on current SurfaceFlinger state.
+ */
+class VSyncModulator {
+public:
+
+ enum TransactionStart {
+ EARLY,
+ NORMAL
+ };
+
+ // Sets the phase offsets
+ //
+ // early: the phase offset when waking up early. May be the same as late, in which case we don't
+ // shift offsets.
+ // late: the regular sf phase offset.
+ void setPhaseOffsets(nsecs_t early, nsecs_t late) {
+ mEarlyPhaseOffset = early;
+ mLatePhaseOffset = late;
+ mPhaseOffset = late;
+ }
+
+ nsecs_t getEarlyPhaseOffset() const {
+ return mEarlyPhaseOffset;
+ }
+
+ void setEventThread(EventThread* eventThread) {
+ mEventThread = eventThread;
+ }
+
+ void setTransactionStart(TransactionStart transactionStart) {
+ if (transactionStart == mTransactionStart) return;
+ mTransactionStart = transactionStart;
+ updatePhaseOffsets();
+ }
+
+ void setLastFrameUsedRenderEngine(bool re) {
+ if (re == mLastFrameUsedRenderEngine) return;
+ mLastFrameUsedRenderEngine = re;
+ updatePhaseOffsets();
+ }
+
+private:
+
+ void updatePhaseOffsets() {
+
+ // Do not change phase offsets if disabled.
+ if (mEarlyPhaseOffset == mLatePhaseOffset) return;
+
+ if (mTransactionStart == TransactionStart::EARLY || mLastFrameUsedRenderEngine) {
+ if (mPhaseOffset != mEarlyPhaseOffset) {
+ if (mEventThread) {
+ mEventThread->setPhaseOffset(mEarlyPhaseOffset);
+ }
+ mPhaseOffset = mEarlyPhaseOffset;
+ }
+ } else {
+ if (mPhaseOffset != mLatePhaseOffset) {
+ if (mEventThread) {
+ mEventThread->setPhaseOffset(mLatePhaseOffset);
+ }
+ mPhaseOffset = mLatePhaseOffset;
+ }
+ }
+ }
+
+ nsecs_t mLatePhaseOffset = 0;
+ nsecs_t mEarlyPhaseOffset = 0;
+ EventThread* mEventThread = nullptr;
+ std::atomic<nsecs_t> mPhaseOffset = 0;
+ std::atomic<TransactionStart> mTransactionStart = TransactionStart::NORMAL;
+ std::atomic<bool> mLastFrameUsedRenderEngine = false;
+};
+
+} // namespace android