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

Commit 5b7f426f authored by Leon Scroggins III's avatar Leon Scroggins III
Browse files

Use a separate thread to decode AnimatedImageDrawable

Bug: 63908092
Test: Manual: Ie18811ba29a1db163aca08472b04ae185e9344f0

Depends on https://skia-review.googlesource.com/#/c/skia/+/101544. That
change removes the Skia class's time checks, and leaving it up to the
client to keep track of the time. In this case, the client wants to keep
track of the time because it only wants to update while it is being
drawn. If it goes off screen (for example), it will just resume where it
left off when it returns on screen. This allows for smooth animations.

If an AnimatedImageDrawable is being drawn to a SkiaRecordingCanvas,
decode on the new (lazily-created) AnimatedImageThread.

When running, always decode one frame ahead on the AnimatedImageThread
so that it will be ready when it is time to display.

During prepareTree, update the time and check whether there is a new
frame ready to draw or the next frame needs to be decoded. In either
case, return true. The next frame to be decoded will be triggered by
onDraw.

Change-Id: If447976e9df417060a950f658dbca9cf7980dd02
parent 86356ec7
Loading
Loading
Loading
Loading
+12 −6
Original line number Diff line number Diff line
@@ -16,16 +16,16 @@

#include "GraphicsJNI.h"
#include "ImageDecoder.h"
#include "core_jni_helpers.h"
#include "Utils.h"
#include "core_jni_helpers.h"

#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
#include <SkColorFilter.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>

using namespace android;

@@ -85,6 +85,9 @@ static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct));
}

// Java's FINISHED relies on this being -1
static_assert(SkAnimatedImage::kFinished == -1);

static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                         jlong canvasPtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
@@ -115,9 +118,6 @@ static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/,
    return drawable->isRunning();
}

// Java's NOT_RUNNING relies on this being -2.0.
static_assert(SkAnimatedImage::kNotRunning == -2.0);

static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    return drawable->start();
@@ -172,6 +172,11 @@ static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/
    return sizeof(drawable);
}

static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    drawable->markInvisible();
}

static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
    { "nCreate",             "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
    { "nGetNativeFinalizer", "()J",                                                          (void*) AnimatedImageDrawable_nGetNativeFinalizer },
@@ -185,6 +190,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
    { "nSetLoopCount",       "(JI)V",                                                        (void*) AnimatedImageDrawable_nSetLoopCount },
    { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
    { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
    { "nMarkInvisible",      "(J)V",                                                         (void*) AnimatedImageDrawable_nMarkInvisible },
};

int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
+35 −13
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.graphics.drawable;

import dalvik.annotation.optimization.FastNative;

import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,7 +62,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
    private int mIntrinsicHeight;

    private boolean mStarting;
    private boolean mRunning;

    private Handler mHandler;

@@ -222,8 +223,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
        return mIntrinsicHeight;
    }

    // nDraw returns -2 if the animation is not running.
    private static final int NOT_RUNNING = -2;
    // nDraw returns -1 if the animation has finished.
    private static final int FINISHED = -1;

    @Override
    public void draw(@NonNull Canvas canvas) {
@@ -235,8 +236,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
            mStarting = false;

            postOnAnimationStart();

            mRunning = true;
        }

        long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper());
@@ -244,12 +243,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
        // will manage the animation
        if (nextUpdate > 0) {
            scheduleSelf(mRunnable, nextUpdate);
        } else if (nextUpdate == NOT_RUNNING) {
            // -2 means the animation ended, when drawn in software mode.
            if (mRunning) {
        } else if (nextUpdate == FINISHED) {
            // This means the animation was drawn in software mode and ended.
            postOnAnimationEnd();
                mRunning = false;
            }
        }
    }

@@ -292,6 +288,19 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        if (!super.setVisible(visible, restart)) {
            return false;
        }

        if (!visible) {
            nMarkInvisible(mState.mNativePtr);
        }

        return true;
    }

    // Animatable overrides
    /**
     *  Return whether the animation is currently running.
@@ -301,7 +310,10 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
     */
    @Override
    public boolean isRunning() {
        return mRunning;
        if (mState == null) {
            throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable");
        }
        return nIsRunning(mState.mNativePtr);
    }

    /**
@@ -336,7 +348,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
            throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
        }
        nStop(mState.mNativePtr);
        mRunning = false;
    }

    // Animatable2 overrides
@@ -405,18 +416,29 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
    private static native long nCreate(long nativeImageDecoder,
            @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
        throws IOException;
    @FastNative
    private static native long nGetNativeFinalizer();
    private static native long nDraw(long nativePtr, long canvasNativePtr);
    @FastNative
    private static native void nSetAlpha(long nativePtr, int alpha);
    @FastNative
    private static native int nGetAlpha(long nativePtr);
    @FastNative
    private static native void nSetColorFilter(long nativePtr, long nativeFilter);
    @FastNative
    private static native boolean nIsRunning(long nativePtr);
    // Return whether the animation started.
    @FastNative
    private static native boolean nStart(long nativePtr);
    @FastNative
    private static native void nStop(long nativePtr);
    @FastNative
    private static native void nSetLoopCount(long nativePtr, int loopCount);
    // Pass the drawable down to native so it can call onAnimationEnd.
    private static native void nSetOnAnimationEndListener(long nativePtr,
            @Nullable AnimatedImageDrawable drawable);
    @FastNative
    private static native long nNativeByteSize(long nativePtr);
    @FastNative
    private static native void nMarkInvisible(long nativePtr);
}
+1 −0
Original line number Diff line number Diff line
@@ -138,6 +138,7 @@ cc_defaults {

    srcs: [
        "hwui/AnimatedImageDrawable.cpp",
        "hwui/AnimatedImageThread.cpp",
        "hwui/Bitmap.cpp",
        "font/CacheTexture.cpp",
        "font/Font.cpp",
+167 −80
Original line number Diff line number Diff line
@@ -15,21 +15,21 @@
 */

#include "AnimatedImageDrawable.h"
#include "AnimatedImageThread.h"

#include "thread/Task.h"
#include "thread/TaskManager.h"
#include "thread/TaskProcessor.h"
#include "utils/TraceUtils.h"

#include <SkPicture.h>
#include <SkRefCnt.h>
#include <SkTime.h>
#include <SkTLazy.h>
#include <SkTime.h>

namespace android {

AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage)
    : mSkAnimatedImage(std::move(animatedImage)) { }
        : mSkAnimatedImage(std::move(animatedImage)) {
    mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration();
}

void AnimatedImageDrawable::syncProperties() {
    mAlpha = mStagingAlpha;
@@ -37,88 +37,78 @@ void AnimatedImageDrawable::syncProperties() {
}

bool AnimatedImageDrawable::start() {
    SkAutoExclusive lock(mLock);
    if (mSkAnimatedImage->isRunning()) {
    if (mRunning) {
        return false;
    }

    if (!mSnapshot) {
        mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
    }

    // While stopped, update() does not decode, but it does advance the time.
    // This prevents us from skipping ahead when we resume.
    const double currentTime = SkTime::GetMSecs();
    mSkAnimatedImage->update(currentTime);
    mSkAnimatedImage->start();
    return mSkAnimatedImage->isRunning();
    mRunning = true;
    return true;
}

void AnimatedImageDrawable::stop() {
    SkAutoExclusive lock(mLock);
    mSkAnimatedImage->stop();
    mRunning = false;
}

bool AnimatedImageDrawable::isRunning() {
    return mSkAnimatedImage->isRunning();
    return mRunning;
}

// This is really a Task<void> but that doesn't really work when Future<>
// expects to be able to get/set a value
class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> {
public:
    AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable)
            : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {}

    sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable;
    bool mIsCompleted = false;
};
bool AnimatedImageDrawable::nextSnapshotReady() const {
    return mNextSnapshot.valid() &&
           mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}

class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> {
public:
    explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager)
            : uirenderer::TaskProcessor<bool>(taskManager) {}
    ~AnimatedImageTaskProcessor() {}
// Only called on the RenderThread while UI thread is locked.
bool AnimatedImageDrawable::isDirty() {
    const double currentTime = SkTime::GetMSecs();
    const double lastWallTime = mLastWallTime;

    virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override {
        ATRACE_NAME("Updating AnimatedImageDrawables");
        AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get());
        t->mAnimatedImageDrawable->update();
        t->mIsCompleted = true;
        task->setResult(true);
    };
};
    mLastWallTime = currentTime;
    if (!mRunning) {
        mDidDraw = false;
        return false;
    }

