Store VkPipelineCache to ShaderCache

Store vulkan pipeline cache in ShaderCache. Avoid writing to disk
unless pipeline cache size has changed or there was a new shader
compilation.

Test: Ran app startup test. Ran Calc app.
Bug: 122659224
Change-Id: Ie54ea8f0ee9c87387c310b369b2350310e20fa13
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 562a3b2..1661905 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -23,6 +23,7 @@
 #include "FileBlobCache.h"
 #include "Properties.h"
 #include "utils/TraceUtils.h"
+#include <GrContext.h>
 
 namespace android {
 namespace uirenderer {
@@ -168,6 +169,24 @@
     const void* value = data.data();
 
     BlobCache* bc = getBlobCacheLocked();
+    if (mInStoreVkPipelineInProgress) {
+        if (mOldPipelineCacheSize == -1) {
+            // Record the initial pipeline cache size stored in the file.
+            mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+        }
+        if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
+            // There has not been change in pipeline cache size. Stop trying to save.
+            mTryToStorePipelineCache = false;
+            return;
+        }
+        mNewPipelineCacheSize = valueSize;
+    } else {
+        mCacheDirty = true;
+        // If there are new shaders compiled, we probably have new pipeline state too.
+        // Store pipeline cache on the next flush.
+        mNewPipelineCacheSize = -1;
+        mTryToStorePipelineCache = true;
+    }
     bc->set(key.data(), keySize, value, valueSize);
 
     if (!mSavePending && mDeferredSaveDelay > 0) {
@@ -175,12 +194,31 @@
         std::thread deferredSaveThread([this]() {
             sleep(mDeferredSaveDelay);
             std::lock_guard<std::mutex> lock(mMutex);
-            saveToDiskLocked();
+            // Store file on disk if there a new shader or Vulkan pipeline cache size changed.
+            if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
+                saveToDiskLocked();
+                mOldPipelineCacheSize = mNewPipelineCacheSize;
+                mTryToStorePipelineCache = false;
+                mCacheDirty = false;
+            }
         });
         deferredSaveThread.detach();
     }
 }
 
+void ShaderCache::onVkFrameFlushed(GrContext* context) {
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        if (!mInitialized || !mTryToStorePipelineCache) {
+            return;
+        }
+    }
+    mInStoreVkPipelineInProgress = true;
+    context->storeVkPipelineCacheData();
+    mInStoreVkPipelineInProgress = false;
+}
+
 } /* namespace skiapipeline */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index d41aadb..0898017 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -75,6 +75,13 @@
      */
     void store(const SkData& key, const SkData& data) override;
 
+    /**
+     * "onVkFrameFlushed" tries to store Vulkan pipeline cache state.
+     * Pipeline cache is saved on disk only if the size of the data has changed or there was
+     * a new shader compiled.
+     */
+    void onVkFrameFlushed(GrContext* context);
+
 private:
     // Creation and (the lack of) destruction is handled internally.
     ShaderCache();
@@ -167,6 +174,33 @@
     mutable std::mutex mMutex;
 
     /**
+     *  If set to "true", the next call to onVkFrameFlushed, will invoke
+     * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk.
+     */
+    bool mTryToStorePipelineCache = true;
+
+    /**
+     * This flag is used by "ShaderCache::store" to distinguish between shader data and
+     * Vulkan pipeline data.
+     */
+    bool mInStoreVkPipelineInProgress = false;
+
+    /**
+     *  "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used
+     *  to prevent unnecessary disk writes, if the pipeline cache size has not changed.
+     */
+    size_t mNewPipelineCacheSize = -1;
+    /**
+     *  "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk.
+     */
+    size_t mOldPipelineCacheSize = -1;
+
+    /**
+     *  "mCacheDirty" is true when there is new shader cache data, which is not saved to disk.
+     */
+    bool mCacheDirty = false;
+
+    /**
      * "sCache" is the singleton ShaderCache object.
      */
     static ShaderCache sCache;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 1d3a244..b9aae98 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -23,6 +23,7 @@
 #include "VkInteropFunctorDrawable.h"
 #include "renderstate/RenderState.h"
 #include "renderthread/Frame.h"
+#include "ShaderCache.h"
 
 #include <SkSurface.h>
 #include <SkTypes.h>
@@ -73,6 +74,7 @@
     }
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
     renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer);
+    ShaderCache::get().onVkFrameFlushed(mRenderThread.getGrContext());
     layerUpdateQueue->clear();
 
     // Draw visual debugging features