diff options
Diffstat (limited to 'opengl')
| -rw-r--r-- | opengl/TEST_MAPPING | 3 | ||||
| -rw-r--r-- | opengl/libs/Android.bp | 6 | ||||
| -rw-r--r-- | opengl/libs/EGL/FileBlobCache.cpp | 6 | ||||
| -rw-r--r-- | opengl/libs/EGL/FileBlobCache.h | 3 | ||||
| -rw-r--r-- | opengl/libs/EGL/Loader.cpp | 1 | ||||
| -rw-r--r-- | opengl/libs/EGL/MultifileBlobCache.cpp | 689 | ||||
| -rw-r--r-- | opengl/libs/EGL/MultifileBlobCache.h | 167 | ||||
| -rw-r--r-- | opengl/libs/EGL/MultifileBlobCache_test.cpp | 200 | ||||
| -rw-r--r-- | opengl/libs/EGL/egl_angle_platform.cpp | 13 | ||||
| -rw-r--r-- | opengl/libs/EGL/egl_cache.cpp | 164 | ||||
| -rw-r--r-- | opengl/libs/EGL/egl_cache.h | 30 | ||||
| -rw-r--r-- | opengl/libs/EGL/egl_platform_entries.cpp | 2 | ||||
| -rw-r--r-- | opengl/tests/EGLTest/Android.bp | 2 | ||||
| -rw-r--r-- | opengl/tests/EGLTest/EGL_test.cpp | 17 | ||||
| -rw-r--r-- | opengl/tests/EGLTest/egl_cache_test.cpp | 156 | ||||
| -rw-r--r-- | opengl/tests/lib/WindowSurface.cpp | 9 |
16 files changed, 1415 insertions, 53 deletions
diff --git a/opengl/TEST_MAPPING b/opengl/TEST_MAPPING index d391dce2de..7c50a945fa 100644 --- a/opengl/TEST_MAPPING +++ b/opengl/TEST_MAPPING @@ -2,6 +2,9 @@ "presubmit": [ { "name": "CtsGpuToolsHostTestCases" + }, + { + "name": "EGL_test" } ] } diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 62cf2555ca..49e1cbafb4 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,11 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp index 751f3be81a..3f7ae7e5f4 100644 --- a/opengl/libs/EGL/FileBlobCache.cpp +++ b/opengl/libs/EGL/FileBlobCache.cpp @@ -185,4 +185,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..8220723b2c 100644 --- a/opengl/libs/EGL/FileBlobCache.h +++ b/opengl/libs/EGL/FileBlobCache.h @@ -33,6 +33,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 dd14bcfb55..34b1251388 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -502,7 +502,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..99af299f8d --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -0,0 +1,689 @@ +/* + ** 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; + +namespace { + +// Open the file and determine the size of the value it contains +size_t getValueSizeFromFile(int fd, const std::string& entryPath) { + // Read the beginning of the file to get header + android::MultifileHeader header; + size_t result = read(fd, static_cast<void*>(&header), sizeof(android::MultifileHeader)); + if (result != sizeof(android::MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(), + std::strerror(errno)); + return 0; + } + + return header.valueSize; +} + +// 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 maxTotalSize, size_t maxHotCacheSize, + const std::string& baseDir) + : mInitialized(false), + mMaxTotalSize(maxTotalSize), + mTotalCacheSize(0), + mHotCacheLimit(maxHotCacheSize), + 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 a limit for max key and value, ensuring at least one entry can always fit in hot cache + mMaxKeySize = mHotCacheLimit / 4; + mMaxValueSize = mHotCacheLimit / 2; + + 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; + } + + // 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; + } + + // Look up the details we track about each file + size_t valueSize = getValueSizeFromFile(fd, fullPath); + + // If the cache entry is damaged or no good, remove it + // TODO: Perform any other checks + if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) { + ALOGV("INIT: Entry %u has a problem! Removing.", entryHash); + 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); + + // Note: Converting from off_t (signed) to size_t (unsigned) + size_t fileSize = static_cast<size_t>(st.st_size); + time_t accessTime = st.st_atime; + + // Track details for rapid lookup later + trackEntry(entryHash, valueSize, fileSize, accessTime); + + // Track the total size + increaseTotalCacheSize(fileSize); + + // Preload the entry for fast retrieval + if ((mHotCacheSize + fileSize) < mHotCacheLimit) { + // 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)); + } + + 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 { + 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) { + ALOGV("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(mMaxTotalSize); + } + + ALOGV("SET: Add %u to cache", entryHash); + + uint8_t* buffer = new uint8_t[fileSize]; + + // Write the key and value after the header + android::MultifileHeader header = {keySize, valueSize}; + memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header), + sizeof(android::MultifileHeader)); + 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("GET: Failed to add %u to hot cache", entryHash); + 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 + 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) { + ALOGV("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); + + if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) { + // Wait for writes to complete if there is an outstanding write for this entry + 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 + 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 + 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 emptry"); + 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(size_t cacheByteLimit) { + // Start with the value provided by egl_cache + size_t limit = cacheByteLimit; + + // Wait for all deferred writes to complete + waitForWorkComplete(); + + size_t size = getTotalSize(); + + // If size is larger than the threshold, remove files using LRU + if (size > limit) { + ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries", + cacheByteLimit); + if (!applyLRU(limit / 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()); + + 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 + 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
\ No newline at end of file diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h new file mode 100644 index 0000000000..c0cc9dc2a9 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -0,0 +1,167 @@ +/* + ** 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 <future> +#include <map> +#include <queue> +#include <string> +#include <thread> +#include <unordered_map> +#include <unordered_set> + +namespace android { + +struct MultifileHeader { + 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 maxTotalSize, size_t maxHotCacheSize, 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(size_t cacheByteLimit); + 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::multimap<uint32_t, uint8_t*> mDeferredWrites; + + // 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..1a55a4fcdd --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -0,0 +1,200 @@ +/* + ** 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 kMaxTotalSize = 32 * 1024; +constexpr size_t kMaxPreloadSize = 8 * 1024; + +constexpr size_t kMaxKeySize = kMaxPreloadSize / 4; +constexpr size_t kMaxValueSize = kMaxPreloadSize / 2; + +class MultifileBlobCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mTempFile.reset(new TemporaryFile()); + mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &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, 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 d38f2eff01..f1122fd098 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..3dc93ee0b7 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,17 @@ #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 kMultifileHotCacheLimit = 8 * 1024 * 1024; +constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024; namespace android { @@ -58,7 +66,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 +81,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 +119,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 +135,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 +166,118 @@ 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", + kMultifileCacheByteLimit)); + + // 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(mCacheByteLimit, kMultifileHotCacheLimit, 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_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp index 7619a5071c..0527c8a45a 100644 --- a/opengl/libs/EGL/egl_platform_entries.cpp +++ b/opengl/libs/EGL/egl_platform_entries.cpp @@ -937,6 +937,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/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp index 51c937614f..d96a89564d 100644 --- a/opengl/tests/EGLTest/Android.bp +++ b/opengl/tests/EGLTest/Android.bp @@ -1,4 +1,3 @@ - package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -11,6 +10,7 @@ package { cc_test { name: "EGL_test", + test_suites: ["general-tests"], srcs: [ "egl_cache_test.cpp", diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp index bbd786d155..cbe4ef9c40 100644 --- a/opengl/tests/EGLTest/EGL_test.cpp +++ b/opengl/tests/EGLTest/EGL_test.cpp @@ -343,6 +343,11 @@ TEST_F(EGLTest, EGLDisplayP3Passthrough) { } TEST_F(EGLTest, EGLDisplayP31010102) { + // This test has been failing since: + // libEGL: When driver doesn't understand P3, map sRGB-encoded P3 to sRGB + // https://android-review.git.corp.google.com/c/platform/frameworks/native/+/793504 + GTEST_SKIP() << "Skipping broken test. See b/120714942 and b/117104367"; + EGLint numConfigs; EGLConfig config; EGLBoolean success; @@ -866,6 +871,12 @@ TEST_F(EGLTest, EGLUnsupportedColorspaceFormatCombo) { EGLConfig config; EGLBoolean success; + if (!hasWideColorDisplay) { + // skip this test if device does not have wide-color display + RecordProperty("hasWideColorDisplay", false); + return; + } + const EGLint attrs[] = { // clang-format off EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -951,6 +962,12 @@ TEST_F(EGLTest, EGLCreateWindowFailAndSucceed) { TEST_F(EGLTest, EGLCreateWindowTwoColorspaces) { EGLConfig config; + if (!hasWideColorDisplay) { + // skip this test if device does not have wide-color display + RecordProperty("hasWideColorDisplay", false); + return; + } + ASSERT_NO_FATAL_FAILURE(get8BitConfig(config)); struct MockConsumer : public BnConsumerListener { diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp index c974f63c13..2b3e3a46af 100644 --- a/opengl/tests/EGLTest/egl_cache_test.cpp +++ b/opengl/tests/EGLTest/egl_cache_test.cpp @@ -24,27 +24,41 @@ #include <android-base/test_utils.h> #include "egl_cache.h" +#include "MultifileBlobCache.h" #include "egl_display.h" #include <memory> +using namespace std::literals; + namespace android { -class EGLCacheTest : public ::testing::Test { +class EGLCacheTest : public ::testing::TestWithParam<egl_cache_t::EGLCacheMode> { protected: virtual void SetUp() { - mCache = egl_cache_t::get(); + // Terminate to clean up any previous cache in this process + mCache->terminate(); + + mTempFile.reset(new TemporaryFile()); + mCache->setCacheFilename(&mTempFile->path[0]); + mCache->setCacheLimit(1024); + mCache->setCacheMode(mCacheMode); } virtual void TearDown() { - mCache->setCacheFilename(""); mCache->terminate(); + mCache->setCacheFilename(""); + mTempFile.reset(nullptr); } - egl_cache_t* mCache; + std::string getCachefileName(); + + egl_cache_t* mCache = egl_cache_t::get(); + std::unique_ptr<TemporaryFile> mTempFile; + egl_cache_t::EGLCacheMode mCacheMode = GetParam(); }; -TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, UninitializedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->setBlob("abcd", 4, "efgh", 4); ASSERT_EQ(0, mCache->getBlob("abcd", 4, buf, 4)); @@ -54,7 +68,7 @@ TEST_F(EGLCacheTest, UninitializedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { +TEST_P(EGLCacheTest, InitializedCacheAlwaysHits) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -65,7 +79,7 @@ TEST_F(EGLCacheTest, InitializedCacheAlwaysHits) { ASSERT_EQ('h', buf[3]); } -TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { +TEST_P(EGLCacheTest, TerminatedCacheAlwaysMisses) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); mCache->setBlob("abcd", 4, "efgh", 4); @@ -77,35 +91,135 @@ TEST_F(EGLCacheTest, TerminatedCacheAlwaysMisses) { ASSERT_EQ(0xee, buf[3]); } -class EGLCacheSerializationTest : public EGLCacheTest { - -protected: +TEST_P(EGLCacheTest, ReinitializedCacheContainsValues) { + uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; + 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]); +} - virtual void SetUp() { - EGLCacheTest::SetUp(); - mTempFile.reset(new TemporaryFile()); +std::string EGLCacheTest::getCachefileName() { + // Return the monolithic filename unless we find the multifile dir + std::string cachePath = &mTempFile->path[0]; + std::string multifileDirName = cachePath + ".multifile"; + std::string cachefileName = ""; + + struct stat info; + if (stat(multifileDirName.c_str(), &info) == 0) { + // Ensure we only have one file to manage + int realFileCount = 0; + + // We have a multifile dir. Return the only real file in it. + DIR* dir; + struct dirent* entry; + if ((dir = opendir(multifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + cachefileName = multifileDirName + "/" + entry->d_name; + realFileCount++; + } + } else { + printf("Unable to open %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); + } + + if (realFileCount != 1) { + // If there was more than one real file in the directory, this + // violates test assumptions + cachefileName = ""; + } + } else { + printf("Unable to stat %s, error: %s\n", + multifileDirName.c_str(), std::strerror(errno)); } - virtual void TearDown() { - mTempFile.reset(nullptr); - EGLCacheTest::TearDown(); - } + return cachefileName; +} - std::unique_ptr<TemporaryFile> mTempFile; -}; +TEST_P(EGLCacheTest, ModifiedCacheMisses) { + // Skip if not in multifile mode + if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) { + GTEST_SKIP() << "Skipping test designed for multifile"; + } -TEST_F(EGLCacheSerializationTest, ReinitializedCacheContainsValues) { uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee }; - mCache->setCacheFilename(&mTempFile->path[0]); 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]); + + // Ensure the cache file is written to disk mCache->terminate(); + + // Depending on the cache mode, the file will be in different locations + std::string cachefileName = getCachefileName(); + ASSERT_TRUE(cachefileName.length() > 0); + + // Stomp on the beginning of the cache file, breaking the key match + const long stomp = 0xbadf00d; + FILE *file = fopen(cachefileName.c_str(), "w"); + fprintf(file, "%ld", stomp); + fflush(file); + fclose(file); + + // Ensure no cache hit + mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY)); + uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee }; + // getBlob may return junk for required size, but should not return a cache hit + mCache->getBlob("abcd", 4, buf2, 4); + ASSERT_EQ(0xee, buf2[0]); + ASSERT_EQ(0xee, buf2[1]); + ASSERT_EQ(0xee, buf2[2]); + ASSERT_EQ(0xee, buf2[3]); +} + +TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) { + uint8_t 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]); + + mCache->setBlob("ijkl", 4, "mnop", 4); + ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4)); + ASSERT_EQ('m', buf[0]); + ASSERT_EQ('n', buf[1]); + ASSERT_EQ('o', buf[2]); + ASSERT_EQ('p', buf[3]); + + mCache->setBlob("qrst", 4, "uvwx", 4); + ASSERT_EQ(4, mCache->getBlob("qrst", 4, buf, 4)); + ASSERT_EQ('u', buf[0]); + ASSERT_EQ('v', buf[1]); + ASSERT_EQ('w', buf[2]); + ASSERT_EQ('x', buf[3]); + + // Cache should contain both the key and the value + // So 8 bytes per entry, at least 24 bytes + ASSERT_GE(mCache->getCacheSize(), 24); + mCache->setCacheLimit(4); + mCache->terminate(); + ASSERT_LE(mCache->getCacheSize(), 4); } +INSTANTIATE_TEST_CASE_P(MonolithicCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic)); +INSTANTIATE_TEST_CASE_P(MultifileCacheTests, + EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Multifile)); } diff --git a/opengl/tests/lib/WindowSurface.cpp b/opengl/tests/lib/WindowSurface.cpp index fd4522e757..e94b565f11 100644 --- a/opengl/tests/lib/WindowSurface.cpp +++ b/opengl/tests/lib/WindowSurface.cpp @@ -36,7 +36,14 @@ WindowSurface::WindowSurface() { return; } - const auto displayToken = SurfaceComposerClient::getInternalDisplayToken(); + const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + if (ids.empty()) { + fprintf(stderr, "Failed to get ID for any displays.\n"); + return; + } + + // display 0 is picked for now, can extend to support all displays if needed + const auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); if (displayToken == nullptr) { fprintf(stderr, "ERROR: no display\n"); return; |