egl_cache.cpp revision 39c24a20bbc697630d2b92c251b70c04d6f9d00c
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.
42static const size_t maxKeySize = MAX_EGL_CACHE_KEY_SIZE;
43static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE;
44static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE;
45
46// Cache file header
47static const char* cacheFileMagic = "EGL$";
48static const size_t cacheFileHeaderSize = 8;
49
50// The time in seconds to wait before saving newly inserted cache entries.
51static const unsigned int deferredSaveDelay = 4;
52
53// ----------------------------------------------------------------------------
54namespace android {
55// ----------------------------------------------------------------------------
56
57#define BC_EXT_STR "EGL_ANDROID_blob_cache"
58
59//
60// Callback functions passed to EGL.
61//
62static 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
67static 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//
75egl_cache_t::egl_cache_t() :
76        mInitialized(false),
77        mBlobCache(NULL) {
78}
79
80egl_cache_t::~egl_cache_t() {
81}
82
83egl_cache_t egl_cache_t::sCache;
84
85egl_cache_t* egl_cache_t::get() {
86    return &sCache;
87}
88
89void 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
127void egl_cache_t::terminate() {
128    Mutex::Autolock lock(mMutex);
129    if (mBlobCache != NULL) {
130        saveBlobCacheLocked();
131        mBlobCache = NULL;
132    }
133    mInitialized = false;
134}
135
136void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize,
137        const void* value, EGLsizeiANDROID valueSize) {
138    Mutex::Autolock lock(mMutex);
139
140    if (keySize < 0 || valueSize < 0) {
141        ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
142        return;
143    }
144
145    if (mInitialized) {
146        sp<BlobCache> bc = getBlobCacheLocked();
147        bc->set(key, keySize, value, valueSize);
148
149        if (!mSavePending) {
150            class DeferredSaveThread : public Thread {
151            public:
152                DeferredSaveThread() : Thread(false) {}
153
154                virtual bool threadLoop() {
155                    sleep(deferredSaveDelay);
156                    egl_cache_t* c = egl_cache_t::get();
157                    Mutex::Autolock lock(c->mMutex);
158                    if (c->mInitialized) {
159                        c->saveBlobCacheLocked();
160                    }
161                    c->mSavePending = false;
162                    return false;
163                }
164            };
165
166            // The thread will hold a strong ref to itself until it has finished
167            // running, so there's no need to keep a ref around.
168            sp<Thread> deferredSaveThread(new DeferredSaveThread());
169            mSavePending = true;
170            deferredSaveThread->run();
171        }
172    }
173}
174
175EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize,
176        void* value, EGLsizeiANDROID valueSize) {
177    Mutex::Autolock lock(mMutex);
178
179    if (keySize < 0 || valueSize < 0) {
180        ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
181        return 0;
182    }
183
184    if (mInitialized) {
185        sp<BlobCache> bc = getBlobCacheLocked();
186        return bc->get(key, keySize, value, valueSize);
187    }
188    return 0;
189}
190
191void egl_cache_t::setCacheFilename(const char* filename) {
192    Mutex::Autolock lock(mMutex);
193    mFilename = filename;
194}
195
196sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
197    if (mBlobCache == NULL) {
198        mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
199        loadBlobCacheLocked();
200    }
201    return mBlobCache;
202}
203
204static uint32_t crc32c(const uint8_t* buf, size_t len) {
205    const uint32_t polyBits = 0x82F63B78;
206    uint32_t r = 0;
207    for (size_t i = 0; i < len; i++) {
208        r ^= buf[i];
209        for (int j = 0; j < 8; j++) {
210            if (r & 1) {
211                r = (r >> 1) ^ polyBits;
212            } else {
213                r >>= 1;
214            }
215        }
216    }
217    return r;
218}
219
220void egl_cache_t::saveBlobCacheLocked() {
221    if (mFilename.length() > 0) {
222        size_t cacheSize = mBlobCache->getFlattenedSize();
223        size_t headerSize = cacheFileHeaderSize;
224        const char* fname = mFilename.string();
225
226        // Try to create the file with no permissions so we can write it
227        // without anyone trying to read it.
228        int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
229        if (fd == -1) {
230            if (errno == EEXIST) {
231                // The file exists, delete it and try again.
232                if (unlink(fname) == -1) {
233                    // No point in retrying if the unlink failed.
234                    ALOGE("error unlinking cache file %s: %s (%d)", fname,
235                            strerror(errno), errno);
236                    return;
237                }
238                // Retry now that we've unlinked the file.
239                fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
240            }
241            if (fd == -1) {
242                ALOGE("error creating cache file %s: %s (%d)", fname,
243                        strerror(errno), errno);
244                return;
245            }
246        }
247
248        size_t fileSize = headerSize + cacheSize;
249
250        uint8_t* buf = new uint8_t [fileSize];
251        if (!buf) {
252            ALOGE("error allocating buffer for cache contents: %s (%d)",
253                    strerror(errno), errno);
254            close(fd);
255            unlink(fname);
256            return;
257        }
258
259        status_t err = mBlobCache->flatten(buf + headerSize, cacheSize, NULL,
260                0);
261        if (err != OK) {
262            ALOGE("error writing cache contents: %s (%d)", strerror(-err),
263                    -err);
264            delete [] buf;
265            close(fd);
266            unlink(fname);
267            return;
268        }
269
270        // Write the file magic and CRC
271        memcpy(buf, cacheFileMagic, 4);
272        uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
273        *crc = crc32c(buf + headerSize, cacheSize);
274
275        if (write(fd, buf, fileSize) == -1) {
276            ALOGE("error writing cache file: %s (%d)", strerror(errno),
277                    errno);
278            delete [] buf;
279            close(fd);
280            unlink(fname);
281            return;
282        }
283
284        delete [] buf;
285        fchmod(fd, S_IRUSR);
286        close(fd);
287    }
288}
289
290void egl_cache_t::loadBlobCacheLocked() {
291    if (mFilename.length() > 0) {
292        size_t headerSize = cacheFileHeaderSize;
293
294        int fd = open(mFilename.string(), O_RDONLY, 0);
295        if (fd == -1) {
296            if (errno != ENOENT) {
297                ALOGE("error opening cache file %s: %s (%d)", mFilename.string(),
298                        strerror(errno), errno);
299            }
300            return;
301        }
302
303        struct stat statBuf;
304        if (fstat(fd, &statBuf) == -1) {
305            ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
306            close(fd);
307            return;
308        }
309
310        // Sanity check the size before trying to mmap it.
311        size_t fileSize = statBuf.st_size;
312        if (fileSize > maxTotalSize * 2) {
313            ALOGE("cache file is too large: %#llx", statBuf.st_size);
314            close(fd);
315            return;
316        }
317
318        uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
319                PROT_READ, MAP_PRIVATE, fd, 0));
320        if (buf == MAP_FAILED) {
321            ALOGE("error mmaping cache file: %s (%d)", strerror(errno),
322                    errno);
323            close(fd);
324            return;
325        }
326
327        // Check the file magic and CRC
328        size_t cacheSize = fileSize - headerSize;
329        if (memcmp(buf, cacheFileMagic, 4) != 0) {
330            ALOGE("cache file has bad mojo");
331            close(fd);
332            return;
333        }
334        uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
335        if (crc32c(buf + headerSize, cacheSize) != *crc) {
336            ALOGE("cache file failed CRC check");
337            close(fd);
338            return;
339        }
340
341        status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL,
342                0);
343        if (err != OK) {
344            ALOGE("error reading cache contents: %s (%d)", strerror(-err),
345                    -err);
346            munmap(buf, fileSize);
347            close(fd);
348            return;
349        }
350
351        munmap(buf, fileSize);
352        close(fd);
353    }
354}
355
356// ----------------------------------------------------------------------------
357}; // namespace android
358// ----------------------------------------------------------------------------
359