blob: 72655dfb5079e6a912df2a37da26b3d2f9f0dd06 [file] [log] [blame]
Jamie Gennisaca51c02011-11-03 17:42:43 -07001/*
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
Jamie Gennis98c63832011-11-07 17:03:54 -080022#include <fcntl.h>
23#include <sys/mman.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26#include <unistd.h>
27
Jamie Gennis89c1d612011-11-19 16:25:24 -080028#ifndef MAX_EGL_CACHE_ENTRY_SIZE
29#define MAX_EGL_CACHE_ENTRY_SIZE (16 * 1024);
30#endif
31
Jamie Gennisf478e6d2012-09-12 11:55:16 -070032#ifndef MAX_EGL_CACHE_KEY_SIZE
33#define MAX_EGL_CACHE_KEY_SIZE (1024);
34#endif
35
Jamie Gennis89c1d612011-11-19 16:25:24 -080036#ifndef MAX_EGL_CACHE_SIZE
37#define MAX_EGL_CACHE_SIZE (64 * 1024);
38#endif
39
Jamie Gennis76601082011-11-06 14:14:33 -080040// Cache size limits.
Jamie Gennisf478e6d2012-09-12 11:55:16 -070041static const size_t maxKeySize = MAX_EGL_CACHE_KEY_SIZE;
Jamie Gennis89c1d612011-11-19 16:25:24 -080042static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE;
43static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE;
Jamie Gennis76601082011-11-06 14:14:33 -080044
Jamie Gennis98c63832011-11-07 17:03:54 -080045// Cache file header
46static const char* cacheFileMagic = "EGL$";
47static const size_t cacheFileHeaderSize = 8;
48
Jamie Gennis99c3d702011-11-08 17:59:36 -080049// The time in seconds to wait before saving newly inserted cache entries.
50static const unsigned int deferredSaveDelay = 4;
51
Jamie Gennisaca51c02011-11-03 17:42:43 -070052// ----------------------------------------------------------------------------
53namespace android {
54// ----------------------------------------------------------------------------
55
56#define BC_EXT_STR "EGL_ANDROID_blob_cache"
57
58//
Jamie Gennis76601082011-11-06 14:14:33 -080059// Callback functions passed to EGL.
Jamie Gennisaca51c02011-11-03 17:42:43 -070060//
Jamie Gennisc42fcf02011-11-09 15:35:34 -080061static void setBlob(const void* key, EGLsizeiANDROID keySize,
62 const void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080063 egl_cache_t::get()->setBlob(key, keySize, value, valueSize);
Jamie Gennisaca51c02011-11-03 17:42:43 -070064}
65
Jamie Gennisc42fcf02011-11-09 15:35:34 -080066static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize,
67 void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080068 return egl_cache_t::get()->getBlob(key, keySize, value, valueSize);
69}
70
71//
72// egl_cache_t definition
73//
74egl_cache_t::egl_cache_t() :
75 mInitialized(false),
76 mBlobCache(NULL) {
77}
78
79egl_cache_t::~egl_cache_t() {
Jamie Gennisaca51c02011-11-03 17:42:43 -070080}
81
Jamie Gennis98c63832011-11-07 17:03:54 -080082egl_cache_t egl_cache_t::sCache;
83
Jamie Gennisaca51c02011-11-03 17:42:43 -070084egl_cache_t* egl_cache_t::get() {
Jamie Gennis98c63832011-11-07 17:03:54 -080085 return &sCache;
Jamie Gennisaca51c02011-11-03 17:42:43 -070086}
87
88void egl_cache_t::initialize(egl_display_t *display) {
Jamie Gennis76601082011-11-06 14:14:33 -080089 Mutex::Autolock lock(mMutex);
Mathias Agopianada798b2012-02-13 17:09:30 -080090
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>(
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800105 cnx->egl.eglGetProcAddress(
106 "eglSetBlobCacheFuncsANDROID"));
Mathias Agopianada798b2012-02-13 17:09:30 -0800107 if (eglSetBlobCacheFuncsANDROID == NULL) {
108 ALOGE("EGL_ANDROID_blob_cache advertised, "
109 "but unable to get eglSetBlobCacheFuncsANDROID");
110 return;
111 }
Jamie Gennisaca51c02011-11-03 17:42:43 -0700112
Mathias Agopianada798b2012-02-13 17:09:30 -0800113 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);
Jamie Gennisaca51c02011-11-03 17:42:43 -0700119 }
120 }
121 }
Mathias Agopianada798b2012-02-13 17:09:30 -0800122
Jamie Gennis76601082011-11-06 14:14:33 -0800123 mInitialized = true;
124}
125
126void egl_cache_t::terminate() {
127 Mutex::Autolock lock(mMutex);
128 if (mBlobCache != NULL) {
129 saveBlobCacheLocked();
130 mBlobCache = NULL;
131 }
132 mInitialized = false;
133}
134
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800135void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize,
136 const void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -0800137 Mutex::Autolock lock(mMutex);
138
139 if (keySize < 0 || valueSize < 0) {
Steve Block32397c12012-01-05 23:22:43 +0000140 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
Jamie Gennis76601082011-11-06 14:14:33 -0800141 return;
142 }
143
144 if (mInitialized) {
145 sp<BlobCache> bc = getBlobCacheLocked();
146 bc->set(key, keySize, value, valueSize);
Jamie Gennis99c3d702011-11-08 17:59:36 -0800147
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 }
Jamie Gennis76601082011-11-06 14:14:33 -0800171 }
172}
173
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800174EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize,
175 void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -0800176 Mutex::Autolock lock(mMutex);
177
178 if (keySize < 0 || valueSize < 0) {
Steve Block32397c12012-01-05 23:22:43 +0000179 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
Jamie Gennis76601082011-11-06 14:14:33 -0800180 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
Jamie Gennis98c63832011-11-07 17:03:54 -0800190void egl_cache_t::setCacheFilename(const char* filename) {
191 Mutex::Autolock lock(mMutex);
192 mFilename = filename;
193}
194
Jamie Gennis76601082011-11-06 14:14:33 -0800195sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
196 if (mBlobCache == NULL) {
197 mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
198 loadBlobCacheLocked();
199 }
200 return mBlobCache;
201}
202
Jamie Gennis98c63832011-11-07 17:03:54 -0800203static 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
Jamie Gennis76601082011-11-06 14:14:33 -0800219void egl_cache_t::saveBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800220 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.
Steve Blocke6f43dd2012-01-06 19:20:56 +0000233 ALOGE("error unlinking cache file %s: %s (%d)", fname,
Jamie Gennis98c63832011-11-07 17:03:54 -0800234 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000241 ALOGE("error creating cache file %s: %s (%d)", fname,
Jamie Gennis98c63832011-11-07 17:03:54 -0800242 strerror(errno), errno);
243 return;
244 }
245 }
246
247 size_t fileSize = headerSize + cacheSize;
Jamie Gennis98c63832011-11-07 17:03:54 -0800248
vijay guptaa30cc7d2012-06-27 16:14:42 -0700249 uint8_t* buf = new uint8_t [fileSize];
250 if (!buf) {
251 ALOGE("error allocating buffer for cache contents: %s (%d)",
252 strerror(errno), errno);
Jamie Gennis98c63832011-11-07 17:03:54 -0800253 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000261 ALOGE("error writing cache contents: %s (%d)", strerror(-err),
Jamie Gennis98c63832011-11-07 17:03:54 -0800262 -err);
vijay guptaa30cc7d2012-06-27 16:14:42 -0700263 delete [] buf;
Jamie Gennis98c63832011-11-07 17:03:54 -0800264 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
vijay guptaa30cc7d2012-06-27 16:14:42 -0700274 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;
Jamie Gennis98c63832011-11-07 17:03:54 -0800284 fchmod(fd, S_IRUSR);
285 close(fd);
286 }
Jamie Gennis76601082011-11-06 14:14:33 -0800287}
288
289void egl_cache_t::loadBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800290 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000296 ALOGE("error opening cache file %s: %s (%d)", mFilename.string(),
Jamie Gennis98c63832011-11-07 17:03:54 -0800297 strerror(errno), errno);
298 }
299 return;
300 }
301
302 struct stat statBuf;
303 if (fstat(fd, &statBuf) == -1) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000304 ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
Jamie Gennis98c63832011-11-07 17:03:54 -0800305 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000312 ALOGE("cache file is too large: %#llx", statBuf.st_size);
Jamie Gennis98c63832011-11-07 17:03:54 -0800313 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000320 ALOGE("error mmaping cache file: %s (%d)", strerror(errno),
Jamie Gennis98c63832011-11-07 17:03:54 -0800321 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000329 ALOGE("cache file has bad mojo");
Jamie Gennis98c63832011-11-07 17:03:54 -0800330 close(fd);
331 return;
332 }
333 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
334 if (crc32c(buf + headerSize, cacheSize) != *crc) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000335 ALOGE("cache file failed CRC check");
Jamie Gennis98c63832011-11-07 17:03:54 -0800336 close(fd);
337 return;
338 }
339
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800340 status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL,
341 0);
Jamie Gennis98c63832011-11-07 17:03:54 -0800342 if (err != OK) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000343 ALOGE("error reading cache contents: %s (%d)", strerror(-err),
Jamie Gennis98c63832011-11-07 17:03:54 -0800344 -err);
345 munmap(buf, fileSize);
346 close(fd);
347 return;
348 }
349
350 munmap(buf, fileSize);
351 close(fd);
352 }
Jamie Gennisaca51c02011-11-03 17:42:43 -0700353}
354
355// ----------------------------------------------------------------------------
356}; // namespace android
357// ----------------------------------------------------------------------------