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
This commit is contained in:
Stan Iliev
2017-10-09 15:49:32 -04:00
parent 2b267dfbe9
commit d495f43992
9 changed files with 487 additions and 5 deletions

View File

@@ -70,6 +70,7 @@ public final class ThreadedRenderer {
* Name of the file that holds the shaders cache.
*/
private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache";
/**
* System property used to enable or disable threaded rendering profiling.
@@ -272,7 +273,9 @@ public final class ThreadedRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
ThreadedRenderer.setupShadersDiskCache(
new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(),
new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath());
}
/**
@@ -1007,7 +1010,7 @@ public final class ThreadedRenderer {
/** Not actually public - internal use only. This doc to make lint happy */
public static native void disableVsync();
static native void setupShadersDiskCache(String cacheFile);
static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile);
private static native void nRotateProcessStatsBuffer();
private static native void nSetProcessStatsBuffer(int fd);

View File

@@ -54,6 +54,7 @@
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
#include <pipeline/skia/ShaderCache.h>
namespace android {
@@ -970,10 +971,14 @@ static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env
// ----------------------------------------------------------------------------
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath) {
jstring diskCachePath, jstring skiaDiskCachePath) {
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
android::egl_set_cache_filename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
}
// ----------------------------------------------------------------------------
@@ -1018,7 +1023,7 @@ static const JNINativeMethod gMethods[] = {
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
{ "nSerializeDisplayListTree", "(J)V", (void*) android_view_ThreadedRenderer_serializeDisplayListTree },
{ "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
{ "setupShadersDiskCache", "(Ljava/lang/String;Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
{ "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},

View File

@@ -68,6 +68,7 @@ cc_defaults {
],
static_libs: [
"libplatformprotos",
"libEGL_blobCache",
],
}
@@ -131,6 +132,7 @@ cc_defaults {
"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 @@ cc_test {
"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",

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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 @@ void CacheManager::configureContext(GrContextOptions* contextOptions) {
}
contextOptions->fExecutor = mTaskProcessor.get();
}
contextOptions->fPersistentCache = &skiapipeline::ShaderCache::get();
}
void CacheManager::trimMemory(TrimMemoryMode mode) {

View File

@@ -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 @@ void RenderThread::initThreadLocals() {
mRenderState = new RenderState(*this);
mVkManager = new VulkanManager(*this);
mCacheManager = new CacheManager(mDisplayInfo);
uirenderer::skiapipeline::ShaderCache::get().initShaderDiskCache();
}
void RenderThread::dumpGraphicsMemory(int fd) {

View File

@@ -107,8 +107,11 @@ void VulkanManager::initialize() {
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) {

View File

@@ -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