Add gltestpersistentcache config that tests GrContextOption's cache.

Uses a new GPU sink that runs each test twice, once to populate the
cache and then again with a new GrContext but a warmed cache. It
verifies that the two generated images are the same.

Change-Id: Iaba195a69751f14ea946afe7174228a813b83a63
Reviewed-on: https://skia-review.googlesource.com/140567
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 11b1943..b7a5c04 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1239,6 +1239,8 @@
     sources = [
       "tools/gpu/GrContextFactory.cpp",
       "tools/gpu/GrTest.cpp",
+      "tools/gpu/MemoryCache.cpp",
+      "tools/gpu/MemoryCache.h",
       "tools/gpu/ProxyUtils.cpp",
       "tools/gpu/TestContext.cpp",
       "tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp",
diff --git a/dm/DM.cpp b/dm/DM.cpp
index b8714d3..5b3642a 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -862,11 +862,18 @@
                 return nullptr;
             }
             if (gpuConfig->getTestThreading()) {
+                SkASSERT(!gpuConfig->getTestPersistentCache());
                 return new GPUThreadTestingSink(
                         contextType, contextOverrides, gpuConfig->getSurfType(),
                         gpuConfig->getSamples(), gpuConfig->getUseDIText(),
                         gpuConfig->getColorType(), gpuConfig->getAlphaType(),
                         sk_ref_sp(gpuConfig->getColorSpace()), FLAGS_gpu_threading, grCtxOptions);
+            } else if (gpuConfig->getTestPersistentCache()) {
+                return new GPUPersistentCacheTestingSink(
+                        contextType, contextOverrides, gpuConfig->getSurfType(),
+                        gpuConfig->getSamples(), gpuConfig->getUseDIText(),
+                        gpuConfig->getColorType(), gpuConfig->getAlphaType(),
+                        sk_ref_sp(gpuConfig->getColorSpace()), FLAGS_gpu_threading, grCtxOptions);
             } else {
                 return new GPUSink(contextType, contextOverrides, gpuConfig->getSurfType(),
                                    gpuConfig->getSamples(), gpuConfig->getUseDIText(),
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index fb67c52..25b87b3 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -11,6 +11,10 @@
 #include "../src/jumper/SkJumper.h"
 #include "DDLPromiseImageHelper.h"
 #include "DDLTileHelper.h"
+#include "GrBackendSurface.h"
+#include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "MemoryCache.h"
 #include "Resources.h"
 #include "SkAndroidCodec.h"
 #include "SkAutoMalloc.h"
@@ -75,10 +79,6 @@
 
 #include "../third_party/skcms/skcms.h"
 
-#include "GrBackendSurface.h"
-#include "GrContextPriv.h"
-#include "GrGpu.h"
-
 DEFINE_bool(multiPage, false, "For document-type backends, render the source"
             " into multiple pages");
 DEFINE_bool(RAW_threading, true, "Allow RAW decodes to run on multiple threads?");
@@ -1486,7 +1486,12 @@
                       const GrContextOptions& baseOptions) const {
     GrContextOptions grOptions = baseOptions;
 
+    // We don't expect the src to mess with the persistent cache or the executor.
+    SkDEBUGCODE(auto cache = grOptions.fPersistentCache);
+    SkDEBUGCODE(auto exec = grOptions.fExecutor);
     src.modifyGrContextOptions(&grOptions);
+    SkASSERT(cache == grOptions.fPersistentCache);
+    SkASSERT(exec == grOptions.fExecutor);
 
     GrContextFactory factory(grOptions);
     const SkISize size = src.size();
@@ -1617,6 +1622,47 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
+GPUPersistentCacheTestingSink::GPUPersistentCacheTestingSink(
+        GrContextFactory::ContextType ct,
+        GrContextFactory::ContextOverrides overrides,
+        SkCommandLineConfigGpu::SurfType surfType,
+        int samples,
+        bool diText,
+        SkColorType colorType,
+        SkAlphaType alphaType,
+        sk_sp<SkColorSpace> colorSpace,
+        bool threaded,
+        const GrContextOptions& grCtxOptions)
+        : INHERITED(ct, overrides, surfType, samples, diText, colorType, alphaType,
+                    std::move(colorSpace), threaded, grCtxOptions) {}
+
+Error GPUPersistentCacheTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream,
+                                          SkString* log) const {
+    // Draw twice, once with a cold cache, and again with a warm cache. Verify that we get the same
+    // result.
+    sk_gpu_test::MemoryCache memoryCache;
+    GrContextOptions contextOptions = this->baseContextOptions();
+    contextOptions.fPersistentCache = &memoryCache;
+
+    Error err = this->onDraw(src, dst, wStream, log, contextOptions);
+    if (!err.isEmpty() || !dst) {
+        return err;
+    }
+
+    SkBitmap reference;
+    SkString refLog;
+    SkDynamicMemoryWStream refStream;
+    memoryCache.resetNumCacheMisses();
+    Error refErr = this->onDraw(src, &reference, &refStream, &refLog, contextOptions);
+    if (!refErr.isEmpty()) {
+        return refErr;
+    }
+    SkASSERT(memoryCache.numCacheMisses());
+
+    return compare_bitmaps(reference, *dst);
+}
+
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 static Error draw_skdocument(const Src& src, SkDocument* doc, SkWStream* dst) {
     if (src.size().isEmpty()) {
         return "Source has empty dimensions";
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index 9c31c0a..86c2279 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -386,6 +386,26 @@
     typedef GPUSink INHERITED;
 };
 
+class GPUPersistentCacheTestingSink : public GPUSink {
+public:
+    GPUPersistentCacheTestingSink(sk_gpu_test::GrContextFactory::ContextType,
+                                  sk_gpu_test::GrContextFactory::ContextOverrides,
+                                  SkCommandLineConfigGpu::SurfType surfType, int samples,
+                                  bool diText, SkColorType colorType, SkAlphaType alphaType,
+                                  sk_sp<SkColorSpace> colorSpace, bool threaded,
+                                  const GrContextOptions& grCtxOptions);
+
+    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+
+    const char* fileExtension() const override {
+        // Suppress writing out results from this config - we just want to do our matching test
+        return nullptr;
+    }
+
+private:
+    typedef GPUSink INHERITED;
+};
+
 class PDFSink : public Sink {
 public:
     PDFSink(bool pdfa, SkScalar rasterDpi) : fPDFA(pdfa), fRasterDpi(rasterDpi) {}
diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp
index aff7a3f..7aca8f5 100644
--- a/tools/flags/SkCommonFlagsConfig.cpp
+++ b/tools/flags/SkCommonFlagsConfig.cpp
@@ -29,6 +29,7 @@
 
 #undef DEFAULT_GPU_CONFIG
 
+// clang-format off
 static const struct {
     const char* predefinedConfig;
     const char* backend;
@@ -67,6 +68,7 @@
     { "gldft",                 "gpu", "api=gl,dit=true" },
     { "glesdft",               "gpu", "api=gles,dit=true" },
     { "gltestthreading",       "gpu", "api=gl,testThreading=true" },
+    { "gltestpersistentcache", "gpu", "api=gl,testPersistentCache=true" },
     { "debuggl",               "gpu", "api=debuggl" },
     { "nullgl",                "gpu", "api=nullgl" },
     { "angle_d3d11_es2",       "gpu", "api=angle_d3d11_es2" },
@@ -103,6 +105,7 @@
     ,{ "mtlmsaa8",              "gpu", "api=metal,samples=8" }
 #endif
 };
+// clang-format on
 
 static const char configHelp[] =
     "Options: 565 8888 srgb f16 nonrendering null pdf pdfa skp pipe svg xps";
@@ -118,63 +121,65 @@
 }
 
 static const char configExtendedHelp[] =
-    "Extended form: 'backend(option=value,...)'\n\n"
-    "Possible backends and options:\n"
-    "\n"
-    "gpu[api=string,color=string,dit=bool,nvpr=bool,inst=bool,samples=int]\n"
-    "\tapi\ttype: string\trequired\n"
-    "\t    Select graphics API to use with gpu backend.\n"
-    "\t    Options:\n"
-    "\t\tgl    \t\t\tUse OpenGL.\n"
-    "\t\tgles  \t\t\tUse OpenGL ES.\n"
-    "\t\tdebuggl \t\tUse debug OpenGL.\n"
-    "\t\tnullgl \t\t\tUse null OpenGL.\n"
-    "\t\tangle_d3d9_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D9 backend.\n"
-    "\t\tangle_d3d11_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D11 backend.\n"
-    "\t\tangle_d3d11_es3\t\tUse OpenGL ES3 on the ANGLE Direct3D11 backend.\n"
-    "\t\tangle_gl_es2\t\tUse OpenGL ES2 on the ANGLE OpenGL backend.\n"
-    "\t\tangle_gl_es3\t\tUse OpenGL ES3 on the ANGLE OpenGL backend.\n"
-    "\t\tcommandbuffer\t\tUse command buffer.\n"
-    "\t\tmock\t\t\tUse mock context.\n"
+        "Extended form: 'backend(option=value,...)'\n\n"
+        "Possible backends and options:\n"
+        "\n"
+        "gpu[api=string,color=string,dit=bool,nvpr=bool,inst=bool,samples=int]\n"
+        "\tapi\ttype: string\trequired\n"
+        "\t    Select graphics API to use with gpu backend.\n"
+        "\t    Options:\n"
+        "\t\tgl    \t\t\tUse OpenGL.\n"
+        "\t\tgles  \t\t\tUse OpenGL ES.\n"
+        "\t\tdebuggl \t\tUse debug OpenGL.\n"
+        "\t\tnullgl \t\t\tUse null OpenGL.\n"
+        "\t\tangle_d3d9_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D9 backend.\n"
+        "\t\tangle_d3d11_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D11 backend.\n"
+        "\t\tangle_d3d11_es3\t\tUse OpenGL ES3 on the ANGLE Direct3D11 backend.\n"
+        "\t\tangle_gl_es2\t\tUse OpenGL ES2 on the ANGLE OpenGL backend.\n"
+        "\t\tangle_gl_es3\t\tUse OpenGL ES3 on the ANGLE OpenGL backend.\n"
+        "\t\tcommandbuffer\t\tUse command buffer.\n"
+        "\t\tmock\t\t\tUse mock context.\n"
 #ifdef SK_VULKAN
-    "\t\tvulkan\t\t\tUse Vulkan.\n"
+        "\t\tvulkan\t\t\tUse Vulkan.\n"
 #endif
 #ifdef SK_METAL
-    "\t\tmetal\t\t\tUse Metal.\n"
+        "\t\tmetal\t\t\tUse Metal.\n"
 #endif
-    "\tcolor\ttype: string\tdefault: 8888.\n"
-    "\t    Select framebuffer color format.\n"
-    "\t    Options:\n"
-    "\t\t8888\t\t\tLinear 8888.\n"
-    "\t\t888x\t\t\tLinear 888x.\n"
-    "\t\t4444\t\t\tLinear 4444.\n"
-    "\t\t565\t\t\tLinear 565.\n"
-    "\t\t1010102\t\t\tLinear 1010102.\n"
-    "\t\tsrgb\t\t\tsRGB 8888.\n"
-    "\t\tesrgb\t\t\tsRGB 16-bit floating point.\n"
-    "\t\tnarrow\t\t\tNarrow gamut 8888.\n"
-    "\t\tenarrow\t\t\tNarrow gamut 16-bit floating point.\n"
-    "\t\tf16\t\t\tLinearly blended 16-bit floating point.\n"
-    "\tdit\ttype: bool\tdefault: false.\n"
-    "\t    Use device independent text.\n"
-    "\tnvpr\ttype: bool\tdefault: false.\n"
-    "\t    Use NV_path_rendering OpenGL and OpenGL ES extension.\n"
-    "\tsamples\ttype: int\tdefault: 0.\n"
-    "\t    Use multisampling with N samples.\n"
-    "\tstencils\ttype: bool\tdefault: true.\n"
-    "\t    Allow the use of stencil buffers.\n"
-    "\ttestThreading\ttype: bool\tdefault: false.\n"
-    "\t    Run with and without worker threads, check that results match.\n"
-    "\tsurf\ttype: string\tdefault: default.\n"
-    "\t    Controls the type of backing store for SkSurfaces.\n"
-    "\t    Options:\n"
-    "\t\tdefault\t\t\tA renderable texture created in Skia's resource cache.\n"
-    "\t\tbetex\t\t\tA wrapped backend texture.\n"
-    "\t\tbert\t\t\tA wrapped backend render target\n"
-    "\n"
-    "Predefined configs:\n\n"
-    // Help text for pre-defined configs is auto-generated from gPredefinedConfigs
-    ;
+        "\tcolor\ttype: string\tdefault: 8888.\n"
+        "\t    Select framebuffer color format.\n"
+        "\t    Options:\n"
+        "\t\t8888\t\t\tLinear 8888.\n"
+        "\t\t888x\t\t\tLinear 888x.\n"
+        "\t\t4444\t\t\tLinear 4444.\n"
+        "\t\t565\t\t\tLinear 565.\n"
+        "\t\t1010102\t\t\tLinear 1010102.\n"
+        "\t\tsrgb\t\t\tsRGB 8888.\n"
+        "\t\tesrgb\t\t\tsRGB 16-bit floating point.\n"
+        "\t\tnarrow\t\t\tNarrow gamut 8888.\n"
+        "\t\tenarrow\t\t\tNarrow gamut 16-bit floating point.\n"
+        "\t\tf16\t\t\tLinearly blended 16-bit floating point.\n"
+        "\tdit\ttype: bool\tdefault: false.\n"
+        "\t    Use device independent text.\n"
+        "\tnvpr\ttype: bool\tdefault: false.\n"
+        "\t    Use NV_path_rendering OpenGL and OpenGL ES extension.\n"
+        "\tsamples\ttype: int\tdefault: 0.\n"
+        "\t    Use multisampling with N samples.\n"
+        "\tstencils\ttype: bool\tdefault: true.\n"
+        "\t    Allow the use of stencil buffers.\n"
+        "\ttestThreading\ttype: bool\tdefault: false.\n"
+        "\t    Run with and without worker threads, check that results match.\n"
+        "\ttestPersistentCache\ttype: bool\tdefault: false.\n"
+        "\t    Run using a pre-warmed GrContextOption::fPersistentCache.\n"
+        "\tsurf\ttype: string\tdefault: default.\n"
+        "\t    Controls the type of backing store for SkSurfaces.\n"
+        "\t    Options:\n"
+        "\t\tdefault\t\t\tA renderable texture created in Skia's resource cache.\n"
+        "\t\tbetex\t\t\tA wrapped backend texture.\n"
+        "\t\tbert\t\t\tA wrapped backend render target\n"
+        "\n"
+        "Predefined configs:\n\n"
+        // Help text for pre-defined configs is auto-generated from gPredefinedConfigs
+        ;
 
 static const char* config_extended_help_fn() {
     static SkString helpString;
@@ -423,7 +428,7 @@
         const SkString& tag, const SkTArray<SkString>& viaParts, ContextType contextType,
         bool useNVPR, bool useDIText, int samples, SkColorType colorType, SkAlphaType alphaType,
         sk_sp<SkColorSpace> colorSpace, bool useStencilBuffers, bool testThreading,
-        SurfType surfType)
+        bool testPersistentCache, SurfType surfType)
         : SkCommandLineConfig(tag, SkString("gpu"), viaParts)
         , fContextType(contextType)
         , fContextOverrides(ContextOverrides::kNone)
@@ -433,6 +438,7 @@
         , fAlphaType(alphaType)
         , fColorSpace(std::move(colorSpace))
         , fTestThreading(testThreading)
+        , fTestPersistentCache(testPersistentCache)
         , fSurfType(surfType) {
     if (useNVPR) {
         fContextOverrides |= ContextOverrides::kRequireNVPRSupport;
@@ -459,6 +465,7 @@
     sk_sp<SkColorSpace> colorSpace = nullptr;
     bool useStencils = true;
     bool testThreading = false;
+    bool testPersistentCache = false;
     SkCommandLineConfigGpu::SurfType surfType = SkCommandLineConfigGpu::SurfType::kDefault;
 
     bool parseSucceeded = false;
@@ -475,16 +482,17 @@
             extendedOptions.get_option_gpu_color("color", &colorType, &alphaType, &colorSpace) &&
             extendedOptions.get_option_bool("stencils", &useStencils) &&
             extendedOptions.get_option_bool("testThreading", &testThreading) &&
-            extendedOptions.get_option_bool("testThreading", &testThreading) &&
+            extendedOptions.get_option_bool("testPersistentCache", &testPersistentCache) &&
             extendedOptions.get_option_gpu_surf_type("surf", &surfType);
 
-    if (!validOptions) {
+    // testing threading and the persistent cache are mutually exclusive.
+    if (!validOptions || (testThreading && testPersistentCache)) {
         return nullptr;
     }
 
     return new SkCommandLineConfigGpu(tag, vias, contextType, useNVPR, useDIText, samples,
                                       colorType, alphaType, colorSpace, useStencils, testThreading,
-                                      surfType);
+                                      testPersistentCache, surfType);
 }
 
 SkCommandLineConfigSvg::SkCommandLineConfigSvg(const SkString& tag,
diff --git a/tools/flags/SkCommonFlagsConfig.h b/tools/flags/SkCommonFlagsConfig.h
index 7c097ea..fa11cc6 100644
--- a/tools/flags/SkCommonFlagsConfig.h
+++ b/tools/flags/SkCommonFlagsConfig.h
@@ -57,7 +57,7 @@
                            ContextType contextType, bool useNVPR, bool useDIText, int samples,
                            SkColorType colorType, SkAlphaType alphaType,
                            sk_sp<SkColorSpace> colorSpace, bool useStencilBuffers,
-                           bool testThreading, SurfType);
+                           bool testThreading, bool testPersistentCache, SurfType);
 
     const SkCommandLineConfigGpu* asConfigGpu() const override { return this; }
     ContextType getContextType() const { return fContextType; }
@@ -73,6 +73,7 @@
     SkAlphaType getAlphaType() const { return fAlphaType; }
     SkColorSpace* getColorSpace() const { return fColorSpace.get(); }
     bool getTestThreading() const { return fTestThreading; }
+    bool getTestPersistentCache() const { return fTestPersistentCache; }
     SurfType getSurfType() const { return fSurfType; }
 
 private:
@@ -84,6 +85,7 @@
     SkAlphaType fAlphaType;
     sk_sp<SkColorSpace> fColorSpace;
     bool fTestThreading;
+    bool fTestPersistentCache;
     SurfType fSurfType;
 };
 
