Skip to content

Commit d90bf39

Browse files
author
Jamie Gennis
committed
EGL: implement loading and saving the cache
This change adds support for saving and loading the contents of the EGL cache. It also adds some simple tests for the EGL cache. Change-Id: I18e5e789e0897a0783d29d1c1e64d26de2dd44c4
1 parent 9cf8b6e commit d90bf39

File tree

5 files changed

+293
-5
lines changed

5 files changed

+293
-5
lines changed

opengl/libs/EGL/egl_cache.cpp

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,21 @@
1919
#include "egl_impl.h"
2020
#include "egldefs.h"
2121

22+
#include <fcntl.h>
23+
#include <sys/mman.h>
24+
#include <sys/stat.h>
25+
#include <sys/types.h>
26+
#include <unistd.h>
27+
2228
// Cache size limits.
2329
static const size_t maxKeySize = 1024;
2430
static const size_t maxValueSize = 4096;
2531
static const size_t maxTotalSize = 64 * 1024;
2632

33+
// Cache file header
34+
static const char* cacheFileMagic = "EGL$";
35+
static const size_t cacheFileHeaderSize = 8;
36+
2737
// ----------------------------------------------------------------------------
2838
namespace android {
2939
// ----------------------------------------------------------------------------
@@ -54,9 +64,10 @@ egl_cache_t::egl_cache_t() :
5464
egl_cache_t::~egl_cache_t() {
5565
}
5666

67+
egl_cache_t egl_cache_t::sCache;
68+
5769
egl_cache_t* egl_cache_t::get() {
58-
static egl_cache_t theCache;
59-
return &theCache;
70+
return &sCache;
6071
}
6172

6273
void egl_cache_t::initialize(egl_display_t *display) {
@@ -136,6 +147,11 @@ EGLsizei egl_cache_t::getBlob(const void* key, EGLsizei keySize, void* value,
136147
return 0;
137148
}
138149

150+
void egl_cache_t::setCacheFilename(const char* filename) {
151+
Mutex::Autolock lock(mMutex);
152+
mFilename = filename;
153+
}
154+
139155
sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
140156
if (mBlobCache == NULL) {
141157
mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
@@ -144,10 +160,154 @@ sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
144160
return mBlobCache;
145161
}
146162

163+
static uint32_t crc32c(const uint8_t* buf, size_t len) {
164+
const uint32_t polyBits = 0x82F63B78;
165+
uint32_t r = 0;
166+
for (size_t i = 0; i < len; i++) {
167+
r ^= buf[i];
168+
for (int j = 0; j < 8; j++) {
169+
if (r & 1) {
170+
r = (r >> 1) ^ polyBits;
171+
} else {
172+
r >>= 1;
173+
}
174+
}
175+
}
176+
return r;
177+
}
178+
147179
void egl_cache_t::saveBlobCacheLocked() {
180+
if (mFilename.length() > 0) {
181+
size_t cacheSize = mBlobCache->getFlattenedSize();
182+
size_t headerSize = cacheFileHeaderSize;
183+
const char* fname = mFilename.string();
184+
185+
// Try to create the file with no permissions so we can write it
186+
// without anyone trying to read it.
187+
int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
188+
if (fd == -1) {
189+
if (errno == EEXIST) {
190+
// The file exists, delete it and try again.
191+
if (unlink(fname) == -1) {
192+
// No point in retrying if the unlink failed.
193+
LOGE("error unlinking cache file %s: %s (%d)", fname,
194+
strerror(errno), errno);
195+
return;
196+
}
197+
// Retry now that we've unlinked the file.
198+
fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
199+
}
200+
if (fd == -1) {
201+
LOGE("error creating cache file %s: %s (%d)", fname,
202+
strerror(errno), errno);
203+
return;
204+
}
205+
}
206+
207+
size_t fileSize = headerSize + cacheSize;
208+
if (ftruncate(fd, fileSize) == -1) {
209+
LOGE("error setting cache file size: %s (%d)", strerror(errno),
210+
errno);
211+
close(fd);
212+
unlink(fname);
213+
return;
214+
}
215+
216+
uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
217+
PROT_WRITE, MAP_SHARED, fd, 0));
218+
if (buf == MAP_FAILED) {
219+
LOGE("error mmaping cache file: %s (%d)", strerror(errno),
220+
errno);
221+
close(fd);
222+
unlink(fname);
223+
return;
224+
}
225+
226+
status_t err = mBlobCache->flatten(buf + headerSize, cacheSize, NULL,
227+
0);
228+
if (err != OK) {
229+
LOGE("error writing cache contents: %s (%d)", strerror(-err),
230+
-err);
231+
munmap(buf, fileSize);
232+
close(fd);
233+
unlink(fname);
234+
return;
235+
}
236+
237+
// Write the file magic and CRC
238+
memcpy(buf, cacheFileMagic, 4);
239+
uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
240+
*crc = crc32c(buf + headerSize, cacheSize);
241+
242+
munmap(buf, fileSize);
243+
fchmod(fd, S_IRUSR);
244+
close(fd);
245+
}
148246
}
149247

