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