This lets us share zip archive processing code with both the runtime (Art, dalvik) and critical java code (StrictJarFile). This change also moves several utility methods to ZipUtils and dedups code across several zip inflation methods. One of the side effects of this change is that several processing loops are now O(n) instead of O(n^2). bug: 10193060 (cherry picked from commit afd31e08299008fdc5c2813f21b2573f29dc53df) Change-Id: I86a2c528575d4a3ca86b94be068dcdc3c17c2ed6
601 lines
19 KiB
C++
601 lines
19 KiB
C++
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "BootAnimation"
|
|
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#include <math.h>
|
|
#include <fcntl.h>
|
|
#include <utils/misc.h>
|
|
#include <signal.h>
|
|
|
|
#include <cutils/properties.h>
|
|
|
|
#include <androidfw/AssetManager.h>
|
|
#include <binder/IPCThreadState.h>
|
|
#include <utils/Atomic.h>
|
|
#include <utils/Errors.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/threads.h>
|
|
|
|
#include <ui/PixelFormat.h>
|
|
#include <ui/Rect.h>
|
|
#include <ui/Region.h>
|
|
#include <ui/DisplayInfo.h>
|
|
#include <ui/FramebufferNativeWindow.h>
|
|
|
|
#include <gui/ISurfaceComposer.h>
|
|
#include <gui/Surface.h>
|
|
#include <gui/SurfaceComposerClient.h>
|
|
|
|
#include <core/SkBitmap.h>
|
|
#include <core/SkStream.h>
|
|
#include <core/SkImageDecoder.h>
|
|
|
|
#include <GLES/gl.h>
|
|
#include <GLES/glext.h>
|
|
#include <EGL/eglext.h>
|
|
|
|
#include "BootAnimation.h"
|
|
|
|
#define USER_BOOTANIMATION_FILE "/data/local/bootanimation.zip"
|
|
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
|
|
#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
|
|
#define EXIT_PROP_NAME "service.bootanim.exit"
|
|
|
|
extern "C" int clock_nanosleep(clockid_t clock_id, int flags,
|
|
const struct timespec *request,
|
|
struct timespec *remain);
|
|
|
|
namespace android {
|
|
|
|
static const int ANIM_ENTRY_NAME_MAX = 256;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
|
|
{
|
|
mSession = new SurfaceComposerClient();
|
|
}
|
|
|
|
BootAnimation::~BootAnimation() {
|
|
if (mZip != NULL) {
|
|
delete mZip;
|
|
}
|
|
}
|
|
|
|
void BootAnimation::onFirstRef() {
|
|
status_t err = mSession->linkToComposerDeath(this);
|
|
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
|
|
if (err == NO_ERROR) {
|
|
run("BootAnimation", PRIORITY_DISPLAY);
|
|
}
|
|
}
|
|
|
|
sp<SurfaceComposerClient> BootAnimation::session() const {
|
|
return mSession;
|
|
}
|
|
|
|
|
|
void BootAnimation::binderDied(const wp<IBinder>&)
|
|
{
|
|
// woah, surfaceflinger died!
|
|
ALOGD("SurfaceFlinger died, exiting...");
|
|
|
|
// calling requestExit() is not enough here because the Surface code
|
|
// might be blocked on a condition variable that will never be updated.
|
|
kill( getpid(), SIGKILL );
|
|
requestExit();
|
|
}
|
|
|
|
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
|
|
const char* name) {
|
|
Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
|
|
if (!asset)
|
|
return NO_INIT;
|
|
SkBitmap bitmap;
|
|
SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(),
|
|
&bitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodePixels_Mode);
|
|
asset->close();
|
|
delete asset;
|
|
|
|
// 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();
|
|
|
|
const int w = bitmap.width();
|
|
const int h = bitmap.height();
|
|
const void* p = bitmap.getPixels();
|
|
|
|
GLint crop[4] = { 0, h, w, -h };
|
|
texture->w = w;
|
|
texture->h = h;
|
|
|
|
glGenTextures(1, &texture->name);
|
|
glBindTexture(GL_TEXTURE_2D, texture->name);
|
|
|
|
switch (bitmap.config()) {
|
|
case SkBitmap::kA8_Config:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA,
|
|
GL_UNSIGNED_BYTE, p);
|
|
break;
|
|
case SkBitmap::kARGB_4444_Config:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
|
|
GL_UNSIGNED_SHORT_4_4_4_4, p);
|
|
break;
|
|
case SkBitmap::kARGB_8888_Config:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, p);
|
|
break;
|
|
case SkBitmap::kRGB_565_Config:
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, p);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t BootAnimation::initTexture(void* buffer, size_t len)
|
|
{
|
|
//StopWatch watch("blah");
|
|
|
|
SkBitmap bitmap;
|
|
SkMemoryStream stream(buffer, len);
|
|
SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
|
|
if (codec) {
|
|
codec->setDitherImage(false);
|
|
codec->decode(&stream, &bitmap,
|
|
SkBitmap::kARGB_8888_Config,
|
|
SkImageDecoder::kDecodePixels_Mode);
|
|
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();
|
|
|
|
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));
|
|
int th = 1 << (31 - __builtin_clz(h));
|
|
if (tw < w) tw <<= 1;
|
|
if (th < h) th <<= 1;
|
|
|
|
switch (bitmap.config()) {
|
|
case SkBitmap::kARGB_8888_Config:
|
|
if (tw != w || th != h) {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, 0);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0,
|
|
0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p);
|
|
} else {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, p);
|
|
}
|
|
break;
|
|
|
|
case SkBitmap::kRGB_565_Config:
|
|
if (tw != w || th != h) {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, 0);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0,
|
|
0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p);
|
|
} else {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB,
|
|
GL_UNSIGNED_SHORT_5_6_5, p);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t BootAnimation::readyToRun() {
|
|
mAssets.addDefaultAssets();
|
|
|
|
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
|
|
ISurfaceComposer::eDisplayIdMain));
|
|
DisplayInfo dinfo;
|
|
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
|
|
if (status)
|
|
return -1;
|
|
|
|
// create the native surface
|
|
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
|
|
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
|
|
|
|
SurfaceComposerClient::openGlobalTransaction();
|
|
control->setLayer(0x40000000);
|
|
SurfaceComposerClient::closeGlobalTransaction();
|
|
|
|
sp<Surface> s = control->getSurface();
|
|
|
|
// initialize opengl and egl
|
|
const EGLint attribs[] = {
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
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);
|
|
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
|
|
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;
|
|
|
|
// If the device has encryption turned on or is in process
|
|
// of being encrypted we show the encrypted boot animation.
|
|
char decrypt[PROPERTY_VALUE_MAX];
|
|
property_get("vold.decrypt", decrypt, "");
|
|
|
|
bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
|
|
|
|
ZipFileRO* zipFile = NULL;
|
|
if ((encryptedAnimation &&
|
|
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
|
|
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
|
|
|
|
((access(USER_BOOTANIMATION_FILE, R_OK) == 0) &&
|
|
((zipFile = ZipFileRO::open(USER_BOOTANIMATION_FILE)) != NULL)) ||
|
|
|
|
((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
|
|
((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
|
|
mZip = zipFile;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool BootAnimation::threadLoop()
|
|
{
|
|
bool r;
|
|
// We have no bootanimation file, so we use the stock android logo
|
|
// animation.
|
|
if (mZip == NULL) {
|
|
r = android();
|
|
} else {
|
|
r = movie();
|
|
}
|
|
|
|
// No need to force exit anymore
|
|
property_set(EXIT_PROP_NAME, "0");
|
|
|
|
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;
|
|
}
|
|
|
|
bool BootAnimation::android()
|
|
{
|
|
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
|
|
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
|
|
|
|
// clear screen
|
|
glShadeModel(GL_FLAT);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glClearColor(0,0,0,1);
|
|
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);
|
|
|
|
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);
|
|
|
|
checkExit();
|
|
} while (!exitPending());
|
|
|
|
glDeleteTextures(1, &mAndroid[0].name);
|
|
glDeleteTextures(1, &mAndroid[1].name);
|
|
return false;
|
|
}
|
|
|
|
|
|
void BootAnimation::checkExit() {
|
|
// Allow surface flinger to gracefully request shutdown
|
|
char value[PROPERTY_VALUE_MAX];
|
|
property_get(EXIT_PROP_NAME, value, "0");
|
|
int exitnow = atoi(value);
|
|
if (exitnow) {
|
|
requestExit();
|
|
}
|
|
}
|
|
|
|
bool BootAnimation::movie()
|
|
{
|
|
ZipEntryRO desc = mZip->findEntryByName("desc.txt");
|
|
ALOGE_IF(!desc, "couldn't find desc.txt");
|
|
if (!desc) {
|
|
return false;
|
|
}
|
|
|
|
FileMap* descMap = mZip->createEntryFileMap(desc);
|
|
mZip->releaseEntry(desc);
|
|
ALOGE_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[ANIM_ENTRY_NAME_MAX];
|
|
char pathType;
|
|
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
|
|
//LOGD("> 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 %s", &pathType, &count, &pause, path) == 4) {
|
|
//LOGD("> type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path);
|
|
Animation::Part part;
|
|
part.playUntilComplete = pathType == 'c';
|
|
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();
|
|
void *cookie = NULL;
|
|
if (!mZip->startIteration(&cookie)) {
|
|
return false;
|
|
}
|
|
|
|
ZipEntryRO entry;
|
|
char name[ANIM_ENTRY_NAME_MAX];
|
|
while ((entry = mZip->nextEntry(cookie)) != NULL) {
|
|
const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
|
|
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
|
|
ALOGE("Error fetching entry file name");
|
|
continue;
|
|
}
|
|
|
|
const String8 entryName(name);
|
|
const String8 path(entryName.getPathDir());
|
|
const String8 leaf(entryName.getPathLeaf());
|
|
if (leaf.size() > 0) {
|
|
for (size_t j=0 ; j<pcount ; j++) {
|
|
if (path == animation.parts[j].path) {
|
|
int method;
|
|
// supports only stored png files
|
|
if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
|
|
if (method == ZipFileRO::kCompressStored) {
|
|
FileMap* map = mZip->createEntryFileMap(entry);
|
|
if (map) {
|
|
Animation::Frame frame;
|
|
frame.name = leaf;
|
|
frame.map = map;
|
|
Animation::Part& part(animation.parts.editItemAt(j));
|
|
part.frames.add(frame);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mZip->endIteration(cookie);
|
|
|
|
// clear screen
|
|
glShadeModel(GL_FLAT);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glDisable(GL_BLEND);
|
|
glClearColor(0,0,0,1);
|
|
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 (size_t i=0 ; i<pcount ; 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++) {
|
|
// Exit any non playuntil complete parts immediately
|
|
if(exitPending() && !part.playUntilComplete)
|
|
break;
|
|
|
|
for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
|
|
const Animation::Frame& frame(part.frames[j]);
|
|
nsecs_t lastFrame = systemTime();
|
|
|
|
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);
|
|
//ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
|
|
lastFrame = now;
|
|
|
|
if (delay > 0) {
|
|
struct timespec spec;
|
|
spec.tv_sec = (now + delay) / 1000000000;
|
|
spec.tv_nsec = (now + delay) % 1000000000;
|
|
int err;
|
|
do {
|
|
err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
|
|
} while (err<0 && errno == EINTR);
|
|
}
|
|
|
|
checkExit();
|
|
}
|
|
|
|
usleep(part.pause * ns2us(frameDuration));
|
|
|
|
// For infinite parts, we've now played them at least once, so perhaps exit
|
|
if(exitPending() && !part.count)
|
|
break;
|
|
}
|
|
|
|
// free the textures for this part
|
|
if (part.count != 1) {
|
|
for (size_t j=0 ; j<fcount ; j++) {
|
|
const Animation::Frame& frame(part.frames[j]);
|
|
glDeleteTextures(1, &frame.tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
}
|
|
; // namespace android
|