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.
CompressBlobCacheData(const size_t cacheSize,const uint8_t * cacheData,angle::MemoryBuffer * compressedData)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 
DecompressBlobCacheData(const uint8_t * compressedData,const size_t compressedSize,angle::MemoryBuffer * uncompressedData)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 
BlobCache(size_t maxCacheSizeBytes)103 BlobCache::BlobCache(size_t maxCacheSizeBytes)
104     : mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr)
105 {}
106 
~BlobCache()107 BlobCache::~BlobCache() {}
108 
put(const BlobCache::Key & key,angle::MemoryBuffer && value)109 void BlobCache::put(const BlobCache::Key &key, angle::MemoryBuffer &&value)
110 {
111     if (areBlobCacheFuncsSet())
112     {
113         // Store the result in the application's cache
114         mSetBlobFunc(key.data(), key.size(), value.data(), value.size());
115     }
116     else
117     {
118         populate(key, std::move(value), CacheSource::Memory);
119     }
120 }
121 
putApplication(const BlobCache::Key & key,const angle::MemoryBuffer & value)122 void BlobCache::putApplication(const BlobCache::Key &key, const angle::MemoryBuffer &value)
123 {
124     std::lock_guard<std::mutex> lock(mBlobCacheMutex);
125     if (areBlobCacheFuncsSet())
126     {
127         mSetBlobFunc(key.data(), key.size(), value.data(), value.size());
128     }
129 }
130 
populate(const BlobCache::Key & key,angle::MemoryBuffer && value,CacheSource source)131 void BlobCache::populate(const BlobCache::Key &key, angle::MemoryBuffer &&value, CacheSource source)
132 {
133     CacheEntry newEntry;
134     newEntry.first  = std::move(value);
135     newEntry.second = source;
136 
137     // Cache it inside blob cache only if caching inside the application is not possible.
138     mBlobCache.put(key, std::move(newEntry), newEntry.first.size());
139 }
140 
get(angle::ScratchBuffer * scratchBuffer,const BlobCache::Key & key,BlobCache::Value * valueOut,size_t * bufferSizeOut)141 bool BlobCache::get(angle::ScratchBuffer *scratchBuffer,
142                     const BlobCache::Key &key,
143                     BlobCache::Value *valueOut,
144                     size_t *bufferSizeOut)
145 {
146     // Look into the application's cache, if there is such a cache
147     if (areBlobCacheFuncsSet())
148     {
149         EGLsizeiANDROID valueSize = mGetBlobFunc(key.data(), key.size(), nullptr, 0);
150         if (valueSize <= 0)
151         {
152             return false;
153         }
154 
155         angle::MemoryBuffer *scratchMemory;
156         bool result = scratchBuffer->get(valueSize, &scratchMemory);
157         if (!result)
158         {
159             ERR() << "Failed to allocate memory for binary blob";
160             return false;
161         }
162 
163         EGLsizeiANDROID originalValueSize = valueSize;
164         valueSize = mGetBlobFunc(key.data(), key.size(), scratchMemory->data(), valueSize);
165 
166         // Make sure the key/value pair still exists/is unchanged after the second call
167         // (modifications to the application cache by another thread are a possibility)
168         if (valueSize != originalValueSize)
169         {
170             // This warning serves to find issues with the application cache, none of which are
171             // currently known to be thread-safe.  If such a use ever arises, this WARN can be
172             // removed.
173             WARN() << "Binary blob no longer available in cache (removed by a thread?)";
174             return false;
175         }
176 
177         *valueOut      = BlobCache::Value(scratchMemory->data(), scratchMemory->size());
178         *bufferSizeOut = valueSize;
179         return true;
180     }
181 
182     // Otherwise we are doing caching internally, so try to find it there
183     const CacheEntry *entry;
184     bool result = mBlobCache.get(key, &entry);
185 
186     if (result)
187     {
188         if (entry->second == CacheSource::Memory)
189         {
190             ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitMemory,
191                                         kCacheResultMax);
192         }
193         else
194         {
195             ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitDisk,
196                                         kCacheResultMax);
197         }
198 
199         *valueOut      = BlobCache::Value(entry->first.data(), entry->first.size());
200         *bufferSizeOut = entry->first.size();
201     }
202     else
203     {
204         ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheMiss,
205                                     kCacheResultMax);
206     }
207 
208     return result;
209 }
210 
getAt(size_t index,const BlobCache::Key ** keyOut,BlobCache::Value * valueOut)211 bool BlobCache::getAt(size_t index, const BlobCache::Key **keyOut, BlobCache::Value *valueOut)
212 {
213     const CacheEntry *valueBuf;
214     bool result = mBlobCache.getAt(index, keyOut, &valueBuf);
215     if (result)
216     {
217         *valueOut = BlobCache::Value(valueBuf->first.data(), valueBuf->first.size());
218     }
219     return result;
220 }
221 
remove(const BlobCache::Key & key)222 void BlobCache::remove(const BlobCache::Key &key)
223 {
224     mBlobCache.eraseByKey(key);
225 }
226 
setBlobCacheFuncs(EGLSetBlobFuncANDROID set,EGLGetBlobFuncANDROID get)227 void BlobCache::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
228 {
229     mSetBlobFunc = set;
230     mGetBlobFunc = get;
231 }
232 
areBlobCacheFuncsSet() const233 bool BlobCache::areBlobCacheFuncsSet() const
234 {
235     // Either none or both of the callbacks should be set.
236     ASSERT((mSetBlobFunc != nullptr) == (mGetBlobFunc != nullptr));
237 
238     return mSetBlobFunc != nullptr && mGetBlobFunc != nullptr;
239 }
240 
241 }  // namespace egl
242