Home | History | Annotate | Download | only in bootanimation
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "BootAnimation"
     18 
     19 #include <stdint.h>
     20 #include <sys/types.h>
     21 #include <math.h>
     22 #include <fcntl.h>
     23 #include <utils/misc.h>
     24 #include <signal.h>
     25 
     26 #include <cutils/properties.h>
     27 
     28 #include <androidfw/AssetManager.h>
     29 #include <binder/IPCThreadState.h>
     30 #include <utils/Atomic.h>
     31 #include <utils/Errors.h>
     32 #include <utils/Log.h>
     33 #include <utils/threads.h>
     34 
     35 #include <ui/PixelFormat.h>
     36 #include <ui/Rect.h>
     37 #include <ui/Region.h>
     38 #include <ui/DisplayInfo.h>
     39 #include <ui/FramebufferNativeWindow.h>
     40 
     41 #include <gui/ISurfaceComposer.h>
     42 #include <gui/Surface.h>
     43 #include <gui/SurfaceComposerClient.h>
     44 
     45 #include <core/SkBitmap.h>
     46 #include <core/SkStream.h>
     47 #include <images/SkImageDecoder.h>
     48 
     49 #include <GLES/gl.h>
     50 #include <GLES/glext.h>
     51 #include <EGL/eglext.h>
     52 
     53 #include "BootAnimation.h"
     54 
     55 #define USER_BOOTANIMATION_FILE "/data/local/bootanimation.zip"
     56 #define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
     57 #define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
     58 #define EXIT_PROP_NAME "service.bootanim.exit"
     59 
     60 extern "C" int clock_nanosleep(clockid_t clock_id, int flags,
     61                            const struct timespec *request,
     62                            struct timespec *remain);
     63 
     64 namespace android {
     65 
     66 // ---------------------------------------------------------------------------
     67 
     68 BootAnimation::BootAnimation() : Thread(false)
     69 {
     70     mSession = new SurfaceComposerClient();
     71 }
     72 
     73 BootAnimation::~BootAnimation() {
     74 }
     75 
     76 void BootAnimation::onFirstRef() {
     77     status_t err = mSession->linkToComposerDeath(this);
     78     ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
     79     if (err == NO_ERROR) {
     80         run("BootAnimation", PRIORITY_DISPLAY);
     81     }
     82 }
     83 
     84 sp<SurfaceComposerClient> BootAnimation::session() const {
     85     return mSession;
     86 }
     87 
     88 
     89 void BootAnimation::binderDied(const wp<IBinder>& who)
     90 {
     91     // woah, surfaceflinger died!
     92     ALOGD("SurfaceFlinger died, exiting...");
     93 
     94     // calling requestExit() is not enough here because the Surface code
     95     // might be blocked on a condition variable that will never be updated.
     96     kill( getpid(), SIGKILL );
     97     requestExit();
     98 }
     99 
    100 status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
    101         const char* name) {
    102     Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
    103     if (!asset)
    104         return NO_INIT;
    105     SkBitmap bitmap;
    106     SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
    107             &bitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodePixels_Mode);
    108     asset->close();
    109     delete asset;
    110 
    111     // ensure we can call getPixels(). No need to call unlock, since the
    112     // bitmap will go out of scope when we return from this method.
    113     bitmap.lockPixels();
    114 
    115     const int w = bitmap.width();
    116     const int h = bitmap.height();
    117     const void* p = bitmap.getPixels();
    118 
    119     GLint crop[4] = { 0, h, w, -h };
    120     texture->w = w;
    121     texture->h = h;
    122 
    123     glGenTextures(1, &texture->name);
    124     glBindTexture(GL_TEXTURE_2D, texture->name);
    125 
    126     switch (bitmap.getConfig()) {
    127         case SkBitmap::kA8_Config:
    128             glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA,
    129                     GL_UNSIGNED_BYTE, p);
    130             break;
    131         case SkBitmap::kARGB_4444_Config:
    132             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
    133                     GL_UNSIGNED_SHORT_4_4_4_4, p);
    134             break;
    135         case SkBitmap::kARGB_8888_Config:
    136             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
    137                     GL_UNSIGNED_BYTE, p);
    138             break;
    139         case SkBitmap::kRGB_565_Config:
    140             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
    141                     GL_UNSIGNED_SHORT_5_6_5, p);
    142             break;
    143         default:
    144             break;
    145     }
    146 
    147     glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
    148     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    149     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    150     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    151     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    152     return NO_ERROR;
    153 }
    154 
    155 status_t BootAnimation::initTexture(void* buffer, size_t len)
    156 {
    157     //StopWatch watch("blah");
    158 
    159     SkBitmap bitmap;
    160     SkMemoryStream  stream(buffer, len);
    161     SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
    162     codec->setDitherImage(false);
    163     if (codec) {
    164         codec->decode(&stream, &bitmap,
    165                 SkBitmap::kARGB_8888_Config,
    166                 SkImageDecoder::kDecodePixels_Mode);
    167         delete codec;
    168     }
    169 
    170     // ensure we can call getPixels(). No need to call unlock, since the
    171     // bitmap will go out of scope when we return from this method.
    172     bitmap.lockPixels();
    173 
    174     const int w = bitmap.width();
    175     const int h = bitmap.height();
    176     const void* p = bitmap.getPixels();
    177 
    178     GLint crop[4] = { 0, h, w, -h };
    179     int tw = 1 << (31 - __builtin_clz(w));
    180     int th = 1 << (31 - __builtin_clz(h));
    181     if (tw < w) tw <<= 1;
    182     if (th < h) th <<= 1;
    183 
    184     switch (bitmap.getConfig()) {
    185         case SkBitmap::kARGB_8888_Config:
    186             if (tw != w || th != h) {
    187                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
    188                         GL_UNSIGNED_BYTE, 0);
    189                 glTexSubImage2D(GL_TEXTURE_2D, 0,
    190                         0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p);
    191             } else {
    192                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
    193                         GL_UNSIGNED_BYTE, p);
    194             }
    195             break;
    196 
    197         case SkBitmap::kRGB_565_Config:
    198             if (tw != w || th != h) {
    199                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB,
    200                         GL_UNSIGNED_SHORT_5_6_5, 0);
    201                 glTexSubImage2D(GL_TEXTURE_2D, 0,
    202                         0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p);
    203             } else {
    204                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB,
    205                         GL_UNSIGNED_SHORT_5_6_5, p);
    206             }
    207             break;
    208         default:
    209             break;
    210     }
    211 
    212     glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
    213 
    214     return NO_ERROR;
    215 }
    216 
    217 status_t BootAnimation::readyToRun() {
    218     mAssets.addDefaultAssets();
    219 
    220     sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
    221             ISurfaceComposer::eDisplayIdMain));
    222     DisplayInfo dinfo;
    223     status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
    224     if (status)
    225         return -1;
    226 
    227     // create the native surface
    228     sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
    229             dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
    230 
    231     SurfaceComposerClient::openGlobalTransaction();
    232     control->setLayer(0x40000000);
    233     SurfaceComposerClient::closeGlobalTransaction();
    234 
    235     sp<Surface> s = control->getSurface();
    236 
    237     // initialize opengl and egl
    238     const EGLint attribs[] = {
    239             EGL_RED_SIZE,   8,
    240             EGL_GREEN_SIZE, 8,
    241             EGL_BLUE_SIZE,  8,
    242             EGL_DEPTH_SIZE, 0,
    243             EGL_NONE
    244     };
    245     EGLint w, h, dummy;
    246     EGLint numConfigs;
    247     EGLConfig config;
    248     EGLSurface surface;
    249     EGLContext context;
    250 
    251     EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    252 
    253     eglInitialize(display, 0, 0);
    254     eglChooseConfig(display, attribs, &config, 1, &numConfigs);
    255     surface = eglCreateWindowSurface(display, config, s.get(), NULL);
    256     context = eglCreateContext(display, config, NULL, NULL);
    257     eglQuerySurface(display, surface, EGL_WIDTH, &w);
    258     eglQuerySurface(display, surface, EGL_HEIGHT, &h);
    259 
    260     if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
    261         return NO_INIT;
    262 
    263     mDisplay = display;
    264     mContext = context;
    265     mSurface = surface;
    266     mWidth = w;
    267     mHeight = h;
    268     mFlingerSurfaceControl = control;
    269     mFlingerSurface = s;
    270 
    271     mAndroidAnimation = true;
    272 
    273     // If the device has encryption turned on or is in process
    274     // of being encrypted we show the encrypted boot animation.
    275     char decrypt[PROPERTY_VALUE_MAX];
    276     property_get("vold.decrypt", decrypt, "");
    277 
    278     bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
    279 
    280     if ((encryptedAnimation &&
    281             (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
    282             (mZip.open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE) == NO_ERROR)) ||
    283 
    284             ((access(USER_BOOTANIMATION_FILE, R_OK) == 0) &&
    285             (mZip.open(USER_BOOTANIMATION_FILE) == NO_ERROR)) ||
    286 
    287             ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
    288             (mZip.open(SYSTEM_BOOTANIMATION_FILE) == NO_ERROR))) {
    289         mAndroidAnimation = false;
    290     }
    291 
    292     return NO_ERROR;
    293 }
    294 
    295 bool BootAnimation::threadLoop()
    296 {
    297     bool r;
    298     if (mAndroidAnimation) {
    299         r = android();
    300     } else {
    301         r = movie();
    302     }
    303 
    304     // No need to force exit anymore
    305     property_set(EXIT_PROP_NAME, "0");
    306 
    307     eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    308     eglDestroyContext(mDisplay, mContext);
    309     eglDestroySurface(mDisplay, mSurface);
    310     mFlingerSurface.clear();
    311     mFlingerSurfaceControl.clear();
    312     eglTerminate(mDisplay);
    313     IPCThreadState::self()->stopProcess();
    314     return r;
    315 }
    316 
    317 bool BootAnimation::android()
    318 {
    319     initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
    320     initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
    321 
    322     // clear screen
    323     glShadeModel(GL_FLAT);
    324     glDisable(GL_DITHER);
    325     glDisable(GL_SCISSOR_TEST);
    326     glClearColor(0,0,0,1);
    327     glClear(GL_COLOR_BUFFER_BIT);
    328     eglSwapBuffers(mDisplay, mSurface);
    329 
    330     glEnable(GL_TEXTURE_2D);
    331     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    332 
    333     const GLint xc = (mWidth  - mAndroid[0].w) / 2;
    334     const GLint yc = (mHeight - mAndroid[0].h) / 2;
    335     const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
    336 
    337     glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
    338             updateRect.height());
    339 
    340     // Blend state
    341     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    342     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    343 
    344     const nsecs_t startTime = systemTime();
    345     do {
    346         nsecs_t now = systemTime();
    347         double time = now - startTime;
    348         float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
    349         GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
    350         GLint x = xc - offset;
    351 
    352         glDisable(GL_SCISSOR_TEST);
    353         glClear(GL_COLOR_BUFFER_BIT);
    354 
    355         glEnable(GL_SCISSOR_TEST);
    356         glDisable(GL_BLEND);
    357         glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
    358         glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);
    359         glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
    360 
    361         glEnable(GL_BLEND);
    362         glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
    363         glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
    364 
    365         EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
    366         if (res == EGL_FALSE)
    367             break;
    368 
    369         // 12fps: don't animate too fast to preserve CPU
    370         const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
    371         if (sleepTime > 0)
    372             usleep(sleepTime);
    373 
    374         checkExit();
    375     } while (!exitPending());
    376 
    377     glDeleteTextures(1, &mAndroid[0].name);
    378     glDeleteTextures(1, &mAndroid[1].name);
    379     return false;
    380 }
    381 
    382 
    383 void BootAnimation::checkExit() {
    384     // Allow surface flinger to gracefully request shutdown
    385     char value[PROPERTY_VALUE_MAX];
    386     property_get(EXIT_PROP_NAME, value, "0");
    387     int exitnow = atoi(value);
    388     if (exitnow) {
    389         requestExit();
    390     }
    391 }
    392 
    393 bool BootAnimation::movie()
    394 {
    395     ZipFileRO& zip(mZip);
    396 
    397     size_t numEntries = zip.getNumEntries();
    398     ZipEntryRO desc = zip.findEntryByName("desc.txt");
    399     FileMap* descMap = zip.createEntryFileMap(desc);
    400     ALOGE_IF(!descMap, "descMap is null");
    401     if (!descMap) {
    402         return false;
    403     }
    404 
    405     String8 desString((char const*)descMap->getDataPtr(),
    406             descMap->getDataLength());
    407     char const* s = desString.string();
    408 
    409     Animation animation;
    410 
    411     // Parse the description file
    412     for (;;) {
    413         const char* endl = strstr(s, "\n");
    414         if (!endl) break;
    415         String8 line(s, endl - s);
    416         const char* l = line.string();
    417         int fps, width, height, count, pause;
    418         char path[256];
    419         char pathType;
    420         if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
    421             //LOGD("> w=%d, h=%d, fps=%d", width, height, fps);
    422             animation.width = width;
    423             animation.height = height;
    424             animation.fps = fps;
    425         }
    426         else if (sscanf(l, " %c %d %d %s", &pathType, &count, &pause, path) == 4) {
    427             //LOGD("> type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path);
    428             Animation::Part part;
    429             part.playUntilComplete = pathType == 'c';
    430             part.count = count;
    431             part.pause = pause;
    432             part.path = path;
    433             animation.parts.add(part);
    434         }
    435 
    436         s = ++endl;
    437     }
    438 
    439     // read all the data structures
    440     const size_t pcount = animation.parts.size();
    441     for (size_t i=0 ; i<numEntries ; i++) {
    442         char name[256];
    443         ZipEntryRO entry = zip.findEntryByIndex(i);
    444         if (zip.getEntryFileName(entry, name, 256) == 0) {
    445             const String8 entryName(name);
    446             const String8 path(entryName.getPathDir());
    447             const String8 leaf(entryName.getPathLeaf());
    448             if (leaf.size() > 0) {
    449                 for (int j=0 ; j<pcount ; j++) {
    450                     if (path == animation.parts[j].path) {
    451                         int method;
    452                         // supports only stored png files
    453                         if (zip.getEntryInfo(entry, &method, 0, 0, 0, 0, 0)) {
    454                             if (method == ZipFileRO::kCompressStored) {
    455                                 FileMap* map = zip.createEntryFileMap(entry);
    456                                 if (map) {
    457                                     Animation::Frame frame;
    458                                     frame.name = leaf;
    459                                     frame.map = map;
    460                                     Animation::Part& part(animation.parts.editItemAt(j));
    461                                     part.frames.add(frame);
    462                                 }
    463                             }
    464                         }
    465                     }
    466                 }
    467             }
    468         }
    469     }
    470 
    471     // clear screen
    472     glShadeModel(GL_FLAT);
    473     glDisable(GL_DITHER);
    474     glDisable(GL_SCISSOR_TEST);
    475     glDisable(GL_BLEND);
    476     glClearColor(0,0,0,1);
    477     glClear(GL_COLOR_BUFFER_BIT);
    478 
    479     eglSwapBuffers(mDisplay, mSurface);
    480 
    481     glBindTexture(GL_TEXTURE_2D, 0);
    482     glEnable(GL_TEXTURE_2D);
    483     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    484     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    485     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    486     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    487     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    488 
    489     const int xc = (mWidth - animation.width) / 2;
    490     const int yc = ((mHeight - animation.height) / 2);
    491     nsecs_t lastFrame = systemTime();
    492     nsecs_t frameDuration = s2ns(1) / animation.fps;
    493 
    494     Region clearReg(Rect(mWidth, mHeight));
    495     clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
    496 
    497     for (int i=0 ; i<pcount ; i++) {
    498         const Animation::Part& part(animation.parts[i]);
    499         const size_t fcount = part.frames.size();
    500         glBindTexture(GL_TEXTURE_2D, 0);
    501 
    502         for (int r=0 ; !part.count || r<part.count ; r++) {
    503             // Exit any non playuntil complete parts immediately
    504             if(exitPending() && !part.playUntilComplete)
    505                 break;
    506 
    507             for (int j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
    508                 const Animation::Frame& frame(part.frames[j]);
    509                 nsecs_t lastFrame = systemTime();
    510 
    511                 if (r > 0) {
    512                     glBindTexture(GL_TEXTURE_2D, frame.tid);
    513                 } else {
    514                     if (part.count != 1) {
    515                         glGenTextures(1, &frame.tid);
    516                         glBindTexture(GL_TEXTURE_2D, frame.tid);
    517                         glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    518                         glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    519                     }
    520                     initTexture(
    521                             frame.map->getDataPtr(),
    522                             frame.map->getDataLength());
    523                 }
    524 
    525                 if (!clearReg.isEmpty()) {
    526                     Region::const_iterator head(clearReg.begin());
    527                     Region::const_iterator tail(clearReg.end());
    528                     glEnable(GL_SCISSOR_TEST);
    529                     while (head != tail) {
    530                         const Rect& r(*head++);
    531                         glScissor(r.left, mHeight - r.bottom,
    532                                 r.width(), r.height());
    533                         glClear(GL_COLOR_BUFFER_BIT);
    534                     }
    535                     glDisable(GL_SCISSOR_TEST);
    536                 }
    537                 glDrawTexiOES(xc, yc, 0, animation.width, animation.height);
    538                 eglSwapBuffers(mDisplay, mSurface);
    539 
    540                 nsecs_t now = systemTime();
    541                 nsecs_t delay = frameDuration - (now - lastFrame);
    542                 //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
    543                 lastFrame = now;
    544 
    545                 if (delay > 0) {
    546                     struct timespec spec;
    547                     spec.tv_sec  = (now + delay) / 1000000000;
    548                     spec.tv_nsec = (now + delay) % 1000000000;
    549                     int err;
    550                     do {
    551                         err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
    552                     } while (err<0 && errno == EINTR);
    553                 }
    554 
    555                 checkExit();
    556             }
    557 
    558             usleep(part.pause * ns2us(frameDuration));
    559 
    560             // For infinite parts, we've now played them at least once, so perhaps exit
    561             if(exitPending() && !part.count)
    562                 break;
    563         }
    564 
    565         // free the textures for this part
    566         if (part.count != 1) {
    567             for (int j=0 ; j<fcount ; j++) {
    568                 const Animation::Frame& frame(part.frames[j]);
    569                 glDeleteTextures(1, &frame.tid);
    570             }
    571         }
    572     }
    573 
    574     return false;
    575 }
    576 
    577 // ---------------------------------------------------------------------------
    578 
    579 }
    580 ; // namespace android
    581