blob: 13a492917d2c6784455bef0a7749d96a7490345c [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 Gennis76601082011-11-06 14:14:33 -080028// Cache size limits.
29static const size_t maxKeySize = 1024;
30static const size_t maxValueSize = 4096;
31static const size_t maxTotalSize = 64 * 1024;
32
Jamie Gennis98c63832011-11-07 17:03:54 -080033// Cache file header
34static const char* cacheFileMagic = "EGL$";
35static const size_t cacheFileHeaderSize = 8;
36
Jamie Gennis99c3d702011-11-08 17:59:36 -080037// The time in seconds to wait before saving newly inserted cache entries.
38static const unsigned int deferredSaveDelay = 4;
39
Jamie Gennisaca51c02011-11-03 17:42:43 -070040// ----------------------------------------------------------------------------
41namespace android {
42// ----------------------------------------------------------------------------
43
44#define BC_EXT_STR "EGL_ANDROID_blob_cache"
45
46//
Jamie Gennis76601082011-11-06 14:14:33 -080047// Callback functions passed to EGL.
Jamie Gennisaca51c02011-11-03 17:42:43 -070048//
Jamie Gennisc42fcf02011-11-09 15:35:34 -080049static void setBlob(const void* key, EGLsizeiANDROID keySize,
50 const void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080051 egl_cache_t::get()->setBlob(key, keySize, value, valueSize);
Jamie Gennisaca51c02011-11-03 17:42:43 -070052}
53
Jamie Gennisc42fcf02011-11-09 15:35:34 -080054static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize,
55 void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080056 return egl_cache_t::get()->getBlob(key, keySize, value, valueSize);
57}
58
59//
60// egl_cache_t definition
61//
62egl_cache_t::egl_cache_t() :
63 mInitialized(false),
64 mBlobCache(NULL) {
65}
66
67egl_cache_t::~egl_cache_t() {
Jamie Gennisaca51c02011-11-03 17:42:43 -070068}
69
Jamie Gennis98c63832011-11-07 17:03:54 -080070egl_cache_t egl_cache_t::sCache;
71
Jamie Gennisaca51c02011-11-03 17:42:43 -070072egl_cache_t* egl_cache_t::get() {
Jamie Gennis98c63832011-11-07 17:03:54 -080073 return &sCache;
Jamie Gennisaca51c02011-11-03 17:42:43 -070074}
75
76void egl_cache_t::initialize(egl_display_t *display) {
Jamie Gennis76601082011-11-06 14:14:33 -080077 Mutex::Autolock lock(mMutex);
Jamie Gennisaca51c02011-11-03 17:42:43 -070078 for (int i = 0; i < IMPL_NUM_IMPLEMENTATIONS; i++) {
79 egl_connection_t* const cnx = &gEGLImpl[i];
80 if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
81 const char* exts = display->disp[i].queryString.extensions;
82 size_t bcExtLen = strlen(BC_EXT_STR);
83 size_t extsLen = strlen(exts);
84 bool equal = !strcmp(BC_EXT_STR, exts);
85 bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1);
86 bool atEnd = (bcExtLen+1) < extsLen &&
87 !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1));
88 bool inMiddle = strstr(" " BC_EXT_STR " ", exts);
89 if (equal || atStart || atEnd || inMiddle) {
Jamie Gennisc42fcf02011-11-09 15:35:34 -080090 PFNEGLSETBLOBCACHEFUNCSANDROIDPROC eglSetBlobCacheFuncsANDROID;
91 eglSetBlobCacheFuncsANDROID =
92 reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSANDROIDPROC>(
93 cnx->egl.eglGetProcAddress(
94 "eglSetBlobCacheFuncsANDROID"));
95 if (eglSetBlobCacheFuncsANDROID == NULL) {
Jamie Gennisaca51c02011-11-03 17:42:43 -070096 LOGE("EGL_ANDROID_blob_cache advertised by display %d, "
Jamie Gennisc42fcf02011-11-09 15:35:34 -080097 "but unable to get eglSetBlobCacheFuncsANDROID", i);
Jamie Gennisaca51c02011-11-03 17:42:43 -070098 continue;
99 }
100
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800101 eglSetBlobCacheFuncsANDROID(display->disp[i].dpy,
102 android::setBlob, android::getBlob);
Jamie Gennisaca51c02011-11-03 17:42:43 -0700103 EGLint err = cnx->egl.eglGetError();
104 if (err != EGL_SUCCESS) {
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800105 LOGE("eglSetBlobCacheFuncsANDROID resulted in an error: "
106 "%#x", err);
Jamie Gennisaca51c02011-11-03 17:42:43 -0700107 }
108 }
109 }
110 }
Jamie Gennis76601082011-11-06 14:14:33 -0800111 mInitialized = true;
112}
113
114void egl_cache_t::terminate() {
115 Mutex::Autolock lock(mMutex);
116 if (mBlobCache != NULL) {
117 saveBlobCacheLocked();
118 mBlobCache = NULL;
119 }
120 mInitialized = false;
121}
122
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800123void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize,
124 const void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -0800125 Mutex::Autolock lock(mMutex);
126
127 if (keySize < 0 || valueSize < 0) {
128 LOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
129 return;
130 }
131
132 if (mInitialized) {
133 sp<BlobCache> bc = getBlobCacheLocked();
134 bc->set(key, keySize, value, valueSize);
Jamie Gennis99c3d702011-11-08 17:59:36 -0800135
136 if (!mSavePending) {
137 class DeferredSaveThread : public Thread {
138 public:
139 DeferredSaveThread() : Thread(false) {}
140
141 virtual bool threadLoop() {
142 sleep(deferredSaveDelay);
143 egl_cache_t* c = egl_cache_t::get();
144 Mutex::Autolock lock(c->mMutex);
145 if (c->mInitialized) {
146 c->saveBlobCacheLocked();
147 }
148 c->mSavePending = false;
149 return false;
150 }
151 };
152
153 // The thread will hold a strong ref to itself until it has finished
154 // running, so there's no need to keep a ref around.
155 sp<Thread> deferredSaveThread(new DeferredSaveThread());
156 mSavePending = true;
157 deferredSaveThread->run();
158 }
Jamie Gennis76601082011-11-06 14:14:33 -0800159 }
160}
161
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800162EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize,
163 void* value, EGLsizeiANDROID valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -0800164 Mutex::Autolock lock(mMutex);
165
166 if (keySize < 0 || valueSize < 0) {
167 LOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
168 return 0;
169 }
170
171 if (mInitialized) {
172 sp<BlobCache> bc = getBlobCacheLocked();
173 return bc->get(key, keySize, value, valueSize);
174 }
175 return 0;
176}
177
Jamie Gennis98c63832011-11-07 17:03:54 -0800178void egl_cache_t::setCacheFilename(const char* filename) {
179 Mutex::Autolock lock(mMutex);
180 mFilename = filename;
181}
182
Jamie Gennis76601082011-11-06 14:14:33 -0800183sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
184 if (mBlobCache == NULL) {
185 mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
186 loadBlobCacheLocked();
187 }
188 return mBlobCache;
189}
190
Jamie Gennis98c63832011-11-07 17:03:54 -0800191static uint32_t crc32c(const uint8_t* buf, size_t len) {
192 const uint32_t polyBits = 0x82F63B78;
193 uint32_t r = 0;
194 for (size_t i = 0; i < len; i++) {
195 r ^= buf[i];
196 for (int j = 0; j < 8; j++) {
197 if (r & 1) {
198 r = (r >> 1) ^ polyBits;
199 } else {
200 r >>= 1;
201 }
202 }
203 }
204 return r;
205}
206
Jamie Gennis76601082011-11-06 14:14:33 -0800207void egl_cache_t::saveBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800208 if (mFilename.length() > 0) {
209 size_t cacheSize = mBlobCache->getFlattenedSize();
210 size_t headerSize = cacheFileHeaderSize;
211 const char* fname = mFilename.string();
212
213 // Try to create the file with no permissions so we can write it
214 // without anyone trying to read it.
215 int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
216 if (fd == -1) {
217 if (errno == EEXIST) {
218 // The file exists, delete it and try again.
219 if (unlink(fname) == -1) {
220 // No point in retrying if the unlink failed.
221 LOGE("error unlinking cache file %s: %s (%d)", fname,
222 strerror(errno), errno);
223 return;
224 }
225 // Retry now that we've unlinked the file.
226 fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
227 }
228 if (fd == -1) {
229 LOGE("error creating cache file %s: %s (%d)", fname,
230 strerror(errno), errno);
231 return;
232 }
233 }
234
235 size_t fileSize = headerSize + cacheSize;
236 if (ftruncate(fd, fileSize) == -1) {
237 LOGE("error setting cache file size: %s (%d)", strerror(errno),
238 errno);
239 close(fd);
240 unlink(fname);
241 return;
242 }
243
244 uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
245 PROT_WRITE, MAP_SHARED, fd, 0));
246 if (buf == MAP_FAILED) {
247 LOGE("error mmaping cache file: %s (%d)", strerror(errno),
248 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 LOGE("error writing cache contents: %s (%d)", strerror(-err),
258 -err);
259 munmap(buf, fileSize);
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 munmap(buf, fileSize);
271 fchmod(fd, S_IRUSR);
272 close(fd);
273 }
Jamie Gennis76601082011-11-06 14:14:33 -0800274}
275
276void egl_cache_t::loadBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800277 if (mFilename.length() > 0) {
278 size_t headerSize = cacheFileHeaderSize;
279
280 int fd = open(mFilename.string(), O_RDONLY, 0);
281 if (fd == -1) {
282 if (errno != ENOENT) {
283 LOGE("error opening cache file %s: %s (%d)", mFilename.string(),
284 strerror(errno), errno);
285 }
286 return;
287 }
288
289 struct stat statBuf;
290 if (fstat(fd, &statBuf) == -1) {
291 LOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
292 close(fd);
293 return;
294 }
295
296 // Sanity check the size before trying to mmap it.
297 size_t fileSize = statBuf.st_size;
298 if (fileSize > maxTotalSize * 2) {
299 LOGE("cache file is too large: %#llx", statBuf.st_size);
300 close(fd);
301 return;
302 }
303
304 uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
305 PROT_READ, MAP_PRIVATE, fd, 0));
306 if (buf == MAP_FAILED) {
307 LOGE("error mmaping cache file: %s (%d)", strerror(errno),
308 errno);
309 close(fd);
310 return;
311 }
312
313 // Check the file magic and CRC
314 size_t cacheSize = fileSize - headerSize;
315 if (memcmp(buf, cacheFileMagic, 4) != 0) {
316 LOGE("cache file has bad mojo");
317 close(fd);
318 return;
319 }
320 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
321 if (crc32c(buf + headerSize, cacheSize) != *crc) {
322 LOGE("cache file failed CRC check");
323 close(fd);
324 return;
325 }
326
Jamie Gennisc42fcf02011-11-09 15:35:34 -0800327 status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL,
328 0);
Jamie Gennis98c63832011-11-07 17:03:54 -0800329 if (err != OK) {
330 LOGE("error reading cache contents: %s (%d)", strerror(-err),
331 -err);
332 munmap(buf, fileSize);
333 close(fd);
334 return;
335 }
336
337 munmap(buf, fileSize);
338 close(fd);
339 }
Jamie Gennisaca51c02011-11-03 17:42:43 -0700340}
341
342// ----------------------------------------------------------------------------
343}; // namespace android
344// ----------------------------------------------------------------------------