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

Commit 66af012f authored by Scott Mertz's avatar Scott Mertz Committed by Steve Kondik
Browse files

bootanimation: add multithreaded decode

Some devices can't keep up on a single thread trying
to decode & display the frames at a high frame rate. This is
observed if the sleep delay between frames is negative:

01-02 04:29:25.114   530   542 D BootAnimation: 63, -22
01-02 04:29:25.176   530   542 D BootAnimation: 61, -20
01-02 04:29:25.248   530   542 D BootAnimation: 72, -30
01-02 04:29:25.315   530   542 D BootAnimation: 66, -24
01-02 04:29:25.381   530   542 D BootAnimation: 66, -24

To mitigate this, take advantage of multiple cores by decoding on
n cores and caching up to m images.  This keeps the memory footprint
small(ish) while still giving the best chance to maintain a
constant frame rate.

I measured boot time and fps for each animation part before
and after the change on an msm8909 with 1.5 GB RAM:

single thread:
01-02 04:40:45.826   540   556 I BootAnimation: fps = 22.40
01-02 04:40:49.457   540   556 I BootAnimation: fps = 13.22
01-02 04:40:51.464   540   556 I BootAnimation: fps = 23.92
01-02 04:41:19.375   540   556 I BootAnimation: fps = 22.89
01-02 04:41:23.942   540   556 I BootAnimation: fps = 15.55

  boot time: 51.05s

multi thread:
01-02 04:38:55.148   526   551 I BootAnimation: fps = 22.56
01-02 04:38:57.205   526   551 I BootAnimation: fps = 23.39
01-02 04:38:59.249   526   551 I BootAnimation: fps = 23.92
01-02 04:39:29.196   526   551 I BootAnimation: fps = 23.16
01-02 04:39:32.186   526   551 I BootAnimation: fps = 23.79

  boot time: 50.50s

Need to test the affect on boot time with an animation that doesn't
cache the textures as much as this to see the real effect.

Change-Id: If7464dc063b08a0bc33ee3f094028247b39650c1
parent 88e7a6c7
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -46,6 +46,10 @@ ifeq ($(TARGET_BOOTANIMATION_USE_RGB565),true)
    LOCAL_CFLAGS += -DUSE_565
endif

ifeq ($(TARGET_BOOTANIMATION_MULTITHREAD_DECODE),true)
    LOCAL_CFLAGS += -DMULTITHREAD_DECODE
endif

LOCAL_MODULE:= bootanimation

ifdef TARGET_32_BIT_SURFACEFLINGER
+159 −11
Original line number Diff line number Diff line
@@ -94,6 +94,11 @@ static pthread_cond_t mp_cond;
static bool isMPlayerPrepared = false;
static bool isMPlayerCompleted = false;

#ifdef MULTITHREAD_DECODE
static const int MAX_DECODE_THREADS = 2;
static const int MAX_DECODE_CACHE = 3;
#endif

class MPlayerListener : public MediaPlayerListener
{
    void notify(int msg, int /*ext1*/, int /*ext2*/, const Parcel * /*obj*/)
@@ -263,16 +268,16 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
    return NO_ERROR;
}

status_t BootAnimation::initTexture(const Animation::Frame& frame)
SkBitmap* BootAnimation::decode(const Animation::Frame& frame)
{
    //StopWatch watch("blah");

    SkBitmap bitmap;
    SkBitmap *bitmap = NULL;
    SkMemoryStream  stream(frame.map->getDataPtr(), frame.map->getDataLength());
    SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
    if (codec != NULL) {
        bitmap = new SkBitmap();
        codec->setDitherImage(false);
        codec->decode(&stream, &bitmap,
        codec->decode(&stream, bitmap,
                #ifdef USE_565
                kRGB_565_SkColorType,
                #else
@@ -282,13 +287,23 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame)
        delete codec;
    }

    // ensure we can call getPixels(). No need to call unlock, since the
    // bitmap will go out of scope when we return from this method.
    bitmap.lockPixels();
    return bitmap;
}

    const int w = bitmap.width();
    const int h = bitmap.height();
    const void* p = bitmap.getPixels();
status_t BootAnimation::initTexture(const Animation::Frame& frame)
{
    //StopWatch watch("blah");
    return initTexture(decode(frame));
}

status_t BootAnimation::initTexture(SkBitmap *bitmap)
{
    // ensure we can call getPixels().
    bitmap->lockPixels();

    const int w = bitmap->width();
    const int h = bitmap->height();
    const void* p = bitmap->getPixels();

    GLint crop[4] = { 0, h, w, -h };
    int tw = 1 << (31 - __builtin_clz(w));
@@ -296,7 +311,7 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame)
    if (tw < w) tw <<= 1;
    if (th < h) th <<= 1;

    switch (bitmap.colorType()) {
    switch (bitmap->colorType()) {
        case kN32_SkColorType:
            if (tw != w || th != h) {
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
@@ -326,6 +341,8 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame)

    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);

    bitmap->unlockPixels();
    delete bitmap;
    return NO_ERROR;
}

@@ -780,6 +797,14 @@ bool BootAnimation::movie()
                    part.backgroundColor[2],
                    1.0f);

