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