Home | History | Annotate | Download | only in EGL
      1 /*
      2  ** Copyright 2011, 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 #include "../egl_impl.h"
     18 
     19 #include "egl_cache.h"
     20 #include "egl_display.h"
     21 #include "egldefs.h"
     22 
     23 #include <fcntl.h>
     24 #include <sys/mman.h>
     25 #include <sys/stat.h>
     26 #include <sys/types.h>
     27 #include <unistd.h>
     28 
     29 #ifndef MAX_EGL_CACHE_ENTRY_SIZE
     30 #define MAX_EGL_CACHE_ENTRY_SIZE (16 * 1024);
     31 #endif
     32 
     33 #ifndef MAX_EGL_CACHE_KEY_SIZE
     34 #define MAX_EGL_CACHE_KEY_SIZE (1024);
     35 #endif
     36 
     37 #ifndef MAX_EGL_CACHE_SIZE
     38 #define MAX_EGL_CACHE_SIZE (64 * 1024);
     39 #endif
     40 
     41 // Cache size limits.
     42 static const size_t maxKeySize = MAX_EGL_CACHE_KEY_SIZE;
     43 static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE;
     44 static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE;
     45 
     46 // Cache file header
     47 static const char* cacheFileMagic = "EGL$";
     48 static const size_t cacheFileHeaderSize = 8;
     49 
     50 // The time in seconds to wait before saving newly inserted cache entries.
     51 static const unsigned int deferredSaveDelay = 4;
     52 
     53 // ----------------------------------------------------------------------------
     54 namespace android {
     55 // ----------------------------------------------------------------------------
     56 
     57 #define BC_EXT_STR "EGL_ANDROID_blob_cache"
     58 
     59 //
     60 // Callback functions passed to EGL.
     61 //
     62 static void setBlob(const void* key, EGLsizeiANDROID keySize,
     63         const void* value, EGLsizeiANDROID valueSize) {
     64     egl_cache_t::get()->setBlob(key, keySize, value, valueSize);
     65 }
     66 
     67 static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize,
     68         void* value, EGLsizeiANDROID valueSize) {
     69     return egl_cache_t::get()->getBlob(key, keySize, value, valueSize);
     70 }
     71 
     72 //
     73 // egl_cache_t definition
     74 //
     75 egl_cache_t::egl_cache_t() :
     76         mInitialized(false),
     77         mBlobCache(NULL) {
     78 }
     79 
     80 egl_cache_t::~egl_cache_t() {
     81 }
     82 
     83 egl_cache_t egl_cache_t::sCache;
     84 
     85 egl_cache_t* egl_cache_t::get() {
     86     return &sCache;
     87 }
     88 
     89 void egl_cache_t::initialize(egl_display_t *display) {
     90     Mutex::Autolock lock(mMutex);
     91 
     92     egl_connection_t* const cnx = &gEGLImpl;
     93     if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
     94         const char* exts = display->disp.queryString.extensions;
     95         size_t bcExtLen = strlen(BC_EXT_STR);
     96         size_t extsLen = strlen(exts);
     97         bool equal = !strcmp(BC_EXT_STR, exts);
     98         bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1);
     99         bool atEnd = (bcExtLen+1) < extsLen &&
    100                 !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1));
    101         bool inMiddle = strstr(exts, " " BC_EXT_STR " ");
    102         if (equal || atStart || atEnd || inMiddle) {
    103             PFNEGLSETBLOBCACHEFUNCSANDROIDPROC eglSetBlobCacheFuncsANDROID;
    104             eglSetBlobCacheFuncsANDROID =
    105                     reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSANDROIDPROC>(
    106                             cnx->egl.eglGetProcAddress(
    107                                     "eglSetBlobCacheFuncsANDROID"));
    108             if (eglSetBlobCacheFuncsANDROID == NULL) {
    109                 ALOGE("EGL_ANDROID_blob_cache advertised, "
    110                         "but unable to get eglSetBlobCacheFuncsANDROID");
    111                 return;
    112             }
    113 
    114             eglSetBlobCacheFuncsANDROID(display->disp.dpy,
    115                     android::setBlob, android::getBlob);
    116             EGLint err = cnx->egl.eglGetError();
    117             if (err != EGL_SUCCESS) {
    118                 ALOGE("eglSetBlobCacheFuncsANDROID resulted in an error: "
    119                         "%#x", err);
    120             }
    121         }
    122     }
    123 
    124     mInitialized = true;
    125 }
    126 
    127 void egl_cache_t::terminate() {
    128     Mutex::Autolock lock(mMutex);
    129     saveBlobCacheLocked();
    130     mBlobCache = NULL;
    131 }
    132 
    133 void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize,
    134         const void* value, EGLsizeiANDROID valueSize) {
    135     Mutex::Autolock lock(mMutex);
    136 
    137     if (keySize < 0 || valueSize < 0) {
    138         ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
    139         return;
    140     }
    141 
    142     if (mInitialized) {
    143         sp<BlobCache> bc = getBlobCacheLocked();
    144         bc->set(key, keySize, value, valueSize);
    145 
    146         if (!mSavePending) {
    147             class DeferredSaveThread : public Thread {
    148             public:
    149                 DeferredSaveThread() : Thread(false) {}
    150 
    151                 virtual bool threadLoop() {
    152                     sleep(deferredSaveDelay);
    153                     egl_cache_t* c = egl_cache_t::get();
    154                     Mutex::Autolock lock(c->mMutex);
    155                     if (c->mInitialized) {
    156                         c->saveBlobCacheLocked();
    157                     }
    158                     c->mSavePending = false;
    159                     return false;
    160                 }
    161             };
    162 
    163             // The thread will hold a strong ref to itself until it has finished
    164             // running, so there's no need to keep a ref around.
    165             sp<Thread> deferredSaveThread(new DeferredSaveThread());
    166             mSavePending = true;
    167             deferredSaveThread->run();
    168         }
    169     }
    170 }
    171 
    172 EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize,
    173         void* value, EGLsizeiANDROID valueSize) {
    174     Mutex::Autolock lock(mMutex);
    175 
    176     if (keySize < 0 || valueSize < 0) {
    177         ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
    178         return 0;
    179     }
    180 
    181     if (mInitialized) {
    182         sp<BlobCache> bc = getBlobCacheLocked();
    183         return bc->get(key, keySize, value, valueSize);
    184     }
    185     return 0;
    186 }
    187 
    188 void egl_cache_t::setCacheFilename(const char* filename) {
    189     Mutex::Autolock lock(mMutex);
    190     mFilename = filename;
    191 }
    192 
    193 sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
    194     if (mBlobCache == NULL) {
    195         mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
    196         loadBlobCacheLocked();
    197     }
    198     return mBlobCache;
    199 }
    200 
    201 static uint32_t crc32c(const uint8_t* buf, size_t len) {
    202     const uint32_t polyBits = 0x82F63B78;
    203     uint32_t r = 0;
    204     for (size_t i = 0; i < len; i++) {
    205         r ^= buf[i];
    206         for (int j = 0; j < 8; j++) {
    207             if (r & 1) {
    208                 r = (r >> 1) ^ polyBits;
    209             } else {
    210                 r >>= 1;
    211             }
    212         }
    213     }
    214     return r;
    215 }
    216 
    217 void egl_cache_t::saveBlobCacheLocked() {
    218     if (mFilename.length() > 0 && mBlobCache != NULL) {
    219         size_t cacheSize = mBlobCache->getFlattenedSize();
    220         size_t headerSize = cacheFileHeaderSize;
    221         const char* fname = mFilename.string();
    222 
    223         // Try to create the file with no permissions so we can write it
    224         // without anyone trying to read it.
    225         int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
    226         if (fd == -1) {
    227             if (errno == EEXIST) {
    228                 // The file exists, delete it and try again.
    229                 if (unlink(fname) == -1) {
    230                     // No point in retrying if the unlink failed.
    231                     ALOGE("error unlinking cache file %s: %s (%d)", fname,
    232                             strerror(errno), errno);
    233                     return;
    234                 }
    235                 // Retry now that we've unlinked the file.
    236                 fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
    237             }
    238             if (fd == -1) {
    239                 ALOGE("error creating cache file %s: %s (%d)", fname,
    240                         strerror(errno), errno);
    241                 return;
    242             }
    243         }
    244 
    245         size_t fileSize = headerSize + cacheSize;
    246 
    247         uint8_t* buf = new uint8_t [fileSize];
    248         if (!buf) {
    249             ALOGE("error allocating buffer for cache contents: %s (%d)",
    250                     strerror(errno), errno);
    251             close(fd);
    252             unlink(fname);
    253             return;
    254         }
    255 
    256         status_t err = mBlobCache->flatten(buf + headerSize, cacheSize);
    257         if (err != OK) {
    258             ALOGE("error writing cache contents: %s (%d)", strerror(-err),
    259                     -err);
    260             delete [] buf;
    261             close(fd);
    262             unlink(fname);
    263             return;
    264         }
    265 
    266         // Write the file magic and CRC
    267         memcpy(buf, cacheFileMagic, 4);
    268         uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
    269         *crc = crc32c(buf + headerSize, cacheSize);
    270 
    271         if (write(fd, buf, fileSize) == -1) {
    272             ALOGE("error writing cache file: %s (%d)", strerror(errno),
    273                     errno);
    274             delete [] buf;
    275             close(fd);
    276             unlink(fname);
    277             return;
    278         }
    279 
    280         delete [] buf;
    281         fchmod(fd, S_IRUSR);
    282         close(fd);
    283     }
    284 }
    285 
    286 void egl_cache_t::loadBlobCacheLocked() {
    287     if (mFilename.length() > 0) {
    288         size_t headerSize = cacheFileHeaderSize;
    289 
    290         int fd = open(mFilename.string(), O_RDONLY, 0);
    291         if (fd == -1) {
    292             if (errno != ENOENT) {
    293                 ALOGE("error opening cache file %s: %s (%d)", mFilename.string(),
    294                         strerror(errno), errno);
    295             }
    296             return;
    297         }
    298 
    299         struct stat statBuf;
    300         if (fstat(fd, &statBuf) == -1) {
    301             ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
    302             close(fd);
    303             return;
    304         }
    305 
    306         // Sanity check the size before trying to mmap it.
    307         size_t fileSize = statBuf.st_size;
    308         if (fileSize > maxTotalSize * 2) {
    309             ALOGE("cache file is too large: %#llx", statBuf.st_size);
    310             close(fd);
    311             return;
    312         }
    313 
    314         uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
    315                 PROT_READ, MAP_PRIVATE, fd, 0));
    316         if (buf == MAP_FAILED) {
    317             ALOGE("error mmaping cache file: %s (%d)", strerror(errno),
    318                     errno);
    319             close(fd);
    320             return;
    321         }
    322 
    323         // Check the file magic and CRC
    324         size_t cacheSize = fileSize - headerSize;
    325         if (memcmp(buf, cacheFileMagic, 4) != 0) {
    326             ALOGE("cache file has bad mojo");
    327             close(fd);
    328             return;
    329         }
    330         uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
    331         if (crc32c(buf + headerSize, cacheSize) != *crc) {
    332             ALOGE("cache file failed CRC check");
    333             close(fd);
    334             return;
    335         }
    336 
    337         status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize);
    338         if (err != OK) {
    339             ALOGE("error reading cache contents: %s (%d)", strerror(-err),
    340                     -err);
    341             munmap(buf, fileSize);
    342             close(fd);
    343             return;
    344         }
    345 
    346         munmap(buf, fileSize);
    347         close(fd);
    348     }
    349 }
    350 
    351 // ----------------------------------------------------------------------------
    352 }; // namespace android
    353 // ----------------------------------------------------------------------------
    354