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