diff --git a/tools/gpu/MemoryCache.cpp b/tools/gpu/MemoryCache.cpp
new file mode 100644
index 0000000..7fc3e1b
--- /dev/null
+++ b/tools/gpu/MemoryCache.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "MemoryCache.h"
+#include "SkBase64.h"
+
+// Change this to 1 to log cache hits/misses/stores using SkDebugf.
+#define LOG_MEMORY_CACHE 0
+
+static SkString data_to_str(const SkData& data) {
+    size_t encodeLength = SkBase64::Encode(data.data(), data.size(), nullptr);
+    SkString str;
+    str.resize(encodeLength);
+    SkBase64::Encode(data.data(), data.size(), str.writable_str());
+    static constexpr size_t kMaxLength = 60;
+    static constexpr char kTail[] = "...";
+    static const size_t kTailLen = strlen(kTail);
+    bool overlength = encodeLength > kMaxLength;
+    if (overlength) {
+        str = SkString(str.c_str(), kMaxLength - kTailLen);
+        str.append(kTail);
+    }
+    return str;
+}
+
+namespace sk_gpu_test {
+
+sk_sp<SkData> MemoryCache::load(const SkData& key) {
+    auto result = fMap.find(key);
+    if (result == fMap.end()) {
+        if (LOG_MEMORY_CACHE) {
+            SkDebugf("Load Key: %s\n\tNot Found.\n\n", data_to_str(key).c_str());
+        }
+        ++fCacheMissCnt;
+        return nullptr;
+    }
+    if (LOG_MEMORY_CACHE) {
+        SkDebugf("Load Key: %s\n\tFound Data: %s\n\n", data_to_str(key).c_str(),
+                 data_to_str(*result->second).c_str());
+    }
+    return result->second;
+}
+
+void MemoryCache::store(const SkData& key, const SkData& data) {
+    if (LOG_MEMORY_CACHE) {
+        SkDebugf("Store Key: %s\n\tData: %s\n\n", data_to_str(key).c_str(),
+                 data_to_str(data).c_str());
+    }
+    fMap[Key(key)] = SkData::MakeWithCopy(data.data(), data.size());
+}
+
+}  // namespace sk_gpu_test
diff --git a/tools/gpu/MemoryCache.h b/tools/gpu/MemoryCache.h
new file mode 100644
index 0000000..568f755
--- /dev/null
+++ b/tools/gpu/MemoryCache.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef MemoryCache_DEFINED
+#define MemoryCache_DEFINED
+
+#include <unordered_map>
+#include "GrContextOptions.h"
+#include "SkChecksum.h"
+#include "SkData.h"
+
+namespace sk_gpu_test {
+
+/**
+ * This class can be used to maintain an in memory record of all programs cached by GrContext.
+ * It can be shared by multiple GrContexts so long as those GrContexts are created with the same
+ * options and will have the same GrCaps (e.g. same backend, same GL context creation parameters,
+ * ...).
+ */
+class MemoryCache : public GrContextOptions::PersistentCache {
+public:
+    MemoryCache() = default;
+    MemoryCache(const MemoryCache&) = delete;
+    MemoryCache& operator=(const MemoryCache&) = delete;
+
+    sk_sp<SkData> load(const SkData& key) override;
+    void store(const SkData& key, const SkData& data) override;
+    int numCacheMisses() const { return fCacheMissCnt; }
+    void resetNumCacheMisses() { fCacheMissCnt = 0; }
+
+private:
+    struct Key {
+        Key() = default;
+        Key(const SkData& key) : fKey(SkData::MakeWithCopy(key.data(), key.size())) {}
+        Key(const Key& that) = default;
+        Key& operator=(const Key&) = default;
+        bool operator==(const Key& that) const {
+            return that.fKey->size() == fKey->size() &&
+                   !memcmp(fKey->data(), that.fKey->data(), that.fKey->size());
+        }
+        sk_sp<const SkData> fKey;
+    };
+
+    struct Hash {
+        using argument_type = Key;
+        using result_type = uint32_t;
+        uint32_t operator()(const Key& key) const {
+            return key.fKey ? SkOpts::hash_fn(key.fKey->data(), key.fKey->size(), 0) : 0;
+        }
+    };
+
+    int fCacheMissCnt = 0;
+    std::unordered_map<Key, sk_sp<SkData>, Hash> fMap;
+};
+
+}  // namespace sk_gpu_test
+
+#endif