Make bootanimations go faster?

kcls

Well-known member
May 5, 2011
901
307
63
I have been using CM7 ever since AOSP, and I am currently using Backside IHO. However, the only boot animation I have ever been able to get to run smoothly is the default silver Android one. Every other one I've used usually starts out jerky, going at quite a low framerate (I'd say close to 4 fps), and by the time the phone is 4 seconds or so from finishing with booting, it'll even out and play at the correct framerate. Currently I'm rocking the Unofficial CM9 Bootanimation ( http://forum.xda-developers.com/showthread.php?t=1513220), and as you can see in the video, it runs like butter. But on the V, it seems to run choppy until it gets to part 2, but even then it isn't smooth until the last 3 seconds of boot.

Any suggestions?
 
I was going to ask the same question....

I use a white Google bootanimation. Its about 4.jog images. :D

I believe I used one.of the CM9 boot images that worked fast. It just had low quality.
 
Any suggestions?

Yeah, take it out of the rom. :p

Boot animations are neat, but they only work when you reboot, which slows your boot times (eating up battery as well!). I don't know about you, but when I boot my phone, I'm doing it so I can use it, not look at the neat startup animation.

This is one of the main reasons Harmonia has no boot animation. The animation you see is actually the framework loading, usually it is a grayish blue Android logo with a glare that moves across. A boot animation just hides this and gives you something to look at. Like I said, I'd prefer a faster boot myself.
 
Actually, bootanimations can be used at anytime, and can be called from any app that wants to, though I've never found an app other than something like my built in bootanimation changer that takes advantage of the ability.
Code:
SystemProperties.set("ctl.start", "bootanim");

Note- want a quick way to run logcat or dmesg? Install chainsfire's LiveLogcat and LiveDmesg apps and then create a shortcut to run the above command via Tasker or something similar. These also work off the bootanimation frameworks. ;)

FYI- the masked android logo bootanimation and the bootanimation.zip are both handled by the same code, but in different manners, and both are handled by the frameworks. The first thing bootanimation does is check for the existence of a bootanimation.zip in one of two locations (or in the case of BACKside, three locations). If a bootanimation.zip is found, it is unzipped, and the description file is parsed, and then the images are played as a slideshow.

The difference in time is due to the unzipping and loading of multiple images when a bootanimation.zip is used, otherwise there is only a mask and an image to load. You can make a bootanimation faster by reducing the filesize of the images in the bootanimation. PNG Crush is a good utility for this, and making the images with the fewest colors in the beginning helps as well.

Power drain is not an issue unless you are rebooting 30 times in a single battery charge.

Note- the only way to truly speed up boot times and/or save a bit of battery juice is to completely disable bootanimations (that is in all IHO roms now I believe).

The following code excerpts are from frameworks/base/cmds/bootanimation/BootAnimation.cpp

Android Logo Masked:
Code:
bool BootAnimation::android()
{
    initTexture(&mAndroid[0], mAssets, "http://forums.androidcentral.com/images/android-logo-mask.png");
    initTexture(&mAndroid[1], mAssets, "http://forums.androidcentral.com/images/android-logo-shine.png");

    // clear screen
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);
    eglSwapBuffers(mDisplay, mSurface);

    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    const GLint xc = (mWidth  - mAndroid[0].w) / 2;
    const GLint yc = (mHeight - mAndroid[0].h) / 2;
    const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);

    // draw and update only what we need
    mFlingerSurface->setSwapRectangle(updateRect);

    glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
            updateRect.height());

    // Blend state
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    const nsecs_t startTime = systemTime();
    do {
        nsecs_t now = systemTime();
        double time = now - startTime;
        float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
        GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
        GLint x = xc - offset;

        glDisable(GL_SCISSOR_TEST);
        glClear(GL_COLOR_BUFFER_BIT);

        glEnable(GL_SCISSOR_TEST);
        glDisable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
        glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);
        glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);

        glEnable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
        glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);

        EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
        if (res == EGL_FALSE)
            break;

        // 12fps: don't animate too fast to preserve CPU
        const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
        if (sleepTime > 0)
            usleep(sleepTime);
    } while (!exitPending());

    glDeleteTextures(1, &mAndroid[0].name);
    glDeleteTextures(1, &mAndroid[1].name);
    return false;
}
Boot Animation Zip:
Code:
bool BootAnimation::movie()
{
    ZipFileRO& zip(mZip);

    size_t numEntries = zip.getNumEntries();
    ZipEntryRO desc = zip.findEntryByName("desc.txt");
    FileMap* descMap = zip.createEntryFileMap(desc);
    LOGE_IF(!descMap, "descMap is null");
    if (!descMap) {
        return false;
    }

    String8 desString((char const*)descMap->getDataPtr(),
            descMap->getDataLength());
    char const* s = desString.string();

    Animation animation;

    // Parse the description file
    for (;;) {
        const char* endl = strstr(s, "\n");
        if (!endl) break;
        String8 line(s, endl - s);
        const char* l = line.string();
        int fps, width, height, count, pause;
        char path[256];
        if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
            //LOGD("> w=%d, h=%d, fps=%d", fps, width, height);
            animation.width = width;
            animation.height = height;
            animation.fps = fps;
        }
        if (sscanf(l, "p %d %d %s", &count, &pause, path) == 3) {
            //LOGD("> count=%d, pause=%d, path=%s", count, pause, path);
            Animation::Part part;
            part.count = count;
            part.pause = pause;
            part.path = path;
            animation.parts.add(part);
        }
        s = ++endl;
    }

    // read all the data structures
    const size_t pcount = animation.parts.size();
    for (size_t i=0 ; i<numEntries ; i++) {
        char name[256];
        ZipEntryRO entry = zip.findEntryByIndex(i);
        if (zip.getEntryFileName(entry, name, 256) == 0) {
            const String8 entryName(name);
            const String8 path(entryName.getPathDir());
            const String8 leaf(entryName.getPathLeaf());
            if (leaf.size() > 0) {
                for (int j=0 ; j<pcount ; j++) {
                    if (path == animation.parts[j].path) {
                        int method;
                        // supports only stored png files
                        if (zip.getEntryInfo(entry, &method, 0, 0, 0, 0, 0)) {
                            if (method == ZipFileRO::kCompressStored) {
                                FileMap* map = zip.createEntryFileMap(entry);
                                if (map) {
                                    Animation::Frame frame;
                                    frame.name = leaf;
                                    frame.map = map;
                                    Animation::Part& part(animation.parts.editItemAt(j));
                                    part.frames.add(frame);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // clear screen
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glDisable(GL_BLEND);
    glClear(GL_COLOR_BUFFER_BIT);

    eglSwapBuffers(mDisplay, mSurface);

    glBindTexture(GL_TEXTURE_2D, 0);
    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    const int xc = (mWidth - animation.width) / 2;
    const int yc = ((mHeight - animation.height) / 2);
    nsecs_t lastFrame = systemTime();
    nsecs_t frameDuration = s2ns(1) / animation.fps;

    Region clearReg(Rect(mWidth, mHeight));
    clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));

    for (int i=0 ; i<pcount && !exitPending() ; i++) {
        const Animation::Part& part(animation.parts[i]);
        const size_t fcount = part.frames.size();
        glBindTexture(GL_TEXTURE_2D, 0);

        for (int r=0 ; !part.count || r<part.count ; r++) {
            for (int j=0 ; j<fcount && !exitPending(); j++) {
                const Animation::Frame& frame(part.frames[j]);

                if (r > 0) {
                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                } else {
                    if (part.count != 1) {
                        glGenTextures(1, &frame.tid);
                        glBindTexture(GL_TEXTURE_2D, frame.tid);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    }
                    initTexture(
                            frame.map->getDataPtr(),
                            frame.map->getDataLength());
                }

                if (!clearReg.isEmpty()) {
                    Region::const_iterator head(clearReg.begin());
                    Region::const_iterator tail(clearReg.end());
                    glEnable(GL_SCISSOR_TEST);
                    while (head != tail) {
                        const Rect& r(*head++);
                        glScissor(r.left, mHeight - r.bottom,
                                r.width(), r.height());
                        glClear(GL_COLOR_BUFFER_BIT);
                    }
                    glDisable(GL_SCISSOR_TEST);
                }
                glDrawTexiOES(xc, yc, 0, animation.width, animation.height);
                eglSwapBuffers(mDisplay, mSurface);

                nsecs_t now = systemTime();
                nsecs_t delay = frameDuration - (now - lastFrame);
                lastFrame = now;
                long wait = ns2us(frameDuration);
                if (wait > 0)
                    usleep(wait);
            }
            usleep(part.pause * ns2us(frameDuration));
        }

        // free the textures for this part
        if (part.count != 1) {
            for (int j=0 ; j<fcount ; j++) {
                const Animation::Frame& frame(part.frames[j]);
                glDeleteTextures(1, &frame.tid);
            }
        }
    }
    return false;
}
I spent a lot of time debugging bootanimation when I added the changer to backside. ;)
Code:
status_t BootAnimation::readyToRun() {
    char previewString[256];
    mAssets.addDefaultAssets();

    DisplayInfo dinfo;
    status_t status = session()->getDisplayInfo(0, &dinfo);
    if (status)
        return -1;

    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(
            getpid(), 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
    session()->openTransaction();
    control->setLayer(0x40000000);
    session()->closeTransaction();

    sp<Surface> s = control->getSurface();

    // initialize opengl and egl
    const EGLint attribs[] = {
            EGL_DEPTH_SIZE, 0,
            EGL_NONE
    };
    EGLint w, h, dummy;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);
    EGLUtils::selectConfigForNativeWindow(display, attribs, s.get(), &config);
    surface = eglCreateWindowSurface(display, config, s.get(), NULL);
    context = eglCreateContext(display, config, NULL, NULL);
    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;

    mDisplay = display;
    mContext = context;
    mSurface = surface;
    mWidth = w;
    mHeight = h;
    mFlingerSurfaceControl = control;
    mFlingerSurface = s;

    mAndroidAnimation = false;
    FILE *mPreviewFile = fopen ( "/data/data/com.cyanogenmod.cmparts/files/preview_bootanim", "r" );
    if(mPreviewFile != NULL) {
        if(fgets(previewString, 256, mPreviewFile) != NULL) {
            status_t err = mZip.open(previewString);
            if(err == NO_ERROR) return NO_ERROR;
        }
    }

    status_t err = mZip.open("/data/data/com.cyanogenmod.cmbootanimation/files/bootanimation.zip");
    if (err != NO_ERROR) {
        err = mZip.open("/data/local/bootanimation.zip");
        if (err != NO_ERROR) {
            err = mZip.open("/system/media/bootanimation.zip");
            if (err != NO_ERROR) {
                mAndroidAnimation = true;
            }
        }
    }
    return NO_ERROR;
}

bool BootAnimation::threadLoop()
{
    bool r;
    if (mAndroidAnimation) {
        r = android();
    } else {
        r = movie();
    }

    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mDisplay, mContext);
    eglDestroySurface(mDisplay, mSurface);
    mFlingerSurface.clear();
    mFlingerSurfaceControl.clear();
    eglTerminate(mDisplay);
    IPCThreadState::self()->stopProcess();
    return r;
}
 
Last edited: