// // Copyright 2017 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // MemoryProgramCache: Stores compiled and linked programs in memory so they don't // always have to be re-compiled. Can be used in conjunction with the platform // layer to warm up the cache from disk. #include "libANGLE/MemoryProgramCache.h" #include #include #include "common/utilities.h" #include "common/version.h" #include "libANGLE/BinaryStream.h" #include "libANGLE/Context.h" #include "libANGLE/Uniform.h" #include "libANGLE/histogram_macros.h" #include "libANGLE/renderer/ProgramImpl.h" #include "platform/Platform.h" namespace gl { namespace { enum CacheResult { kCacheMiss, kCacheHitMemory, kCacheHitDisk, kCacheResultMax, }; constexpr unsigned int kWarningLimit = 3; void WriteShaderVar(BinaryOutputStream *stream, const sh::ShaderVariable &var) { stream->writeInt(var.type); stream->writeInt(var.precision); stream->writeString(var.name); stream->writeString(var.mappedName); stream->writeIntVector(var.arraySizes); stream->writeInt(var.staticUse); stream->writeString(var.structName); ASSERT(var.fields.empty()); } void LoadShaderVar(BinaryInputStream *stream, sh::ShaderVariable *var) { var->type = stream->readInt(); var->precision = stream->readInt(); var->name = stream->readString(); var->mappedName = stream->readString(); stream->readIntVector(&var->arraySizes); var->staticUse = stream->readBool(); var->structName = stream->readString(); } void WriteShaderVariableBuffer(BinaryOutputStream *stream, const ShaderVariableBuffer &var) { stream->writeInt(var.binding); stream->writeInt(var.dataSize); stream->writeInt(var.vertexStaticUse); stream->writeInt(var.fragmentStaticUse); stream->writeInt(var.computeStaticUse); stream->writeInt(var.memberIndexes.size()); for (unsigned int memberCounterIndex : var.memberIndexes) { stream->writeInt(memberCounterIndex); } } void LoadShaderVariableBuffer(BinaryInputStream *stream, ShaderVariableBuffer *var) { var->binding = stream->readInt(); var->dataSize = stream->readInt(); var->vertexStaticUse = stream->readBool(); var->fragmentStaticUse = stream->readBool(); var->computeStaticUse = stream->readBool(); unsigned int numMembers = stream->readInt(); for (unsigned int blockMemberIndex = 0; blockMemberIndex < numMembers; blockMemberIndex++) { var->memberIndexes.push_back(stream->readInt()); } } void WriteBufferVariable(BinaryOutputStream *stream, const BufferVariable &var) { WriteShaderVar(stream, var); stream->writeInt(var.bufferIndex); stream->writeInt(var.blockInfo.offset); stream->writeInt(var.blockInfo.arrayStride); stream->writeInt(var.blockInfo.matrixStride); stream->writeInt(var.blockInfo.isRowMajorMatrix); stream->writeInt(var.blockInfo.topLevelArrayStride); stream->writeInt(var.topLevelArraySize); stream->writeInt(var.vertexStaticUse); stream->writeInt(var.fragmentStaticUse); stream->writeInt(var.computeStaticUse); } void LoadBufferVariable(BinaryInputStream *stream, BufferVariable *var) { LoadShaderVar(stream, var); var->bufferIndex = stream->readInt(); var->blockInfo.offset = stream->readInt(); var->blockInfo.arrayStride = stream->readInt(); var->blockInfo.matrixStride = stream->readInt(); var->blockInfo.isRowMajorMatrix = stream->readBool(); var->blockInfo.topLevelArrayStride = stream->readInt(); var->topLevelArraySize = stream->readInt(); var->vertexStaticUse = stream->readBool(); var->fragmentStaticUse = stream->readBool(); var->computeStaticUse = stream->readBool(); } void WriteInterfaceBlock(BinaryOutputStream *stream, const InterfaceBlock &block) { stream->writeString(block.name); stream->writeString(block.mappedName); stream->writeInt(block.isArray); stream->writeInt(block.arrayElement); WriteShaderVariableBuffer(stream, block); } void LoadInterfaceBlock(BinaryInputStream *stream, InterfaceBlock *block) { block->name = stream->readString(); block->mappedName = stream->readString(); block->isArray = stream->readBool(); block->arrayElement = stream->readInt(); LoadShaderVariableBuffer(stream, block); } class HashStream final : angle::NonCopyable { public: std::string str() { return mStringStream.str(); } template HashStream &operator<<(T value) { mStringStream << value << kSeparator; return *this; } private: static constexpr char kSeparator = ':'; std::ostringstream mStringStream; }; HashStream &operator<<(HashStream &stream, const Shader *shader) { if (shader) { stream << shader->getSourceString().c_str() << shader->getSourceString().length() << shader->getCompilerResourcesString().c_str(); } return stream; } HashStream &operator<<(HashStream &stream, const Program::Bindings &bindings) { for (const auto &binding : bindings) { stream << binding.first << binding.second; } return stream; } HashStream &operator<<(HashStream &stream, const std::vector &strings) { for (const auto &str : strings) { stream << str; } return stream; } } // anonymous namespace MemoryProgramCache::MemoryProgramCache(size_t maxCacheSizeBytes) : mProgramBinaryCache(maxCacheSizeBytes), mIssuedWarnings(0) { } MemoryProgramCache::~MemoryProgramCache() { } // static LinkResult MemoryProgramCache::Deserialize(const Context *context, const Program *program, ProgramState *state, const uint8_t *binary, size_t length, InfoLog &infoLog) { BinaryInputStream stream(binary, length); unsigned char commitString[ANGLE_COMMIT_HASH_SIZE]; stream.readBytes(commitString, ANGLE_COMMIT_HASH_SIZE); if (memcmp(commitString, ANGLE_COMMIT_HASH, sizeof(unsigned char) * ANGLE_COMMIT_HASH_SIZE) != 0) { infoLog << "Invalid program binary version."; return false; } int majorVersion = stream.readInt(); int minorVersion = stream.readInt(); if (majorVersion != context->getClientMajorVersion() || minorVersion != context->getClientMinorVersion()) { infoLog << "Cannot load program binaries across different ES context versions."; return false; } state->mComputeShaderLocalSize[0] = stream.readInt(); state->mComputeShaderLocalSize[1] = stream.readInt(); state->mComputeShaderLocalSize[2] = stream.readInt(); state->mNumViews = stream.readInt(); static_assert(MAX_VERTEX_ATTRIBS <= sizeof(unsigned long) * 8, "Too many vertex attribs for mask"); state->mActiveAttribLocationsMask = stream.readInt(); unsigned int attribCount = stream.readInt(); ASSERT(state->mAttributes.empty()); for (unsigned int attribIndex = 0; attribIndex < attribCount; ++attribIndex) { sh::Attribute attrib; LoadShaderVar(&stream, &attrib); attrib.location = stream.readInt(); state->mAttributes.push_back(attrib); } unsigned int uniformCount = stream.readInt(); ASSERT(state->mUniforms.empty()); for (unsigned int uniformIndex = 0; uniformIndex < uniformCount; ++uniformIndex) { LinkedUniform uniform; LoadShaderVar(&stream, &uniform); uniform.bufferIndex = stream.readInt(); uniform.blockInfo.offset = stream.readInt(); uniform.blockInfo.arrayStride = stream.readInt(); uniform.blockInfo.matrixStride = stream.readInt(); uniform.blockInfo.isRowMajorMatrix = stream.readBool(); uniform.typeInfo = &GetUniformTypeInfo(uniform.type); state->mUniforms.push_back(uniform); } const unsigned int uniformIndexCount = stream.readInt(); ASSERT(state->mUniformLocations.empty()); for (unsigned int uniformIndexIndex = 0; uniformIndexIndex < uniformIndexCount; uniformIndexIndex++) { VariableLocation variable; stream.readInt(&variable.arrayIndex); stream.readInt(&variable.index); stream.readBool(&variable.ignored); state->mUniformLocations.push_back(variable); } unsigned int uniformBlockCount = stream.readInt(); ASSERT(state->mUniformBlocks.empty()); for (unsigned int uniformBlockIndex = 0; uniformBlockIndex < uniformBlockCount; ++uniformBlockIndex) { InterfaceBlock uniformBlock; LoadInterfaceBlock(&stream, &uniformBlock); state->mUniformBlocks.push_back(uniformBlock); state->mActiveUniformBlockBindings.set(uniformBlockIndex, uniformBlock.binding != 0); } unsigned int bufferVariableCount = stream.readInt(); ASSERT(state->mBufferVariables.empty()); for (unsigned int index = 0; index < bufferVariableCount; ++index) { BufferVariable bufferVariable; LoadBufferVariable(&stream, &bufferVariable); state->mBufferVariables.push_back(bufferVariable); } unsigned int shaderStorageBlockCount = stream.readInt(); ASSERT(state->mShaderStorageBlocks.empty()); for (unsigned int shaderStorageBlockIndex = 0; shaderStorageBlockIndex < shaderStorageBlockCount; ++shaderStorageBlockIndex) { InterfaceBlock shaderStorageBlock; LoadInterfaceBlock(&stream, &shaderStorageBlock); state->mShaderStorageBlocks.push_back(shaderStorageBlock); } unsigned int atomicCounterBufferCount = stream.readInt(); ASSERT(state->mAtomicCounterBuffers.empty()); for (unsigned int bufferIndex = 0; bufferIndex < atomicCounterBufferCount; ++bufferIndex) { AtomicCounterBuffer atomicCounterBuffer; LoadShaderVariableBuffer(&stream, &atomicCounterBuffer); state->mAtomicCounterBuffers.push_back(atomicCounterBuffer); } unsigned int transformFeedbackVaryingCount = stream.readInt(); // Reject programs that use transform feedback varyings if the hardware cannot support them. if (transformFeedbackVaryingCount > 0 && context->getWorkarounds().disableProgramCachingForTransformFeedback) { infoLog << "Current driver does not support transform feedback in binary programs."; return false; } ASSERT(state->mLinkedTransformFeedbackVaryings.empty()); for (unsigned int transformFeedbackVaryingIndex = 0; transformFeedbackVaryingIndex < transformFeedbackVaryingCount; ++transformFeedbackVaryingIndex) { sh::Varying varying; stream.readIntVector(&varying.arraySizes); stream.readInt(&varying.type); stream.readString(&varying.name); GLuint arrayIndex = stream.readInt(); state->mLinkedTransformFeedbackVaryings.emplace_back(varying, arrayIndex); } stream.readInt(&state->mTransformFeedbackBufferMode); unsigned int outputCount = stream.readInt(); ASSERT(state->mOutputVariables.empty()); for (unsigned int outputIndex = 0; outputIndex < outputCount; ++outputIndex) { sh::OutputVariable output; LoadShaderVar(&stream, &output); output.location = stream.readInt(); state->mOutputVariables.push_back(output); } unsigned int outputVarCount = stream.readInt(); ASSERT(state->mOutputLocations.empty()); for (unsigned int outputIndex = 0; outputIndex < outputVarCount; ++outputIndex) { VariableLocation locationData; stream.readInt(&locationData.arrayIndex); stream.readInt(&locationData.index); stream.readBool(&locationData.ignored); state->mOutputLocations.push_back(locationData); } unsigned int outputTypeCount = stream.readInt(); for (unsigned int outputIndex = 0; outputIndex < outputTypeCount; ++outputIndex) { state->mOutputVariableTypes.push_back(stream.readInt()); } static_assert(IMPLEMENTATION_MAX_DRAW_BUFFERS < 8 * sizeof(uint32_t), "All bits of DrawBufferMask can be contained in an uint32_t"); state->mActiveOutputVariables = stream.readInt(); unsigned int samplerRangeLow = stream.readInt(); unsigned int samplerRangeHigh = stream.readInt(); state->mSamplerUniformRange = RangeUI(samplerRangeLow, samplerRangeHigh); unsigned int samplerCount = stream.readInt(); for (unsigned int samplerIndex = 0; samplerIndex < samplerCount; ++samplerIndex) { GLenum textureType = stream.readInt(); size_t bindingCount = stream.readInt(); bool unreferenced = stream.readBool(); state->mSamplerBindings.emplace_back( SamplerBinding(textureType, bindingCount, unreferenced)); } unsigned int imageRangeLow = stream.readInt(); unsigned int imageRangeHigh = stream.readInt(); state->mImageUniformRange = RangeUI(imageRangeLow, imageRangeHigh); unsigned int imageBindingCount = stream.readInt(); for (unsigned int imageIndex = 0; imageIndex < imageBindingCount; ++imageIndex) { unsigned int elementCount = stream.readInt(); ImageBinding imageBinding(elementCount); for (unsigned int i = 0; i < elementCount; ++i) { imageBinding.boundImageUnits[i] = stream.readInt(); } state->mImageBindings.emplace_back(imageBinding); } unsigned int atomicCounterRangeLow = stream.readInt(); unsigned int atomicCounterRangeHigh = stream.readInt(); state->mAtomicCounterUniformRange = RangeUI(atomicCounterRangeLow, atomicCounterRangeHigh); static_assert(SHADER_TYPE_MAX <= sizeof(unsigned long) * 8, "Too many shader types"); state->mLinkedShaderStages = stream.readInt(); return program->getImplementation()->load(context, infoLog, &stream); } // static void MemoryProgramCache::Serialize(const Context *context, const gl::Program *program, angle::MemoryBuffer *binaryOut) { BinaryOutputStream stream; stream.writeBytes(reinterpret_cast(ANGLE_COMMIT_HASH), ANGLE_COMMIT_HASH_SIZE); // nullptr context is supported when computing binary length. if (context) { stream.writeInt(context->getClientVersion().major); stream.writeInt(context->getClientVersion().minor); } else { stream.writeInt(2); stream.writeInt(0); } const auto &state = program->getState(); const auto &computeLocalSize = state.getComputeShaderLocalSize(); stream.writeInt(computeLocalSize[0]); stream.writeInt(computeLocalSize[1]); stream.writeInt(computeLocalSize[2]); stream.writeInt(state.mNumViews); stream.writeInt(state.getActiveAttribLocationsMask().to_ulong()); stream.writeInt(state.getAttributes().size()); for (const sh::Attribute &attrib : state.getAttributes()) { WriteShaderVar(&stream, attrib); stream.writeInt(attrib.location); } stream.writeInt(state.getUniforms().size()); for (const LinkedUniform &uniform : state.getUniforms()) { WriteShaderVar(&stream, uniform); // FIXME: referenced stream.writeInt(uniform.bufferIndex); stream.writeInt(uniform.blockInfo.offset); stream.writeInt(uniform.blockInfo.arrayStride); stream.writeInt(uniform.blockInfo.matrixStride); stream.writeInt(uniform.blockInfo.isRowMajorMatrix); } stream.writeInt(state.getUniformLocations().size()); for (const auto &variable : state.getUniformLocations()) { stream.writeInt(variable.arrayIndex); stream.writeIntOrNegOne(variable.index); stream.writeInt(variable.ignored); } stream.writeInt(state.getUniformBlocks().size()); for (const InterfaceBlock &uniformBlock : state.getUniformBlocks()) { WriteInterfaceBlock(&stream, uniformBlock); } stream.writeInt(state.getBufferVariables().size()); for (const BufferVariable &bufferVariable : state.getBufferVariables()) { WriteBufferVariable(&stream, bufferVariable); } stream.writeInt(state.getShaderStorageBlocks().size()); for (const InterfaceBlock &shaderStorageBlock : state.getShaderStorageBlocks()) { WriteInterfaceBlock(&stream, shaderStorageBlock); } stream.writeInt(state.mAtomicCounterBuffers.size()); for (const auto &atomicCounterBuffer : state.mAtomicCounterBuffers) { WriteShaderVariableBuffer(&stream, atomicCounterBuffer); } // Warn the app layer if saving a binary with unsupported transform feedback. if (!state.getLinkedTransformFeedbackVaryings().empty() && context->getWorkarounds().disableProgramCachingForTransformFeedback) { WARN() << "Saving program binary with transform feedback, which is not supported on this " "driver."; } stream.writeInt(state.getLinkedTransformFeedbackVaryings().size()); for (const auto &var : state.getLinkedTransformFeedbackVaryings()) { stream.writeIntVector(var.arraySizes); stream.writeInt(var.type); stream.writeString(var.name); stream.writeIntOrNegOne(var.arrayIndex); } stream.writeInt(state.getTransformFeedbackBufferMode()); stream.writeInt(state.getOutputVariables().size()); for (const sh::OutputVariable &output : state.getOutputVariables()) { WriteShaderVar(&stream, output); stream.writeInt(output.location); } stream.writeInt(state.getOutputLocations().size()); for (const auto &outputVar : state.getOutputLocations()) { stream.writeInt(outputVar.arrayIndex); stream.writeIntOrNegOne(outputVar.index); stream.writeInt(outputVar.ignored); } stream.writeInt(state.mOutputVariableTypes.size()); for (const auto &outputVariableType : state.mOutputVariableTypes) { stream.writeInt(outputVariableType); } static_assert(IMPLEMENTATION_MAX_DRAW_BUFFERS < 8 * sizeof(uint32_t), "All bits of DrawBufferMask can be contained in an uint32_t"); stream.writeInt(static_cast(state.mActiveOutputVariables.to_ulong())); stream.writeInt(state.getSamplerUniformRange().low()); stream.writeInt(state.getSamplerUniformRange().high()); stream.writeInt(state.getSamplerBindings().size()); for (const auto &samplerBinding : state.getSamplerBindings()) { stream.writeInt(samplerBinding.textureType); stream.writeInt(samplerBinding.boundTextureUnits.size()); stream.writeInt(samplerBinding.unreferenced); } stream.writeInt(state.getImageUniformRange().low()); stream.writeInt(state.getImageUniformRange().high()); stream.writeInt(state.getImageBindings().size()); for (const auto &imageBinding : state.getImageBindings()) { stream.writeInt(imageBinding.boundImageUnits.size()); for (size_t i = 0; i < imageBinding.boundImageUnits.size(); ++i) { stream.writeInt(imageBinding.boundImageUnits[i]); } } stream.writeInt(state.getAtomicCounterUniformRange().low()); stream.writeInt(state.getAtomicCounterUniformRange().high()); stream.writeInt(state.getLinkedShaderStages().to_ulong()); program->getImplementation()->save(context, &stream); ASSERT(binaryOut); binaryOut->resize(stream.length()); memcpy(binaryOut->data(), stream.data(), stream.length()); } // static void MemoryProgramCache::ComputeHash(const Context *context, const Program *program, ProgramHash *hashOut) { const Shader *vertexShader = program->getAttachedVertexShader(); const Shader *fragmentShader = program->getAttachedFragmentShader(); const Shader *computeShader = program->getAttachedComputeShader(); // Compute the program hash. Start with the shader hashes and resource strings. HashStream hashStream; hashStream << vertexShader << fragmentShader << computeShader; // Add some ANGLE metadata and Context properties, such as version and back-end. hashStream << ANGLE_COMMIT_HASH << context->getClientMajorVersion() << context->getClientMinorVersion() << context->getString(GL_RENDERER); // Hash pre-link program properties. hashStream << program->getAttributeBindings() << program->getUniformLocationBindings() << program->getFragmentInputBindings() << program->getState().getTransformFeedbackVaryingNames() << program->getState().getTransformFeedbackBufferMode(); // Call the secure SHA hashing function. const std::string &programKey = hashStream.str(); angle::base::SHA1HashBytes(reinterpret_cast(programKey.c_str()), programKey.length(), hashOut->data()); } LinkResult MemoryProgramCache::getProgram(const Context *context, const Program *program, ProgramState *state, ProgramHash *hashOut) { ComputeHash(context, program, hashOut); const angle::MemoryBuffer *binaryProgram = nullptr; LinkResult result(false); if (get(*hashOut, &binaryProgram)) { InfoLog infoLog; ANGLE_TRY_RESULT(Deserialize(context, program, state, binaryProgram->data(), binaryProgram->size(), infoLog), result); ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.ProgramCache.LoadBinarySuccess", result.getResult()); if (!result.getResult()) { // Cache load failed, evict. if (mIssuedWarnings++ < kWarningLimit) { WARN() << "Failed to load binary from cache: " << infoLog.str(); if (mIssuedWarnings == kWarningLimit) { WARN() << "Reaching warning limit for cache load failures, silencing " "subsequent warnings."; } } remove(*hashOut); } } return result; } bool MemoryProgramCache::get(const ProgramHash &programHash, const angle::MemoryBuffer **programOut) { const CacheEntry *entry = nullptr; if (!mProgramBinaryCache.get(programHash, &entry)) { ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheMiss, kCacheResultMax); return false; } if (entry->second == CacheSource::PutProgram) { ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitMemory, kCacheResultMax); } else { ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitDisk, kCacheResultMax); } *programOut = &entry->first; return true; } bool MemoryProgramCache::getAt(size_t index, ProgramHash *hashOut, const angle::MemoryBuffer **programOut) { const CacheEntry *entry = nullptr; if (!mProgramBinaryCache.getAt(index, hashOut, &entry)) { return false; } *programOut = &entry->first; return true; } void MemoryProgramCache::remove(const ProgramHash &programHash) { bool result = mProgramBinaryCache.eraseByKey(programHash); ASSERT(result); } void MemoryProgramCache::putProgram(const ProgramHash &programHash, const Context *context, const Program *program) { CacheEntry newEntry; Serialize(context, program, &newEntry.first); newEntry.second = CacheSource::PutProgram; ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramBinarySizeBytes", static_cast(newEntry.first.size())); const CacheEntry *result = mProgramBinaryCache.put(programHash, std::move(newEntry), newEntry.first.size()); if (!result) { ERR() << "Failed to store binary program in memory cache, program is too large."; } else { auto *platform = ANGLEPlatformCurrent(); platform->cacheProgram(platform, programHash, result->first.size(), result->first.data()); } } void MemoryProgramCache::updateProgram(const Context *context, const Program *program) { gl::ProgramHash programHash; ComputeHash(context, program, &programHash); putProgram(programHash, context, program); } void MemoryProgramCache::putBinary(const ProgramHash &programHash, const uint8_t *binary, size_t length) { // Copy the binary. CacheEntry newEntry; newEntry.first.resize(length); memcpy(newEntry.first.data(), binary, length); newEntry.second = CacheSource::PutBinary; // Store the binary. const CacheEntry *result = mProgramBinaryCache.put(programHash, std::move(newEntry), length); if (!result) { ERR() << "Failed to store binary program in memory cache, program is too large."; } } void MemoryProgramCache::clear() { mProgramBinaryCache.clear(); mIssuedWarnings = 0; } void MemoryProgramCache::resize(size_t maxCacheSizeBytes) { mProgramBinaryCache.resize(maxCacheSizeBytes); } size_t MemoryProgramCache::entryCount() const { return mProgramBinaryCache.entryCount(); } size_t MemoryProgramCache::trim(size_t limit) { return mProgramBinaryCache.shrinkToSize(limit); } size_t MemoryProgramCache::size() const { return mProgramBinaryCache.size(); } size_t MemoryProgramCache::maxSize() const { return mProgramBinaryCache.maxSize(); } } // namespace gl