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
20namespace egl
21{
22
23// In oder to store more cache in blob cache, compress cacheData to compressedData
24// before being stored.
25bool 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
57bool 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
91BlobCache::BlobCache(size_t maxCacheSizeBytes)
92 : mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr)
93{}
94
95BlobCache::~BlobCache() {}
96
97void 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
111bool 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
127void 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
136void 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
147bool 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
204bool 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
216BlobCache::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
243void BlobCache::remove(const BlobCache::Key &key)
244{
245 std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
246 mBlobCache.eraseByKey(key);
247}
248
249void BlobCache::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
250{
251 std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
252 mSetBlobFunc = set;
253 mGetBlobFunc = get;
254}
255
256bool 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

source code of flutter_engine/third_party/angle/src/libANGLE/BlobCache.cpp