Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4afdd1ca authored by Leon Scroggins III's avatar Leon Scroggins III
Browse files

AnimatedImageDrawable: Eliminate unnecessary calls to redraw

Bug: 78866720
Test: Manual + systrace; existing CTS

Previously, we set hasAnimations to true when the AnimatedImageDrawable,
so that we would get a call to redraw. But if the image does not need to
show its next frame yet, the redraw was unnecessary.

Instead, add a new field to TreeInfo::Out, representing the delay time
until the image will need to be redrawn - i.e. when the duration of the
current frame has passed. Each call to prepareTree will post at most one
message to redraw, in time for the earliest animated image to be
redrawn. Post the message for one rendered frame ahead of time, so that
when it is time to show the next frame, the image has already gotten the
message to update.

On a screen with a single animated image, this drops the number of calls
to dispatchFrameCallbacks to as infrequent as possible. It is called
only when we need to draw a new frame of the image. On a screen with
multiple animated images, the calls may be redundant, but they will not
be more frequent than they would be without this change.

Switch to nsecs_t and systemTime internally, matching the rest of HWUI.

Remove mDidDraw and related. Its purpose was to prevent advancing the
animation while the image is not being drawn. But it isn't really
necessary. If it's not drawn, onDraw is not called, which is where we
trigger decoding. And onDraw already has a defense against getting too
far ahead - if its timer indicates that it should skip a frame or show
it very briefly, it will back up its timer. More importantly, mDidDraw
caused a bug, when combined with less frequent redraws. If the display
list containing the drawable doesn't need to be redrawn for other
reasons, the drawable's timer never advanced, so its animation stopped.

Fix software drawing. Compute the milliseconds in the future to draw the
next frame, and add that to SystemClock.uptimeMillis() to compute the
time to pass to scheduleSelf.

Change-Id: I13aab49922fa300f73b327be25561d7120c09ec4
parent 1936031c
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -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<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 @@ 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 },
};

+2 −20
Original line number Diff line number Diff line
@@ -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);
}
+6 −0
Original line number Diff line number Diff line
@@ -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
+47 −36
Original line number Diff line number Diff line
@@ -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 @@ 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;
    }

    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
+14 −13
Original line number Diff line number Diff line
@@ -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 @@ public:
    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 @@ 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<SkPicture> 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.
Loading