summaryrefslogtreecommitdiff
path: root/opengl/libs
diff options
context:
space:
mode:
Diffstat (limited to 'opengl/libs')
-rw-r--r--opengl/libs/Android.bp7
-rw-r--r--opengl/libs/EGL/BlobCache.cpp6
-rw-r--r--opengl/libs/EGL/FileBlobCache.cpp15
-rw-r--r--opengl/libs/EGL/FileBlobCache.h5
-rw-r--r--opengl/libs/EGL/Loader.cpp25
-rw-r--r--opengl/libs/EGL/MultifileBlobCache.cpp735
-rw-r--r--opengl/libs/EGL/MultifileBlobCache.h174
-rw-r--r--opengl/libs/EGL/MultifileBlobCache_test.cpp219
-rw-r--r--opengl/libs/EGL/egl_angle_platform.cpp13
-rw-r--r--opengl/libs/EGL/egl_cache.cpp166
-rw-r--r--opengl/libs/EGL/egl_cache.h30
-rw-r--r--opengl/libs/EGL/egl_display.cpp5
-rw-r--r--opengl/libs/EGL/egl_platform_entries.cpp11
-rw-r--r--opengl/libs/EGL/fuzzer/Android.bp42
-rw-r--r--opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp158
15 files changed, 1564 insertions, 47 deletions
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 62cf2555ca..16de3908f8 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -144,6 +144,7 @@ cc_library_static {
srcs: [
"EGL/BlobCache.cpp",
"EGL/FileBlobCache.cpp",
+ "EGL/MultifileBlobCache.cpp",
],
export_include_dirs: ["EGL"],
}
@@ -204,6 +205,12 @@ cc_test {
srcs: [
"EGL/BlobCache.cpp",
"EGL/BlobCache_test.cpp",
+ "EGL/FileBlobCache.cpp",
+ "EGL/MultifileBlobCache.cpp",
+ "EGL/MultifileBlobCache_test.cpp",
+ ],
+ shared_libs: [
+ "libutils",
],
}
diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp
index aecfc6b077..b3a4bc19fd 100644
--- a/opengl/libs/EGL/BlobCache.cpp
+++ b/opengl/libs/EGL/BlobCache.cpp
@@ -15,6 +15,7 @@
*/
//#define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "BlobCache.h"
@@ -22,6 +23,7 @@
#include <errno.h>
#include <inttypes.h>
#include <log/log.h>
+#include <utils/Trace.h>
#include <chrono>
@@ -230,6 +232,8 @@ int BlobCache::flatten(void* buffer, size_t size) const {
}
int BlobCache::unflatten(void const* buffer, size_t size) {
+ ATRACE_NAME("BlobCache::unflatten");
+
// All errors should result in the BlobCache being in an empty state.
clear();
@@ -293,6 +297,8 @@ long int BlobCache::blob_random() {
}
void BlobCache::clean() {
+ ATRACE_NAME("BlobCache::clean");
+
// Remove a random cache entry until the total cache size gets below half
// the maximum total cache size.
while (mTotalSize > mMaxTotalSize / 2) {
diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp
index 751f3be81a..4a0fac4ce5 100644
--- a/opengl/libs/EGL/FileBlobCache.cpp
+++ b/opengl/libs/EGL/FileBlobCache.cpp
@@ -14,6 +14,8 @@
** limitations under the License.
*/
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
#include "FileBlobCache.h"
#include <errno.h>
@@ -24,6 +26,7 @@
#include <unistd.h>
#include <log/log.h>
+#include <utils/Trace.h>
// Cache file header
static const char* cacheFileMagic = "EGL$";
@@ -31,7 +34,7 @@ static const size_t cacheFileHeaderSize = 8;
namespace android {
-static uint32_t crc32c(const uint8_t* buf, size_t len) {
+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++) {
@@ -51,6 +54,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT
const std::string& filename)
: BlobCache(maxKeySize, maxValueSize, maxTotalSize)
, mFilename(filename) {
+ ATRACE_CALL();
+
if (mFilename.length() > 0) {
size_t headerSize = cacheFileHeaderSize;
@@ -117,6 +122,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT
}
void FileBlobCache::writeToFile() {
+ ATRACE_CALL();
+
if (mFilename.length() > 0) {
size_t cacheSize = getFlattenedSize();
size_t headerSize = cacheFileHeaderSize;
@@ -185,4 +192,10 @@ void FileBlobCache::writeToFile() {
}
}
+size_t FileBlobCache::getSize() {
+ if (mFilename.length() > 0) {
+ return getFlattenedSize() + cacheFileHeaderSize;
+ }
+ return 0;
+}
}
diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h
index 393703f234..f083b0d6ca 100644
--- a/opengl/libs/EGL/FileBlobCache.h
+++ b/opengl/libs/EGL/FileBlobCache.h
@@ -22,6 +22,8 @@
namespace android {
+uint32_t crc32c(const uint8_t* buf, size_t len);
+
class FileBlobCache : public BlobCache {
public:
// FileBlobCache attempts to load the saved cache contents from disk into
@@ -33,6 +35,9 @@ public:
// disk.
void writeToFile();
+ // Return the total size of the cache
+ size_t getSize();
+
private:
// mFilename is the name of the file for storing cache contents.
std::string mFilename;
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 6ea400721d..2c3ce16f66 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -139,9 +139,10 @@ static void* load_wrapper(const char* path) {
static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
-static const char* HAL_SUBNAME_KEY_PROPERTIES[2] = {
- DRIVER_SUFFIX_PROPERTY,
- "ro.board.platform",
+static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
+ "persist.graphics.egl",
+ DRIVER_SUFFIX_PROPERTY,
+ "ro.board.platform",
};
static bool should_unload_system_driver(egl_connection_t* cnx) {
@@ -208,8 +209,7 @@ void* Loader::open(egl_connection_t* cnx)
ATRACE_CALL();
const nsecs_t openTime = systemTime();
- if (!android::GraphicsEnv::getInstance().angleIsSystemDriver() &&
- should_unload_system_driver(cnx)) {
+ if (should_unload_system_driver(cnx)) {
unload_system_driver(cnx);
}
@@ -218,12 +218,8 @@ void* Loader::open(egl_connection_t* cnx)
return cnx->dso;
}
- // Firstly, try to load ANGLE driver, unless we know that we shouldn't.
- bool shouldForceLegacyDriver = android::GraphicsEnv::getInstance().shouldForceLegacyDriver();
- driver_t* hnd = nullptr;
- if (!shouldForceLegacyDriver) {
- hnd = attempt_to_load_angle(cnx);
- }
+ // Firstly, try to load ANGLE driver.
+ driver_t* hnd = attempt_to_load_angle(cnx);
if (!hnd) {
// Secondly, try to load from driver apk.
@@ -285,8 +281,10 @@ void* Loader::open(egl_connection_t* cnx)
}
LOG_ALWAYS_FATAL_IF(!hnd,
- "couldn't find an OpenGL ES implementation, make sure you set %s or %s",
- HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1]);
+ "couldn't find an OpenGL ES implementation, make sure one of %s, %s and %s "
+ "is set",
+ HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1],
+ HAL_SUBNAME_KEY_PROPERTIES[2]);
if (!cnx->libEgl) {
cnx->libEgl = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so");
@@ -499,7 +497,6 @@ static void* load_angle(const char* kind, android_namespace_t* ns) {
void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
if (so) {
- ALOGD("dlopen_ext from APK (%s) success at %p", name.c_str(), so);
return so;
} else {
ALOGE("dlopen_ext(\"%s\") failed: %s", name.c_str(), dlerror());
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
new file mode 100644
index 0000000000..7ffdac7ea7
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -0,0 +1,735 @@
+/*
+ ** Copyright 2022, 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_NDEBUG 0
+
+#include "MultifileBlobCache.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <algorithm>
+#include <chrono>
+#include <limits>
+#include <locale>
+
+#include <utils/JenkinsHash.h>
+
+using namespace std::literals;
+
+constexpr uint32_t kMultifileMagic = 'MFB$';
+constexpr uint32_t kCrcPlaceholder = 0;
+
+namespace {
+
+// Helper function to close entries or free them
+void freeHotCacheEntry(android::MultifileHotCache& entry) {
+ if (entry.entryFd != -1) {
+ // If we have an fd, then this entry was added to hot cache via INIT or GET
+ // We need to unmap and close the entry
+ munmap(entry.entryBuffer, entry.entrySize);
+ close(entry.entryFd);
+ } else {
+ // Otherwise, this was added to hot cache during SET, so it was never mapped
+ // and fd was only on the deferred thread.
+ delete[] entry.entryBuffer;
+ }
+}
+
+} // namespace
+
+namespace android {
+
+MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
+ const std::string& baseDir)
+ : mInitialized(false),
+ mMaxKeySize(maxKeySize),
+ mMaxValueSize(maxValueSize),
+ mMaxTotalSize(maxTotalSize),
+ mTotalCacheSize(0),
+ mHotCacheLimit(0),
+ mHotCacheSize(0),
+ mWorkerThreadIdle(true) {
+ if (baseDir.empty()) {
+ ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
+ return;
+ }
+
+ // Establish the name of our multifile directory
+ mMultifileDirName = baseDir + ".multifile";
+
+ // Set the hotcache limit to be large enough to contain one max entry
+ // This ensure the hot cache is always large enough for single entry
+ mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
+
+ ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
+ mMaxKeySize, mMaxValueSize);
+
+ // Initialize our cache with the contents of the directory
+ mTotalCacheSize = 0;
+
+ // Create the worker thread
+ mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
+
+ // See if the dir exists, and initialize using its contents
+ struct stat st;
+ if (stat(mMultifileDirName.c_str(), &st) == 0) {
+ // Read all the files and gather details, then preload their contents
+ DIR* dir;
+ struct dirent* entry;
+ if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+
+ std::string entryName = entry->d_name;
+ std::string fullPath = mMultifileDirName + "/" + entryName;
+
+ // The filename is the same as the entryHash
+ uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
+
+ ALOGV("INIT: Checking entry %u", entryHash);
+
+ // Look up the details of the file
+ struct stat st;
+ if (stat(fullPath.c_str(), &st) != 0) {
+ ALOGE("Failed to stat %s", fullPath.c_str());
+ return;
+ }
+
+ // If the cache entry is damaged or no good, remove it
+ if (st.st_size <= 0 || st.st_atime <= 0) {
+ ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
+ if (remove(fullPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+ }
+ continue;
+ }
+
+ // Open the file so we can read its header
+ int fd = open(fullPath.c_str(), O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
+ std::strerror(errno));
+ return;
+ }
+
+ // Read the beginning of the file to get header
+ MultifileHeader header;
+ size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
+ if (result != sizeof(MultifileHeader)) {
+ ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
+ fullPath.c_str(), std::strerror(errno));
+ return;
+ }
+
+ // Verify header magic
+ if (header.magic != kMultifileMagic) {
+ ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
+ if (remove(fullPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+ }
+ continue;
+ }
+
+ // Note: Converting from off_t (signed) to size_t (unsigned)
+ size_t fileSize = static_cast<size_t>(st.st_size);
+
+ // Memory map the file
+ uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
+ mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (mappedEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ return;
+ }
+
+ // Ensure we have a good CRC
+ if (header.crc !=
+ crc32c(mappedEntry + sizeof(MultifileHeader),
+ fileSize - sizeof(MultifileHeader))) {
+ ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash);
+ if (remove(fullPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+ }
+ continue;
+ }
+
+ // If the cache entry is damaged or no good, remove it
+ if (header.keySize <= 0 || header.valueSize <= 0) {
+ ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
+ "removing.",
+ entryHash, header.keySize, header.valueSize);
+ if (remove(fullPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+ }
+ continue;
+ }
+
+ ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
+
+ // Track details for rapid lookup later
+ trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
+
+ // Track the total size
+ increaseTotalCacheSize(fileSize);
+
+ // Preload the entry for fast retrieval
+ if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
+ ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
+ "entryHash %u",
+ fd, mappedEntry, entryHash);
+
+ // Track the details of the preload so they can be retrieved later
+ if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
+ ALOGE("INIT Failed to add %u to hot cache", entryHash);
+ munmap(mappedEntry, fileSize);
+ close(fd);
+ return;
+ }
+ } else {
+ // If we're not keeping it in hot cache, unmap it now
+ munmap(mappedEntry, fileSize);
+ close(fd);
+ }
+ }
+ closedir(dir);
+ } else {
+ ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
+ }
+ } else {
+ // If the multifile directory does not exist, create it and start from scratch
+ if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+ ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
+ }
+ }
+
+ mInitialized = true;
+}
+
+MultifileBlobCache::~MultifileBlobCache() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Inform the worker thread we're done
+ ALOGV("DESCTRUCTOR: Shutting down worker thread");
+ DeferredTask task(TaskCommand::Exit);
+ queueTask(std::move(task));
+
+ // Wait for it to complete
+ ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
+ waitForWorkComplete();
+ if (mTaskThread.joinable()) {
+ mTaskThread.join();
+ }
+}
+
+// Set will add the entry to hot cache and start a deferred process to write it to disk
+void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize) {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Ensure key and value are under their limits
+ if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
+ ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+ valueSize, mMaxValueSize);
+ return;
+ }
+
+ // Generate a hash of the key and use it to track this entry
+ uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+ size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
+
+ // If we're going to be over the cache limit, kick off a trim to clear space
+ if (getTotalSize() + fileSize > mMaxTotalSize) {
+ ALOGV("SET: Cache is full, calling trimCache to clear space");
+ trimCache();
+ }
+
+ ALOGV("SET: Add %u to cache", entryHash);
+
+ uint8_t* buffer = new uint8_t[fileSize];
+
+ // Write placeholders for magic and CRC until deferred thread completes the write
+ android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
+ memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
+ sizeof(android::MultifileHeader));
+ // Write the key and value after the header
+ memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
+ keySize);
+ memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
+ static_cast<const void*>(value), valueSize);
+
+ std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+ // Track the size and access time for quick recall
+ trackEntry(entryHash, valueSize, fileSize, time(0));
+
+ // Update the overall cache size
+ increaseTotalCacheSize(fileSize);
+
+ // Keep the entry in hot cache for quick retrieval
+ ALOGV("SET: Adding %u to hot cache.", entryHash);
+
+ // Sending -1 as the fd indicates we don't have an fd for this
+ if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
+ ALOGE("SET: Failed to add %u to hot cache", entryHash);
+ delete[] buffer;
+ return;
+ }
+
+ // Track that we're creating a pending write for this entry
+ // Include the buffer to handle the case when multiple writes are pending for an entry
+ {
+ // Synchronize access to deferred write status
+ std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
+ mDeferredWrites.insert(std::make_pair(entryHash, buffer));
+ }
+
+ // Create deferred task to write to storage
+ ALOGV("SET: Adding task to queue.");
+ DeferredTask task(TaskCommand::WriteToDisk);
+ task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
+ queueTask(std::move(task));
+}
+
+// Get will check the hot cache, then load it from disk if needed
+EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize) {
+ if (!mInitialized) {
+ return 0;
+ }
+
+ // Ensure key and value are under their limits
+ if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
+ ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
+ valueSize, mMaxValueSize);
+ return 0;
+ }
+
+ // Generate a hash of the key and use it to track this entry
+ uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+ // See if we have this file
+ if (!contains(entryHash)) {
+ ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
+ return 0;
+ }
+
+ // Look up the data for this entry
+ MultifileEntryStats entryStats = getEntryStats(entryHash);
+
+ size_t cachedValueSize = entryStats.valueSize;
+ if (cachedValueSize > valueSize) {
+ ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
+ "size (%zu)",
+ valueSize, entryHash, cachedValueSize);
+ return cachedValueSize;
+ }
+
+ // We have the file and have enough room to write it out, return the entry
+ ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
+
+ // Look up the size of the file
+ size_t fileSize = entryStats.fileSize;
+ if (keySize > fileSize) {
+ ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
+ "file",
+ keySize, fileSize);
+ return 0;
+ }
+
+ std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+ // Open the hashed filename path
+ uint8_t* cacheEntry = 0;
+
+ // Check hot cache
+ if (mHotCache.find(entryHash) != mHotCache.end()) {
+ ALOGV("GET: HotCache HIT for entry %u", entryHash);
+ cacheEntry = mHotCache[entryHash].entryBuffer;
+ } else {
+ ALOGV("GET: HotCache MISS for entry: %u", entryHash);
+
+ // Wait for writes to complete if there is an outstanding write for this entry
+ bool wait = false;
+ {
+ // Synchronize access to deferred write status
+ std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
+ wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
+ }
+
+ if (wait) {
+ ALOGV("GET: Waiting for write to complete for %u", entryHash);
+ waitForWorkComplete();
+ }
+
+ // Open the entry file
+ int fd = open(fullPath.c_str(), O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
+ std::strerror(errno));
+ return 0;
+ }
+
+ // Memory map the file
+ cacheEntry =
+ reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (cacheEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ close(fd);
+ return 0;
+ }
+
+ ALOGV("GET: Adding %u to hot cache", entryHash);
+ if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
+ ALOGE("GET: Failed to add %u to hot cache", entryHash);
+ return 0;
+ }
+
+ cacheEntry = mHotCache[entryHash].entryBuffer;
+ }
+
+ // Ensure the header matches
+ MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
+ if (header->keySize != keySize || header->valueSize != valueSize) {
+ ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
+ "to cache header values for fullPath: %s",
+ keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
+ removeFromHotCache(entryHash);
+ return 0;
+ }
+
+ // Compare the incoming key with our stored version (the beginning of the entry)
+ uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
+ int compare = memcmp(cachedKey, key, keySize);
+ if (compare != 0) {
+ ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
+ removeFromHotCache(entryHash);
+ return 0;
+ }
+
+ // Remaining entry following the key is the value
+ uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
+ memcpy(value, cachedValue, cachedValueSize);
+
+ return cachedValueSize;
+}
+
+void MultifileBlobCache::finish() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Wait for all deferred writes to complete
+ ALOGV("FINISH: Waiting for work to complete.");
+ waitForWorkComplete();
+
+ // Close all entries in the hot cache
+ for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
+ uint32_t entryHash = hotCacheIter->first;
+ MultifileHotCache entry = hotCacheIter->second;
+
+ ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
+ freeHotCacheEntry(entry);
+
+ mHotCache.erase(hotCacheIter++);
+ }
+}
+
+void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
+ time_t accessTime) {
+ mEntries.insert(entryHash);
+ mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
+}
+
+bool MultifileBlobCache::contains(uint32_t hashEntry) const {
+ return mEntries.find(hashEntry) != mEntries.end();
+}
+
+MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
+ return mEntryStats[entryHash];
+}
+
+void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
+ mTotalCacheSize += fileSize;
+}
+
+void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
+ mTotalCacheSize -= fileSize;
+}
+
+bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
+ size_t newEntrySize) {
+ ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
+
+ // Clear space if we need to
+ if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
+ ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
+ "mHotCacheLimit "
+ "(%zu), freeing up space for %u",
+ mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
+
+ // Wait for all the files to complete writing so our hot cache is accurate
+ ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
+ waitForWorkComplete();
+
+ // Free up old entries until under the limit
+ for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
+ uint32_t oldEntryHash = hotCacheIter->first;
+ MultifileHotCache oldEntry = hotCacheIter->second;
+
+ // Move our iterator before deleting the entry
+ hotCacheIter++;
+ if (!removeFromHotCache(oldEntryHash)) {
+ ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
+ return false;
+ }
+
+ // Clear at least half the hot cache
+ if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
+ ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
+ break;
+ }
+ }
+ }
+
+ // Track it
+ mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
+ mHotCacheSize += newEntrySize;
+
+ ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
+
+ return true;
+}
+
+bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
+ if (mHotCache.find(entryHash) != mHotCache.end()) {
+ ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
+
+ // Wait for all the files to complete writing so our hot cache is accurate
+ ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
+ waitForWorkComplete();
+
+ ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
+ MultifileHotCache entry = mHotCache[entryHash];
+ freeHotCacheEntry(entry);
+
+ // Delete the entry from our tracking
+ mHotCacheSize -= entry.entrySize;
+ mHotCache.erase(entryHash);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
+ // Walk through our map of sorted last access times and remove files until under the limit
+ for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
+ uint32_t entryHash = cacheEntryIter->first;
+
+ ALOGV("LRU: Removing entryHash %u", entryHash);
+
+ // Track the overall size
+ MultifileEntryStats entryStats = getEntryStats(entryHash);
+ decreaseTotalCacheSize(entryStats.fileSize);
+
+ // Remove it from hot cache if present
+ removeFromHotCache(entryHash);
+
+ // Remove it from the system
+ std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
+ if (remove(entryPath.c_str()) != 0) {
+ ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
+ return false;
+ }
+
+ // Increment the iterator before clearing the entry
+ cacheEntryIter++;
+
+ // Delete the entry from our tracking
+ size_t count = mEntryStats.erase(entryHash);
+ if (count != 1) {
+ ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
+ return false;
+ }
+
+ // See if it has been reduced enough
+ size_t totalCacheSize = getTotalSize();
+ if (totalCacheSize <= cacheLimit) {
+ // Success
+ ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
+ return true;
+ }
+ }
+
+ ALOGV("LRU: Cache is empty");
+ return false;
+}
+
+// When removing files, what fraction of the overall limit should be reached when removing files
+// A divisor of two will decrease the cache to 50%, four to 25% and so on
+constexpr uint32_t kCacheLimitDivisor = 2;
+
+// Calculate the cache size and remove old entries until under the limit
+void MultifileBlobCache::trimCache() {
+ // Wait for all deferred writes to complete
+ ALOGV("TRIM: Waiting for work to complete.");
+ waitForWorkComplete();
+
+ ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
+ if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
+ ALOGE("Error when clearing multifile shader cache");
+ return;
+ }
+}
+
+// This function performs a task. It only knows how to write files to disk,
+// but it could be expanded if needed.
+void MultifileBlobCache::processTask(DeferredTask& task) {
+ switch (task.getTaskCommand()) {
+ case TaskCommand::Exit: {
+ ALOGV("DEFERRED: Shutting down");
+ return;
+ }
+ case TaskCommand::WriteToDisk: {
+ uint32_t entryHash = task.getEntryHash();
+ std::string& fullPath = task.getFullPath();
+ uint8_t* buffer = task.getBuffer();
+ size_t bufferSize = task.getBufferSize();
+
+ // Create the file or reset it if already present, read+write for user only
+ int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd == -1) {
+ ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
+ fullPath.c_str(), std::strerror(errno));
+ return;
+ }
+
+ ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
+
+ // Add CRC check to the header (always do this last!)
+ MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
+ header->crc =
+ crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader));
+
+ ssize_t result = write(fd, buffer, bufferSize);
+ if (result != bufferSize) {
+ ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
+ std::strerror(errno));
+ return;
+ }
+
+ ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
+ close(fd);
+
+ // Erase the entry from mDeferredWrites
+ // Since there could be multiple outstanding writes for an entry, find the matching one
+ {
+ // Synchronize access to deferred write status
+ std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
+ typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
+ std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
+ for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
+ if (it->second == buffer) {
+ ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
+ it->second);
+ mDeferredWrites.erase(it);
+ break;
+ }
+ }
+ }
+
+ return;
+ }
+ default: {
+ ALOGE("DEFERRED: Unhandled task type");
+ return;
+ }
+ }
+}
+
+// This function will wait until tasks arrive, then execute them
+// If the exit command is submitted, the loop will terminate
+void MultifileBlobCache::processTasksImpl(bool* exitThread) {
+ while (true) {
+ std::unique_lock<std::mutex> lock(mWorkerMutex);
+ if (mTasks.empty()) {
+ ALOGV("WORKER: No tasks available, waiting");
+ mWorkerThreadIdle = true;
+ mWorkerIdleCondition.notify_all();
+ // Only wake if notified and command queue is not empty
+ mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
+ }
+
+ ALOGV("WORKER: Task available, waking up.");
+ mWorkerThreadIdle = false;
+ DeferredTask task = std::move(mTasks.front());
+ mTasks.pop();
+
+ if (task.getTaskCommand() == TaskCommand::Exit) {
+ ALOGV("WORKER: Exiting work loop.");
+ *exitThread = true;
+ mWorkerThreadIdle = true;
+ mWorkerIdleCondition.notify_one();
+ return;
+ }
+
+ lock.unlock();
+ processTask(task);
+ }
+}
+
+// Process tasks until the exit task is submitted
+void MultifileBlobCache::processTasks() {
+ while (true) {
+ bool exitThread = false;
+ processTasksImpl(&exitThread);
+ if (exitThread) {
+ break;
+ }
+ }
+}
+
+// Add a task to the queue to be processed by the worker thread
+void MultifileBlobCache::queueTask(DeferredTask&& task) {
+ std::lock_guard<std::mutex> queueLock(mWorkerMutex);
+ mTasks.emplace(std::move(task));
+ mWorkAvailableCondition.notify_one();
+}
+
+// Wait until all tasks have been completed
+void MultifileBlobCache::waitForWorkComplete() {
+ std::unique_lock<std::mutex> lock(mWorkerMutex);
+ mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
+}
+
+}; // namespace android
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
new file mode 100644
index 0000000000..5e527dcf35
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -0,0 +1,174 @@
+/*
+ ** Copyright 2022, 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.
+ */
+
+#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H
+#define ANDROID_MULTIFILE_BLOB_CACHE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <android-base/thread_annotations.h>
+#include <future>
+#include <map>
+#include <queue>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "FileBlobCache.h"
+
+namespace android {
+
+struct MultifileHeader {
+ uint32_t magic;
+ uint32_t crc;
+ EGLsizeiANDROID keySize;
+ EGLsizeiANDROID valueSize;
+};
+
+struct MultifileEntryStats {
+ EGLsizeiANDROID valueSize;
+ size_t fileSize;
+ time_t accessTime;
+};
+
+struct MultifileHotCache {
+ int entryFd;
+ uint8_t* entryBuffer;
+ size_t entrySize;
+};
+
+enum class TaskCommand {
+ Invalid = 0,
+ WriteToDisk,
+ Exit,
+};
+
+class DeferredTask {
+public:
+ DeferredTask(TaskCommand command)
+ : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {}
+
+ TaskCommand getTaskCommand() { return mCommand; }
+
+ void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer,
+ size_t bufferSize) {
+ mCommand = TaskCommand::WriteToDisk;
+ mEntryHash = entryHash;
+ mFullPath = std::move(fullPath);
+ mBuffer = buffer;
+ mBufferSize = bufferSize;
+ }
+
+ uint32_t getEntryHash() { return mEntryHash; }
+ std::string& getFullPath() { return mFullPath; }
+ uint8_t* getBuffer() { return mBuffer; }
+ size_t getBufferSize() { return mBufferSize; };
+
+private:
+ TaskCommand mCommand;
+
+ // Parameters for WriteToDisk
+ uint32_t mEntryHash;
+ std::string mFullPath;
+ uint8_t* mBuffer;
+ size_t mBufferSize;
+};
+
+class MultifileBlobCache {
+public:
+ MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
+ const std::string& baseDir);
+ ~MultifileBlobCache();
+
+ void set(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize);
+ EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize);
+
+ void finish();
+
+ size_t getTotalSize() const { return mTotalCacheSize; }
+
+private:
+ void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
+ time_t accessTime);
+ bool contains(uint32_t entryHash) const;
+ bool removeEntry(uint32_t entryHash);
+ MultifileEntryStats getEntryStats(uint32_t entryHash);
+
+ size_t getFileSize(uint32_t entryHash);
+ size_t getValueSize(uint32_t entryHash);
+
+ void increaseTotalCacheSize(size_t fileSize);
+ void decreaseTotalCacheSize(size_t fileSize);
+
+ bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
+ bool removeFromHotCache(uint32_t entryHash);
+
+ void trimCache();
+ bool applyLRU(size_t cacheLimit);
+
+ bool mInitialized;
+ std::string mMultifileDirName;
+
+ std::unordered_set<uint32_t> mEntries;
+ std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
+ std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
+
+ size_t mMaxKeySize;
+ size_t mMaxValueSize;
+ size_t mMaxTotalSize;
+ size_t mTotalCacheSize;
+ size_t mHotCacheLimit;
+ size_t mHotCacheEntryLimit;
+ size_t mHotCacheSize;
+
+ // Below are the components used for deferred writes
+
+ // Track whether we have pending writes for an entry
+ std::mutex mDeferredWriteStatusMutex;
+ std::multimap<uint32_t, uint8_t*> mDeferredWrites GUARDED_BY(mDeferredWriteStatusMutex);
+
+ // Functions to work through tasks in the queue
+ void processTasks();
+ void processTasksImpl(bool* exitThread);
+ void processTask(DeferredTask& task);
+
+ // Used by main thread to create work for the worker thread
+ void queueTask(DeferredTask&& task);
+
+ // Used by main thread to wait for worker thread to complete all outstanding work.
+ void waitForWorkComplete();
+
+ std::thread mTaskThread;
+ std::queue<DeferredTask> mTasks;
+ std::mutex mWorkerMutex;
+
+ // This condition will block the worker thread until a task is queued
+ std::condition_variable mWorkAvailableCondition;
+
+ // This condition will block the main thread while the worker thread still has tasks
+ std::condition_variable mWorkerIdleCondition;
+
+ // This bool will track whether all tasks have been completed
+ bool mWorkerThreadIdle;
+};
+
+}; // namespace android
+
+#endif // ANDROID_MULTIFILE_BLOB_CACHE_H
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
new file mode 100644
index 0000000000..dbee13bb2e
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -0,0 +1,219 @@
+/*
+ ** Copyright 2023, 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.
+ */
+
+#include "MultifileBlobCache.h"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <memory>
+
+namespace android {
+
+template <typename T>
+using sp = std::shared_ptr<T>;
+
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
+constexpr size_t kMaxTotalSize = 32 * 1024;
+
+class MultifileBlobCacheTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ mTempFile.reset(new TemporaryFile());
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
+ &mTempFile->path[0]));
+ }
+
+ virtual void TearDown() { mMBC.reset(); }
+
+ std::unique_ptr<TemporaryFile> mTempFile;
+ std::unique_ptr<MultifileBlobCache> mMBC;
+};
+
+TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("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(MultifileBlobCacheTest, CacheTwoValuesSucceeds) {
+ unsigned char buf[2] = {0xee, 0xee};
+ mMBC->set("ab", 2, "cd", 2);
+ mMBC->set("ef", 2, "gh", 2);
+ ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+ ASSERT_EQ('c', buf[0]);
+ ASSERT_EQ('d', buf[1]);
+ ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2));
+ ASSERT_EQ('g', buf[0]);
+ ASSERT_EQ('h', buf[1]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) {
+ unsigned char buf[2] = {0xee, 0xee};
+ mMBC->set("ab", 2, "cd", 2);
+ ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+ ASSERT_EQ('c', buf[0]);
+ ASSERT_EQ('d', buf[1]);
+ // Use the same key, but different value
+ mMBC->set("ab", 2, "ef", 2);
+ ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+ ASSERT_EQ('e', buf[0]);
+ ASSERT_EQ('f', buf[1]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) {
+ unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4));
+ ASSERT_EQ(0xee, buf[0]);
+ ASSERT_EQ('e', buf[1]);
+ ASSERT_EQ('f', buf[2]);
+ ASSERT_EQ('g', buf[3]);
+ ASSERT_EQ('h', buf[4]);
+ ASSERT_EQ(0xee, buf[5]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) {
+ unsigned char buf[3] = {0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3));
+ ASSERT_EQ(0xee, buf[0]);
+ ASSERT_EQ(0xee, buf[1]);
+ ASSERT_EQ(0xee, buf[2]);
+}
+
+TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) {
+ mMBC->set("abcd", 4, "efgh", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0));
+}
+
+TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) {
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ mMBC->set("abcd", 4, "ijkl", 4);
+ ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4));
+ ASSERT_EQ('i', buf[0]);
+ ASSERT_EQ('j', buf[1]);
+ ASSERT_EQ('k', buf[2]);
+ ASSERT_EQ('l', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) {
+ unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee};
+ mMBC->set("abcd", 4, "efgh", 4);
+ mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
+ ASSERT_EQ(size_t(4), mMBC->get("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(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) {
+ char key[kMaxKeySize + 1];
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ for (int i = 0; i < kMaxKeySize + 1; i++) {
+ key[i] = 'a';
+ }
+ mMBC->set(key, kMaxKeySize + 1, "bbbb", 4);
+ ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4));
+ ASSERT_EQ(0xee, buf[0]);
+ ASSERT_EQ(0xee, buf[1]);
+ ASSERT_EQ(0xee, buf[2]);
+ ASSERT_EQ(0xee, buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) {
+ char buf[kMaxValueSize + 1];
+ for (int i = 0; i < kMaxValueSize + 1; i++) {
+ buf[i] = 'b';
+ }
+ mMBC->set("abcd", 4, buf, kMaxValueSize + 1);
+ for (int i = 0; i < kMaxValueSize + 1; i++) {
+ buf[i] = 0xee;
+ }
+ ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1));
+ for (int i = 0; i < kMaxValueSize + 1; i++) {
+ SCOPED_TRACE(i);
+ ASSERT_EQ(0xee, buf[i]);
+ }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) {
+ char key[kMaxKeySize];
+ unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+ for (int i = 0; i < kMaxKeySize; i++) {
+ key[i] = 'a';
+ }
+ mMBC->set(key, kMaxKeySize, "wxyz", 4);
+ ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4));
+ ASSERT_EQ('w', buf[0]);
+ ASSERT_EQ('x', buf[1]);
+ ASSERT_EQ('y', buf[2]);
+ ASSERT_EQ('z', buf[3]);
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) {
+ char buf[kMaxValueSize];
+ for (int i = 0; i < kMaxValueSize; i++) {
+ buf[i] = 'b';
+ }
+ mMBC->set("abcd", 4, buf, kMaxValueSize);
+ for (int i = 0; i < kMaxValueSize; i++) {
+ buf[i] = 0xee;
+ }
+ mMBC->get("abcd", 4, buf, kMaxValueSize);
+ for (int i = 0; i < kMaxValueSize; i++) {
+ SCOPED_TRACE(i);
+ ASSERT_EQ('b', buf[i]);
+ }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) {
+ char key[kMaxKeySize];
+ for (int i = 0; i < kMaxKeySize; i++) {
+ key[i] = 'a';
+ }
+ char buf[kMaxValueSize];
+ for (int i = 0; i < kMaxValueSize; i++) {
+ buf[i] = 'b';
+ }
+ mMBC->set(key, kMaxKeySize, buf, kMaxValueSize);
+ for (int i = 0; i < kMaxValueSize; i++) {
+ buf[i] = 0xee;
+ }
+ mMBC->get(key, kMaxKeySize, buf, kMaxValueSize);
+ for (int i = 0; i < kMaxValueSize; i++) {
+ SCOPED_TRACE(i);
+ ASSERT_EQ('b', buf[i]);
+ }
+}
+
+TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
+ unsigned char buf[1] = {0xee};
+ mMBC->set("x", 1, "y", 1);
+ ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1));
+ ASSERT_EQ('y', buf[0]);
+}
+
+} // namespace android
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index c0ead5096f..9a6bb7a61c 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -68,9 +68,9 @@ static void logInfo(PlatformMethods* /*platform*/, const char* infoMessage) {
static TraceEventHandle addTraceEvent(
PlatformMethods* /**platform*/, char phase, const unsigned char* /*category_group_enabled*/,
- const char* name, unsigned long long /*id*/, double /*timestamp*/, int /*num_args*/,
- const char** /*arg_names*/, const unsigned char* /*arg_types*/,
- const unsigned long long* /*arg_values*/, unsigned char /*flags*/) {
+ const char* name, unsigned long long /*id*/, double /*timestamp*/, int num_args,
+ const char** arg_names, const unsigned char* /*arg_types*/,
+ const unsigned long long* arg_values, unsigned char /*flags*/) {
switch (phase) {
case 'B': {
ATRACE_BEGIN(name);
@@ -84,6 +84,13 @@ static TraceEventHandle addTraceEvent(
ATRACE_NAME(name);
break;
}
+ case 'C': {
+ for(int i=0; i<num_args ; i++)
+ {
+ ATRACE_INT(arg_names[i],arg_values[i]);
+ }
+ break;
+ }
default:
// Could handle other event types here
break;
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index efa67dbc1c..1b68344cb1 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -14,8 +14,12 @@
** limitations under the License.
*/
+// #define LOG_NDEBUG 0
+
#include "egl_cache.h"
+#include <android-base/properties.h>
+#include <inttypes.h>
#include <log/log.h>
#include <private/EGL/cache.h>
#include <unistd.h>
@@ -25,13 +29,18 @@
#include "../egl_impl.h"
#include "egl_display.h"
-// Cache size limits.
-static const size_t maxKeySize = 12 * 1024;
-static const size_t maxValueSize = 64 * 1024;
-static const size_t maxTotalSize = 2 * 1024 * 1024;
+// Monolithic cache size limits.
+static const size_t kMaxMonolithicKeySize = 12 * 1024;
+static const size_t kMaxMonolithicValueSize = 64 * 1024;
+static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024;
+
+// The time in seconds to wait before saving newly inserted monolithic cache entries.
+static const unsigned int kDeferredMonolithicSaveDelay = 4;
-// The time in seconds to wait before saving newly inserted cache entries.
-static const unsigned int deferredSaveDelay = 4;
+// Multifile cache size limits
+constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024;
namespace android {
@@ -58,7 +67,8 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v
//
// egl_cache_t definition
//
-egl_cache_t::egl_cache_t() : mInitialized(false) {}
+egl_cache_t::egl_cache_t()
+ : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {}
egl_cache_t::~egl_cache_t() {}
@@ -72,7 +82,7 @@ void egl_cache_t::initialize(egl_display_t* display) {
std::lock_guard<std::mutex> lock(mMutex);
egl_connection_t* const cnx = &gEGLImpl;
- if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
+ if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
const char* exts = display->disp.queryString.extensions;
size_t bcExtLen = strlen(BC_EXT_STR);
size_t extsLen = strlen(exts);
@@ -110,6 +120,11 @@ void egl_cache_t::terminate() {
mBlobCache->writeToFile();
}
mBlobCache = nullptr;
+ if (mMultifileBlobCache) {
+ mMultifileBlobCache->finish();
+ }
+ mMultifileBlobCache = nullptr;
+ mInitialized = false;
}
void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* value,
@@ -121,21 +136,28 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void*
return;
}
+ updateMode();
+
if (mInitialized) {
- BlobCache* bc = getBlobCacheLocked();
- bc->set(key, keySize, value, valueSize);
-
- if (!mSavePending) {
- mSavePending = true;
- std::thread deferredSaveThread([this]() {
- sleep(deferredSaveDelay);
- std::lock_guard<std::mutex> lock(mMutex);
- if (mInitialized && mBlobCache) {
- mBlobCache->writeToFile();
- }
- mSavePending = false;
- });
- deferredSaveThread.detach();
+ if (mMultifileMode) {
+ MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
+ mbc->set(key, keySize, value, valueSize);
+ } else {
+ BlobCache* bc = getBlobCacheLocked();
+ bc->set(key, keySize, value, valueSize);
+
+ if (!mSavePending) {
+ mSavePending = true;
+ std::thread deferredSaveThread([this]() {
+ sleep(kDeferredMonolithicSaveDelay);
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mInitialized && mBlobCache) {
+ mBlobCache->writeToFile();
+ }
+ mSavePending = false;
+ });
+ deferredSaveThread.detach();
+ }
}
}
}
@@ -145,27 +167,119 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v
std::lock_guard<std::mutex> lock(mMutex);
if (keySize < 0 || valueSize < 0) {
- ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
+ ALOGW("EGL_ANDROID_blob_cache get: negative sizes are not allowed");
return 0;
}
+ updateMode();
+
if (mInitialized) {
- BlobCache* bc = getBlobCacheLocked();
- return bc->get(key, keySize, value, valueSize);
+ if (mMultifileMode) {
+ MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
+ return mbc->get(key, keySize, value, valueSize);
+ } else {
+ BlobCache* bc = getBlobCacheLocked();
+ return bc->get(key, keySize, value, valueSize);
+ }
}
+
return 0;
}
+void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) {
+ mMultifileMode = (cacheMode == EGLCacheMode::Multifile);
+}
+
void egl_cache_t::setCacheFilename(const char* filename) {
std::lock_guard<std::mutex> lock(mMutex);
mFilename = filename;
}
+void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ if (!mMultifileMode) {
+ // If we're not in multifile mode, ensure the cache limit is only being lowered,
+ // not increasing above the hard coded platform limit
+ if (cacheByteLimit > kMaxMonolithicTotalSize) {
+ return;
+ }
+ }
+
+ mCacheByteLimit = cacheByteLimit;
+}
+
+size_t egl_cache_t::getCacheSize() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mMultifileBlobCache) {
+ return mMultifileBlobCache->getTotalSize();
+ }
+ if (mBlobCache) {
+ return mBlobCache->getSize();
+ }
+ return 0;
+}
+
+void egl_cache_t::updateMode() {
+ // We don't set the mode in the constructor because these checks have
+ // a non-trivial cost, and not all processes that instantiate egl_cache_t
+ // will use it.
+
+ // If we've already set the mode, skip these checks
+ static bool checked = false;
+ if (checked) {
+ return;
+ }
+ checked = true;
+
+ // Check the device config to decide whether multifile should be used
+ if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) {
+ mMultifileMode = true;
+ ALOGV("Using multifile EGL blobcache");
+ }
+
+ // Allow forcing the mode for debug purposes
+ std::string mode = base::GetProperty("debug.egl.blobcache.multifile", "");
+ if (mode == "true") {
+ ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
+ mMultifileMode = true;
+ } else if (mode == "false") {
+ ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str());
+ mMultifileMode = false;
+ }
+
+ if (mMultifileMode) {
+ mCacheByteLimit = static_cast<size_t>(
+ base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
+ kMaxMultifileTotalSize));
+
+ // Check for a debug value
+ int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1);
+ if (debugCacheSize >= 0) {
+ ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit",
+ mCacheByteLimit, debugCacheSize);
+ mCacheByteLimit = debugCacheSize;
+ }
+
+ ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit);
+ }
+}
+
BlobCache* egl_cache_t::getBlobCacheLocked() {
if (mBlobCache == nullptr) {
- mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
+ mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize,
+ mCacheByteLimit, mFilename));
}
return mBlobCache.get();
}
+MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() {
+ if (mMultifileBlobCache == nullptr) {
+ mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize,
+ kMaxMultifileValueSize, mCacheByteLimit,
+ mFilename));
+ }
+ return mMultifileBlobCache.get();
+}
+
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index d10a6154b7..ae6d38146e 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -25,6 +25,7 @@
#include <string>
#include "FileBlobCache.h"
+#include "MultifileBlobCache.h"
namespace android {
@@ -32,6 +33,11 @@ class egl_display_t;
class EGLAPI egl_cache_t {
public:
+ enum class EGLCacheMode {
+ Monolithic,
+ Multifile,
+ };
+
// get returns a pointer to the singleton egl_cache_t object. This
// singleton object will never be destroyed.
static egl_cache_t* get();
@@ -64,6 +70,15 @@ public:
// cache contents from one program invocation to another.
void setCacheFilename(const char* filename);
+ // Allow setting monolithic or multifile modes
+ void setCacheMode(EGLCacheMode cacheMode);
+
+ // Allow the fixed cache limit to be overridden
+ void setCacheLimit(int64_t cacheByteLimit);
+
+ // Return the byte total for cache file(s)
+ size_t getCacheSize();
+
private:
// Creation and (the lack of) destruction is handled internally.
egl_cache_t();
@@ -73,12 +88,18 @@ private:
egl_cache_t(const egl_cache_t&); // not implemented
void operator=(const egl_cache_t&); // not implemented
+ // Check system properties to determine which blobcache mode should be used
+ void updateMode();
+
// getBlobCacheLocked returns the BlobCache object being used to store the
// key/value blob pairs. If the BlobCache object has not yet been created,
// this will do so, loading the serialized cache contents from disk if
// possible.
BlobCache* getBlobCacheLocked();
+ // Get or create the multifile blobcache
+ MultifileBlobCache* getMultifileBlobCacheLocked();
+
// mInitialized indicates whether the egl_cache_t is in the initialized
// state. It is initialized to false at construction time, and gets set to
// true when initialize is called. It is set back to false when terminate
@@ -92,6 +113,9 @@ private:
// first time it's needed.
std::unique_ptr<FileBlobCache> mBlobCache;
+ // The multifile version of blobcache allowing larger contents to be stored
+ std::unique_ptr<MultifileBlobCache> mMultifileBlobCache;
+
// 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
@@ -112,6 +136,12 @@ private:
// sCache is the singleton egl_cache_t object.
static egl_cache_t sCache;
+
+ // Whether to use multiple files to store cache entries
+ bool mMultifileMode;
+
+ // Cache limit
+ size_t mCacheByteLimit;
};
}; // namespace android
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 6593c1bb16..525fed115d 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -353,8 +353,9 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) {
// Typically that means there is an HDR capable display attached, but could be
// support for attaching an HDR display. In either case, advertise support for
// HDR color spaces.
- mExtensionString.append(
- "EGL_EXT_gl_colorspace_bt2020_linear EGL_EXT_gl_colorspace_bt2020_pq ");
+ mExtensionString.append("EGL_EXT_gl_colorspace_bt2020_hlg "
+ "EGL_EXT_gl_colorspace_bt2020_linear "
+ "EGL_EXT_gl_colorspace_bt2020_pq ");
}
char const* start = gExtensionString;
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 4b637dc60b..aefa1f0db5 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -18,6 +18,7 @@
#include "egl_platform_entries.h"
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/hardware_buffer.h>
@@ -29,7 +30,6 @@
#include <private/android/AHardwareBufferHelpers.h>
#include <stdlib.h>
#include <string.h>
-#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <condition_variable>
#include <deque>
@@ -422,6 +422,8 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelForm
return HAL_DATASPACE_V0_SCRGB;
} else if (colorspace == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) {
return HAL_DATASPACE_V0_SCRGB_LINEAR;
+ } else if (colorspace == EGL_GL_COLORSPACE_BT2020_HLG_EXT) {
+ return static_cast<android_dataspace>(HAL_DATASPACE_BT2020_HLG);
} else if (colorspace == EGL_GL_COLORSPACE_BT2020_LINEAR_EXT) {
if (pixelFormat == PixelFormat::RGBA_FP16) {
return static_cast<android_dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
@@ -433,6 +435,7 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelForm
} else if (colorspace == EGL_GL_COLORSPACE_BT2020_PQ_EXT) {
return HAL_DATASPACE_BT2020_PQ;
}
+
return HAL_DATASPACE_UNKNOWN;
}
@@ -459,6 +462,9 @@ static std::vector<EGLint> getDriverColorSpaces(egl_display_t* dp) {
if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_scrgb_linear")) {
colorSpaces.push_back(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT);
}
+ if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_hlg")) {
+ colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_HLG_EXT);
+ }
if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_linear")) {
colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_LINEAR_EXT);
}
@@ -492,6 +498,7 @@ static EGLBoolean processAttributes(egl_display_t* dp, ANativeWindow* window,
case EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT:
case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
case EGL_GL_COLORSPACE_SCRGB_EXT:
+ case EGL_GL_COLORSPACE_BT2020_HLG_EXT:
case EGL_GL_COLORSPACE_BT2020_LINEAR_EXT:
case EGL_GL_COLORSPACE_BT2020_PQ_EXT:
case EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT:
@@ -943,6 +950,8 @@ EGLContext eglCreateContextImpl(EGLDisplay dpy, EGLConfig config, EGLContext sha
android::GraphicsEnv::getInstance().setTargetStats(
android::GpuStatsInfo::Stats::GLES_1_IN_USE);
}
+ android::GraphicsEnv::getInstance().setTargetStats(
+ android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT);
egl_context_t* c = new egl_context_t(dpy, context, config, cnx, version);
return c;
}
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
new file mode 100644
index 0000000000..022a2a3f06
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+ name: "MultifileBlobCache_fuzzer",
+
+ fuzz_config: {
+ cc: ["cnorthrop@google.com"],
+ libfuzzer_options: ["len_control=0"],
+ },
+
+ static_libs: [
+ "libbase",
+ "libEGL_blobCache",
+ "liblog",
+ "libutils",
+ ],
+
+ srcs: [
+ "MultifileBlobCache_fuzzer.cpp",
+ ],
+}
diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
new file mode 100644
index 0000000000..633cc9c51b
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -0,0 +1,158 @@
+/*
+ ** Copyright 2023, 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.
+ */
+
+#include "MultifileBlobCache.h"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+namespace android {
+
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
+constexpr size_t kMaxTotalSize = 32 * 1024;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ // To fuzz this, we're going to create a key/value pair from data
+ // and use them with MultifileBlobCache in a random order
+ // - Use the first entry in data to determine keySize
+ // - Use the second entry in data to determine valueSize
+ // - Mod each of them against half the remaining size, ensuring both fit
+ // - Create key and value using sizes from data
+ // - Use remaining data to switch between GET and SET while
+ // tweaking the keys slightly
+ // - Ensure two cache cleaning scenarios are hit at the end
+
+ // Ensure we have enough data to create interesting key/value pairs
+ size_t kMinInputLength = 128;
+ if (size < kMinInputLength) {
+ return 0;
+ }
+
+ // Need non-zero sizes for interesting results
+ if (data[0] == 0 || data[1] == 0) {
+ return 0;
+ }
+
+ // We need to divide the data up into buffers and sizes
+ FuzzedDataProvider fdp(data, size);
+
+ // Pull two values from data for key and value size
+ EGLsizeiANDROID keySize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+ EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+ size -= 2 * sizeof(uint8_t);
+
+ // Ensure key and value fit in the remaining space (cap them at half data size)
+ keySize = keySize % (size >> 1);
+ valueSize = valueSize % (size >> 1);
+
+ // If either size ended up zero, just move on to save time
+ if (keySize == 0 || valueSize == 0) {
+ return 0;
+ }
+
+ // Create key and value from remaining data
+ std::vector<uint8_t> key;
+ std::vector<uint8_t> value;
+ key = fdp.ConsumeBytes<uint8_t>(keySize);
+ value = fdp.ConsumeBytes<uint8_t>(valueSize);
+
+ // Create a tempfile and a cache
+ std::unique_ptr<TemporaryFile> tempFile;
+ std::unique_ptr<MultifileBlobCache> mbc;
+
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(
+ new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+ // With remaining data, select different paths below
+ int loopCount = 1;
+ uint8_t bumpCount = 0;
+ while (fdp.remaining_bytes() > 0) {
+ // Bounce back and forth between gets and sets
+ if (fdp.ConsumeBool()) {
+ mbc->set(key.data(), keySize, value.data(), valueSize);
+ } else {
+ uint8_t* buffer = new uint8_t[valueSize];
+ mbc->get(key.data(), keySize, buffer, valueSize);
+ delete[] buffer;
+ }
+
+ // Bump the key and values periodically, causing different hits/misses
+ if (fdp.ConsumeBool()) {
+ key[0]++;
+ value[0]++;
+ bumpCount++;
+ }
+
+ // Reset the key and value periodically to hit old entries
+ if (fdp.ConsumeBool()) {
+ key[0] -= bumpCount;
+ value[0] -= bumpCount;
+ bumpCount = 0;
+ }
+
+ loopCount++;
+ }
+ mbc->finish();
+
+ // Fill 2 keys and 2 values to max size with unique values
+ std::vector<uint8_t> maxKey1, maxKey2, maxValue1, maxValue2;
+ maxKey1.resize(kMaxKeySize, 0);
+ maxKey2.resize(kMaxKeySize, 0);
+ maxValue1.resize(kMaxValueSize, 0);
+ maxValue2.resize(kMaxValueSize, 0);
+ for (int i = 0; i < keySize && i < kMaxKeySize; ++i) {
+ maxKey1[i] = key[i];
+ maxKey2[i] = key[i] - 1;
+ }
+ for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) {
+ maxValue1[i] = value[i];
+ maxValue2[i] = value[i] - 1;
+ }
+
+ // Trigger hot cache trimming
+ // Place the maxKey/maxValue twice
+ // The first will fit, the second will trigger hot cache trimming
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(
+ new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+ uint8_t* buffer = new uint8_t[kMaxValueSize];
+ mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
+ mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
+ mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
+ mbc->finish();
+
+ // Trigger cold cache trimming
+ // Create a total size small enough only one entry fits
+ // Since the cache will add a header, 2 * key + value will only hold one value, the second will
+ // overflow
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
+ &tempFile->path[0]));
+ mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
+ mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
+ mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
+ mbc->finish();
+
+ delete[] buffer;
+ return 0;
+}
+
+} // namespace android