Merge "AnimatedImageDrawable: Eliminate unnecessary calls to redraw" into pi-dev
am: a2113aa4ad
Change-Id: Ice3ac92ebd4ae68d1fe9b8ae46f86e320058d169
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 7166c75..8dd5f5f 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -239,11 +239,6 @@
return drawable->byteSize();
}
-static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
- auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->markInvisible();
-}
-
static void AnimatedImageDrawable_nSetMirrored(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jboolean mirrored) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
@@ -264,7 +259,6 @@
{ "nSetRepeatCount", "(JI)V", (void*) AnimatedImageDrawable_nSetRepeatCount },
{ "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
{ "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize },
- { "nMarkInvisible", "(J)V", (void*) AnimatedImageDrawable_nMarkInvisible },
{ "nSetMirrored", "(JZ)V", (void*) AnimatedImageDrawable_nSetMirrored },
};
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 898939e..4f467d9 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -348,7 +349,7 @@
if (mRunnable == null) {
mRunnable = this::invalidateSelf;
}
- scheduleSelf(mRunnable, nextUpdate);
+ scheduleSelf(mRunnable, nextUpdate + SystemClock.uptimeMillis());
} else if (nextUpdate == FINISHED) {
// This means the animation was drawn in software mode and ended.
postOnAnimationEnd();
@@ -430,23 +431,6 @@
return mState.mAutoMirrored;
}
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- if (!super.setVisible(visible, restart)) {
- return false;
- }
-
- if (mState.mNativePtr == 0) {
- throw new IllegalStateException("called setVisible on empty AnimatedImageDrawable");
- }
-
- if (!visible) {
- nMarkInvisible(mState.mNativePtr);
- }
-
- return true;
- }
-
// Animatable overrides
/**
* Return whether the animation is currently running.
@@ -616,7 +600,5 @@
@FastNative
private static native long nNativeByteSize(long nativePtr);
@FastNative
- private static native void nMarkInvisible(long nativePtr);
- @FastNative
private static native void nSetMirrored(long nativePtr, boolean mirror);
}
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index d701269..b37f2cf 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -108,6 +108,12 @@
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
bool canDrawThisFrame = true;
+ // Sentinel for animatedImageDelay meaning there is no need to post such
+ // a message.
+ static constexpr nsecs_t kNoAnimatedImageDelay = -1;
+ // This is used to post a message to redraw when it is time to draw the
+ // next frame of an AnimatedImageDrawable.
+ nsecs_t animatedImageDelay = kNoAnimatedImageDelay;
} out;
// This flag helps to disable projection for receiver nodes that do not have any backward
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index c529f87..007961a 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -22,13 +22,12 @@
#include <SkPicture.h>
#include <SkRefCnt.h>
#include <SkTLazy.h>
-#include <SkTime.h>
namespace android {
AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed)
: mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
- mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration();
+ mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration());
}
void AnimatedImageDrawable::syncProperties() {
@@ -62,28 +61,42 @@
}
// Only called on the RenderThread while UI thread is locked.
-bool AnimatedImageDrawable::isDirty() {
- const double currentTime = SkTime::GetMSecs();
- const double lastWallTime = mLastWallTime;
+bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) {
+ *outDelay = 0;
+ const nsecs_t currentTime = systemTime(CLOCK_MONOTONIC);
+ const nsecs_t lastWallTime = mLastWallTime;
mLastWallTime = currentTime;
if (!mRunning) {
- mDidDraw = false;
return false;
}
std::unique_lock lock{mSwapLock};
- if (mDidDraw) {
- mCurrentTime += currentTime - lastWallTime;
- mDidDraw = false;
- }
+ mCurrentTime += currentTime - lastWallTime;
if (!mNextSnapshot.valid()) {
// Need to trigger onDraw in order to start decoding the next frame.
+ *outDelay = mTimeToShowNextSnapshot - mCurrentTime;
return true;
}
- return nextSnapshotReady() && mCurrentTime >= mTimeToShowNextSnapshot;
+ if (mTimeToShowNextSnapshot > mCurrentTime) {
+ *outDelay = mTimeToShowNextSnapshot - mCurrentTime;
+ } else if (nextSnapshotReady()) {
+ // We have not yet updated mTimeToShowNextSnapshot. Read frame duration
+ // directly from mSkAnimatedImage.
+ lock.unlock();
+ std::unique_lock imageLock{mImageLock};
+ *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration());
+ return true;
+ } else {
+ // The next snapshot has not yet been decoded, but we've already passed
+ // time to draw it. There's not a good way to know when decoding will
+ // finish, so request an update immediately.
+ *outDelay = 0;
+ }
+
+ return false;
}
// Only called on the AnimatedImageThread.
@@ -91,7 +104,7 @@
Snapshot snap;
{
std::unique_lock lock{mImageLock};
- snap.mDuration = mSkAnimatedImage->decodeNextFrame();
+ snap.mDurationMS = mSkAnimatedImage->decodeNextFrame();
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
}
@@ -105,7 +118,7 @@
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
- snap.mDuration = mSkAnimatedImage->currentFrameDuration();
+ snap.mDurationMS = mSkAnimatedImage->currentFrameDuration();
}
return snap;
@@ -127,8 +140,6 @@
canvas->scale(-1, 1);
}
- mDidDraw = true;
-
const bool starting = mStarting;
mStarting = false;
@@ -157,12 +168,12 @@
std::unique_lock lock{mSwapLock};
if (mCurrentTime >= mTimeToShowNextSnapshot) {
mSnapshot = mNextSnapshot.get();
- const double timeToShowCurrentSnap = mTimeToShowNextSnapshot;
- if (mSnapshot.mDuration == SkAnimatedImage::kFinished) {
+ const nsecs_t timeToShowCurrentSnap = mTimeToShowNextSnapshot;
+ if (mSnapshot.mDurationMS == SkAnimatedImage::kFinished) {
finalFrame = true;
mRunning = false;
} else {
- mTimeToShowNextSnapshot += mSnapshot.mDuration;
+ mTimeToShowNextSnapshot += ms2ns(mSnapshot.mDurationMS);
if (mCurrentTime >= mTimeToShowNextSnapshot) {
// This would mean showing the current frame very briefly. It's
// possible that not being displayed for a time resulted in
@@ -192,7 +203,7 @@
}
}
-double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
+int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
SkAutoCanvasRestore acr(canvas, false);
if (mStagingProperties.mAlpha != SK_AlphaOPAQUE || mStagingProperties.mColorFilter.get()) {
SkPaint paint;
@@ -211,69 +222,69 @@
// to redraw.
std::unique_lock lock{mImageLock};
canvas->drawDrawable(mSkAnimatedImage.get());
- return 0.0;
+ return 0;
}
if (mStarting) {
mStarting = false;
- double duration = 0.0;
+ int durationMS = 0;
{
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
- duration = mSkAnimatedImage->currentFrameDuration();
+ durationMS = mSkAnimatedImage->currentFrameDuration();
}
{
std::unique_lock lock{mSwapLock};
- mLastWallTime = 0.0;
- mTimeToShowNextSnapshot = duration;
+ mLastWallTime = 0;
+ // The current time will be added later, below.
+ mTimeToShowNextSnapshot = ms2ns(durationMS);
}
}
bool update = false;
{
- const double currentTime = SkTime::GetMSecs();
+ const nsecs_t currentTime = systemTime(CLOCK_MONOTONIC);
std::unique_lock lock{mSwapLock};
// mLastWallTime starts off at 0. If it is still 0, just update it to
// the current time and avoid updating
- if (mLastWallTime == 0.0) {
+ if (mLastWallTime == 0) {
mCurrentTime = currentTime;
// mTimeToShowNextSnapshot is already set to the duration of the
// first frame.
mTimeToShowNextSnapshot += currentTime;
- } else if (mRunning && mDidDraw) {
+ } else if (mRunning) {
mCurrentTime += currentTime - mLastWallTime;
update = mCurrentTime >= mTimeToShowNextSnapshot;
}
mLastWallTime = currentTime;
}
- double duration = 0.0;
+ int durationMS = 0;
{
std::unique_lock lock{mImageLock};
if (update) {
- duration = mSkAnimatedImage->decodeNextFrame();
+ durationMS = mSkAnimatedImage->decodeNextFrame();
}
canvas->drawDrawable(mSkAnimatedImage.get());
}
- mDidDraw = true;
-
std::unique_lock lock{mSwapLock};
if (update) {
- if (duration == SkAnimatedImage::kFinished) {
+ if (durationMS == SkAnimatedImage::kFinished) {
mRunning = false;
- return duration;
+ return SkAnimatedImage::kFinished;
}
- const double timeToShowCurrentSnapshot = mTimeToShowNextSnapshot;
- mTimeToShowNextSnapshot += duration;
+ const nsecs_t timeToShowCurrentSnapshot = mTimeToShowNextSnapshot;
+ mTimeToShowNextSnapshot += ms2ns(durationMS);
if (mCurrentTime >= mTimeToShowNextSnapshot) {
// As in onDraw, prevent speedy catch-up behavior.
mCurrentTime = timeToShowCurrentSnapshot;
}
}
- return mTimeToShowNextSnapshot;
+
+ return ns2ms(mTimeToShowNextSnapshot - mCurrentTime);
}
} // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index a92b62d..115c45a 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -19,6 +19,7 @@
#include <cutils/compiler.h>
#include <utils/Macros.h>
#include <utils/RefBase.h>
+#include <utils/Timers.h>
#include <SkAnimatedImage.h>
#include <SkCanvas.h>
@@ -50,12 +51,15 @@
AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed);
/**
- * This updates the internal time and returns true if the animation needs
- * to be redrawn.
+ * This updates the internal time and returns true if the image needs
+ * to be redrawn this frame.
*
* This is called on RenderThread, while the UI thread is locked.
+ *
+ * @param outDelay Nanoseconds in the future when the following frame
+ * will need to be drawn. 0 if not running.
*/
- bool isDirty();
+ bool isDirty(nsecs_t* outDelay);
int getStagingAlpha() const { return mStagingProperties.mAlpha; }
void setStagingAlpha(int alpha) { mStagingProperties.mAlpha = alpha; }
@@ -68,7 +72,9 @@
virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); }
// Draw to software canvas, and return time to next draw.
- double drawStaging(SkCanvas* canvas);
+ // 0 means the animation is not running.
+ // -1 means the animation advanced to the final frame.
+ int drawStaging(SkCanvas* canvas);
// Returns true if the animation was started; false otherwise (e.g. it was
// already running)
@@ -84,11 +90,9 @@
mEndListener = std::move(listener);
}
- void markInvisible() { mDidDraw = false; }
-
struct Snapshot {
sk_sp<SkPicture> mPic;
- int mDuration;
+ int mDurationMS;
Snapshot() = default;
@@ -124,16 +128,13 @@
bool nextSnapshotReady() const;
// When to switch from mSnapshot to mNextSnapshot.
- double mTimeToShowNextSnapshot = 0.0;
+ nsecs_t mTimeToShowNextSnapshot = 0;
// The current time for the drawable itself.
- double mCurrentTime = 0.0;
+ nsecs_t mCurrentTime = 0;
// The wall clock of the last time we called isDirty.
- double mLastWallTime = 0.0;
-
- // Whether we drew since the last call to isDirty.
- bool mDidDraw = false;
+ nsecs_t mLastWallTime = 0;
// Locked when assigning snapshots and times. Operations while this is held
// should be short.
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index aa14699..82179a3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -93,12 +93,18 @@
bool isDirty = false;
for (auto& animatedImage : mAnimatedImages) {
+ nsecs_t timeTilNextFrame = TreeInfo::Out::kNoAnimatedImageDelay;
// If any animated image in the display list needs updated, then damage the node.
- if (animatedImage->isDirty()) {
+ if (animatedImage->isDirty(&timeTilNextFrame)) {
isDirty = true;
}
- if (animatedImage->isRunning()) {
- info.out.hasAnimations = true;
+
+ if (animatedImage->isRunning() &&
+ timeTilNextFrame != TreeInfo::Out::kNoAnimatedImageDelay) {
+ auto& delay = info.out.animatedImageDelay;
+ if (delay == TreeInfo::Out::kNoAnimatedImageDelay || timeTilNextFrame < delay) {
+ delay = timeTilNextFrame;
+ }
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f4d8051..2ddf55b 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -140,6 +140,7 @@
IContextFactory* contextFactory,
std::unique_ptr<IRenderPipeline> renderPipeline)
: mRenderThread(thread)
+ , mGenerationID(0)
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
, mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo())
@@ -196,6 +197,7 @@
mSwapHistory.clear();
} else {
mRenderThread.removeFrameCallback(this);
+ mGenerationID++;
}
}
@@ -204,6 +206,7 @@
}
bool CanvasContext::pauseSurface() {
+ mGenerationID++;
return mRenderThread.removeFrameCallback(this);
}
@@ -211,6 +214,7 @@
if (mStopped != stopped) {
mStopped = stopped;
if (mStopped) {
+ mGenerationID++;
mRenderThread.removeFrameCallback(this);
mRenderPipeline->onStop();
} else if (mIsDirty && hasSurface()) {
@@ -383,6 +387,7 @@
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
}
+ bool postedFrameCallback = false;
if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
info.out.requiresUiRedraw = true;
@@ -391,6 +396,24 @@
// If animationsNeedsRedraw is set don't bother posting for an RT anim
// as we will just end up fighting the UI thread.
mRenderThread.postFrameCallback(this);
+ postedFrameCallback = true;
+ }
+ }
+
+ if (!postedFrameCallback &&
+ info.out.animatedImageDelay != TreeInfo::Out::kNoAnimatedImageDelay) {
+ // Subtract the time of one frame so it can be displayed on time.
+ const nsecs_t kFrameTime = mRenderThread.timeLord().frameIntervalNanos();
+ if (info.out.animatedImageDelay <= kFrameTime) {
+ mRenderThread.postFrameCallback(this);
+ } else {
+ const auto delay = info.out.animatedImageDelay - kFrameTime;
+ int genId = mGenerationID;
+ mRenderThread.queue().postDelayed(delay, [this, genId]() {
+ if (mGenerationID == genId) {
+ mRenderThread.postFrameCallback(this);
+ }
+ });
}
}
}
@@ -398,6 +421,7 @@
void CanvasContext::stopDrawing() {
mRenderThread.removeFrameCallback(this);
mAnimationContext->pauseAnimators();
+ mGenerationID++;
}
void CanvasContext::notifyFramePending() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index c2cc72a..e52b644 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -213,6 +213,9 @@
// stopped indicates the CanvasContext will reject actual redraw operations,
// and defer repaint until it is un-stopped
bool mStopped = false;
+ // Incremented each time the CanvasContext is stopped. Used to ignore
+ // delayed messages that are triggered after stopping.
+ int mGenerationID;
// CanvasContext is dirty if it has received an update that it has not
// painted onto its surface.
bool mIsDirty = false;