// // 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 zlib first, otherwise FAR gets defined elsewhere. #define USE_SYSTEM_ZLIB #include "compression_utils_portable.h" #include "libANGLE/MemoryProgramCache.h" #include #include #include "common/BinaryStream.h" #include "common/angle_version_info.h" #include "common/utilities.h" #include "libANGLE/Context.h" #include "libANGLE/Debug.h" #include "libANGLE/Uniform.h" #include "libANGLE/capture/FrameCapture.h" #include "libANGLE/histogram_macros.h" #include "libANGLE/renderer/ProgramImpl.h" #include "platform/PlatformMethods.h" namespace gl { namespace { void WriteProgramBindings(BinaryOutputStream *stream, const ProgramBindings &bindings) { for (const auto &binding : bindings.getStableIterationMap()) { stream->writeString(binding.first); stream->writeInt(binding.second); } } void WriteProgramAliasedBindings(BinaryOutputStream *stream, const ProgramAliasedBindings &bindings) { for (const auto &binding : bindings.getStableIterationMap()) { stream->writeString(binding.first); stream->writeInt(binding.second.location); } } void WriteVariableLocations(BinaryOutputStream *stream, const std::vector &locations) { for (const auto &loc : locations) { stream->writeInt(loc.index); stream->writeInt(loc.arrayIndex); stream->writeBool(loc.ignored); } } } // anonymous namespace MemoryProgramCache::MemoryProgramCache(egl::BlobCache &blobCache) : mBlobCache(blobCache) {} MemoryProgramCache::~MemoryProgramCache() {} void MemoryProgramCache::ComputeHash(const Context *context, const Program *program, egl::BlobCache::Key *hashOut) { // Compute the program hash. Start with the shader hashes. BinaryOutputStream hashStream; for (ShaderType shaderType : AllShaderTypes()) { Shader *shader = program->getAttachedShader(shaderType); if (shader) { shader->writeShaderKey(&hashStream); } } // Add some ANGLE metadata and Context properties, such as version and back-end. hashStream.writeString(angle::GetANGLEShaderProgramVersion()); hashStream.writeInt(angle::GetANGLESHVersion()); hashStream.writeInt(context->getClientMajorVersion()); hashStream.writeInt(context->getClientMinorVersion()); hashStream.writeString(reinterpret_cast(context->getString(GL_RENDERER))); // Hash pre-link program properties. WriteProgramBindings(&hashStream, program->getAttributeBindings()); WriteProgramAliasedBindings(&hashStream, program->getUniformLocationBindings()); WriteProgramAliasedBindings(&hashStream, program->getFragmentOutputLocations()); WriteProgramAliasedBindings(&hashStream, program->getFragmentOutputIndexes()); for (const std::string &transformFeedbackVaryingName : program->getState().getTransformFeedbackVaryingNames()) { hashStream.writeString(transformFeedbackVaryingName); } hashStream.writeInt(program->getState().getTransformFeedbackBufferMode()); WriteVariableLocations(&hashStream, program->getState().getOutputLocations()); WriteVariableLocations(&hashStream, program->getState().getSecondaryOutputLocations()); // Include the status of FrameCapture, which adds source strings to the binary hashStream.writeBool(context->getShareGroup()->getFrameCaptureShared()->enabled()); // Call the secure SHA hashing function. const std::vector &programKey = hashStream.getData(); angle::base::SHA1HashBytes(programKey.data(), programKey.size(), hashOut->data()); } angle::Result MemoryProgramCache::getProgram(const Context *context, Program *program, egl::BlobCache::Key *hashOut) { // If caching is effectively disabled, don't bother calculating the hash. if (!mBlobCache.isCachingEnabled()) { return angle::Result::Incomplete; } ComputeHash(context, program, hashOut); angle::MemoryBuffer uncompressedData; switch (mBlobCache.getAndDecompress(context->getScratchBuffer(), *hashOut, &uncompressedData)) { case egl::BlobCache::GetAndDecompressResult::NotFound: return angle::Result::Incomplete; case egl::BlobCache::GetAndDecompressResult::DecompressFailure: ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, "Error decompressing program binary data fetched from cache."); return angle::Result::Incomplete; case egl::BlobCache::GetAndDecompressResult::GetSuccess: angle::Result result = program->loadBinary(context, GL_PROGRAM_BINARY_ANGLE, uncompressedData.data(), static_cast(uncompressedData.size())); ANGLE_TRY(result); if (result == angle::Result::Continue) return angle::Result::Continue; // Cache load failed, evict ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, "Failed to load program binary from cache."); remove(*hashOut); return angle::Result::Incomplete; } UNREACHABLE(); return angle::Result::Incomplete; } bool MemoryProgramCache::getAt(size_t index, const egl::BlobCache::Key **hashOut, egl::BlobCache::Value *programOut) { return mBlobCache.getAt(index, hashOut, programOut); } void MemoryProgramCache::remove(const egl::BlobCache::Key &programHash) { mBlobCache.remove(programHash); } angle::Result MemoryProgramCache::putProgram(const egl::BlobCache::Key &programHash, const Context *context, const Program *program) { // If caching is effectively disabled, don't bother serializing the program. if (!mBlobCache.isCachingEnabled()) { return angle::Result::Incomplete; } angle::MemoryBuffer serializedProgram; ANGLE_TRY(program->serialize(context, &serializedProgram)); angle::MemoryBuffer compressedData; if (!egl::CompressBlobCacheData(serializedProgram.size(), serializedProgram.data(), &compressedData)) { ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, "Error compressing binary data."); return angle::Result::Incomplete; } { std::scoped_lock lock(mBlobCache.getMutex()); // TODO: http://anglebug.com/7568 // This was a workaround for Chrome until it added support for EGL_ANDROID_blob_cache, // tracked by http://anglebug.com/2516. This issue has since been closed, but removing this // still causes a test failure. auto *platform = ANGLEPlatformCurrent(); platform->cacheProgram(platform, programHash, compressedData.size(), compressedData.data()); } mBlobCache.put(programHash, std::move(compressedData)); return angle::Result::Continue; } angle::Result MemoryProgramCache::updateProgram(const Context *context, const Program *program) { egl::BlobCache::Key programHash; ComputeHash(context, program, &programHash); return putProgram(programHash, context, program); } bool MemoryProgramCache::putBinary(const egl::BlobCache::Key &programHash, const uint8_t *binary, size_t length) { // Copy the binary. angle::MemoryBuffer newEntry; if (!newEntry.resize(length)) { return false; } memcpy(newEntry.data(), binary, length); // Store the binary. mBlobCache.populate(programHash, std::move(newEntry)); return true; } void MemoryProgramCache::clear() { mBlobCache.clear(); } void MemoryProgramCache::resize(size_t maxCacheSizeBytes) { mBlobCache.resize(maxCacheSizeBytes); } size_t MemoryProgramCache::entryCount() const { return mBlobCache.entryCount(); } size_t MemoryProgramCache::trim(size_t limit) { return mBlobCache.trim(limit); } size_t MemoryProgramCache::size() const { return mBlobCache.size(); } size_t MemoryProgramCache::maxSize() const { return mBlobCache.maxSize(); } } // namespace gl