blob: ed2bef3d4309c79af34e8ba72e1e1e4700b2a755 [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
32#ifndef MAX_EGL_CACHE_SIZE
33#define MAX_EGL_CACHE_SIZE (64 * 1024);
34#endif
35
Jamie Gennis76601082011-11-06 14:14:33 -080036// Cache size limits.
37static const size_t maxKeySize = 1024;
Jamie Gennis89c1d612011-11-19 16:25:24 -080038static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE;
39static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE;
Jamie Gennis76601082011-11-06 14:14:33 -080040
Jamie Gennis98c63832011-11-07 17:03:54 -080041// Cache file header
42static const char* cacheFileMagic = "EGL$";
43static const size_t cacheFileHeaderSize = 8;
44
Jamie Gennis99c3d702011-11-08 17:59:36 -080045// The time in seconds to wait before saving newly inserted cache entries.
46static const unsigned int deferredSaveDelay = 4;
47
Jamie Gennisaca51c02011-11-03 17:42:43 -070048// ----------------------------------------------------------------------------
49namespace android {
50// ----------------------------------------------------------------------------
51
52#define BC_EXT_STR "EGL_ANDROID_blob_cache"
53
54//
Jamie Gennis76601082011-11-06 14:14:33 -080055// Callback functions passed to EGL.
Jamie Gennisaca51c02011-11-03 17:42:43 -070056//
Jamie Gennisc42fcf02011-11-09 15:35:34 -080057static void setBlob(const void* key, EGLsizeiANDROID keySize,
58 const void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080059 egl_cache_t::get()->setBlob(key, keySize, value, valueSize);
Jamie Gennisaca51c02011-11-03 17:42:43 -070060}
61
Jamie Gennisc42fcf02011-11-09 15:35:34 -080062static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize,
63 void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080064 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() {
Jamie Gennisaca51c02011-11-03 17:42:43 -070076}
77
Jamie Gennis98c63832011-11-07 17:03:54 -080078egl_cache_t egl_cache_t::sCache;
79
Jamie Gennisaca51c02011-11-03 17:42:43 -070080egl_cache_t* egl_cache_t::get() {
Jamie Gennis98c63832011-11-07 17:03:54 -080081 return &sCache;
Jamie Gennisaca51c02011-11-03 17:42:43 -070082}
83
84void egl_cache_t::initialize(egl_display_t *display) {
Jamie Gennis76601082011-11-06 14:14:33 -080085 Mutex::Autolock lock(mMutex);
Mathias Agopianada798b2012-02-13 17:09:30 -080086
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>(
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800101 cnx->egl.eglGetProcAddress(
102 "eglSetBlobCacheFuncsANDROID"));
Mathias Agopianada798b2012-02-13 17:09:30 -0800103 if (eglSetBlobCacheFuncsANDROID == NULL) {
104 ALOGE("EGL_ANDROID_blob_cache advertised, "
105 "but unable to get eglSetBlobCacheFuncsANDROID");
106 return;
107 }
Jamie Gennisaca51c02011-11-03 17:42:43 -0700108
Mathias Agopianada798b2012-02-13 17:09:30 -0800109 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);
Jamie Gennisaca51c02011-11-03 17:42:43 -0700115 }
116 }
117 }
Mathias Agopianada798b2012-02-13 17:09:30 -0800118
Jamie Gennis76601082011-11-06 14:14:33 -0800119 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
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800131void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize,
132 const void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -0800133 Mutex::Autolock lock(mMutex);
134
135 if (keySize < 0 || valueSize < 0) {
Steve Block32397c12012-01-05 23:22:43 +0000136 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
Jamie Gennis76601082011-11-06 14:14:33 -0800137 return;
138 }
139
140 if (mInitialized) {
141 sp<BlobCache> bc = getBlobCacheLocked();
142 bc->set(key, keySize, value, valueSize);
Jamie Gennis99c3d702011-11-08 17:59:36 -0800143
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 }
Jamie Gennis76601082011-11-06 14:14:33 -0800167 }
168}
169
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800170EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize,
171 void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -0800172 Mutex::Autolock lock(mMutex);
173
174 if (keySize < 0 || valueSize < 0) {
Steve Block32397c12012-01-05 23:22:43 +0000175 ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
Jamie Gennis76601082011-11-06 14:14:33 -0800176 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
Jamie Gennis98c63832011-11-07 17:03:54 -0800186void egl_cache_t::setCacheFilename(const char* filename) {
187 Mutex::Autolock lock(mMutex);
188 mFilename = filename;
189}
190
Jamie Gennis76601082011-11-06 14:14:33 -0800191sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
192 if (mBlobCache == NULL) {
193 mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
194 loadBlobCacheLocked();
195 }
196 return mBlobCache;
197}
198
Jamie Gennis98c63832011-11-07 17:03:54 -0800199static 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
Jamie Gennis76601082011-11-06 14:14:33 -0800215void egl_cache_t::saveBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800216 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.
Steve Blocke6f43dd2012-01-06 19:20:56 +0000229 ALOGE("error unlinking cache file %s: %s (%d)", fname,
Jamie Gennis98c63832011-11-07 17:03:54 -0800230 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000237 ALOGE("error creating cache file %s: %s (%d)", fname,
Jamie Gennis98c63832011-11-07 17:03:54 -0800238 strerror(errno), errno);
239 return;
240 }
241 }
242
243 size_t fileSize = headerSize + cacheSize;
Jamie Gennis98c63832011-11-07 17:03:54 -0800244
vijay guptaa30cc7d2012-06-27 16:14:42 -0700245 uint8_t* buf = new uint8_t [fileSize];
246 if (!buf) {
247 ALOGE("error allocating buffer for cache contents: %s (%d)",
248 strerror(errno), errno);
Jamie Gennis98c63832011-11-07 17:03:54 -0800249 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000257 ALOGE("error writing cache contents: %s (%d)", strerror(-err),
Jamie Gennis98c63832011-11-07 17:03:54 -0800258 -err);
vijay guptaa30cc7d2012-06-27 16:14:42 -0700259 delete [] buf;
Jamie Gennis98c63832011-11-07 17:03:54 -0800260 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
vijay guptaa30cc7d2012-06-27 16:14:42 -0700270 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;
Jamie Gennis98c63832011-11-07 17:03:54 -0800280 fchmod(fd, S_IRUSR);
281 close(fd);
282 }
Jamie Gennis76601082011-11-06 14:14:33 -0800283}
284
285void egl_cache_t::loadBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800286 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000292 ALOGE("error opening cache file %s: %s (%d)", mFilename.string(),
Jamie Gennis98c63832011-11-07 17:03:54 -0800293 strerror(errno), errno);
294 }
295 return;
296 }
297
298 struct stat statBuf;
299 if (fstat(fd, &statBuf) == -1) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000300 ALOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
Jamie Gennis98c63832011-11-07 17:03:54 -0800301 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000308 ALOGE("cache file is too large: %#llx", statBuf.st_size);
Jamie Gennis98c63832011-11-07 17:03:54 -0800309 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000316 ALOGE("error mmaping cache file: %s (%d)", strerror(errno),
Jamie Gennis98c63832011-11-07 17:03:54 -0800317 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) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000325 ALOGE("cache file has bad mojo");
Jamie Gennis98c63832011-11-07 17:03:54 -0800326 close(fd);
327 return;
328 }
329 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
330 if (crc32c(buf + headerSize, cacheSize) != *crc) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000331 ALOGE("cache file failed CRC check");
Jamie Gennis98c63832011-11-07 17:03:54 -0800332 close(fd);
333 return;
334 }
335
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800336 status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL,
337 0);
Jamie Gennis98c63832011-11-07 17:03:54 -0800338 if (err != OK) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000339 ALOGE("error reading cache contents: %s (%d)", strerror(-err),
Jamie Gennis98c63832011-11-07 17:03:54 -0800340 -err);
341 munmap(buf, fileSize);
342 close(fd);
343 return;
344 }
345
346 munmap(buf, fileSize);
347 close(fd);
348 }
Jamie Gennisaca51c02011-11-03 17:42:43 -0700349}
350
351// ----------------------------------------------------------------------------
352}; // namespace android
353// ----------------------------------------------------------------------------