From 66af012f8b1e8adb18132a669d2ed2e81d7bdf42 Mon Sep 17 00:00:00 2001 From: Scott Mertz Date: Tue, 2 Aug 2016 11:06:46 -0700 Subject: 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 --- cmds/bootanimation/Android.mk | 4 + cmds/bootanimation/BootAnimation.cpp | 170 ++++++++++++++++++++++++++++++++--- cmds/bootanimation/BootAnimation.h | 55 ++++++++++++ 3 files changed, 218 insertions(+), 11 deletions(-) (limited to 'cmds') diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index 0c05ded..d15c274 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -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 diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 5afe1e8..1274b69 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -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 ; jnext()); +#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& 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 diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index a0f84da..090894f 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -26,6 +26,8 @@ #include #include +#include + 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 mSession; sp 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& 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& mFrames; + Vector mDecodedFrames; + pthread_mutex_t mBitmapsMutex; + pthread_cond_t mSpaceAvailableCondition; + pthread_cond_t mBitmapReadyCondition; + bool mExit; + Vector > mThreads; +}; + +#endif + // --------------------------------------------------------------------------- }; // namespace android -- cgit v1.1