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 namespace egl
18 {
BlobCache(size_t maxCacheSizeBytes)19 BlobCache::BlobCache(size_t maxCacheSizeBytes)
20 : mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr)
21 {}
22
~BlobCache()23 BlobCache::~BlobCache() {}
24
put(const gl::Context * context,const BlobCache::Key & key,angle::MemoryBuffer && value)25 void BlobCache::put(const gl::Context *context,
26 const BlobCache::Key &key,
27 angle::MemoryBuffer &&value)
28 {
29 if (areBlobCacheFuncsSet() || (context && context->areBlobCacheFuncsSet()))
30 {
31 putApplication(context, key, value);
32 }
33 else
34 {
35 populate(key, std::move(value), CacheSource::Memory);
36 }
37 }
38
compressAndPut(const gl::Context * context,const BlobCache::Key & key,angle::MemoryBuffer && uncompressedValue,size_t * compressedSize)39 bool BlobCache::compressAndPut(const gl::Context *context,
40 const BlobCache::Key &key,
41 angle::MemoryBuffer &&uncompressedValue,
42 size_t *compressedSize)
43 {
44 angle::MemoryBuffer compressedValue;
45 if (!angle::CompressBlob(uncompressedValue.size(), uncompressedValue.data(), &compressedValue))
46 {
47 return false;
48 }
49 if (compressedSize != nullptr)
50 *compressedSize = compressedValue.size();
51 put(context, key, std::move(compressedValue));
52 return true;
53 }
54
putApplication(const gl::Context * context,const BlobCache::Key & key,const angle::MemoryBuffer & value)55 void BlobCache::putApplication(const gl::Context *context,
56 const BlobCache::Key &key,
57 const angle::MemoryBuffer &value)
58 {
59 if (context && context->areBlobCacheFuncsSet())
60 {
61 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
62 const gl::BlobCacheCallbacks &contextCallbacks =
63 context->getState().getBlobCacheCallbacks();
64 contextCallbacks.setFunction(key.data(), key.size(), value.data(), value.size(),
65 contextCallbacks.userParam);
66 }
67 else if (areBlobCacheFuncsSet())
68 {
69 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
70 mSetBlobFunc(key.data(), key.size(), value.data(), value.size());
71 }
72 }
73
populate(const BlobCache::Key & key,angle::MemoryBuffer && value,CacheSource source)74 void BlobCache::populate(const BlobCache::Key &key, angle::MemoryBuffer &&value, CacheSource source)
75 {
76 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
77 CacheEntry newEntry;
78 newEntry.first = std::move(value);
79 newEntry.second = source;
80
81 // Cache it inside blob cache only if caching inside the application is not possible.
82 mBlobCache.put(key, std::move(newEntry), newEntry.first.size());
83 }
84
get(const gl::Context * context,angle::ScratchBuffer * scratchBuffer,const BlobCache::Key & key,BlobCache::Value * valueOut)85 bool BlobCache::get(const gl::Context *context,
86 angle::ScratchBuffer *scratchBuffer,
87 const BlobCache::Key &key,
88 BlobCache::Value *valueOut)
89 {
90 // Look into the application's cache, if there is such a cache
91 if (areBlobCacheFuncsSet() || (context && context->areBlobCacheFuncsSet()))
92 {
93 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
94 EGLsizeiANDROID valueSize =
95 callBlobGetCallback(context, key.data(), key.size(), nullptr, 0);
96 if (valueSize <= 0)
97 {
98 return false;
99 }
100
101 angle::MemoryBuffer *scratchMemory;
102 bool result = scratchBuffer->get(valueSize, &scratchMemory);
103 if (!result)
104 {
105 ERR() << "Failed to allocate memory for binary blob";
106 return false;
107 }
108
109 EGLsizeiANDROID originalValueSize = valueSize;
110 valueSize =
111 callBlobGetCallback(context, key.data(), key.size(), scratchMemory->data(), valueSize);
112
113 // Make sure the key/value pair still exists/is unchanged after the second call
114 // (modifications to the application cache by another thread are a possibility)
115 if (valueSize != originalValueSize)
116 {
117 // This warning serves to find issues with the application cache, none of which are
118 // currently known to be thread-safe. If such a use ever arises, this WARN can be
119 // removed.
120 WARN() << "Binary blob no longer available in cache (removed by a thread?)";
121 return false;
122 }
123
124 *valueOut = BlobCache::Value(scratchMemory->data(), valueSize);
125 return true;
126 }
127
128 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
129 // Otherwise we are doing caching internally, so try to find it there
130 const CacheEntry *entry;
131 bool result = mBlobCache.get(key, &entry);
132
133 if (result)
134 {
135 *valueOut = BlobCache::Value(entry->first.data(), entry->first.size());
136 }
137
138 return result;
139 }
140
getAt(size_t index,const BlobCache::Key ** keyOut,BlobCache::Value * valueOut)141 bool BlobCache::getAt(size_t index, const BlobCache::Key **keyOut, BlobCache::Value *valueOut)
142 {
143 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
144 const CacheEntry *valueBuf;
145 bool result = mBlobCache.getAt(index, keyOut, &valueBuf);
146 if (result)
147 {
148 *valueOut = BlobCache::Value(valueBuf->first.data(), valueBuf->first.size());
149 }
150 return result;
151 }
152
getAndDecompress(const gl::Context * context,angle::ScratchBuffer * scratchBuffer,const BlobCache::Key & key,size_t maxUncompressedDataSize,angle::MemoryBuffer * uncompressedValueOut)153 BlobCache::GetAndDecompressResult BlobCache::getAndDecompress(
154 const gl::Context *context,
155 angle::ScratchBuffer *scratchBuffer,
156 const BlobCache::Key &key,
157 size_t maxUncompressedDataSize,
158 angle::MemoryBuffer *uncompressedValueOut)
159 {
160 ASSERT(uncompressedValueOut);
161
162 Value compressedValue;
163 if (!get(context, scratchBuffer, key, &compressedValue))
164 {
165 return GetAndDecompressResult::NotFound;
166 }
167
168 {
169 // This needs to be locked because `DecompressBlob` is reading shared memory from
170 // `compressedValue.data()`.
171 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
172 if (!angle::DecompressBlob(compressedValue.data(), compressedValue.size(),
173 maxUncompressedDataSize, uncompressedValueOut))
174 {
175 return GetAndDecompressResult::DecompressFailure;
176 }
177 }
178
179 return GetAndDecompressResult::Success;
180 }
181
remove(const BlobCache::Key & key)182 void BlobCache::remove(const BlobCache::Key &key)
183 {
184 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
185 mBlobCache.eraseByKey(key);
186 }
187
setBlobCacheFuncs(EGLSetBlobFuncANDROID set,EGLGetBlobFuncANDROID get)188 void BlobCache::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
189 {
190 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
191 mSetBlobFunc = set;
192 mGetBlobFunc = get;
193 }
194
areBlobCacheFuncsSet() const195 bool BlobCache::areBlobCacheFuncsSet() const
196 {
197 std::scoped_lock<angle::SimpleMutex> lock(mBlobCacheMutex);
198 // Either none or both of the callbacks should be set.
199 ASSERT((mSetBlobFunc != nullptr) == (mGetBlobFunc != nullptr));
200
201 return mSetBlobFunc != nullptr && mGetBlobFunc != nullptr;
202 }
203
isCachingEnabled(const gl::Context * context) const204 bool BlobCache::isCachingEnabled(const gl::Context *context) const
205 {
206 return areBlobCacheFuncsSet() || (context && context->areBlobCacheFuncsSet()) || maxSize() > 0;
207 }
208
callBlobGetCallback(const gl::Context * context,const void * key,size_t keySize,void * value,size_t valueSize)209 size_t BlobCache::callBlobGetCallback(const gl::Context *context,
210 const void *key,
211 size_t keySize,
212 void *value,
213 size_t valueSize)
214 {
215 if (context && context->areBlobCacheFuncsSet())
216 {
217 const gl::BlobCacheCallbacks &contextCallbacks =
218 context->getState().getBlobCacheCallbacks();
219 return contextCallbacks.getFunction(key, keySize, value, valueSize,
220 contextCallbacks.userParam);
221 }
222 else
223 {
224 ASSERT(mGetBlobFunc);
225 return mGetBlobFunc(key, keySize, value, valueSize);
226 }
227 }
228
229 } // namespace egl
230