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/BinaryStream.h"
20 #include "common/angle_version_info.h"
21 #include "common/utilities.h"
22 #include "libANGLE/Context.h"
23 #include "libANGLE/Debug.h"
24 #include "libANGLE/Uniform.h"
25 #include "libANGLE/capture/FrameCapture.h"
26 #include "libANGLE/histogram_macros.h"
27 #include "libANGLE/renderer/ProgramImpl.h"
28 #include "platform/PlatformMethods.h"
29
30 namespace gl
31 {
32
33 namespace
34 {
35
36 // Limit decompressed programs to 10MB. If they're larger then this there is a good chance the data
37 // is not what we expect. This limits the amount of memory we will allocate based on a binary blob
38 // we believe is compressed data.
39 static constexpr size_t kMaxUncompressedProgramSize = 10 * 1024 * 1024;
40
WriteProgramBindings(BinaryOutputStream * stream,const ProgramBindings & bindings)41 void WriteProgramBindings(BinaryOutputStream *stream, const ProgramBindings &bindings)
42 {
43 for (const auto &binding : bindings.getStableIterationMap())
44 {
45 stream->writeString(binding.first);
46 stream->writeInt(binding.second);
47 }
48 }
49
WriteProgramAliasedBindings(BinaryOutputStream * stream,const ProgramAliasedBindings & bindings)50 void WriteProgramAliasedBindings(BinaryOutputStream *stream, const ProgramAliasedBindings &bindings)
51 {
52 for (const auto &binding : bindings.getStableIterationMap())
53 {
54 stream->writeString(binding.first);
55 stream->writeInt(binding.second.location);
56 }
57 }
58
59 } // anonymous namespace
60
MemoryProgramCache(egl::BlobCache & blobCache)61 MemoryProgramCache::MemoryProgramCache(egl::BlobCache &blobCache) : mBlobCache(blobCache) {}
62
~MemoryProgramCache()63 MemoryProgramCache::~MemoryProgramCache() {}
64
ComputeHash(const Context * context,const Program * program,egl::BlobCache::Key * hashOut)65 void MemoryProgramCache::ComputeHash(const Context *context,
66 const Program *program,
67 egl::BlobCache::Key *hashOut)
68 {
69 // Compute the program hash. Start with the shader hashes.
70 BinaryOutputStream hashStream;
71 ShaderBitSet shaders;
72 for (ShaderType shaderType : AllShaderTypes())
73 {
74 Shader *shader = program->getAttachedShader(shaderType);
75 if (shader)
76 {
77 shaders.set(shaderType);
78 shader->writeShaderKey(&hashStream);
79 }
80 }
81
82 hashStream.writeInt(shaders.bits());
83
84 // Add some ANGLE metadata and Context properties, such as version and back-end.
85 hashStream.writeString(angle::GetANGLEShaderProgramVersion());
86 hashStream.writeInt(angle::GetANGLESHVersion());
87 hashStream.writeInt(context->getClientMajorVersion());
88 hashStream.writeInt(context->getClientMinorVersion());
89 hashStream.writeString(reinterpret_cast<const char *>(context->getString(GL_RENDERER)));
90
91 // Hash pre-link program properties.
92 WriteProgramBindings(&hashStream, program->getAttributeBindings());
93 WriteProgramAliasedBindings(&hashStream, program->getUniformLocationBindings());
94 WriteProgramAliasedBindings(&hashStream, program->getFragmentOutputLocations());
95 WriteProgramAliasedBindings(&hashStream, program->getFragmentOutputIndexes());
96 for (const std::string &transformFeedbackVaryingName :
97 program->getState().getTransformFeedbackVaryingNames())
98 {
99 hashStream.writeString(transformFeedbackVaryingName);
100 }
101 hashStream.writeInt(program->getTransformFeedbackBufferMode());
102
103 // Include the status of FrameCapture, which adds source strings to the binary
104 hashStream.writeBool(context->getShareGroup()->getFrameCaptureShared()->enabled());
105
106 // Call the secure SHA hashing function.
107 const std::vector<uint8_t> &programKey = hashStream.getData();
108 angle::base::SHA1HashBytes(programKey.data(), programKey.size(), hashOut->data());
109 }
110
getProgram(const Context * context,Program * program,egl::BlobCache::Key * hashOut,egl::CacheGetResult * resultOut)111 angle::Result MemoryProgramCache::getProgram(const Context *context,
112 Program *program,
113 egl::BlobCache::Key *hashOut,
114 egl::CacheGetResult *resultOut)
115 {
116 *resultOut = egl::CacheGetResult::NotFound;
117
118 // If caching is effectively disabled, don't bother calculating the hash.
119 if (!mBlobCache.isCachingEnabled(context))
120 {
121 return angle::Result::Continue;
122 }
123
124 ComputeHash(context, program, hashOut);
125
126 angle::MemoryBuffer uncompressedData;
127 switch (mBlobCache.getAndDecompress(context, context->getScratchBuffer(), *hashOut,
128 kMaxUncompressedProgramSize, &uncompressedData))
129 {
130 case egl::BlobCache::GetAndDecompressResult::NotFound:
131 return angle::Result::Continue;
132
133 case egl::BlobCache::GetAndDecompressResult::DecompressFailure:
134 ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
135 "Error decompressing program binary data fetched from cache.");
136 remove(*hashOut);
137 // Consider this blob "not found". As far as the rest of the code is considered,
138 // corrupted cache might as well not have existed.
139 return angle::Result::Continue;
140
141 case egl::BlobCache::GetAndDecompressResult::Success:
142 ANGLE_TRY(program->loadBinary(context, uncompressedData.data(),
143 static_cast<int>(uncompressedData.size()), resultOut));
144
145 // Result is either Success or Rejected
146 ASSERT(*resultOut != egl::CacheGetResult::NotFound);
147
148 // If cache load failed, evict the entry
149 if (*resultOut == egl::CacheGetResult::Rejected)
150 {
151 ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
152 "Failed to load program binary from cache.");
153 remove(*hashOut);
154 }
155
156 return angle::Result::Continue;
157 }
158
159 UNREACHABLE();
160 return angle::Result::Continue;
161 }
162
getAt(size_t index,const egl::BlobCache::Key ** hashOut,egl::BlobCache::Value * programOut)163 bool MemoryProgramCache::getAt(size_t index,
164 const egl::BlobCache::Key **hashOut,
165 egl::BlobCache::Value *programOut)
166 {
167 return mBlobCache.getAt(index, hashOut, programOut);
168 }
169
remove(const egl::BlobCache::Key & programHash)170 void MemoryProgramCache::remove(const egl::BlobCache::Key &programHash)
171 {
172 mBlobCache.remove(programHash);
173 }
174
putProgram(const egl::BlobCache::Key & programHash,const Context * context,Program * program)175 angle::Result MemoryProgramCache::putProgram(const egl::BlobCache::Key &programHash,
176 const Context *context,
177 Program *program)
178 {
179 // If caching is effectively disabled, don't bother serializing the program.
180 if (!mBlobCache.isCachingEnabled(context))
181 {
182 return angle::Result::Continue;
183 }
184
185 ANGLE_TRY(program->serialize(context));
186 const angle::MemoryBuffer &serializedProgram = program->getSerializedBinary();
187
188 angle::MemoryBuffer compressedData;
189 if (!angle::CompressBlob(serializedProgram.size(), serializedProgram.data(), &compressedData))
190 {
191 ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
192 "Error compressing binary data.");
193 return angle::Result::Continue;
194 }
195
196 {
197 std::scoped_lock<angle::SimpleMutex> lock(mBlobCache.getMutex());
198 // TODO: http://anglebug.com/42266037
199 // This was a workaround for Chrome until it added support for EGL_ANDROID_blob_cache,
200 // tracked by http://anglebug.com/42261225. This issue has since been closed, but removing
201 // this still causes a test failure.
202 auto *platform = ANGLEPlatformCurrent();
203 platform->cacheProgram(platform, programHash, compressedData.size(), compressedData.data());
204 }
205
206 mBlobCache.put(context, programHash, std::move(compressedData));
207 return angle::Result::Continue;
208 }
209
updateProgram(const Context * context,Program * program)210 angle::Result MemoryProgramCache::updateProgram(const Context *context, Program *program)
211 {
212 egl::BlobCache::Key programHash;
213 ComputeHash(context, program, &programHash);
214 return putProgram(programHash, context, program);
215 }
216
putBinary(const egl::BlobCache::Key & programHash,const uint8_t * binary,size_t length)217 bool MemoryProgramCache::putBinary(const egl::BlobCache::Key &programHash,
218 const uint8_t *binary,
219 size_t length)
220 {
221 // Copy the binary.
222 angle::MemoryBuffer newEntry;
223 if (!newEntry.resize(length))
224 {
225 return false;
226 }
227 memcpy(newEntry.data(), binary, length);
228
229 // Store the binary.
230 mBlobCache.populate(programHash, std::move(newEntry));
231
232 return true;
233 }
234
clear()235 void MemoryProgramCache::clear()
236 {
237 mBlobCache.clear();
238 }
239
resize(size_t maxCacheSizeBytes)240 void MemoryProgramCache::resize(size_t maxCacheSizeBytes)
241 {
242 mBlobCache.resize(maxCacheSizeBytes);
243 }
244
entryCount() const245 size_t MemoryProgramCache::entryCount() const
246 {
247 return mBlobCache.entryCount();
248 }
249
trim(size_t limit)250 size_t MemoryProgramCache::trim(size_t limit)
251 {
252 return mBlobCache.trim(limit);
253 }
254
size() const255 size_t MemoryProgramCache::size() const
256 {
257 return mBlobCache.size();
258 }
259
maxSize() const260 size_t MemoryProgramCache::maxSize() const
261 {
262 return mBlobCache.maxSize();
263 }
264
265 } // namespace gl
266