BlobCache.cpp (8615B)
1 // 2 // Copyright 2018 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 // BlobCache: Stores keyed blobs in memory to support EGL_ANDROID_blob_cache. 7 // Can be used in conjunction with the platform layer to warm up the cache from 8 // disk. MemoryProgramCache uses this to handle caching of compiled programs. 9 10 #include "libANGLE/BlobCache.h" 11 #include "common/utilities.h" 12 #include "libANGLE/Context.h" 13 #include "libANGLE/Display.h" 14 #include "libANGLE/histogram_macros.h" 15 #include "platform/PlatformMethods.h" 16 17 #define USE_SYSTEM_ZLIB 18 #include "compression_utils_portable.h" 19 20 namespace egl 21 { 22 23 namespace 24 { 25 enum CacheResult 26 { 27 kCacheMiss, 28 kCacheHitMemory, 29 kCacheHitDisk, 30 kCacheResultMax, 31 }; 32 33 } // anonymous namespace 34 35 // In oder to store more cache in blob cache, compress cacheData to compressedData 36 // before being stored. 37 bool CompressBlobCacheData(const size_t cacheSize, 38 const uint8_t *cacheData, 39 angle::MemoryBuffer *compressedData) 40 { 41 uLong uncompressedSize = static_cast<uLong>(cacheSize); 42 uLong expectedCompressedSize = zlib_internal::GzipExpectedCompressedSize(uncompressedSize); 43 44 // Allocate memory. 45 if (!compressedData->resize(expectedCompressedSize)) 46 { 47 ERR() << "Failed to allocate memory for compression"; 48 return false; 49 } 50 51 int zResult = zlib_internal::GzipCompressHelper(compressedData->data(), &expectedCompressedSize, 52 cacheData, uncompressedSize, nullptr, nullptr); 53 54 if (zResult != Z_OK) 55 { 56 ERR() << "Failed to compress cache data: " << zResult; 57 return false; 58 } 59 60 // Resize it to expected size. 61 if (!compressedData->resize(expectedCompressedSize)) 62 { 63 return false; 64 } 65 66 return true; 67 } 68 69 bool DecompressBlobCacheData(const uint8_t *compressedData, 70 const size_t compressedSize, 71 angle::MemoryBuffer *uncompressedData) 72 { 73 // Call zlib function to decompress. 74 uint32_t uncompressedSize = 75 zlib_internal::GetGzipUncompressedSize(compressedData, compressedSize); 76 77 // Allocate enough memory. 78 if (!uncompressedData->resize(uncompressedSize)) 79 { 80 ERR() << "Failed to allocate memory for decompression"; 81 return false; 82 } 83 84 uLong destLen = uncompressedSize; 85 int zResult = zlib_internal::GzipUncompressHelper( 86 uncompressedData->data(), &destLen, compressedData, static_cast<uLong>(compressedSize)); 87 88 if (zResult != Z_OK) 89 { 90 ERR() << "Failed to decompress data: " << zResult << "\n"; 91 return false; 92 } 93 94 // Resize it to expected size. 95 if (!uncompressedData->resize(destLen)) 96 { 97 return false; 98 } 99 100 return true; 101 } 102 103 BlobCache::BlobCache(size_t maxCacheSizeBytes) 104 : mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr) 105 {} 106 107 BlobCache::~BlobCache() {} 108 109 void BlobCache::put(const BlobCache::Key &key, angle::MemoryBuffer &&value) 110 { 111 if (areBlobCacheFuncsSet()) 112 { 113 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 114 // Store the result in the application's cache 115 mSetBlobFunc(key.data(), key.size(), value.data(), value.size()); 116 } 117 else 118 { 119 populate(key, std::move(value), CacheSource::Memory); 120 } 121 } 122 123 bool BlobCache::compressAndPut(const BlobCache::Key &key, 124 angle::MemoryBuffer &&uncompressedValue, 125 size_t *compressedSize) 126 { 127 angle::MemoryBuffer compressedValue; 128 if (!CompressBlobCacheData(uncompressedValue.size(), uncompressedValue.data(), 129 &compressedValue)) 130 { 131 return false; 132 } 133 if (compressedSize != nullptr) 134 *compressedSize = compressedValue.size(); 135 put(key, std::move(compressedValue)); 136 return true; 137 } 138 139 void BlobCache::putApplication(const BlobCache::Key &key, const angle::MemoryBuffer &value) 140 { 141 if (areBlobCacheFuncsSet()) 142 { 143 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 144 mSetBlobFunc(key.data(), key.size(), value.data(), value.size()); 145 } 146 } 147 148 void BlobCache::populate(const BlobCache::Key &key, angle::MemoryBuffer &&value, CacheSource source) 149 { 150 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 151 CacheEntry newEntry; 152 newEntry.first = std::move(value); 153 newEntry.second = source; 154 155 // Cache it inside blob cache only if caching inside the application is not possible. 156 mBlobCache.put(key, std::move(newEntry), newEntry.first.size()); 157 } 158 159 bool BlobCache::get(angle::ScratchBuffer *scratchBuffer, 160 const BlobCache::Key &key, 161 BlobCache::Value *valueOut, 162 size_t *bufferSizeOut) 163 { 164 // Look into the application's cache, if there is such a cache 165 if (areBlobCacheFuncsSet()) 166 { 167 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 168 EGLsizeiANDROID valueSize = mGetBlobFunc(key.data(), key.size(), nullptr, 0); 169 if (valueSize <= 0) 170 { 171 return false; 172 } 173 174 angle::MemoryBuffer *scratchMemory; 175 bool result = scratchBuffer->get(valueSize, &scratchMemory); 176 if (!result) 177 { 178 ERR() << "Failed to allocate memory for binary blob"; 179 return false; 180 } 181 182 EGLsizeiANDROID originalValueSize = valueSize; 183 valueSize = mGetBlobFunc(key.data(), key.size(), scratchMemory->data(), valueSize); 184 185 // Make sure the key/value pair still exists/is unchanged after the second call 186 // (modifications to the application cache by another thread are a possibility) 187 if (valueSize != originalValueSize) 188 { 189 // This warning serves to find issues with the application cache, none of which are 190 // currently known to be thread-safe. If such a use ever arises, this WARN can be 191 // removed. 192 WARN() << "Binary blob no longer available in cache (removed by a thread?)"; 193 return false; 194 } 195 196 *valueOut = BlobCache::Value(scratchMemory->data(), scratchMemory->size()); 197 *bufferSizeOut = valueSize; 198 return true; 199 } 200 201 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 202 // Otherwise we are doing caching internally, so try to find it there 203 const CacheEntry *entry; 204 bool result = mBlobCache.get(key, &entry); 205 206 if (result) 207 { 208 209 *valueOut = BlobCache::Value(entry->first.data(), entry->first.size()); 210 *bufferSizeOut = entry->first.size(); 211 } 212 213 return result; 214 } 215 216 bool BlobCache::getAt(size_t index, const BlobCache::Key **keyOut, BlobCache::Value *valueOut) 217 { 218 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 219 const CacheEntry *valueBuf; 220 bool result = mBlobCache.getAt(index, keyOut, &valueBuf); 221 if (result) 222 { 223 *valueOut = BlobCache::Value(valueBuf->first.data(), valueBuf->first.size()); 224 } 225 return result; 226 } 227 228 BlobCache::GetAndDecompressResult BlobCache::getAndDecompress( 229 angle::ScratchBuffer *scratchBuffer, 230 const BlobCache::Key &key, 231 angle::MemoryBuffer *uncompressedValueOut) 232 { 233 ASSERT(uncompressedValueOut); 234 235 Value compressedValue; 236 size_t compressedSize; 237 if (!get(scratchBuffer, key, &compressedValue, &compressedSize)) 238 { 239 return GetAndDecompressResult::NotFound; 240 } 241 242 { 243 // This needs to be locked because `DecompressBlobCacheData` is reading shared memory from 244 // `compressedValue.data()`. 245 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 246 if (!DecompressBlobCacheData(compressedValue.data(), compressedSize, uncompressedValueOut)) 247 { 248 return GetAndDecompressResult::DecompressFailure; 249 } 250 } 251 252 return GetAndDecompressResult::GetSuccess; 253 } 254 255 void BlobCache::remove(const BlobCache::Key &key) 256 { 257 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 258 mBlobCache.eraseByKey(key); 259 } 260 261 void BlobCache::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) 262 { 263 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 264 mSetBlobFunc = set; 265 mGetBlobFunc = get; 266 } 267 268 bool BlobCache::areBlobCacheFuncsSet() const 269 { 270 std::scoped_lock<std::mutex> lock(mBlobCacheMutex); 271 // Either none or both of the callbacks should be set. 272 ASSERT((mSetBlobFunc != nullptr) == (mGetBlobFunc != nullptr)); 273 274 return mSetBlobFunc != nullptr && mGetBlobFunc != nullptr; 275 } 276 277 } // namespace egl