#ifdef MULTITHREAD_DECODE
            FrameManager *frameManager = NULL;
            if (r == 0 || needSaveMem) {
                frameManager = new FrameManager(MAX_DECODE_THREADS,
                    MAX_DECODE_CACHE, part.frames);
            }
#endif

            for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
                const Animation::Frame& frame(part.frames[j]);
                nsecs_t lastFrame = systemTime();
@@ -793,7 +818,11 @@ bool BootAnimation::movie()
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    }
#ifdef MULTITHREAD_DECODE
                    initTexture(frameManager->next());
#else
                    initTexture(frame);
#endif
                }

                if (!clearReg.isEmpty()) {
@@ -834,6 +863,12 @@ bool BootAnimation::movie()

            usleep(part.pause * ns2us(frameDuration));

#ifdef MULTITHREAD_DECODE
            if (frameManager) {
                delete frameManager;
            }
#endif

            // For infinite parts, we've now played them at least once, so perhaps exit
            if(exitPending() && !part.count)
                break;
@@ -1011,6 +1046,119 @@ bool BootAnimation::checkBootState(void)
    return ret;
}

#ifdef MULTITHREAD_DECODE

FrameManager::FrameManager(int numThreads, size_t maxSize, const SortedVector<BootAnimation::Animation::Frame>& frames) :
    mMaxSize(maxSize),
    mFrameCounter(0),
    mNextIdx(0),
    mFrames(frames),
    mExit(false)
{
    pthread_mutex_init(&mBitmapsMutex, NULL);
    pthread_cond_init(&mSpaceAvailableCondition, NULL);
    pthread_cond_init(&mBitmapReadyCondition, NULL);
    for (int i = 0; i < numThreads; i++) {
        DecodeThread *thread = new DecodeThread(this);
        thread->run("bootanimation", PRIORITY_URGENT_DISPLAY);
        mThreads.add(thread);
    }
}

FrameManager::~FrameManager()
{
    mExit = true;
    pthread_cond_broadcast(&mSpaceAvailableCondition);
    pthread_cond_broadcast(&mBitmapReadyCondition);
    for (size_t i = 0; i < mThreads.size(); i++) {
        mThreads.itemAt(i)->requestExitAndWait();
    }

    // Any bitmap left in the queue won't get cleaned up by
    // the consumer.  Clean up now.
    for(size_t i = 0; i < mDecodedFrames.size(); i++) {
        delete mDecodedFrames[i].bitmap;
    }
}

SkBitmap* FrameManager::next()
{
    pthread_mutex_lock(&mBitmapsMutex);

    while(mDecodedFrames.size() == 0 ||
            mDecodedFrames.itemAt(0).idx != mNextIdx) {
        pthread_cond_wait(&mBitmapReadyCondition, &mBitmapsMutex);
    }
    DecodeWork work = mDecodedFrames.itemAt(0);
    mDecodedFrames.removeAt(0);
    mNextIdx++;
    pthread_cond_signal(&mSpaceAvailableCondition);
    pthread_mutex_unlock(&mBitmapsMutex);
    // The caller now owns the bitmap
    return work.bitmap;
}

