Implement SkSL cache

Implement SkSL cache by reusing code and logic from egl_cache_t.

Test: Improves startup times for gmail by 15ms and 10ms for calc app.
Bug: 66740665
Change-Id: I9ba479c649ba97a2c29a48d40579ba001264c957
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 2644292..24ce1e4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -68,6 +68,7 @@
     ],
     static_libs: [
         "libplatformprotos",
+        "libEGL_blobCache",
     ],
 }
 
@@ -131,6 +132,7 @@
         "pipeline/skia/LayerDrawable.cpp",
         "pipeline/skia/RenderNodeDrawable.cpp",
         "pipeline/skia/ReorderBarrierDrawables.cpp",
+        "pipeline/skia/ShaderCache.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
         "pipeline/skia/SkiaOpenGLPipeline.cpp",
         "pipeline/skia/SkiaOpenGLReadback.cpp",
@@ -346,6 +348,7 @@
         "tests/unit/RecordingCanvasTests.cpp",
         "tests/unit/RenderNodeTests.cpp",
         "tests/unit/RenderPropertiesTests.cpp",
+        "tests/unit/ShaderCacheTests.cpp",
         "tests/unit/SkiaBehaviorTests.cpp",
         "tests/unit/SkiaDisplayListTests.cpp",
         "tests/unit/SkiaPipelineTests.cpp",
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
new file mode 100644
index 0000000..87edd69
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ShaderCache.h"
+#include <algorithm>
+#include <log/log.h>
+#include <thread>
+#include "FileBlobCache.h"
+#include "utils/TraceUtils.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// Cache size limits.
+static const size_t maxKeySize = 1024;
+static const size_t maxValueSize = 64 * 1024;
+static const size_t maxTotalSize = 512 * 1024;
+
+ShaderCache::ShaderCache() {
+    // There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header.
+}
+
+ShaderCache ShaderCache::sCache;
+
+ShaderCache& ShaderCache::get() {
+    return sCache;
+}
+
+void ShaderCache::initShaderDiskCache() {
+    ATRACE_NAME("initShaderDiskCache");
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mFilename.length() > 0) {
+        mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
+        mInitialized = true;
+    }
+}
+
+void ShaderCache::setFilename(const char* filename) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mFilename = filename;
+}
+
+BlobCache* ShaderCache::getBlobCacheLocked() {
+    LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
+    return mBlobCache.get();
+}
+
+sk_sp<SkData> ShaderCache::load(const SkData& key) {
+    ATRACE_NAME("ShaderCache::load");
+    size_t keySize = key.size();
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!mInitialized) {
+        ALOGE("ShaderCache::load not initialized");
+        return nullptr;
+    }
+
+    // mObservedBlobValueSize is reasonably big to avoid memory reallocation
+    // Allocate a buffer with malloc. SkData takes ownership of that allocation and will call free.
+    void* valueBuffer = malloc(mObservedBlobValueSize);
+    if (!valueBuffer) {
+        return nullptr;
+    }
+    BlobCache* bc = getBlobCacheLocked();
+    size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+    int maxTries = 3;
+    while (valueSize > mObservedBlobValueSize && maxTries > 0) {
+        mObservedBlobValueSize = std::min(valueSize, maxValueSize);
+        valueBuffer = realloc(valueBuffer, mObservedBlobValueSize);
+        if (!valueBuffer) {
+            return nullptr;
+        }
+        valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+        maxTries--;
+    }
+    if (!valueSize) {
+        free(valueBuffer);
+        return nullptr;
+    }
+    if (valueSize > mObservedBlobValueSize) {
+        ALOGE("ShaderCache::load value size is too big %d", (int) valueSize);
+        free(valueBuffer);
+        return nullptr;
+    }
+    return SkData::MakeFromMalloc(valueBuffer, valueSize);
+}
+
+void ShaderCache::store(const SkData& key, const SkData& data) {
+    ATRACE_NAME("ShaderCache::store");
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    if (!mInitialized) {
+        ALOGE("ShaderCache::store not initialized");
+        return;
+    }
+
+    size_t valueSize = data.size();
+    size_t keySize = key.size();
+    if (keySize == 0 || valueSize == 0 || valueSize >= maxValueSize) {
+        ALOGW("ShaderCache::store: sizes %d %d not allowed", (int)keySize, (int)valueSize);
+        return;
+    }
+
+    const void* value = data.data();
+
+    BlobCache* bc = getBlobCacheLocked();
+    bc->set(key.data(), keySize, value, valueSize);
+
+    if (!mSavePending && mDeferredSaveDelay > 0) {
+        mSavePending = true;
+        std::thread deferredSaveThread([this]() {
+            sleep(mDeferredSaveDelay);
+            std::lock_guard<std::mutex> lock(mMutex);
+            ATRACE_NAME("ShaderCache::saveToDisk");
+            if (mInitialized && mBlobCache) {
+                mBlobCache->writeToFile();
+            }
+            mSavePending = false;
+        });
+        deferredSaveThread.detach();
+    }
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
new file mode 100644
index 0000000..27473d6
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cutils/compiler.h>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+#include <GrContextOptions.h>
+
+namespace android {
+
+class BlobCache;
+class FileBlobCache;
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class ShaderCache : public GrContextOptions::PersistentCache {
+public:
+    /**
+     * "get" returns a pointer to the singleton ShaderCache object.  This
+     * singleton object will never be destroyed.
+     */
+    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.
+     */
+    virtual void initShaderDiskCache();
+
+    /**
+     * "setFilename" sets the name of the file that should be used to store
+     * cache contents from one program invocation to another. This function does not perform any
+     * disk operation and it should be invoked before "initShaderCache".
+     */
+    virtual void setFilename(const char* filename);
+
+    /**
+     * "load" attempts to retrieve the value blob associated with a given key
+     * blob from cache.  This will be called by Skia, when it needs to compile a new SKSL shader.
+     */
+    sk_sp<SkData> load(const SkData& key) override;
+
+    /**
+     * "store" attempts to insert a new key/value blob pair into the cache.
+     * This will be called by Skia after it compiled a new SKSL shader
+     */
+    void store(const SkData& key, const SkData& data) override;
+
+private:
+    // Creation and (the lack of) destruction is handled internally.
+    ShaderCache();
+
+    // Copying is disallowed.
+    ShaderCache(const ShaderCache&) = delete;
+    void operator=(const ShaderCache&) = delete;
+
+    /**
+     * "getBlobCacheLocked" returns the BlobCache object being used to store the
+     * key/value blob pairs.  If the BlobCache object has not yet been created,
+     * this will do so, loading the serialized cache contents from disk if
+     * possible.
+     */
+    BlobCache* getBlobCacheLocked();
+
+    /**
+     * "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.
+     * When in this state, the cache behaves as normal.  When not,
+     * the load and store methods will return without performing any cache
+     * operations.
+     */
+    bool mInitialized = false;
+
+    /**
+     * "mBlobCache" is the cache in which the key/value blob pairs are stored.  It
+     * is initially NULL, and will be initialized by getBlobCacheLocked the
+     * first time it's needed.
+     * The blob cache contains the Android build number. We treat version mismatches as an empty
+     * cache (logic implemented in BlobCache::unflatten).
+     */
+    std::unique_ptr<FileBlobCache> mBlobCache;
+
+    /**
+     * "mFilename" is the name of the file for storing cache contents in between
+     * program invocations.  It is initialized to an empty string at
+     * construction time, and can be set with the setCacheFilename method.  An
+     * empty string indicates that the cache should not be saved to or restored
+     * from disk.
+     */
+    std::string mFilename;
+
+    /**
+     * "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.
+     * This will wait some amount of time and then trigger a save of the cache
+     * contents to disk.
+     */
+    bool mSavePending = false;
+
+    /**
+     *  "mObservedBlobValueSize" is the maximum value size observed by the cache reading function.
+     */
+    size_t mObservedBlobValueSize = 20*1024;
+
+    /**
+     *  The time in seconds to wait before saving newly inserted cache entries.
+     */
+    unsigned int mDeferredSaveDelay = 4;
+
+    /**
+     * "mMutex" is the mutex used to prevent concurrent access to the member
+     * variables. It must be locked whenever the member variables are accessed.
+     */
+    mutable std::mutex mMutex;
+
+    /**
+     * "sCache" is the singleton ShaderCache object.
+     */
+    static ShaderCache sCache;
+
+    friend class ShaderCacheTestUtils; //used for unit testing
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index a33b287..c22364b 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -18,6 +18,7 @@
 
 #include "Layer.h"
 #include "RenderThread.h"
+#include "pipeline/skia/ShaderCache.h"
 #include "renderstate/RenderState.h"
 
 #include <GrContextOptions.h>
@@ -127,6 +128,8 @@
         }
         contextOptions->fExecutor = mTaskProcessor.get();
     }
+
+    contextOptions->fPersistentCache = &skiapipeline::ShaderCache::get();
 }
 
 void CacheManager::trimMemory(TrimMemoryMode mode) {
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index c117cb8..574bb02 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -16,6 +16,7 @@
 
 #include "RenderThread.h"
 
+#include "pipeline/skia/ShaderCache.h"
 #include "CanvasContext.h"
 #include "EglManager.h"
 #include "OpenGLReadback.h"
@@ -105,6 +106,7 @@
     mRenderState = new RenderState(*this);
     mVkManager = new VulkanManager(*this);
     mCacheManager = new CacheManager(mDisplayInfo);
+    uirenderer::skiapipeline::ShaderCache::get().initShaderDiskCache();
 }
 
 void RenderThread::dumpGraphicsMemory(int fd) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 3272d69..1d8cdd6 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -107,8 +107,11 @@
 
     mGetDeviceQueue(mBackendContext->fDevice, mPresentQueueIndex, 0, &mPresentQueue);
 
