Tune scheduling a bit, avoid a binder ipc
Don't query running behind if it's not possible to be behind such
as having received a vsync since the last call to swap buffers.
This also avoids an accidental-starvation issue where if surface
flinger was a bit sluggish to dequeue then renderthread would drop
thinking the queue was full.
Also be a bit smarter about tracking if we've already drawn for this
vsync target to avoid producing two frames for the same vsync
Change-Id: Ib266500a693c27000b2e8ea578f111229d75147a
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 7c0f0b6..37cb510 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -27,6 +27,7 @@
#include "renderstate/RenderState.h"
#include "renderstate/Stencil.h"
#include "protos/hwui.pb.h"
+#include "utils/TimeUtils.h"
#if HWUI_NEW_OPS
#include "BakedOpRenderer.h"
@@ -108,6 +109,7 @@
const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
mHaveNewSurface = true;
+ mSwapHistory.clear();
makeCurrent();
} else {
mRenderThread.removeFrameCallback(this);
@@ -217,13 +219,30 @@
return;
}
- int runningBehind = 0;
- // TODO: This query is moderately expensive, investigate adding some sort
- // of fast-path based off when we last called eglSwapBuffers() as well as
- // last vsync time. Or something.
- mNativeWindow->query(mNativeWindow.get(),
- NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
- info.out.canDrawThisFrame = !runningBehind;
+ if (CC_LIKELY(mSwapHistory.size())) {
+ nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
+ const SwapHistory& lastSwap = mSwapHistory.back();
+ int vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
+ // The slight fudge-factor is to deal with cases where
+ // the vsync was estimated due to being slow handling the signal.
+ // See the logic in TimeLord#computeFrameTimeNanos or in
+ // Choreographer.java for details on when this happens
+ if (vsyncDelta < 2_ms) {
+ // Already drew for this vsync pulse, UI draw request missed
+ // the deadline for RT animations
+ info.out.canDrawThisFrame = false;
+ } else if (lastSwap.swapTime < latestVsync) {
+ info.out.canDrawThisFrame = true;
+ } else {
+ // We're maybe behind? Find out for sure
+ int runningBehind = 0;
+ mNativeWindow->query(mNativeWindow.get(),
+ NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+ info.out.canDrawThisFrame = !runningBehind;
+ }
+ } else {
+ info.out.canDrawThisFrame = true;
+ }
if (!info.out.canDrawThisFrame) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -297,7 +316,7 @@
// last frame so there's nothing to union() against
// Therefore we only care about the > 1 case.
if (frame.bufferAge() > 1) {
- if (frame.bufferAge() > (int) mDamageHistory.size()) {
+ if (frame.bufferAge() > (int) mSwapHistory.size()) {
// We don't have enough history to handle this old of a buffer
// Just do a full-draw
dirty.set(0, 0, frame.width(), frame.height());
@@ -305,16 +324,13 @@
// At this point we haven't yet added the latest frame
// to the damage history (happens below)
// So we need to damage
- for (int i = mDamageHistory.size() - 1;
- i > ((int) mDamageHistory.size()) - frame.bufferAge(); i--) {
- dirty.join(mDamageHistory[i]);
+ for (int i = mSwapHistory.size() - 1;
+ i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) {
+ dirty.join(mSwapHistory[i].damage);
}
}
}
- // Add the screen damage to the ring buffer.
- mDamageHistory.next() = screenDirty;
-
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
@@ -445,6 +461,10 @@
if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
setSurface(nullptr);
}
+ SwapHistory& swap = mSwapHistory.next();
+ swap.damage = screenDirty;
+ swap.swapTime = systemTime(CLOCK_MONOTONIC);
+ swap.vsyncTime = mRenderThread.timeLord().latestVsync();
mHaveNewSurface = false;
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 16956e6..db3aeb1 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -150,7 +150,13 @@
EGLSurface mEglSurface = EGL_NO_SURFACE;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;
- RingBuffer<SkRect, 3> mDamageHistory;
+ struct SwapHistory {
+ SkRect damage;
+ nsecs_t vsyncTime;
+ nsecs_t swapTime;
+ };
+
+ RingBuffer<SwapHistory, 3> mSwapHistory;
bool mOpaque;
OpenGLRenderer* mCanvas = nullptr;
diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h
new file mode 100644
index 0000000..8d42d7e
--- /dev/null
+++ b/libs/hwui/utils/TimeUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef UTILS_TIMEUTILS_H
+#define UTILS_TIMEUTILS_H
+
+#include <utils/Timers.h>
+
+namespace android {
+namespace uirenderer {
+
+constexpr nsecs_t operator"" _ms (unsigned long long ms) {
+ return milliseconds_to_nanoseconds(ms);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* UTILS_TIMEUTILS_H */