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