• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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