diff options
-rw-r--r-- | opengl/libs/EGL/egl_cache.cpp | 164 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_cache.h | 17 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_display.h | 3 | ||||
-rw-r--r-- | opengl/tests/EGLTest/Android.mk | 4 | ||||
-rw-r--r-- | opengl/tests/EGLTest/egl_cache_test.cpp | 110 |
5 files changed, 293 insertions, 5 deletions
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index adb2a13..aa40d58 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -19,11 +19,21 @@ #include "egl_impl.h" #include "egldefs.h" +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + // Cache size limits. static const size_t maxKeySize = 1024; static const size_t maxValueSize = 4096; static const size_t maxTotalSize = 64 * 1024; +// Cache file header +static const char* cacheFileMagic = "EGL$"; +static const size_t cacheFileHeaderSize = 8; + // ---------------------------------------------------------------------------- namespace android { // ---------------------------------------------------------------------------- @@ -54,9 +64,10 @@ egl_cache_t::egl_cache_t() : egl_cache_t::~egl_cache_t() { } +egl_cache_t egl_cache_t::sCache; + egl_cache_t* egl_cache_t::get() { - static egl_cache_t theCache; - return &theCache; + return &sCache; } 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, return 0; } +void egl_cache_t::setCacheFilename(const char* filename) { + Mutex::Autolock lock(mMutex); + mFilename = filename; +} + sp<BlobCache> egl_cache_t::getBlobCacheLocked() { if (mBlobCache == NULL) { mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize); @@ -144,10 +160,154 @@ sp<BlobCache> egl_cache_t::getBlobCacheLocked() { return mBlobCache; } +static uint32_t crc32c(const uint8_t* buf, size_t len) { + const uint32_t polyBits = 0x82F63B78; + uint32_t r = 0; + for (size_t i = 0; i < len; i++) { + r ^= buf[i]; + for (int j = 0; j < 8; j++) { + if (r & 1) { + r = (r >> 1) ^ polyBits; + } else { + r >>= 1; + } + } + } + return r; +} + void egl_cache_t::saveBlobCacheLocked() { + if (mFilename.length() > 0) { + size_t cacheSize = mBlobCache->getFlattenedSize(); + size_t headerSize = cacheFileHeaderSize; + const char* fname = mFilename.string(); + + // Try to create the file with no permissions so we can write it + // without anyone trying to read it. + int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0); + if (fd == -1) { + if (errno == EEXIST) { + // The file exists, delete it and try again. + if (unlink(fname) == -1) { + // No point in retrying if the unlink failed. + LOGE("error unlinking cache file %s: %s (%d)", fname, + strerror(errno), errno); + return; + } + // Retry now that we've unlinked the file. + fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0); + } + if (fd == -1) { + LOGE("error creating cache file %s: %s (%d)", fname, + strerror(errno), errno); + return; + } + } + + size_t fileSize = headerSize + cacheSize; + if (ftruncate(fd, fileSize) == -1) { + LOGE("error setting cache file size: %s (%d)", strerror(errno), + errno); + close(fd); + unlink(fname); + return; + } + + uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize, + PROT_WRITE, MAP_SHARED, fd, 0)); + if (buf == MAP_FAILED) { + LOGE("error mmaping cache file: %s (%d)", strerror(errno), + errno); + close(fd); + unlink(fname); + return; + } + + status_t err = mBlobCache->flatten(buf + headerSize, cacheSize, NULL, + 0); + if (err != OK) { + LOGE("error writing cache contents: %s (%d)", strerror(-err), + -err); + munmap(buf, fileSize); + close(fd); + unlink(fname); + return; + } + + // Write the file magic and CRC + memcpy(buf, cacheFileMagic, 4); + uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4); + *crc = crc32c(buf + headerSize, cacheSize); + + munmap(buf, fileSize); + fchmod(fd, S_IRUSR); + close(fd); + } } void egl_cache_t::loadBlobCacheLocked() { + if (mFilename.length() > 0) { + size_t headerSize = cacheFileHeaderSize; + + int fd = open(mFilename.string(), O_RDONLY, 0); + if (fd == -1) { + if (errno != ENOENT) { + LOGE("error opening cache file %s: %s (%d)", mFilename.string(), + strerror(errno), errno); + } + return; + } + + struct stat statBuf; + if (fstat(fd, &statBuf) == -1) { + LOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno); + close(fd); + return; + } + + // Sanity check the size before trying to mmap it. + size_t fileSize = statBuf.st_size; + if (fileSize > maxTotalSize * 2) { + LOGE("cache file is too large: %#llx", statBuf.st_size); + close(fd); + return; + } + + uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize, + PROT_READ, MAP_PRIVATE, fd, 0)); + if (buf == MAP_FAILED) { + LOGE("error mmaping cache file: %s (%d)", strerror(errno), + errno); + close(fd); + return; + } + + // Check the file magic and CRC + size_t cacheSize = fileSize - headerSize; + if (memcmp(buf, cacheFileMagic, 4) != 0) { + LOGE("cache file has bad mojo"); + close(fd); + return; + } + uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4); + if (crc32c(buf + headerSize, cacheSize) != *crc) { + LOGE("cache file failed CRC check"); + close(fd); + return; + } + + status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL, 0); + if (err != OK) { + LOGE("error reading cache contents: %s (%d)", strerror(-err), + -err); + munmap(buf, fileSize); + close(fd); + return; + } + + munmap(buf, fileSize); + close(fd); + } } // ---------------------------------------------------------------------------- diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index 7021752..05d5873 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -21,6 +21,7 @@ #include <EGL/eglext.h> #include <utils/BlobCache.h> +#include <utils/String8.h> #include <utils/StrongPointer.h> // ---------------------------------------------------------------------------- @@ -29,7 +30,7 @@ namespace android { class egl_display_t; -class egl_cache_t { +class EGLAPI egl_cache_t { public: // get returns a pointer to the singleton egl_cache_t object. This @@ -60,6 +61,10 @@ public: EGLsizei getBlob(const void* key, EGLsizei keySize, void* value, EGLsizei valueSize); + // setCacheFilename sets the name of the file that should be used to store + // cache contents from one program invocation to another. + void setCacheFilename(const char* filename); + private: // Creation and (the lack of) destruction is handled internally. egl_cache_t(); @@ -96,9 +101,19 @@ private: // first time it's needed. sp<BlobCache> mBlobCache; + // mFilename is the name of the file for storing cache contents in between + // program invocations. It is initialized to an empty string at + // construction time, and can be set with the setCacheFilename method. An + // empty string indicates that the cache should not be saved to or restored + // from disk. + String8 mFilename; + // mMutex is the mutex used to prevent concurrent access to the member // variables. It must be locked whenever the member variables are accessed. mutable Mutex mMutex; + + // sCache is the singleton egl_cache_t object. + static egl_cache_t sCache; }; // ---------------------------------------------------------------------------- diff --git a/opengl/libs/EGL/egl_display.h b/opengl/libs/EGL/egl_display.h index 113595f..1c1092c 100644 --- a/opengl/libs/EGL/egl_display.h +++ b/opengl/libs/EGL/egl_display.h @@ -59,7 +59,7 @@ struct egl_config_t { // ---------------------------------------------------------------------------- -class egl_display_t { +class EGLAPI egl_display_t { // marked as EGLAPI for testing purposes static egl_display_t sDisplay[NUM_DISPLAYS]; EGLDisplay getDisplay(EGLNativeDisplayType display); @@ -141,4 +141,3 @@ EGLBoolean validate_display_surface(EGLDisplay dpy, EGLSurface surface); // ---------------------------------------------------------------------------- #endif // ANDROID_EGL_DISPLAY_H - diff --git a/opengl/tests/EGLTest/Android.mk b/opengl/tests/EGLTest/Android.mk index 92d7eb1..14104d1 100644 --- a/opengl/tests/EGLTest/Android.mk +++ b/opengl/tests/EGLTest/Android.mk @@ -7,6 +7,7 @@ LOCAL_MODULE := EGL_test LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ + egl_cache_test.cpp \ EGL_test.cpp \ LOCAL_SHARED_LIBRARIES := \ @@ -21,9 +22,12 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_C_INCLUDES := \ bionic \ + bionic/libc/private \ bionic/libstdc++/include \ external/gtest/include \ external/stlport/stlport \ + frameworks/base/opengl/libs \ + frameworks/base/opengl/libs/EGL \ include $(BUILD_EXECUTABLE) diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp new file mode 100644 index 0000000..c7d9e3e --- /dev/null +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "EGL_test" +//#define LOG_NDEBUG 0 + +#include <gtest/gtest.h> + +#include <utils/Log.h> + +#include "egl_cache.h" +#include "egl_display.h" + +namespace android { + +class EGLCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mCache = egl_cache_t::get(); + } + + virtual void TearDown() { + mCache->setCacheFilename(""); + mCache->terminate(); + } + + egl_cache_t* mCache; +}; + +TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->setBlob("abcd", 4, "efgh", 4); + ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + mCache->setBlob("abcd", 4, "efgh", 4); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + mCache->setBlob("abcd", 4, "efgh", 4); + mCache->terminate(); + ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +class EGLCacheSerializationTest : public EGLCacheTest { + +protected: + + virtual void SetUp() { + EGLCacheTest::SetUp(); + + char* tn = tempnam("/sdcard", "EGL_test-cache-"); + mFilename = tn; + free(tn); + } + + virtual void TearDown() { + unlink(mFilename.string()); + EGLCacheTest::TearDown(); + } + + String8 mFilename; +}; + +TEST_F(EGLCacheSerializationTest, ReinitializedCacheContainsValues) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mCache->setCacheFilename(mFilename); + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + mCache->setBlob("abcd", 4, "efgh", 4); + mCache->terminate(); + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +} |