150248
void egl_cache_t::loadBlobCacheLocked() {
249+
if (mFilename.length() > 0) {
250+
size_t headerSize = cacheFileHeaderSize;
251+
252+
int fd = open(mFilename.string(), O_RDONLY, 0);
253+
if (fd == -1) {
254+
if (errno != ENOENT) {
255+
LOGE("error opening cache file %s: %s (%d)", mFilename.string(),
256+
strerror(errno), errno);
257+
}
258+
return;
259+
}
260+
261+
struct stat statBuf;
262+
if (fstat(fd, &statBuf) == -1) {
263+
LOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
264+
close(fd);
265+
return;
266+
}
267+
268+
// Sanity check the size before trying to mmap it.
269+
size_t fileSize = statBuf.st_size;
270+
if (fileSize > maxTotalSize * 2) {
271+
LOGE("cache file is too large: %#llx", statBuf.st_size);
272+
close(fd);
273+
return;
274+
}
275+
276+
uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
277+
PROT_READ, MAP_PRIVATE, fd, 0));
278+
if (buf == MAP_FAILED) {
279+
LOGE("error mmaping cache file: %s (%d)", strerror(errno),
280+
errno);
281+
close(fd);
282+
return;
283+
}
284+
285+
// Check the file magic and CRC
286+
size_t cacheSize = fileSize - headerSize;
287+
if (memcmp(buf, cacheFileMagic, 4) != 0) {
288+
LOGE("cache file has bad mojo");
289+
close(fd);
290+
return;
291+
}
292+
uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
293+
if (crc32c(buf + headerSize, cacheSize) != *crc) {
294+
LOGE("cache file failed CRC check");
295+
close(fd);
296+
return;
297+
}
298+
299+
status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL, 0);
300+
if (err != OK) {
301+
LOGE("error reading cache contents: %s (%d)", strerror(-err),
302+
-err);
303+
munmap(buf, fileSize);
304+
close(fd);
305+
return;
306+
}
307+
308+
munmap(buf, fileSize);
309+
close(fd);
310+
}
151311
}
152312

153313
// ----------------------------------------------------------------------------

opengl/libs/EGL/egl_cache.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <EGL/eglext.h>
2222

2323
#include <utils/BlobCache.h>
24+
#include <utils/String8.h>
2425
#include <utils/StrongPointer.h>
2526

2627
// ----------------------------------------------------------------------------
@@ -29,7 +30,7 @@ namespace android {
2930

3031
class egl_display_t;
3132

32-
class egl_cache_t {
33+
class EGLAPI egl_cache_t {
3334
public:
3435

3536
// get returns a pointer to the singleton egl_cache_t object. This
@@ -60,6 +61,10 @@ class egl_cache_t {
6061
EGLsizei getBlob(const void* key, EGLsizei keySize, void* value,
6162
EGLsizei valueSize);
6263

64+
// setCacheFilename sets the name of the file that should be used to store
65+
// cache contents from one program invocation to another.
66+
void setCacheFilename(const char* filename);
67+
6368
private:
6469
// Creation and (the lack of) destruction is handled internally.
6570
egl_cache_t();
@@ -96,9 +101,19 @@ class egl_cache_t {
96101
// first time it's needed.
97102
sp<BlobCache> mBlobCache;
98103

104+
// mFilename is the name of the file for storing cache contents in between
105+
// program invocations. It is initialized to an empty string at
106+
// construction time, and can be set with the setCacheFilename method. An
107+
// empty string indicates that the cache should not be saved to or restored
108+
// from disk.
109+
String8 mFilename;
110+
99111
// mMutex is the mutex used to prevent concurrent access to the member
100112
// variables. It must be locked whenever the member variables are accessed.
101113
mutable Mutex mMutex;
114+
115+
// sCache is the singleton egl_cache_t object.
116+
static egl_cache_t sCache;
102117
};
103118

104119
// ----------------------------------------------------------------------------

opengl/libs/EGL/egl_display.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct egl_config_t {
5959

6060
// ----------------------------------------------------------------------------
6161

62-
class egl_display_t {
62+
class EGLAPI egl_display_t { // marked as EGLAPI for testing purposes
6363
static egl_display_t sDisplay[NUM_DISPLAYS];
6464
EGLDisplay getDisplay(EGLNativeDisplayType display);
6565

@@ -141,4 +141,3 @@ EGLBoolean validate_display_surface(EGLDisplay dpy, EGLSurface surface);
141141
// ----------------------------------------------------------------------------
142142

143143
#endif // ANDROID_EGL_DISPLAY_H
144-

opengl/tests/EGLTest/Android.mk

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ LOCAL_MODULE := EGL_test
77
LOCAL_MODULE_TAGS := tests
88

99
LOCAL_SRC_FILES := \
10+
egl_cache_test.cpp \
1011
EGL_test.cpp \
1112

1213
LOCAL_SHARED_LIBRARIES := \
@@ -21,9 +22,12 @@ LOCAL_STATIC_LIBRARIES := \
2122

2223
LOCAL_C_INCLUDES := \
2324
bionic \
25+
bionic/libc/private \
2426
bionic/libstdc++/include \
2527
external/gtest/include \
2628
external/stlport/stlport \
29+
frameworks/base/opengl/libs \
30+
frameworks/base/opengl/libs/EGL \
2731

2832
include $(BUILD_EXECUTABLE)
2933

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright (C) 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+
#define LOG_TAG "EGL_test"
18+
//#define LOG_NDEBUG 0
19+
20+
#include <gtest/gtest.h>
21+
22+
#include <utils/Log.h>
23+
24+
#include "egl_cache.h"
25+
#include "egl_display.h"
26+
27+
namespace android {
28+
29+
class EGLCacheTest : public ::testing::Test {
30+
protected:
31+
virtual void SetUp() {
32+
mCache = egl_cache_t::get();
33+
}
34+
35+
virtual void TearDown() {
36+
mCache->setCacheFilename("");
37+
mCache->terminate();
38+
}
39+
40+
egl_cache_t* mCache;
41+
};
42+
43+
TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) {
44+
char buf[4] = { 0xee, 0xee, 0xee, 0xee };
45+
mCache->setBlob("abcd", 4, "efgh", 4);
46+
ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
47+
ASSERT_EQ(0xee, buf[0]);
48+
ASSERT_EQ(0xee, buf[1]);
49+
ASSERT_EQ(0xee, buf[2]);
50+
ASSERT_EQ(0xee, buf[3]);
51+
}
52+
53+
TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) {
54+
char buf[4] = { 0xee, 0xee, 0xee, 0xee };
55+
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
56+
mCache->setBlob("abcd", 4, "efgh", 4);
57+
ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
58+
ASSERT_EQ('e', buf[0]);
59+
ASSERT_EQ('f', buf[1]);
60+
ASSERT_EQ('g', buf[2]);
61+
ASSERT_EQ('h', buf[3]);
62+
}
63+
64+
TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) {
65+
char buf[4] = { 0xee, 0xee, 0xee, 0xee };
66+
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
67+
mCache->setBlob("abcd", 4, "efgh", 4);
68+
mCache->terminate();
69+
ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4));
70+
ASSERT_EQ(0xee, buf[0]);
71+
ASSERT_EQ(0xee, buf[1]);
72+
ASSERT_EQ(0xee, buf[2]);
73+
ASSERT_EQ(0xee, buf[3]);
74+
}
75+
76+
class EGLCacheSerializationTest : public EGLCacheTest {
77+
78+
protected:
79+
80+
virtual void SetUp() {
81+
EGLCacheTest::SetUp();
82+
83+
char* tn = tempnam("/sdcard", "EGL_test-cache-");
84+
mFilename = tn;
85+
free(tn);
86+
}
87+
88+
virtual void TearDown() {
89+
unlink(mFilename.string());
90+
EGLCacheTest::TearDown();
91+
}
92+
93+
String8 mFilename;
94+
};
95+
96+
TEST_F(EGLCacheSerializationTest, ReinitializedCacheContainsValues) {
97+
char buf[4] = { 0xee, 0xee, 0xee, 0xee };
98+
mCache->setCacheFilename(mFilename);
99+
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
100+
mCache->setBlob("abcd", 4, "efgh", 4);
101+
mCache->terminate();
102+
mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
103+
ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
104+
ASSERT_EQ('e', buf[0]);
105+
ASSERT_EQ('f', buf[1]);
106+
ASSERT_EQ('g', buf[2]);
107+
ASSERT_EQ('h', buf[3]);
108+
}
109+
110+
}

0 commit comments

Comments
 (0)