FrameManager::DecodeWork FrameManager::getWork()
{
    DecodeWork work = {
        .frame = NULL,
        .bitmap = NULL,
        .idx = 0
    };

    pthread_mutex_lock(&mBitmapsMutex);

    while(mDecodedFrames.size() >= mMaxSize && !mExit) {
        pthread_cond_wait(&mSpaceAvailableCondition, &mBitmapsMutex);
    }

    if (!mExit) {
        work.frame = &mFrames.itemAt(mFrameCounter % mFrames.size());
        work.idx = mFrameCounter;
        mFrameCounter++;
    }

    pthread_mutex_unlock(&mBitmapsMutex);
    return work;
}

void FrameManager::completeWork(DecodeWork work) {
    size_t insertIdx;
    pthread_mutex_lock(&mBitmapsMutex);

    for(insertIdx = 0; insertIdx < mDecodedFrames.size(); insertIdx++) {
        if (work.idx < mDecodedFrames.itemAt(insertIdx).idx) {
            break;
        }
    }

    mDecodedFrames.insertAt(work, insertIdx);
    pthread_cond_signal(&mBitmapReadyCondition);

    pthread_mutex_unlock(&mBitmapsMutex);
}

FrameManager::DecodeThread::DecodeThread(FrameManager* manager) :
    Thread(false),
    mManager(manager)
{

}

bool FrameManager::DecodeThread::threadLoop()
{
    DecodeWork work = mManager->getWork();
    if (work.frame != NULL) {
        work.bitmap = BootAnimation::decode(*work.frame);
        mManager->completeWork(work);
        return true;
    }

    return false;
}

#endif

// ---------------------------------------------------------------------------

}; // namespace android
+55 −0
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@
#include <EGL/egl.h>
#include <GLES/gl.h>

#include <utils/Thread.h>

class SkBitmap;

namespace android {
@@ -34,11 +36,17 @@ class AudioPlayer;
class Surface;
class SurfaceComposerClient;
class SurfaceControl;
#ifdef MULTITHREAD_DECODE
class FrameManager;
#endif

// ---------------------------------------------------------------------------

class BootAnimation : public Thread, public IBinder::DeathRecipient
{
#ifdef MULTITHREAD_DECODE
    friend class FrameManager;
#endif
public:
    enum {
        eOrientationDefault     = 0,
@@ -89,6 +97,7 @@ private:

    status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
    status_t initTexture(const Animation::Frame& frame);
    status_t initTexture(SkBitmap *bitmap);
    bool android();
    bool readFile(const char* name, String8& outString);
    bool movie();
@@ -101,6 +110,8 @@ private:
    void checkExit();
    void checkShowAndroid();

    static SkBitmap *decode(const Animation::Frame& frame);

    sp<SurfaceComposerClient>       mSession;
    sp<AudioPlayer>                 mAudioPlayer;
    AssetManager mAssets;
@@ -115,6 +126,50 @@ private:
    ZipFileRO   *mZip;
};

#ifdef MULTITHREAD_DECODE

class FrameManager {
public:
    struct DecodeWork {
        const BootAnimation::Animation::Frame *frame;
        SkBitmap *bitmap;
        size_t idx;
    };

    FrameManager(int numThreads, size_t maxSize, const SortedVector<BootAnimation::Animation::Frame>& frames);
    virtual ~FrameManager();

    SkBitmap* next();

protected:
    DecodeWork getWork();
    void completeWork(DecodeWork work);

private:

    class DecodeThread : public Thread {
    public:
        DecodeThread(FrameManager* manager);
        virtual ~DecodeThread() {}
    private:
        virtual bool threadLoop();
        FrameManager *mManager;
    };

    size_t mMaxSize;
    size_t mFrameCounter;
    size_t mNextIdx;
    const SortedVector<BootAnimation::Animation::Frame>& mFrames;
    Vector<DecodeWork> mDecodedFrames;
    pthread_mutex_t mBitmapsMutex;
    pthread_cond_t mSpaceAvailableCondition;
    pthread_cond_t mBitmapReadyCondition;
    bool mExit;
    Vector<sp<DecodeThread> > mThreads;
};

#endif

// ---------------------------------------------------------------------------

}; // namespace android