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

Commit eafcb997 authored by Yegor Malyshev's avatar Yegor Malyshev
Browse files

Support animation parts fading out

This support helps with several things:
- simpler animation structure (size, number of items)
- more room for designers avoiding thinking about exit part
- time saving as complete parts now can be safely used as fading parts

Bug: 167352662
Test: Manual testing of major/edge cases
  - backward compatibility => "f 0" is mapped to "p", "p" still works
  - only trimmed areas are faded out
  - clock is not faded out (wear case)
  - adb shell su root bootanimation
  - [c, (exit) f 120, c] => "f 120" fades out and "c" completes
  - [c, (exit) f 120, f 320] => "f 120" fades out and "f 320" skipped
  - [c, f 16, (exit) f 120] => "f 16" played, "f 120" fades out
  - [c, (exit) f 0, f 120] => "f 0" skipped, "f 120" fades out

Change-Id: I9b200ee7107ef9b3dc8d711658ed1042b83739c2
Merged-In: I9b200ee7107ef9b3dc8d711658ed1042b83739c2
parent ec3a0131
Loading
Loading
Loading
Loading
+92 −18
Original line number Diff line number Diff line
@@ -459,6 +459,8 @@ status_t BootAnimation::readyToRun() {
    mFlingerSurface = s;
    mTargetInset = -1;

    projectSceneToWindow();

    // Register a display event receiver
    mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>();
    status_t status = mDisplayEventReceiver->initCheck();
@@ -470,6 +472,16 @@ status_t BootAnimation::readyToRun() {
    return NO_ERROR;
}

void BootAnimation::projectSceneToWindow() {
    glViewport(0, 0, mWidth, mHeight);
    glScissor(0, 0, mWidth, mHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void BootAnimation::resizeSurface(int newWidth, int newHeight) {
    // We assume this function is called on the animation thread.
    if (newWidth == mWidth && newHeight == mHeight) {
@@ -494,8 +506,8 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) {
        SLOGE("Can't make the new surface current. Error %d", eglGetError());
        return;
    }
    glViewport(0, 0, mWidth, mHeight);
    glScissor(0, 0, mWidth, mHeight);

    projectSceneToWindow();

    mSurface = surface;
}
@@ -776,6 +788,37 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) {
    return status;
}

void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth,
                              const int frameHeight, const Animation::Part& part,
                              const int fadedFramesCount) {
    glEnable(GL_BLEND);
    glEnableClientState(GL_VERTEX_ARRAY);
    glDisable(GL_TEXTURE_2D);
    // avoid creating a hole due to mixing result alpha with GL_REPLACE texture
    glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);

    const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount;
    glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha);

    const float frameStartX = static_cast<float>(frameLeft);
    const float frameStartY = static_cast<float>(frameBottom);
    const float frameEndX = frameStartX + frameWidth;
    const float frameEndY = frameStartY + frameHeight;
    const GLfloat frameRect[] = {
        frameStartX, frameStartY,
        frameEndX,   frameStartY,
        frameEndX,   frameEndY,
        frameStartX, frameEndY
    };
    glVertexPointer(2, GL_FLOAT, 0, frameRect);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_TEXTURE_2D);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisable(GL_BLEND);
}

void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
    glEnable(GL_BLEND);  // Allow us to draw on top of the animation
    glBindTexture(GL_TEXTURE_2D, font.texture.name);
@@ -867,23 +910,34 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
        int height = 0;
        int count = 0;
        int pause = 0;
        int framesToFadeCount = 0;
        char path[ANIM_ENTRY_NAME_MAX];
        char color[7] = "000000"; // default to black if unspecified
        char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
        char clockPos2[TEXT_POS_LEN_MAX + 1] = "";

        char pathType;

        int nextReadPos;

        if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
            // SLOGD("> w=%d, h=%d, fps=%d", width, height, fps);
            animation.width = width;
            animation.height = height;
            animation.fps = fps;
        } else if (sscanf(l, " %c %d %d %" STRTO(ANIM_PATH_MAX) "s #%6s %16s %16s",
                          &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
            //SLOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
            //    pathType, count, pause, path, color, clockPos1, clockPos2);
        } else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
                          &pathType, &count, &pause, path, &nextReadPos) >= 4) {
            if (pathType == 'f') {
                sscanf(l + nextReadPos, " %d #%6s %16s %16s", &framesToFadeCount, color, clockPos1,
                       clockPos2);
            } else {
                sscanf(l + nextReadPos, " #%6s %16s %16s", color, clockPos1, clockPos2);
            }
            // SLOGD("> type=%c, count=%d, pause=%d, path=%s, framesToFadeCount=%d, color=%s, "
            //       "clockPos1=%s, clockPos2=%s",
            //       pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
            Animation::Part part;
            part.playUntilComplete = pathType == 'c';
            part.framesToFadeCount = framesToFadeCount;
            part.count = count;
            part.pause = pause;
            part.path = path;
@@ -902,6 +956,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
            // SLOGD("> SYSTEM");
            Animation::Part part;
            part.playUntilComplete = false;
            part.framesToFadeCount = 0;
            part.count = 1;
            part.pause = 0;
            part.audioData = nullptr;
@@ -1098,12 +1153,19 @@ bool BootAnimation::movie() {
    return false;
}

bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part, const int fadedFramesCount) {
    // stop playing only if it is time to exit and it's a partial part which has been faded out
    return exitPending() && !part.playUntilComplete && fadedFramesCount >= part.framesToFadeCount;
}

