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

Commit 83070147 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use a separate thread to decode AnimatedImageDrawable"

parents cf40a9ab 5b7f426f
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