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. |
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(input_size: uncompressedSize); |
31 | |
32 | // Allocate memory. |
33 | if (!compressedData->resize(size: expectedCompressedSize)) |
34 | { |
35 | ERR() << "Failed to allocate memory for compression" ; |
36 | return false; |
37 | } |
38 | |
39 | int zResult = zlib_internal::GzipCompressHelper(dest: compressedData->data(), dest_length: &expectedCompressedSize, |
40 | source: cacheData, source_length: uncompressedSize, malloc_fn: nullptr, free_fn: 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(size: expectedCompressedSize)) |
50 | { |
51 | return false; |
52 | } |
53 | |
54 | return true; |
55 | } |
56 | |
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(compressed_data: compressedData, length: compressedSize); |
64 | |
65 | // Allocate enough memory. |
66 | if (!uncompressedData->resize(size: 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 | dest: uncompressedData->data(), dest_length: &destLen, source: compressedData, source_length: 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(size: destLen)) |
84 | { |
85 | return false; |
86 | } |
87 | |
88 | return true; |
89 | } |
90 | |
91 | BlobCache::BlobCache(size_t maxCacheSizeBytes) |
92 | : mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr) |
93 | {} |
94 | |
95 | BlobCache::~BlobCache() {} |
96 | |
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, value: std::move(value), source: CacheSource::Memory); |
108 | } |
109 | } |
110 | |
111 | bool BlobCache::compressAndPut(const BlobCache::Key &key, |
112 | angle::MemoryBuffer &&uncompressedValue, |
113 | size_t *compressedSize) |
114 | { |
115 | angle::MemoryBuffer compressedValue; |
116 | if (!CompressBlobCacheData(cacheSize: uncompressedValue.size(), cacheData: uncompressedValue.data(), |
117 | compressedData: &compressedValue)) |
118 | { |
119 | return false; |
120 | } |
121 | if (compressedSize != nullptr) |
122 | *compressedSize = compressedValue.size(); |
123 | put(key, value: std::move(compressedValue)); |
124 | return true; |
125 | } |
126 | |
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 | |
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, value: std::move(newEntry), size: newEntry.first.size()); |
145 | } |
146 | |
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(requestedSize: valueSize, memoryBufferOut: &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, valueOut: &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 | |
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, valueOut: &valueBuf); |
209 | if (result) |
210 | { |
211 | *valueOut = BlobCache::Value(valueBuf->first.data(), valueBuf->first.size()); |
212 | } |
213 | return result; |
214 | } |
215 | |
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, valueOut: &compressedValue, bufferSizeOut: &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(compressedData: compressedValue.data(), compressedSize, uncompressedData: uncompressedValueOut)) |
235 | { |
236 | return GetAndDecompressResult::DecompressFailure; |
237 | } |
238 | } |
239 | |
240 | return GetAndDecompressResult::GetSuccess; |
241 | } |
242 | |
243 | void BlobCache::remove(const BlobCache::Key &key) |
244 | { |
245 | std::scoped_lock<std::mutex> lock(mBlobCacheMutex); |
246 | mBlobCache.eraseByKey(key); |
247 | } |
248 | |
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 | |
256 | 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 | |