bool BootAnimation::playAnimation(const Animation& animation) {
    const size_t pcount = animation.parts.size();
    nsecs_t frameDuration = s2ns(1) / animation.fps;

    SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
            elapsedRealtime());

    int fadedFramesCount = 0;
    for (size_t i=0 ; i<pcount ; i++) {
        const Animation::Part& part(animation.parts[i]);
        const size_t fcount = part.frames.size();
@@ -1117,10 +1179,9 @@ bool BootAnimation::playAnimation(const Animation& animation) {
            continue; //to next part
        }

        for (int r=0 ; !part.count || r<part.count ; r++) {
            // Exit any non playuntil complete parts immediately
            if(exitPending() && !part.playUntilComplete)
                break;
        // process the part not only while the count allows but also if already fading
        for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
            if (shouldStopPlayingPart(part, fadedFramesCount)) break;

            mCallbacks->playPart(i, part, r);

@@ -1130,7 +1191,9 @@ bool BootAnimation::playAnimation(const Animation& animation) {
                    part.backgroundColor[2],
                    1.0f);

            for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
            for (size_t j=0 ; j<fcount ; j++) {
                if (shouldStopPlayingPart(part, fadedFramesCount)) break;

                processDisplayEvents();

                const int animationX = (mWidth - animation.width) / 2;
@@ -1169,11 +1232,22 @@ bool BootAnimation::playAnimation(const Animation& animation) {
                }
                // specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
                // which is equivalent to mHeight - (yc + frame.trimHeight)
                glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
                              0, frame.trimWidth, frame.trimHeight);
                const int frameDrawY = mHeight - (yc + frame.trimHeight);
                glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);

                // if the part hasn't been stopped yet then continue fading if necessary
                if (exitPending() && part.hasFadingPhase()) {
                    fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
                              ++fadedFramesCount);
                    if (fadedFramesCount >= part.framesToFadeCount) {
                        fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
                    }
                }

                if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
                    drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
                }

                handleViewport(frameDuration);

                eglSwapBuffers(mDisplay, mSurface);
@@ -1198,11 +1272,11 @@ bool BootAnimation::playAnimation(const Animation& animation) {

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

            // For infinite parts, we've now played them at least once, so perhaps exit
            if(exitPending() && !part.count && mCurrentInset >= mTargetInset)
                break;
            if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
                !part.hasFadingPhase()) {
                break; // exit the infinite non-fading part when it has been played at least once
            }
        }

    }

    // Free textures created for looping parts now that the animation is done.
+12 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include <vector>
#include <queue>
#include <climits>

#include <stdint.h>
#include <sys/types.h>
@@ -45,6 +46,8 @@ class SurfaceControl;
class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
    static constexpr int MAX_FADED_FRAMES_COUNT = std::numeric_limits<int>::max();

    struct Texture {
        GLint   w;
        GLint   h;
@@ -84,10 +87,15 @@ public:
            String8 trimData;
            SortedVector<Frame> frames;
            bool playUntilComplete;
            int framesToFadeCount;
            float backgroundColor[3];
            uint8_t* audioData;
            int audioLength;
            Animation* animation;

            bool hasFadingPhase() const {
                return !playUntilComplete && framesToFadeCount > 0;
            }
        };
        int fps;
        int width;
@@ -160,6 +168,8 @@ private:
    bool movie();
    void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
    void drawClock(const Font& font, const int xPos, const int yPos);
    void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
                   const Animation::Part& part, int fadedFramesCount);
    bool validClock(const Animation::Part& part);
    Animation* loadAnimation(const String8&);
    bool playAnimation(const Animation&);
@@ -172,7 +182,9 @@ private:
    EGLConfig getEglConfig(const EGLDisplay&);
    ui::Size limitSurfaceSize(int width, int height) const;
    void resizeSurface(int newWidth, int newHeight);
    void projectSceneToWindow();

    bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount);
    void checkExit();

    void handleViewport(nsecs_t timestep);
+7 −1
Original line number Diff line number Diff line
@@ -30,14 +30,20 @@ The first line defines the general parameters of the animation:

It is followed by a number of rows of the form:

    TYPE COUNT PAUSE PATH [#RGBHEX [CLOCK1 [CLOCK2]]]
    TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]

  * **TYPE:** a single char indicating what type of animation segment this is:
      + `p` -- this part will play unless interrupted by the end of the boot
      + `c` -- this part will play to completion, no matter what
      + `f` -- same as `p` but in addition the specified number of frames is being faded out while
        continue playing. Only the first interrupted `f` part is faded out, other subsequent `f`
        parts are skipped
  * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete
  * **PAUSE:** number of FRAMES to delay after this part ends
  * **PATH:** directory in which to find the frames for this part (e.g. `part0`)
  * **FADE:** _(ONLY FOR `f` TYPE)_ number of frames to fade out when interrupted where `0` means
              _immediately_ which makes `f ... 0` behave like `p` and doesn't count it as a fading
              part
  * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`
  * **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches):
      + If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate