centralize runtime effect caching

Relative runtimes for creating luma color filter:

    - cached SkColorFilter   -->   1x
    - cached SkRuntimeEffect -->  12x
    - no caching             --> 186x
    - this CL                -->  24x

Added insert_or_update() to SkLRUCache because
I keep falling into this trap using insert()...

Change-Id: Ic3dad32d38fc001701d8e1ad5662ad382503418f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/376776
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/core/SkColorFilter.cpp b/src/core/SkColorFilter.cpp
index 4031d5e..606c7b9 100644
--- a/src/core/SkColorFilter.cpp
+++ b/src/core/SkColorFilter.cpp
@@ -443,16 +443,13 @@
         return cf1;
     }
 
-    static SkRuntimeEffect* effect = [&]{
-        auto [effect,err] = SkRuntimeEffect::Make(SkString{
-            "uniform shader cf0;"
-            "uniform shader cf1;"
-            "uniform half   weight;"
-            "half4 main() { return mix(sample(cf0), sample(cf1), weight); }"
-        });
-        SkASSERT(effect && err.isEmpty());
-        return effect.release();
-    }();
+    auto [effect,err] = SkRuntimeEffect::Make(SkString{
+        "uniform shader cf0;"
+        "uniform shader cf1;"
+        "uniform half   weight;"
+        "half4 main() { return mix(sample(cf0), sample(cf1), weight); }"
+    });
+    SkASSERT(effect && err.isEmpty());
 
     sk_sp<SkColorFilter> inputs[] = {cf0,cf1};
     return effect->makeColorFilter(SkData::MakeWithCopy(&weight, sizeof(weight)),
diff --git a/src/core/SkLRUCache.h b/src/core/SkLRUCache.h
index d5be4f3..50429d5 100644
--- a/src/core/SkLRUCache.h
+++ b/src/core/SkLRUCache.h
@@ -56,6 +56,8 @@
     }
 
     V* insert(const K& key, V value) {
+        SkASSERT(!this->find(key));
+
         Entry* entry = new Entry(key, std::move(value));
         fMap.set(entry);
         fLRU.addToHead(entry);
@@ -65,6 +67,15 @@
         return &entry->fValue;
     }
 
+    V* insert_or_update(const K& key, V value) {
+        if (V* found = this->find(key)) {
+            *found = std::move(value);
+            return found;
+        } else {
+            return this->insert(key, std::move(value));
+        }
+    }
+
     int count() {
         return fMap.count();
     }
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 868bf6d..f14d7df 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -15,7 +15,9 @@
 #include "src/core/SkColorFilterBase.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkLRUCache.h"
 #include "src/core/SkMatrixProvider.h"
+#include "src/core/SkOpts.h"
 #include "src/core/SkRasterPipeline.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkUtils.h"
@@ -125,7 +127,42 @@
     return false;
 }
 
+SK_BEGIN_REQUIRE_DENSE;
+struct Key {
+    uint32_t skslHashA;
+    uint32_t skslHashB;
+    int      inlineThreshold;
+
+    bool operator==(const Key& that) const {
+        return this->skslHashA        == that.skslHashA
+            && this->skslHashB        == that.skslHashB
+            && this->inlineThreshold  == that.inlineThreshold;
+    }
+
+    Key(const SkString& sksl, const SkRuntimeEffect::Options& options)
+        : skslHashA(SkOpts::hash(sksl.c_str(), sksl.size(), 0))
+        , skslHashB(SkOpts::hash(sksl.c_str(), sksl.size(), 1))
+        , inlineThreshold(options.inlineThreshold) {}
+};
+SK_END_REQUIRE_DENSE;
+
+static SkMutex gCacheLock;
+
+static SkLRUCache<Key, sk_sp<SkRuntimeEffect>>* cache() {
+    gCacheLock.assertHeld();
+    static auto* cache = new SkLRUCache<Key, sk_sp<SkRuntimeEffect>>(11/*totally arbitrary*/);
+    return cache;
+}
+
 SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl, const Options& options) {
+    Key key(sksl, options);
+    {
+        SkAutoMutexExclusive _(gCacheLock);
+        if (sk_sp<SkRuntimeEffect>* found = cache()->find(key)) {
+            return Result{*found, SkString()};
+        }
+    }
+
     SkSL::SharedCompiler compiler;
     SkSL::Program::Settings settings;
     settings.fInlineThreshold = options.inlineThreshold;
@@ -246,6 +283,10 @@
                                                       std::move(varyings),
                                                       usesSampleCoords,
                                                       allowColorFilter));
+    {
+        SkAutoMutexExclusive _(gCacheLock);
+        cache()->insert_or_update(key, effect);
+    }
     return Result{std::move(effect), SkString()};
 }
 
diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp
index 2d527c4..e6eed85 100644
--- a/src/core/SkVMBlitter.cpp
+++ b/src/core/SkVMBlitter.cpp
@@ -563,12 +563,7 @@
             if (SkLRUCache<Key, skvm::Program>* cache = try_acquire_program_cache()) {
                 auto cache_program = [&](skvm::Program&& program, Coverage coverage) {
                     if (!program.empty()) {
-                        Key key = fKey.withCoverage(coverage);
-                        if (skvm::Program* found = cache->find(key)) {
-                            *found = std::move(program);
-                        } else {
-                            cache->insert(key, std::move(program));
-                        }
+                        cache->insert_or_update(fKey.withCoverage(coverage), std::move(program));
                     }
                 };
                 cache_program(std::move(fBlitH),         Coverage::Full);
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
index e8dfc58..a0fcd2f 100644
--- a/src/effects/SkHighContrastFilter.cpp
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -18,38 +18,35 @@
 
     struct Uniforms { float grayscale, invertStyle, contrast; };
 
-    static SkRuntimeEffect* effect = []{
-        SkString code{R"(
-            uniform shader input;
-            uniform half grayscale, invertStyle, contrast;
-        )"};
-        code += kRGB_to_HSL_sksl;
-        code += kHSL_to_RGB_sksl;
-        code += R"(
-            half4 main() {
-                half4 c = sample(input);  // linear unpremul RGBA in dst gamut.
-                if (grayscale == 1) {
-                    c.rgb = dot(half3(0.2126, 0.7152, 0.0722), c.rgb).rrr;
-                }
-                if (invertStyle == 1/*brightness*/) {
-                    c.rgb = 1 - c.rgb;
-                } else if (invertStyle == 2/*lightness*/) {
-                    c.rgb = rgb_to_hsl(c.rgb);
-                    c.b = 1 - c.b;
-                    c.rgb = hsl_to_rgb(c.rgb);
-                }
-                c.rgb = mix(half3(0.5), c.rgb, contrast);
-                return half4(saturate(c.rgb), c.a);
+    SkString code{R"(
+        uniform shader input;
+        uniform half grayscale, invertStyle, contrast;
+    )"};
+    code += kRGB_to_HSL_sksl;
+    code += kHSL_to_RGB_sksl;
+    code += R"(
+        half4 main() {
+            half4 c = sample(input);  // linear unpremul RGBA in dst gamut.
+            if (grayscale == 1) {
+                c.rgb = dot(half3(0.2126, 0.7152, 0.0722), c.rgb).rrr;
             }
-        )";
-
-        auto [effect, err] = SkRuntimeEffect::Make(code);
-        if (!err.isEmpty()) {
-            SkDebugf("%s\n%s\n", code.c_str(), err.c_str());
+            if (invertStyle == 1/*brightness*/) {
+                c.rgb = 1 - c.rgb;
+            } else if (invertStyle == 2/*lightness*/) {
+                c.rgb = rgb_to_hsl(c.rgb);
+                c.b = 1 - c.b;
+                c.rgb = hsl_to_rgb(c.rgb);
+            }
+            c.rgb = mix(half3(0.5), c.rgb, contrast);
+            return half4(saturate(c.rgb), c.a);
         }
-        SkASSERT(effect && err.isEmpty());
-        return effect.release();
-    }();
+    )";
+
+    auto [effect, err] = SkRuntimeEffect::Make(code);
+    if (!err.isEmpty()) {
+        SkDebugf("%s\n%s\n", code.c_str(), err.c_str());
+    }
+    SkASSERT(effect && err.isEmpty());
 
     // A contrast setting of exactly +1 would divide by zero (1+c)/(1-c), so pull in to +1-ε.
     // I'm not exactly sure why we've historically pinned -1 up to -1+ε, maybe just symmetry?
diff --git a/src/effects/SkLumaColorFilter.cpp b/src/effects/SkLumaColorFilter.cpp
index df234b1..a62432a 100644
--- a/src/effects/SkLumaColorFilter.cpp
+++ b/src/effects/SkLumaColorFilter.cpp
@@ -10,17 +10,14 @@
 #include "include/effects/SkRuntimeEffect.h"
 
 sk_sp<SkColorFilter> SkLumaColorFilter::Make() {
-    static SkColorFilter* filter = []{
-        const char* code =
-            "uniform shader input;"
-            "half4 main() {"
-                "return saturate(dot(half3(0.2126, 0.7152, 0.0722), sample(input).rgb)).000r;"
-            "}";
-        auto [effect, err] = SkRuntimeEffect::Make(SkString{code}, SkRuntimeEffect::Options{});
-        SkASSERT(effect && err.isEmpty());
+    const char* code =
+        "uniform shader input;"
+        "half4 main() {"
+            "return saturate(dot(half3(0.2126, 0.7152, 0.0722), sample(input).rgb)).000r;"
+        "}";
+    auto [effect, err] = SkRuntimeEffect::Make(SkString{code});
+    SkASSERT(effect && err.isEmpty());
 
-        sk_sp<SkColorFilter> input = nullptr;
-        return effect->makeColorFilter(SkData::MakeEmpty(), &input, 1).release();
-    }();
-    return sk_ref_sp(filter);
+    sk_sp<SkColorFilter> input = nullptr;
+    return effect->makeColorFilter(SkData::MakeEmpty(), &input, 1);
 }