Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d495f439 authored by Stan Iliev's avatar Stan Iliev
Browse files

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
parent 2b267dfb
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -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);
+7 −2
Original line number Diff line number Diff line
@@ -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},
+3 −0
Original line number Diff line number Diff line
@@ -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",
+139 −0
Original line number Diff line number Diff line
/*
 * 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 */
+148 −0
Original line number Diff line number Diff line
/*
 * 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 */
Loading