summaryrefslogtreecommitdiffstats
path: root/cmds
diff options
context:
space:
mode:
authorScott Mertz <scott@cyngn.com>2016-08-02 11:06:46 -0700
committerSteve Kondik <shade@chemlab.org>2016-08-04 23:57:52 -0700
commit66af012f8b1e8adb18132a669d2ed2e81d7bdf42 (patch)
tree9cef29c0a0cea664e746208446018f1c4cf2f102 /cmds
parent88e7a6c7fcaf71d6dae2b9067db19aaeba7de39b (diff)
downloadframeworks_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')
-rw-r--r--cmds/bootanimation/Android.mk4
-rw-r--r--cmds/bootanimation/BootAnimation.cpp170
-rw-r--r--cmds/bootanimation/BootAnimation.h55
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