+    GrContextOptions options;
+    options.fDisableDistanceFieldPaths = true;
+    mRenderThread.cacheManager().configureContext(&options);
     mRenderThread.setGrContext(
-            GrContext::Create(kVulkan_GrBackend, (GrBackendContext)mBackendContext.get()));
+            GrContext::Create(kVulkan_GrBackend, (GrBackendContext)mBackendContext.get(), options));
     DeviceInfo::initialize(mRenderThread.getGrContext()->caps()->maxRenderTargetSize());
 
     if (Properties::enablePartialUpdates && Properties::useBufferAge) {
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
new file mode 100644
index 0000000..43080a9
--- /dev/null
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <dirent.h>
+#include <cutils/properties.h>
+#include <cstdint>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+#include "pipeline/skia/ShaderCache.h"
+#include "FileBlobCache.h"
+
+using namespace android::uirenderer::skiapipeline;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ShaderCacheTestUtils {
+public:
+    /**
+     * "setSaveDelay" sets the time in seconds to wait before saving newly inserted cache entries.
+     * If set to 0, then deferred save is disabled.
+     */
+    static void setSaveDelay(ShaderCache& cache, unsigned int saveDelay) {
+        cache.mDeferredSaveDelay = saveDelay;
+    }
+
+    /**
+     * "terminate" optionally stores the BlobCache on disk and release all in-memory cache.
+     * Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
+     */
+    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.mBlobCache = NULL;
+    }
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
+
+
+namespace {
+
+std::string getExternalStorageFolder() {
+    return getenv("EXTERNAL_STORAGE");
+}
+
+bool folderExist(const std::string& folderName) {
+    DIR* dir = opendir(folderName.c_str());
+    if (dir) {
+        closedir(dir);
+        return true;
+    }
+    return false;
+}
+
+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());
+}
+
+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());
+}
+
+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());
+}
+
+
+
+#define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get())
+
+TEST(ShaderCacheTest, testWriteAndRead) {
+    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);
+
+    //read the cache from a file that does not exist
+    ShaderCache::get().setFilename(cacheFile1.c_str());
+    ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save
+    ShaderCache::get().initShaderDiskCache();
+
+    //read a key - should not be found since the cache is empty
+    sk_sp<SkData> outVS;
+    ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
+
+    //write to the in-memory cache without storing on disk and verify we read the same values
+    sk_sp<SkData> inVS;
+    setShader(inVS, "sassas");
+    ShaderCache::get().store(GrProgramDescTest(100), *inVS.get());
+    setShader(inVS, "someVS");
+    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+    ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
+    ASSERT_TRUE(checkShader(outVS, "sassas"));
+    ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
+    ASSERT_TRUE(checkShader(outVS, "someVS"));
+
+    //store content to disk and release in-memory cache
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
+
+    //change to a file that does not exist and verify load fails
+    ShaderCache::get().setFilename(cacheFile2.c_str());
+    ShaderCache::get().initShaderDiskCache();
+    ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+
+    //load again content from disk from an existing file and check the data is read correctly
+    ShaderCache::get().setFilename(cacheFile1.c_str());
+    ShaderCache::get().initShaderDiskCache();
+    sk_sp<SkData> outVS2;
+    ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
+    ASSERT_TRUE(checkShader(outVS2, "someVS"));
+
+    //change data, store to disk, read back again and verify data has been changed
+    setShader(inVS, "ewData1");
+    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
+    ShaderCache::get().initShaderDiskCache();
+    ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
+    ASSERT_TRUE(checkShader(outVS2, "ewData1"));
+
+
+    //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;
+    }
+    setShader(inVS, dataBuffer);
+    ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
+    ShaderCache::get().initShaderDiskCache();
+    ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
+    ASSERT_TRUE(checkShader(outVS2, dataBuffer));
+
+    ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+    remove(cacheFile1.c_str());
+}
+
+}  // namespace