diff options
| author | 2018-09-04 16:04:40 +0000 | |
|---|---|---|
| committer | 2018-09-04 16:04:40 +0000 | |
| commit | 0a874d257af5c21efecd18707990eeef6012b52a (patch) | |
| tree | bc9f81290da84d8393fe3362f524e6429fb30de5 | |
| parent | f66fddf49fc3d9cc57855576f96c31432d16ce01 (diff) | |
| parent | 9f959556201f59305cd80ad32a1c117d5b7c4113 (diff) | |
Merge "Add cache validation to ensure the validity"
| -rw-r--r-- | libs/hwui/Android.bp | 1 | ||||
| -rw-r--r-- | libs/hwui/pipeline/skia/ShaderCache.cpp | 56 | ||||
| -rw-r--r-- | libs/hwui/pipeline/skia/ShaderCache.h | 46 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CacheManager.cpp | 7 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CacheManager.h | 2 | ||||
| -rw-r--r-- | libs/hwui/renderthread/RenderThread.cpp | 4 | ||||
| -rw-r--r-- | libs/hwui/renderthread/VulkanManager.cpp | 3 | ||||
| -rw-r--r-- | libs/hwui/tests/unit/ShaderCacheTests.cpp | 143 |
8 files changed, 229 insertions, 33 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d5829838cb3f..edce3050255b 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -72,6 +72,7 @@ cc_defaults { "libft2", "libminikin", "libandroidfw", + "libcrypto", ], static_libs: [ "libEGL_blobCache", diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 670074871c71..073b4814305e 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -18,6 +18,8 @@ #include <algorithm> #include <log/log.h> #include <thread> +#include <array> +#include <openssl/sha.h> #include "FileBlobCache.h" #include "Properties.h" #include "utils/TraceUtils.h" @@ -41,7 +43,40 @@ ShaderCache& ShaderCache::get() { return sCache; } -void ShaderCache::initShaderDiskCache() { +bool ShaderCache::validateCache(const void* identity, ssize_t size) { + if (nullptr == identity && size == 0) + return true; + + if (nullptr == identity || size < 0) { + if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { + ALOGW("ShaderCache::validateCache invalid cache identity"); + } + mBlobCache->clear(); + return false; + } + + SHA256_CTX ctx; + SHA256_Init(&ctx); + + SHA256_Update(&ctx, identity, size); + mIDHash.resize(SHA256_DIGEST_LENGTH); + SHA256_Final(mIDHash.data(), &ctx); + + std::array<uint8_t, SHA256_DIGEST_LENGTH> hash; + auto key = sIDKey; + auto loaded = mBlobCache->get(&key, sizeof(key), hash.data(), hash.size()); + + if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin())) + return true; + + if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { + ALOGW("ShaderCache::validateCache cache validation fails"); + } + mBlobCache->clear(); + return false; +} + +void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { ATRACE_NAME("initShaderDiskCache"); std::lock_guard<std::mutex> lock(mMutex); @@ -50,6 +85,7 @@ void ShaderCache::initShaderDiskCache() { // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds. if (!Properties::runningInEmulator && mFilename.length() > 0) { mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); + validateCache(identity, size); mInitialized = true; } } @@ -104,6 +140,18 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return SkData::MakeFromMalloc(valueBuffer, valueSize); } +void ShaderCache::saveToDiskLocked() { + ATRACE_NAME("ShaderCache::saveToDiskLocked"); + if (mInitialized && mBlobCache && mSavePending) { + if (mIDHash.size()) { + auto key = sIDKey; + mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size()); + } + mBlobCache->writeToFile(); + } + mSavePending = false; +} + void ShaderCache::store(const SkData& key, const SkData& data) { ATRACE_NAME("ShaderCache::store"); std::lock_guard<std::mutex> lock(mMutex); @@ -129,11 +177,7 @@ void ShaderCache::store(const SkData& key, const SkData& data) { std::thread deferredSaveThread([this]() { sleep(mDeferredSaveDelay); std::lock_guard<std::mutex> lock(mMutex); - ATRACE_NAME("ShaderCache::saveToDisk"); - if (mInitialized && mBlobCache) { - mBlobCache->writeToFile(); - } - mSavePending = false; + saveToDiskLocked(); }); deferredSaveThread.detach(); } diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 27473d67bd1a..82804cf93785 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -40,12 +40,21 @@ public: ANDROID_API static ShaderCache& get(); /** - * "initShaderDiskCache" loads the serialized cache contents from disk and puts the ShaderCache - * into an initialized state, such that it is able to insert and retrieve entries from the - * cache. This should be called when HWUI pipeline is initialized. When not in the initialized - * state the load and store methods will return without performing any cache operations. + * initShaderDiskCache" loads the serialized cache contents from disk, + * optionally checks that the on-disk cache matches a provided identity, + * and puts the ShaderCache into an initialized state, such that it is + * able to insert and retrieve entries from the cache. If identity is + * non-null and validation fails, the cache is initialized but contains + * no data. If size is less than zero, the cache is initilaized but + * contains no data. + * + * This should be called when HWUI pipeline is initialized. When not in + * the initialized state the load and store methods will return without + * performing any cache operations. */ - virtual void initShaderDiskCache(); + virtual void initShaderDiskCache(const void *identity, ssize_t size); + + virtual void initShaderDiskCache() { initShaderDiskCache(nullptr, 0); } /** * "setFilename" sets the name of the file that should be used to store @@ -83,6 +92,19 @@ private: BlobCache* getBlobCacheLocked(); /** + * "validateCache" updates the cache to match the given identity. If the + * cache currently has the wrong identity, all entries in the cache are cleared. + */ + bool validateCache(const void* identity, ssize_t size); + + /** + * "saveToDiskLocked" attemps to save the current contents of the cache to + * disk. If the identity hash exists, we will insert the identity hash into + * the cache for next validation. + */ + void saveToDiskLocked(); + + /** * "mInitialized" indicates whether the ShaderCache is in the initialized * state. It is initialized to false at construction time, and gets set to * true when initialize is called. @@ -111,6 +133,15 @@ private: std::string mFilename; /** + * "mIDHash" is the current identity hash for the cache validation. It is + * initialized to an empty vector at construction time, and its content is + * generated in the call of the validateCache method. An empty vector + * indicates that cache validation is not performed, and the hash should + * not be stored on disk. + */ + std::vector<uint8_t> mIDHash; + + /** * "mSavePending" indicates whether or not a deferred save operation is * pending. Each time a key/value pair is inserted into the cache via * load, a deferred save is initiated if one is not already pending. @@ -140,6 +171,11 @@ private: */ static ShaderCache sCache; + /** + * "sIDKey" is the cache key of the identity hash + */ + static constexpr uint8_t sIDKey = 0; + friend class ShaderCacheTestUtils; //used for unit testing }; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index bec80b1e6011..82bfc4943526 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -50,7 +50,6 @@ CacheManager::CacheManager(const DisplayInfo& display) : mMaxSurfaceArea(display mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas( mMaxSurfaceArea / 2, skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface); - skiapipeline::ShaderCache::get().initShaderDiskCache(); } void CacheManager::reset(sk_sp<GrContext> context) { @@ -103,7 +102,7 @@ public: } }; -void CacheManager::configureContext(GrContextOptions* contextOptions) { +void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity, ssize_t size) { contextOptions->fAllowPathMaskCaching = true; float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f; @@ -133,7 +132,9 @@ void CacheManager::configureContext(GrContextOptions* contextOptions) { contextOptions->fExecutor = mTaskProcessor.get(); } - contextOptions->fPersistentCache = &skiapipeline::ShaderCache::get(); + auto& cache = skiapipeline::ShaderCache::get(); + cache.initShaderDiskCache(identity, size); + contextOptions->fPersistentCache = &cache; contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting; } diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 7d733525194f..35fc91a42510 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -44,7 +44,7 @@ class CacheManager { public: enum class TrimMemoryMode { Complete, UiHidden }; - void configureContext(GrContextOptions* context); + void configureContext(GrContextOptions* context, const void* identity, ssize_t size); void trimMemory(TrimMemoryMode mode); void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index c1284ec02655..36ffaee2458b 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -191,7 +191,9 @@ void RenderThread::requireGlContext() { GrContextOptions options; options.fPreferExternalImagesOverES3 = true; options.fDisableDistanceFieldPaths = true; - cacheManager().configureContext(&options); + auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + auto size = glesVersion ? strlen(glesVersion) : -1; + cacheManager().configureContext(&options, glesVersion, size); sk_sp<GrContext> grContext(GrContext::MakeGL(std::move(glInterface), options)); LOG_ALWAYS_FATAL_IF(!grContext.get()); setGrContext(grContext); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 1517f579a084..cc4b87adfd7b 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -391,7 +391,8 @@ void VulkanManager::initialize() { GrContextOptions options; options.fDisableDistanceFieldPaths = true; - mRenderThread.cacheManager().configureContext(&options); + // TODO: get a string describing the SPIR-V compiler version and use it here + mRenderThread.cacheManager().configureContext(&options, nullptr, 0); sk_sp<GrContext> grContext(GrContext::MakeVulkan(backendContext, options)); LOG_ALWAYS_FATAL_IF(!grContext.get()); mRenderThread.setGrContext(grContext); diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 43080a9460b3..1433aa0349f4 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -48,11 +48,18 @@ public: */ static void terminate(ShaderCache& cache, bool saveContent) { std::lock_guard<std::mutex> lock(cache.mMutex); - if (cache.mInitialized && cache.mBlobCache && saveContent) { - cache.mBlobCache->writeToFile(); - } + cache.mSavePending = saveContent; + cache.saveToDiskLocked(); cache.mBlobCache = NULL; } + + /** + * + */ + template <typename T> + static bool validateCache(ShaderCache& cache, std::vector<T> hash) { + return cache.validateCache(hash.data(), hash.size() * sizeof(T)); + } }; } /* namespace skiapipeline */ @@ -75,26 +82,39 @@ bool folderExist(const std::string& folderName) { return false; } -bool checkShader(const sk_sp<SkData>& shader, const char* program) { +inline bool +checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) { + return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() + && 0 == memcmp(shader1->data(), shader2->data(), shader1->size()); +} + +inline bool +checkShader(const sk_sp<SkData>& shader, const char* program) { sk_sp<SkData> shader2 = SkData::MakeWithCString(program); - return shader->size() == shader2->size() - && 0 == memcmp(shader->data(), shader2->data(), shader->size()); + return checkShader(shader, shader2); } -bool checkShader(const sk_sp<SkData>& shader, std::vector<char>& program) { - sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size()); - return shader->size() == shader2->size() - && 0 == memcmp(shader->data(), shader2->data(), shader->size()); +template <typename T> +bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) { + sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T)); + return checkShader(shader, shader2); } void setShader(sk_sp<SkData>& shader, const char* program) { shader = SkData::MakeWithCString(program); } -void setShader(sk_sp<SkData>& shader, std::vector<char>& program) { - shader = SkData::MakeWithCopy(program.data(), program.size()); +template <typename T> +void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) { + shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T)); } +template <typename T> +void genRandomData(std::vector<T>& buffer) { + for (auto& data : buffer) { + data = T(std::rand()); + } +} #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get()) @@ -110,6 +130,7 @@ TEST(ShaderCacheTest, testWriteAndRead) { //remove any test files from previous test run int deleteFile = remove(cacheFile1.c_str()); ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + std::srand(0); //read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); @@ -158,10 +179,8 @@ TEST(ShaderCacheTest, testWriteAndRead) { //write and read big data chunk (50K) size_t dataSize = 50*1024; - std::vector<char> dataBuffer(dataSize); - for (size_t i = 0; i < dataSize; i++) { - dataBuffer[0] = dataSize % 256; - } + std::vector<uint8_t> dataBuffer(dataSize); + genRandomData(dataBuffer); setShader(inVS, dataBuffer); ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); @@ -173,4 +192,96 @@ TEST(ShaderCacheTest, testWriteAndRead) { remove(cacheFile1.c_str()); } +TEST(ShaderCacheTest, testCacheValidation) { + if (!folderExist(getExternalStorageFolder())) { + //don't run the test if external storage folder is not available + return; + } + std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; + std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; + + //remove any test files from previous test run + int deleteFile = remove(cacheFile1.c_str()); + ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + std::srand(0); + + //generate identity and read the cache from a file that does not exist + ShaderCache::get().setFilename(cacheFile1.c_str()); + ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save + std::vector<uint8_t> identity(1024); + genRandomData(identity); + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + + // generate random content in cache and store to disk + constexpr size_t numBlob(10); + constexpr size_t keySize(1024); + constexpr size_t dataSize(50 * 1024); + + std::vector< std::pair<sk_sp<SkData>, sk_sp<SkData>> > blobVec(numBlob); + for (auto& blob : blobVec) { + std::vector<uint8_t> keyBuffer(keySize); + std::vector<uint8_t> dataBuffer(dataSize); + genRandomData(keyBuffer); + genRandomData(dataBuffer); + + sk_sp<SkData> key, data; + setShader(key, keyBuffer); + setShader(data, dataBuffer); + + blob = std::make_pair(key, data); + ShaderCache::get().store(*key.get(), *data.get()); + } + ShaderCacheTestUtils::terminate(ShaderCache::get(), true); + + // change to a file that does not exist and verify validation fails + ShaderCache::get().setFilename(cacheFile2.c_str()); + ShaderCache::get().initShaderDiskCache(); + ASSERT_FALSE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + + // restore the original file and verify validation succeeds + ShaderCache::get().setFilename(cacheFile1.c_str()); + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + for (const auto& blob : blobVec) { + auto outVS = ShaderCache::get().load(*blob.first.get()); + ASSERT_TRUE( checkShader(outVS, blob.second) ); + } + + // generate error identity and verify load fails + ShaderCache::get().initShaderDiskCache(identity.data(), -1); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + ShaderCache::get().initShaderDiskCache(nullptr, identity.size() * + sizeof(decltype(identity)::value_type)); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + + // verify the cache validation again after load fails + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + for (const auto& blob : blobVec) { + auto outVS = ShaderCache::get().load(*blob.first.get()); + ASSERT_TRUE( checkShader(outVS, blob.second) ); + } + + // generate another identity and verify load fails + for (auto& data : identity) { + data += std::rand(); + } + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + remove(cacheFile1.c_str()); +} + } // namespace |