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