void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) {
    if (!mSkAnimatedImage->isRunning()
            || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) {
        return;
    std::unique_lock lock{mSwapLock};
    if (mDidDraw) {
        mCurrentTime += currentTime - lastWallTime;
        mDidDraw = false;
    }

    if (!mDecodeTaskProcessor.get()) {
        mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager);
    if (!mNextSnapshot.valid()) {
        // Need to trigger onDraw in order to start decoding the next frame.
        return true;
    }

    // TODO get one frame ahead and only schedule updates when you need to replenish
    mDecodeTask = new AnimatedImageTask(this);
    mDecodeTaskProcessor->add(mDecodeTask);
    return nextSnapshotReady() && mCurrentTime >= mTimeToShowNextSnapshot;
}

void AnimatedImageDrawable::update() {
    SkAutoExclusive lock(mLock);
// Only called on the AnimatedImageThread.
AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() {
    Snapshot snap;
    {
        std::unique_lock lock{mImageLock};
        snap.mDuration = mSkAnimatedImage->decodeNextFrame();
        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
    }

    if (!mSkAnimatedImage->isRunning()) {
        return;
    return snap;
}

    const double currentTime = SkTime::GetMSecs();
    if (currentTime >= mNextFrameTime) {
        mNextFrameTime = mSkAnimatedImage->update(currentTime);
        mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
        mIsDirty = true;
// Only called on the AnimatedImageThread.
AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
    Snapshot snap;
    {
        std::unique_lock lock{mImageLock};
        mSkAnimatedImage->reset();
        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
        snap.mDuration = mSkAnimatedImage->currentFrameDuration();
    }

    return snap;
}

// Only called on the RenderThread.
void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
    SkTLazy<SkPaint> lazyPaint;
    if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) {
@@ -128,25 +118,71 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
        lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality);
    }

    SkAutoExclusive lock(mLock);
    if (mSnapshot) {
        canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull());
    } else {
        // TODO: we could potentially keep the cached surface around if there is a paint and we know
        // the drawable is attached to the view system
    mDidDraw = true;

    bool drewDirectly = false;
    if (!mSnapshot.mPic) {
        // The image is not animating, and never was. Draw directly from
        // mSkAnimatedImage.
        SkAutoCanvasRestore acr(canvas, false);
        if (lazyPaint.isValid()) {
            canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get());
        }

        std::unique_lock lock{mImageLock};
        mSkAnimatedImage->draw(canvas);
        drewDirectly = true;
    }

    if (mRunning && mFinished) {
        auto& thread = uirenderer::AnimatedImageThread::getInstance();
        mNextSnapshot = thread.reset(sk_ref_sp(this));
        mFinished = false;
    }

    bool finalFrame = false;
    if (mRunning && nextSnapshotReady()) {
        std::unique_lock lock{mSwapLock};
        if (mCurrentTime >= mTimeToShowNextSnapshot) {
            mSnapshot = mNextSnapshot.get();
            const double timeToShowCurrentSnap = mTimeToShowNextSnapshot;
            if (mSnapshot.mDuration == SkAnimatedImage::kFinished) {
                finalFrame = true;
                mRunning = false;
                mFinished = true;
            } else {
                mTimeToShowNextSnapshot += mSnapshot.mDuration;
                if (mCurrentTime >= mTimeToShowNextSnapshot) {
                    // This would mean showing the current frame very briefly. It's
                    // possible that not being displayed for a time resulted in
                    // mCurrentTime being far ahead. Prevent showing many frames
                    // rapidly by going back to the beginning of this frame time.
                    mCurrentTime = timeToShowCurrentSnap;
                }
            }
        }
    }

    if (mRunning && !mNextSnapshot.valid()) {
        auto& thread = uirenderer::AnimatedImageThread::getInstance();
        mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this));
    }

    mIsDirty = false;
    if (!drewDirectly) {
        // No other thread will modify mCurrentSnap so this should be safe to
        // use without locking.
        canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull());
    }

    if (finalFrame) {
        if (mEndListener) {
            mEndListener->onAnimationEnd();
            mEndListener = nullptr;
        }
    }
}

double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
    // update the drawable with the current time
    double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs());
    SkAutoCanvasRestore acr(canvas, false);
    if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) {
        SkPaint paint;
@@ -154,8 +190,59 @@ double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
        paint.setColorFilter(mStagingColorFilter);
        canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint);
    }

    if (mFinished && !mRunning) {
        // Continue drawing the last frame, and return 0 to indicate no need to
        // redraw.
        std::unique_lock lock{mImageLock};
        canvas->drawDrawable(mSkAnimatedImage.get());
        return 0.0;
    }

    bool update = false;
    {
        const double currentTime = SkTime::GetMSecs();
        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) {
            mCurrentTime = currentTime;
        } else if (mRunning) {
            if (mFinished) {
                mCurrentTime = currentTime;
                {
                    std::unique_lock lock{mImageLock};
                    mSkAnimatedImage->reset();
                }
                mTimeToShowNextSnapshot = currentTime + mSkAnimatedImage->currentFrameDuration();
            } else {
                mCurrentTime += currentTime - mLastWallTime;
                update = mCurrentTime >= mTimeToShowNextSnapshot;
            }
        }
        mLastWallTime = currentTime;
    }

    double duration = 0.0;
    {
        std::unique_lock lock{mImageLock};
        if (update) {
            duration = mSkAnimatedImage->decodeNextFrame();
        }

        canvas->drawDrawable(mSkAnimatedImage.get());
    return nextUpdate;
    }

};  // namespace android
    std::unique_lock lock{mSwapLock};
    if (update) {
        if (duration == SkAnimatedImage::kFinished) {
            mRunning = false;
            mFinished = true;
        } else {
            mTimeToShowNextSnapshot += duration;
        }
    }
    return mTimeToShowNextSnapshot;
}

}  // namespace android
+63 −31
Original line number Diff line number Diff line
@@ -17,22 +17,20 @@
#pragma once

