diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp index 7166c757e602c..8dd5f5f0e561b 100644 --- a/core/jni/android/graphics/AnimatedImageDrawable.cpp +++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp @@ -239,11 +239,6 @@ static jlong AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz* return drawable->byteSize(); } -static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { - auto* drawable = reinterpret_cast(nativePtr); - drawable->markInvisible(); -} - static void AnimatedImageDrawable_nSetMirrored(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jboolean mirrored) { auto* drawable = reinterpret_cast(nativePtr); @@ -264,7 +259,6 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "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 898939edabf0e..4f467d9aabae9 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -31,6 +31,7 @@ import android.graphics.PixelFormat; 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 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { 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 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { 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 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { @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 d7012697a91ed..b37f2cfe7fee4 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -108,6 +108,12 @@ public: // *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 c529f8773bbd2..007961a6bedbf 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -22,13 +22,12 @@ #include #include #include -#include namespace android { AnimatedImageDrawable::AnimatedImageDrawable(sk_sp animatedImage, size_t bytesUsed) : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) { - mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration(); + mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration()); } void AnimatedImageDrawable::syncProperties() { @@ -62,28 +61,42 @@ bool AnimatedImageDrawable::nextSnapshotReady() const { } // 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 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { Snapshot snap; { std::unique_lock lock{mImageLock}; - snap.mDuration = mSkAnimatedImage->decodeNextFrame(); + snap.mDurationMS = mSkAnimatedImage->decodeNextFrame(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } @@ -105,7 +118,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { 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 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { canvas->scale(-1, 1); } - mDidDraw = true; - const bool starting = mStarting; mStarting = false; @@ -157,12 +168,12 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { 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 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } } -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 @@ double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { // 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 a92b62db14f0a..115c45ae4bcb1 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -50,12 +51,15 @@ public: AnimatedImageDrawable(sk_sp 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 @@ public: 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 @@ public: mEndListener = std::move(listener); } - void markInvisible() { mDidDraw = false; } - struct Snapshot { sk_sp mPic; - int mDuration; + int mDurationMS; Snapshot() = default; @@ -124,16 +128,13 @@ private: 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 aa14699ae4c20..82179a37f5bee 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -93,12 +93,18 @@ bool SkiaDisplayList::prepareListAndChildren( 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 f4d8051466f0f..2ddf55be75746 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -140,6 +140,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* IContextFactory* contextFactory, std::unique_ptr renderPipeline) : mRenderThread(thread) + , mGenerationID(0) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) , mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo()) @@ -196,6 +197,7 @@ void CanvasContext::setSurface(sp&& surface) { mSwapHistory.clear(); } else { mRenderThread.removeFrameCallback(this); + mGenerationID++; } } @@ -204,6 +206,7 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { } bool CanvasContext::pauseSurface() { + mGenerationID++; return mRenderThread.removeFrameCallback(this); } @@ -211,6 +214,7 @@ void CanvasContext::setStopped(bool stopped) { if (mStopped != stopped) { mStopped = stopped; if (mStopped) { + mGenerationID++; mRenderThread.removeFrameCallback(this); mRenderPipeline->onStop(); } else if (mIsDirty && hasSurface()) { @@ -383,6 +387,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy 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 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // 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::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy 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 c2cc72a6917f1..e52b6447a611d 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -213,6 +213,9 @@ private: // 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;