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