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