diff options
author | Scott Mertz <scott@cyngn.com> | 2016-08-02 11:06:46 -0700 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2016-08-04 23:57:52 -0700 |
commit | 66af012f8b1e8adb18132a669d2ed2e81d7bdf42 (patch) | |
tree | 9cef29c0a0cea664e746208446018f1c4cf2f102 /cmds/bootanimation | |
parent | 88e7a6c7fcaf71d6dae2b9067db19aaeba7de39b (diff) | |
download | frameworks_base-66af012f8b1e8adb18132a669d2ed2e81d7bdf42.zip frameworks_base-66af012f8b1e8adb18132a669d2ed2e81d7bdf42.tar.gz frameworks_base-66af012f8b1e8adb18132a669d2ed2e81d7bdf42.tar.bz2 |
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
Diffstat (limited to 'cmds/bootanimation')
-rw-r--r-- | cmds/bootanimation/Android.mk | 4 | ||||
-rw-r--r-- | cmds/bootanimation/BootAnimation.cpp | 170 | ||||
-rw-r--r-- | cmds/bootanimation/BootAnimation.h | 55 |
3 files changed, 218 insertions, 11 deletions
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 ; 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 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 <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 |