blob: 3dd1b945cc8e73ac8b479fced55f587f2377edf2 [file] [log] [blame]
Jamie Madill4f86d052017-06-05 12:59:26 -04001//
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 "libANGLE/MemoryProgramCache.h"
11
12#include <GLSLANG/ShaderVars.h>
Jamie Madill32447362017-06-28 14:53:52 -040013#include <anglebase/sha1.h>
Jamie Madill4f86d052017-06-05 12:59:26 -040014
15#include "common/version.h"
16#include "libANGLE/BinaryStream.h"
17#include "libANGLE/Context.h"
18#include "libANGLE/Uniform.h"
19#include "libANGLE/renderer/ProgramImpl.h"
Jamie Madill360daee2017-06-29 10:36:19 -040020#include "platform/Platform.h"
Jamie Madill4f86d052017-06-05 12:59:26 -040021
22namespace gl
23{
24
25namespace
26{
Jamie Madill32447362017-06-28 14:53:52 -040027constexpr unsigned int kWarningLimit = 3;
Jamie Madill4f86d052017-06-05 12:59:26 -040028
29void WriteShaderVar(BinaryOutputStream *stream, const sh::ShaderVariable &var)
30{
31 stream->writeInt(var.type);
32 stream->writeInt(var.precision);
33 stream->writeString(var.name);
34 stream->writeString(var.mappedName);
35 stream->writeInt(var.arraySize);
36 stream->writeInt(var.staticUse);
37 stream->writeString(var.structName);
38 ASSERT(var.fields.empty());
39}
40
41void LoadShaderVar(BinaryInputStream *stream, sh::ShaderVariable *var)
42{
43 var->type = stream->readInt<GLenum>();
44 var->precision = stream->readInt<GLenum>();
45 var->name = stream->readString();
46 var->mappedName = stream->readString();
47 var->arraySize = stream->readInt<unsigned int>();
48 var->staticUse = stream->readBool();
49 var->structName = stream->readString();
50}
51
Jamie Madill32447362017-06-28 14:53:52 -040052class HashStream final : angle::NonCopyable
53{
54 public:
55 std::string str() { return mStringStream.str(); }
56
57 template <typename T>
58 HashStream &operator<<(T value)
59 {
60 mStringStream << value << kSeparator;
61 return *this;
62 }
63
64 private:
65 static constexpr char kSeparator = ':';
66 std::ostringstream mStringStream;
67};
68
69HashStream &operator<<(HashStream &stream, const Shader *shader)
70{
71 if (shader)
72 {
73 stream << shader->getSourceString().c_str() << shader->getSourceString().length()
74 << shader->getCompilerResourcesString().c_str();
75 }
76 return stream;
77}
78
79HashStream &operator<<(HashStream &stream, const Program::Bindings &bindings)
80{
81 for (const auto &binding : bindings)
82 {
83 stream << binding.first << binding.second;
84 }
85 return stream;
86}
87
88HashStream &operator<<(HashStream &stream, const std::vector<std::string> &strings)
89{
90 for (const auto &str : strings)
91 {
92 stream << str;
93 }
94 return stream;
95}
96
Jamie Madill4f86d052017-06-05 12:59:26 -040097} // anonymous namespace
98
Jamie Madill32447362017-06-28 14:53:52 -040099MemoryProgramCache::MemoryProgramCache(size_t maxCacheSizeBytes)
100 : mProgramBinaryCache(maxCacheSizeBytes), mIssuedWarnings(0)
101{
102}
103
104MemoryProgramCache::~MemoryProgramCache()
105{
106}
107
Jamie Madill4f86d052017-06-05 12:59:26 -0400108// static
109LinkResult MemoryProgramCache::Deserialize(const Context *context,
110 const Program *program,
111 ProgramState *state,
112 const uint8_t *binary,
113 size_t length,
114 InfoLog &infoLog)
115{
116 BinaryInputStream stream(binary, length);
117
118 unsigned char commitString[ANGLE_COMMIT_HASH_SIZE];
119 stream.readBytes(commitString, ANGLE_COMMIT_HASH_SIZE);
120 if (memcmp(commitString, ANGLE_COMMIT_HASH, sizeof(unsigned char) * ANGLE_COMMIT_HASH_SIZE) !=
121 0)
122 {
123 infoLog << "Invalid program binary version.";
124 return false;
125 }
126
127 int majorVersion = stream.readInt<int>();
128 int minorVersion = stream.readInt<int>();
129 if (majorVersion != context->getClientMajorVersion() ||
130 minorVersion != context->getClientMinorVersion())
131 {
132 infoLog << "Cannot load program binaries across different ES context versions.";
133 return false;
134 }
135
136 state->mComputeShaderLocalSize[0] = stream.readInt<int>();
137 state->mComputeShaderLocalSize[1] = stream.readInt<int>();
138 state->mComputeShaderLocalSize[2] = stream.readInt<int>();
139
140 static_assert(MAX_VERTEX_ATTRIBS <= sizeof(unsigned long) * 8,
141 "Too many vertex attribs for mask");
142 state->mActiveAttribLocationsMask = stream.readInt<unsigned long>();
143
144 unsigned int attribCount = stream.readInt<unsigned int>();
145 ASSERT(state->mAttributes.empty());
146 for (unsigned int attribIndex = 0; attribIndex < attribCount; ++attribIndex)
147 {
148 sh::Attribute attrib;
149 LoadShaderVar(&stream, &attrib);
150 attrib.location = stream.readInt<int>();
151 state->mAttributes.push_back(attrib);
152 }
153
154 unsigned int uniformCount = stream.readInt<unsigned int>();
155 ASSERT(state->mUniforms.empty());
156 for (unsigned int uniformIndex = 0; uniformIndex < uniformCount; ++uniformIndex)
157 {
158 LinkedUniform uniform;
159 LoadShaderVar(&stream, &uniform);
160
161 uniform.blockIndex = stream.readInt<int>();
162 uniform.blockInfo.offset = stream.readInt<int>();
163 uniform.blockInfo.arrayStride = stream.readInt<int>();
164 uniform.blockInfo.matrixStride = stream.readInt<int>();
165 uniform.blockInfo.isRowMajorMatrix = stream.readBool();
166
167 state->mUniforms.push_back(uniform);
168 }
169
170 const unsigned int uniformIndexCount = stream.readInt<unsigned int>();
171 ASSERT(state->mUniformLocations.empty());
172 for (unsigned int uniformIndexIndex = 0; uniformIndexIndex < uniformIndexCount;
173 uniformIndexIndex++)
174 {
175 VariableLocation variable;
176 stream.readString(&variable.name);
177 stream.readInt(&variable.element);
178 stream.readInt(&variable.index);
179 stream.readBool(&variable.used);
180 stream.readBool(&variable.ignored);
181
182 state->mUniformLocations.push_back(variable);
183 }
184
185 unsigned int uniformBlockCount = stream.readInt<unsigned int>();
186 ASSERT(state->mUniformBlocks.empty());
187 for (unsigned int uniformBlockIndex = 0; uniformBlockIndex < uniformBlockCount;
188 ++uniformBlockIndex)
189 {
190 UniformBlock uniformBlock;
191 stream.readString(&uniformBlock.name);
192 stream.readBool(&uniformBlock.isArray);
193 stream.readInt(&uniformBlock.arrayElement);
194 stream.readInt(&uniformBlock.binding);
195 stream.readInt(&uniformBlock.dataSize);
196 stream.readBool(&uniformBlock.vertexStaticUse);
197 stream.readBool(&uniformBlock.fragmentStaticUse);
198
199 unsigned int numMembers = stream.readInt<unsigned int>();
200 for (unsigned int blockMemberIndex = 0; blockMemberIndex < numMembers; blockMemberIndex++)
201 {
202 uniformBlock.memberUniformIndexes.push_back(stream.readInt<unsigned int>());
203 }
204
205 state->mUniformBlocks.push_back(uniformBlock);
Jamie Madill4f86d052017-06-05 12:59:26 -0400206
jchen107a20b972017-06-13 14:25:26 +0800207 state->mActiveUniformBlockBindings.set(uniformBlockIndex, uniformBlock.binding != 0);
Jamie Madill4f86d052017-06-05 12:59:26 -0400208 }
209
210 unsigned int transformFeedbackVaryingCount = stream.readInt<unsigned int>();
Jamie Madillffe00c02017-06-27 16:26:55 -0400211
212 // Reject programs that use transform feedback varyings if the hardware cannot support them.
213 if (transformFeedbackVaryingCount > 0 &&
214 context->getWorkarounds().disableProgramCachingForTransformFeedback)
215 {
216 infoLog << "Current driver does not support transform feedback in binary programs.";
217 return false;
218 }
219
Jamie Madill4f86d052017-06-05 12:59:26 -0400220 ASSERT(state->mLinkedTransformFeedbackVaryings.empty());
221 for (unsigned int transformFeedbackVaryingIndex = 0;
222 transformFeedbackVaryingIndex < transformFeedbackVaryingCount;
223 ++transformFeedbackVaryingIndex)
224 {
225 sh::Varying varying;
226 stream.readInt(&varying.arraySize);
227 stream.readInt(&varying.type);
228 stream.readString(&varying.name);
229
230 GLuint arrayIndex = stream.readInt<GLuint>();
231
232 state->mLinkedTransformFeedbackVaryings.emplace_back(varying, arrayIndex);
233 }
234
235 stream.readInt(&state->mTransformFeedbackBufferMode);
236
237 unsigned int outputCount = stream.readInt<unsigned int>();
238 ASSERT(state->mOutputVariables.empty());
239 for (unsigned int outputIndex = 0; outputIndex < outputCount; ++outputIndex)
240 {
241 sh::OutputVariable output;
242 LoadShaderVar(&stream, &output);
243 output.location = stream.readInt<int>();
244 state->mOutputVariables.push_back(output);
245 }
246
247 unsigned int outputVarCount = stream.readInt<unsigned int>();
248 for (unsigned int outputIndex = 0; outputIndex < outputVarCount; ++outputIndex)
249 {
250 int locationIndex = stream.readInt<int>();
251 VariableLocation locationData;
252 stream.readInt(&locationData.element);
253 stream.readInt(&locationData.index);
254 stream.readString(&locationData.name);
255 state->mOutputLocations[locationIndex] = locationData;
256 }
257
258 unsigned int outputTypeCount = stream.readInt<unsigned int>();
259 for (unsigned int outputIndex = 0; outputIndex < outputTypeCount; ++outputIndex)
260 {
261 state->mOutputVariableTypes.push_back(stream.readInt<GLenum>());
262 }
263 static_assert(IMPLEMENTATION_MAX_DRAW_BUFFERS < 8 * sizeof(uint32_t),
264 "All bits of DrawBufferMask can be contained in an uint32_t");
265 state->mActiveOutputVariables = stream.readInt<uint32_t>();
266
Xinghua Cao65ec0b22017-03-28 16:10:52 +0800267 unsigned int samplerRangeLow = stream.readInt<unsigned int>();
268 unsigned int samplerRangeHigh = stream.readInt<unsigned int>();
269 state->mSamplerUniformRange = RangeUI(samplerRangeLow, samplerRangeHigh);
Jamie Madill4f86d052017-06-05 12:59:26 -0400270 unsigned int samplerCount = stream.readInt<unsigned int>();
271 for (unsigned int samplerIndex = 0; samplerIndex < samplerCount; ++samplerIndex)
272 {
273 GLenum textureType = stream.readInt<GLenum>();
274 size_t bindingCount = stream.readInt<size_t>();
275 state->mSamplerBindings.emplace_back(SamplerBinding(textureType, bindingCount));
276 }
277
Xinghua Cao65ec0b22017-03-28 16:10:52 +0800278 unsigned int imageRangeLow = stream.readInt<unsigned int>();
279 unsigned int imageRangeHigh = stream.readInt<unsigned int>();
280 state->mImageUniformRange = RangeUI(imageRangeLow, imageRangeHigh);
281 unsigned int imageCount = stream.readInt<unsigned int>();
282 for (unsigned int imageIndex = 0; imageIndex < imageCount; ++imageIndex)
283 {
284 GLuint boundImageUnit = stream.readInt<unsigned int>();
285 size_t elementCount = stream.readInt<size_t>();
286 state->mImageBindings.emplace_back(ImageBinding(boundImageUnit, elementCount));
287 }
288
Jamie Madill4f86d052017-06-05 12:59:26 -0400289 return program->getImplementation()->load(context, infoLog, &stream);
290}
291
292// static
293void MemoryProgramCache::Serialize(const Context *context,
294 const gl::Program *program,
295 angle::MemoryBuffer *binaryOut)
296{
297 BinaryOutputStream stream;
298
299 stream.writeBytes(reinterpret_cast<const unsigned char *>(ANGLE_COMMIT_HASH),
300 ANGLE_COMMIT_HASH_SIZE);
301
302 // nullptr context is supported when computing binary length.
303 if (context)
304 {
305 stream.writeInt(context->getClientVersion().major);
306 stream.writeInt(context->getClientVersion().minor);
307 }
308 else
309 {
310 stream.writeInt(2);
311 stream.writeInt(0);
312 }
313
314 const auto &state = program->getState();
315
316 const auto &computeLocalSize = state.getComputeShaderLocalSize();
317
318 stream.writeInt(computeLocalSize[0]);
319 stream.writeInt(computeLocalSize[1]);
320 stream.writeInt(computeLocalSize[2]);
321
322 stream.writeInt(state.getActiveAttribLocationsMask().to_ulong());
323
324 stream.writeInt(state.getAttributes().size());
325 for (const sh::Attribute &attrib : state.getAttributes())
326 {
327 WriteShaderVar(&stream, attrib);
328 stream.writeInt(attrib.location);
329 }
330
331 stream.writeInt(state.getUniforms().size());
332 for (const LinkedUniform &uniform : state.getUniforms())
333 {
334 WriteShaderVar(&stream, uniform);
335
336 // FIXME: referenced
337
338 stream.writeInt(uniform.blockIndex);
339 stream.writeInt(uniform.blockInfo.offset);
340 stream.writeInt(uniform.blockInfo.arrayStride);
341 stream.writeInt(uniform.blockInfo.matrixStride);
342 stream.writeInt(uniform.blockInfo.isRowMajorMatrix);
343 }
344
345 stream.writeInt(state.getUniformLocations().size());
346 for (const auto &variable : state.getUniformLocations())
347 {
348 stream.writeString(variable.name);
349 stream.writeInt(variable.element);
350 stream.writeInt(variable.index);
351 stream.writeInt(variable.used);
352 stream.writeInt(variable.ignored);
353 }
354
355 stream.writeInt(state.getUniformBlocks().size());
356 for (const UniformBlock &uniformBlock : state.getUniformBlocks())
357 {
358 stream.writeString(uniformBlock.name);
359 stream.writeInt(uniformBlock.isArray);
360 stream.writeInt(uniformBlock.arrayElement);
361 stream.writeInt(uniformBlock.binding);
362 stream.writeInt(uniformBlock.dataSize);
363
364 stream.writeInt(uniformBlock.vertexStaticUse);
365 stream.writeInt(uniformBlock.fragmentStaticUse);
366
367 stream.writeInt(uniformBlock.memberUniformIndexes.size());
368 for (unsigned int memberUniformIndex : uniformBlock.memberUniformIndexes)
369 {
370 stream.writeInt(memberUniformIndex);
371 }
372 }
373
Jamie Madillffe00c02017-06-27 16:26:55 -0400374 // Warn the app layer if saving a binary with unsupported transform feedback.
375 if (!state.getLinkedTransformFeedbackVaryings().empty() &&
376 context->getWorkarounds().disableProgramCachingForTransformFeedback)
377 {
378 WARN() << "Saving program binary with transform feedback, which is not supported on this "
379 "driver.";
380 }
381
Jamie Madill4f86d052017-06-05 12:59:26 -0400382 stream.writeInt(state.getLinkedTransformFeedbackVaryings().size());
383 for (const auto &var : state.getLinkedTransformFeedbackVaryings())
384 {
385 stream.writeInt(var.arraySize);
386 stream.writeInt(var.type);
387 stream.writeString(var.name);
388
389 stream.writeIntOrNegOne(var.arrayIndex);
390 }
391
392 stream.writeInt(state.getTransformFeedbackBufferMode());
393
394 stream.writeInt(state.getOutputVariables().size());
395 for (const sh::OutputVariable &output : state.getOutputVariables())
396 {
397 WriteShaderVar(&stream, output);
398 stream.writeInt(output.location);
399 }
400
401 stream.writeInt(state.getOutputLocations().size());
402 for (const auto &outputPair : state.getOutputLocations())
403 {
404 stream.writeInt(outputPair.first);
405 stream.writeIntOrNegOne(outputPair.second.element);
406 stream.writeInt(outputPair.second.index);
407 stream.writeString(outputPair.second.name);
408 }
409
410 stream.writeInt(state.mOutputVariableTypes.size());
411 for (const auto &outputVariableType : state.mOutputVariableTypes)
412 {
413 stream.writeInt(outputVariableType);
414 }
415
416 static_assert(IMPLEMENTATION_MAX_DRAW_BUFFERS < 8 * sizeof(uint32_t),
417 "All bits of DrawBufferMask can be contained in an uint32_t");
418 stream.writeInt(static_cast<uint32_t>(state.mActiveOutputVariables.to_ulong()));
419
420 stream.writeInt(state.getSamplerUniformRange().low());
421 stream.writeInt(state.getSamplerUniformRange().high());
422
423 stream.writeInt(state.getSamplerBindings().size());
424 for (const auto &samplerBinding : state.getSamplerBindings())
425 {
426 stream.writeInt(samplerBinding.textureType);
427 stream.writeInt(samplerBinding.boundTextureUnits.size());
428 }
429
Xinghua Cao65ec0b22017-03-28 16:10:52 +0800430 stream.writeInt(state.getImageUniformRange().low());
431 stream.writeInt(state.getImageUniformRange().high());
432
433 stream.writeInt(state.getImageBindings().size());
434 for (const auto &imageBinding : state.getImageBindings())
435 {
436 stream.writeInt(imageBinding.boundImageUnit);
437 stream.writeInt(imageBinding.elementCount);
438 }
439
Jamie Madill4f86d052017-06-05 12:59:26 -0400440 program->getImplementation()->save(&stream);
441
442 ASSERT(binaryOut);
443 binaryOut->resize(stream.length());
444 memcpy(binaryOut->data(), stream.data(), stream.length());
445}
446
Jamie Madill32447362017-06-28 14:53:52 -0400447// static
448void MemoryProgramCache::ComputeHash(const Context *context,
449 const Program *program,
450 ProgramHash *hashOut)
451{
452 auto vertexShader = program->getAttachedVertexShader();
453 auto fragmentShader = program->getAttachedFragmentShader();
454 auto computeShader = program->getAttachedComputeShader();
455
456 // Compute the program hash. Start with the shader hashes and resource strings.
457 HashStream hashStream;
458 hashStream << vertexShader << fragmentShader << computeShader;
459
460 // Add some ANGLE metadata and Context properties, such as version and back-end.
461 hashStream << ANGLE_COMMIT_HASH << context->getClientMajorVersion()
462 << context->getClientMinorVersion() << context->getString(GL_RENDERER);
463
464 // Hash pre-link program properties.
465 hashStream << program->getAttributeBindings() << program->getUniformLocationBindings()
466 << program->getFragmentInputBindings()
467 << program->getState().getTransformFeedbackVaryingNames()
468 << program->getState().getTransformFeedbackBufferMode();
469
470 // Call the secure SHA hashing function.
471 const std::string &programKey = hashStream.str();
472 angle::base::SHA1HashBytes(reinterpret_cast<const unsigned char *>(programKey.c_str()),
473 programKey.length(), hashOut->data());
474}
475
476LinkResult MemoryProgramCache::getProgram(const Context *context,
477 const Program *program,
478 ProgramState *state,
479 ProgramHash *hashOut)
480{
481 ComputeHash(context, program, hashOut);
482 const angle::MemoryBuffer *binaryProgram = nullptr;
483 LinkResult result(false);
484 if (get(*hashOut, &binaryProgram))
485 {
486 InfoLog infoLog;
487 ANGLE_TRY_RESULT(Deserialize(context, program, state, binaryProgram->data(),
488 binaryProgram->size(), infoLog),
489 result);
490 if (!result.getResult())
491 {
492 // Cache load failed, evict.
493 if (mIssuedWarnings++ < kWarningLimit)
494 {
495 WARN() << "Failed to load binary from cache: " << infoLog.str();
496
497 if (mIssuedWarnings == kWarningLimit)
498 {
499 WARN() << "Reaching warning limit for cache load failures, silencing "
500 "subsequent warnings.";
501 }
502 }
503 remove(*hashOut);
504 }
505 }
506 return result;
507}
508
509bool MemoryProgramCache::get(const ProgramHash &programHash, const angle::MemoryBuffer **programOut)
510{
511 return mProgramBinaryCache.get(programHash, programOut);
512}
513
514void MemoryProgramCache::remove(const ProgramHash &programHash)
515{
516 bool result = mProgramBinaryCache.eraseByKey(programHash);
517 ASSERT(result);
518}
519
Jamie Madill360daee2017-06-29 10:36:19 -0400520void MemoryProgramCache::put(const ProgramHash &program,
521 const Context *context,
522 angle::MemoryBuffer &&binaryProgram)
Jamie Madill32447362017-06-28 14:53:52 -0400523{
Jamie Madill360daee2017-06-29 10:36:19 -0400524 const angle::MemoryBuffer *result =
525 mProgramBinaryCache.put(program, std::move(binaryProgram), binaryProgram.size());
526 if (!result)
Jamie Madill32447362017-06-28 14:53:52 -0400527 {
528 ERR() << "Failed to store binary program in memory cache, program is too large.";
529 }
Jamie Madill360daee2017-06-29 10:36:19 -0400530 else
531 {
532 auto *platform = ANGLEPlatformCurrent();
533 platform->cacheProgram(platform, program, result->size(), result->data());
534 }
Jamie Madill32447362017-06-28 14:53:52 -0400535}
536
537void MemoryProgramCache::putProgram(const ProgramHash &programHash,
538 const Context *context,
539 const Program *program)
540{
541 angle::MemoryBuffer binaryProgram;
542 Serialize(context, program, &binaryProgram);
Jamie Madill360daee2017-06-29 10:36:19 -0400543 put(programHash, context, std::move(binaryProgram));
Jamie Madill32447362017-06-28 14:53:52 -0400544}
545
546void MemoryProgramCache::putBinary(const Context *context,
547 const Program *program,
548 const uint8_t *binary,
549 size_t length)
550{
551 // Copy the binary.
552 angle::MemoryBuffer binaryProgram;
553 binaryProgram.resize(length);
554 memcpy(binaryProgram.data(), binary, length);
555
556 // Compute the hash.
557 ProgramHash programHash;
558 ComputeHash(context, program, &programHash);
559
560 // Store the binary.
Jamie Madill360daee2017-06-29 10:36:19 -0400561 put(programHash, context, std::move(binaryProgram));
Jamie Madill32447362017-06-28 14:53:52 -0400562}
563
564void MemoryProgramCache::clear()
565{
566 mProgramBinaryCache.clear();
567 mIssuedWarnings = 0;
568}
569
Jamie Madill4f86d052017-06-05 12:59:26 -0400570} // namespace gl