1 //
2 // Copyright 2017 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 // MemoryProgramCache: Stores compiled and linked programs in memory so they don't
7 // always have to be re-compiled. Can be used in conjunction with the platform
8 // layer to warm up the cache from disk.
9
10 // Include zlib first, otherwise FAR gets defined elsewhere.
11 #define USE_SYSTEM_ZLIB
12 #include "compression_utils_portable.h"
13
14 #include "libANGLE/MemoryProgramCache.h"
15
16 #include <GLSLANG/ShaderVars.h>
17 #include <anglebase/sha1.h>
18
19 #include "common/angle_version.h"
20 #include "common/utilities.h"
21 #include "libANGLE/BinaryStream.h"
22 #include "libANGLE/Context.h"
23 #include "libANGLE/Uniform.h"
24 #include "libANGLE/histogram_macros.h"
25 #include "libANGLE/renderer/ProgramImpl.h"
26 #include "platform/PlatformMethods.h"
27
28 namespace gl
29 {
30
31 namespace
32 {
33 constexpr unsigned int kWarningLimit = 3;
34
35 class HashStream final : angle::NonCopyable
36 {
37 public:
str()38 std::string str() { return mStringStream.str(); }
39
40 template <typename T>
operator <<(T value)41 HashStream &operator<<(T value)
42 {
43 mStringStream << value << kSeparator;
44 return *this;
45 }
46
47 private:
48 static constexpr char kSeparator = ':';
49 std::ostringstream mStringStream;
50 };
51
operator <<(HashStream & stream,Shader * shader)52 HashStream &operator<<(HashStream &stream, Shader *shader)
53 {
54 if (shader)
55 {
56 stream << shader->getSourceString().c_str() << shader->getSourceString().length()
57 << shader->getCompilerResourcesString().c_str();
58 }
59 return stream;
60 }
61
operator <<(HashStream & stream,const ProgramBindings & bindings)62 HashStream &operator<<(HashStream &stream, const ProgramBindings &bindings)
63 {
64 for (const auto &binding : bindings.getStableIterationMap())
65 {
66 stream << binding.first << binding.second;
67 }
68 return stream;
69 }
70
operator <<(HashStream & stream,const ProgramAliasedBindings & bindings)71 HashStream &operator<<(HashStream &stream, const ProgramAliasedBindings &bindings)
72 {
73 for (const auto &binding : bindings.getStableIterationMap())
74 {
75 stream << binding.first << binding.second.location;
76 }
77 return stream;
78 }
79
operator <<(HashStream & stream,const std::vector<std::string> & strings)80 HashStream &operator<<(HashStream &stream, const std::vector<std::string> &strings)
81 {
82 for (const auto &str : strings)
83 {
84 stream << str;
85 }
86 return stream;
87 }
88
operator <<(HashStream & stream,const std::vector<gl::VariableLocation> & locations)89 HashStream &operator<<(HashStream &stream, const std::vector<gl::VariableLocation> &locations)
90 {
91 for (const auto &loc : locations)
92 {
93 stream << loc.index << loc.arrayIndex << loc.ignored;
94 }
95 return stream;
96 }
97
98 } // anonymous namespace
99
MemoryProgramCache(egl::BlobCache & blobCache)100 MemoryProgramCache::MemoryProgramCache(egl::BlobCache &blobCache)
101 : mBlobCache(blobCache), mIssuedWarnings(0)
102 {}
103
~MemoryProgramCache()104 MemoryProgramCache::~MemoryProgramCache() {}
105
ComputeHash(const Context * context,const Program * program,egl::BlobCache::Key * hashOut)106 void MemoryProgramCache::ComputeHash(const Context *context,
107 const Program *program,
108 egl::BlobCache::Key *hashOut)
109 {
110 // Compute the program hash. Start with the shader hashes and resource strings.
111 HashStream hashStream;
112 for (ShaderType shaderType : AllShaderTypes())
113 {
114 hashStream << program->getAttachedShader(shaderType);
115 }
116
117 // Add some ANGLE metadata and Context properties, such as version and back-end.
118 hashStream << ANGLE_COMMIT_HASH << context->getClientMajorVersion()
119 << context->getClientMinorVersion() << context->getString(GL_RENDERER);
120
121 // Hash pre-link program properties.
122 hashStream << program->getAttributeBindings() << program->getUniformLocationBindings()
123 << program->getFragmentOutputLocations() << program->getFragmentOutputIndexes()
124 << program->getState().getTransformFeedbackVaryingNames()
125 << program->getState().getTransformFeedbackBufferMode()
126 << program->getState().getOutputLocations()
127 << program->getState().getSecondaryOutputLocations();
128
129 // Call the secure SHA hashing function.
130 const std::string &programKey = hashStream.str();
131 angle::base::SHA1HashBytes(reinterpret_cast<const unsigned char *>(programKey.c_str()),
132 programKey.length(), hashOut->data());
133 }
134
getProgram(const Context * context,Program * program,egl::BlobCache::Key * hashOut)135 angle::Result MemoryProgramCache::getProgram(const Context *context,
136 Program *program,
137 egl::BlobCache::Key *hashOut)
138 {
139 // If caching is effectively disabled, don't bother calculating the hash.
140 if (!mBlobCache.isCachingEnabled())
141 {
142 return angle::Result::Incomplete;
143 }
144
145 ComputeHash(context, program, hashOut);
146 egl::BlobCache::Value binaryProgram;
147 size_t programSize = 0;
148 if (get(context, *hashOut, &binaryProgram, &programSize))
149 {
150 angle::MemoryBuffer uncompressedData;
151 if (!egl::DecompressBlobCacheData(binaryProgram.data(), programSize, &uncompressedData))
152 {
153 ERR() << "Error decompressing binary data.";
154 return angle::Result::Incomplete;
155 }
156
157 angle::Result result =
158 program->loadBinary(context, GL_PROGRAM_BINARY_ANGLE, uncompressedData.data(),
159 static_cast<int>(uncompressedData.size()));
160 ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.ProgramCache.LoadBinarySuccess",
161 result == angle::Result::Continue);
162 ANGLE_TRY(result);
163
164 if (result == angle::Result::Continue)
165 return angle::Result::Continue;
166
167 // Cache load failed, evict.
168 if (mIssuedWarnings++ < kWarningLimit)
169 {
170 WARN() << "Failed to load binary from cache.";
171
172 if (mIssuedWarnings == kWarningLimit)
173 {
174 WARN() << "Reaching warning limit for cache load failures, silencing "
175 "subsequent warnings.";
176 }
177 }
178 remove(*hashOut);
179 }
180 return angle::Result::Incomplete;
181 }
182
get(const Context * context,const egl::BlobCache::Key & programHash,egl::BlobCache::Value * programOut,size_t * programSizeOut)183 bool MemoryProgramCache::get(const Context *context,
184 const egl::BlobCache::Key &programHash,
185 egl::BlobCache::Value *programOut,
186 size_t *programSizeOut)
187 {
188 return mBlobCache.get(context->getScratchBuffer(), programHash, programOut, programSizeOut);
189 }
190
getAt(size_t index,const egl::BlobCache::Key ** hashOut,egl::BlobCache::Value * programOut)191 bool MemoryProgramCache::getAt(size_t index,
192 const egl::BlobCache::Key **hashOut,
193 egl::BlobCache::Value *programOut)
194 {
195 return mBlobCache.getAt(index, hashOut, programOut);
196 }
197
remove(const egl::BlobCache::Key & programHash)198 void MemoryProgramCache::remove(const egl::BlobCache::Key &programHash)
199 {
200 mBlobCache.remove(programHash);
201 }
202
putProgram(const egl::BlobCache::Key & programHash,const Context * context,const Program * program)203 angle::Result MemoryProgramCache::putProgram(const egl::BlobCache::Key &programHash,
204 const Context *context,
205 const Program *program)
206 {
207 // If caching is effectively disabled, don't bother serializing the program.
208 if (!mBlobCache.isCachingEnabled())
209 {
210 return angle::Result::Incomplete;
211 }
212
213 angle::MemoryBuffer serializedProgram;
214 ANGLE_TRY(program->serialize(context, &serializedProgram));
215
216 angle::MemoryBuffer compressedData;
217 if (!egl::CompressBlobCacheData(serializedProgram.size(), serializedProgram.data(),
218 &compressedData))
219 {
220 ERR() << "Error compressing binary data.";
221 return angle::Result::Incomplete;
222 }
223
224 ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramBinarySizeBytes",
225 static_cast<int>(compressedData.size()));
226
227 // TODO(syoussefi): to be removed. Compatibility for Chrome until it supports
228 // EGL_ANDROID_blob_cache. http://anglebug.com/2516
229 auto *platform = ANGLEPlatformCurrent();
230 platform->cacheProgram(platform, programHash, compressedData.size(), compressedData.data());
231
232 mBlobCache.put(programHash, std::move(compressedData));
233 return angle::Result::Continue;
234 }
235
updateProgram(const Context * context,const Program * program)236 angle::Result MemoryProgramCache::updateProgram(const Context *context, const Program *program)
237 {
238 egl::BlobCache::Key programHash;
239 ComputeHash(context, program, &programHash);
240 return putProgram(programHash, context, program);
241 }
242
putBinary(const egl::BlobCache::Key & programHash,const uint8_t * binary,size_t length)243 bool MemoryProgramCache::putBinary(const egl::BlobCache::Key &programHash,
244 const uint8_t *binary,
245 size_t length)
246 {
247 // Copy the binary.
248 angle::MemoryBuffer newEntry;
249 if (!newEntry.resize(length))
250 {
251 return false;
252 }
253 memcpy(newEntry.data(), binary, length);
254
255 // Store the binary.
256 mBlobCache.populate(programHash, std::move(newEntry));
257
258 return true;
259 }
260
clear()261 void MemoryProgramCache::clear()
262 {
263 mBlobCache.clear();
264 mIssuedWarnings = 0;
265 }
266
resize(size_t maxCacheSizeBytes)267 void MemoryProgramCache::resize(size_t maxCacheSizeBytes)
268 {
269 mBlobCache.resize(maxCacheSizeBytes);
270 }
271
entryCount() const272 size_t MemoryProgramCache::entryCount() const
273 {
274 return mBlobCache.entryCount();
275 }
276
trim(size_t limit)277 size_t MemoryProgramCache::trim(size_t limit)
278 {
279 return mBlobCache.trim(limit);
280 }
281
size() const282 size_t MemoryProgramCache::size() const
283 {
284 return mBlobCache.size();
285 }
286
maxSize() const287 size_t MemoryProgramCache::maxSize() const
288 {
289 return mBlobCache.maxSize();
290 }
291
292 } // namespace gl
293