#include <cutils/compiler.h>
#include <utils/Macros.h>
#include <utils/RefBase.h>

#include <SkAnimatedImage.h>
#include <SkCanvas.h>
#include <SkColorFilter.h>
#include <SkDrawable.h>
#include <SkMutex.h>
#include <SkPicture.h>

class SkPicture;
#include <future>
#include <mutex>

namespace android {

namespace uirenderer {
class TaskManager;
}

class OnAnimationEndListener {
public:
    virtual ~OnAnimationEndListener() {}
@@ -41,68 +39,102 @@ public:
};

/**
 * Native component of android.graphics.drawable.AnimatedImageDrawables.java.  This class can be
 * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread.
 * Native component of android.graphics.drawable.AnimatedImageDrawables.java.
 * This class can be drawn into Canvas.h and maintains the state needed to drive
 * the animation from the RenderThread.
 */
class ANDROID_API AnimatedImageDrawable : public SkDrawable {
public:
    AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage);

    /**
     * This returns true if the animation has updated and signals that the next draw will contain
     * new content.
     * This updates the internal time and returns true if the animation needs
     * to be redrawn.
     *
     * This is called on RenderThread, while the UI thread is locked.
     */
    bool isDirty() const { return mIsDirty; }
    bool isDirty();

    int getStagingAlpha() const { return mStagingAlpha; }
    void setStagingAlpha(int alpha) { mStagingAlpha = alpha; }
    void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; }
    void syncProperties();

    virtual SkRect onGetBounds() override {
        return mSkAnimatedImage->getBounds();
    }
    virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); }

    // Draw to software canvas, and return time to next draw.
    double drawStaging(SkCanvas* canvas);

    // Returns true if the animation was started; false otherwise (e.g. it was already running)
    // Returns true if the animation was started; false otherwise (e.g. it was
    // already running)
    bool start();
    void stop();
    bool isRunning();
    void setRepetitionCount(int count) {
        mSkAnimatedImage->setRepetitionCount(count);
    }
    void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }

    void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) {
        mEndListener = std::move(listener);
    }

    void scheduleUpdate(uirenderer::TaskManager* taskManager);
    void markInvisible() { mDidDraw = false; }

    struct Snapshot {
        sk_sp<SkPicture> mPic;
        int mDuration;

        Snapshot() = default;

        Snapshot(Snapshot&&) = default;
        Snapshot& operator=(Snapshot&&) = default;

        PREVENT_COPY_AND_ASSIGN(Snapshot);
    };

    // These are only called on AnimatedImageThread.
    Snapshot decodeNextFrame();
    Snapshot reset();

protected:
    virtual void onDraw(SkCanvas* canvas) override;

private:
    void update();

    sk_sp<SkAnimatedImage> mSkAnimatedImage;
    sk_sp<SkPicture> mSnapshot;
    SkMutex mLock;
    bool mRunning = false;
    bool mFinished = false;

    // A snapshot of the current frame to draw.
    Snapshot mSnapshot;

    std::future<Snapshot> mNextSnapshot;

    bool nextSnapshotReady() const;

    // When to switch from mSnapshot to mNextSnapshot.
    double mTimeToShowNextSnapshot = 0.0;

    // The current time for the drawable itself.
    double mCurrentTime = 0.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;

    // Locked when assigning snapshots and times. Operations while this is held
    // should be short.
    std::mutex mSwapLock;

    // Locked when mSkAnimatedImage is being updated or drawn.
    std::mutex mImageLock;

    int mStagingAlpha = SK_AlphaOPAQUE;
    sk_sp<SkColorFilter> mStagingColorFilter;

    int mAlpha = SK_AlphaOPAQUE;
    sk_sp<SkColorFilter> mColorFilter;
    double mNextFrameTime = 0.0;
    bool mIsDirty = false;

    class AnimatedImageTask;
    class AnimatedImageTaskProcessor;
    sp<AnimatedImageTask> mDecodeTask;
    sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor;

    std::unique_ptr<OnAnimationEndListener> mEndListener;
};

};  // namespace android
}  // namespace android
Loading