Add native caching of uniquely keyed GrTextureProxies (take 2)

TBR=bsalomon@google.com

Change-Id: I590dcdc85fb60706c7eb06277694791dc04c9141
Reviewed-on: https://skia-review.googlesource.com/49543
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/gm/image_pict.cpp b/gm/image_pict.cpp
index 73fedec..25c3b1c 100644
--- a/gm/image_pict.cpp
+++ b/gm/image_pict.cpp
@@ -152,7 +152,7 @@
         : SkImageGenerator(info)
         , fCtx(SkRef(ctx)) {
 
-        sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, 0,
+        sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx, SkBudgeted::kYes, info, 0,
                                                              kTopLeft_GrSurfaceOrigin, nullptr));
         if (surface) {
             surface->getCanvas()->clear(0);
@@ -188,7 +188,7 @@
         sk_sp<GrSurfaceContext> dstContext(fCtx->contextPriv().makeDeferredSurfaceContext(
                                                                             desc,
                                                                             SkBackingFit::kExact,
-                                                                            SkBudgeted::kNo));
+                                                                            SkBudgeted::kYes));
         if (!dstContext) {
             return nullptr;
         }
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 1a5687e..fbc976b 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -185,6 +185,7 @@
   "$_src/gpu/GrTessellator.h",
   "$_src/gpu/GrTextureOpList.cpp",
   "$_src/gpu/GrTextureOpList.h",
+  "$_src/gpu/GrTextureProxyCacheAccess.h",
   "$_src/gpu/GrTracing.h",
   "$_src/gpu/GrTestUtils.cpp",
   "$_src/gpu/GrTestUtils.h",
diff --git a/gn/tests.gni b/gn/tests.gni
index c2f3759..9cea3ea 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -248,6 +248,7 @@
   "$_tests/TestUtils.cpp",
   "$_tests/TextBlobCacheTest.cpp",
   "$_tests/TextBlobTest.cpp",
+  "$_tests/TextureProxyTest.cpp",
   "$_tests/Time.cpp",
   "$_tests/TLSTest.cpp",
   "$_tests/TopoSortTest.cpp",
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 12ba91b..fcb839c 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -378,7 +378,7 @@
 
     bool instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt, bool needsStencil,
                          GrSurfaceFlags flags, bool isMipMapped,
-                         SkDestinationSurfaceColorMode mipColorMode);
+                         SkDestinationSurfaceColorMode mipColorMode, const GrUniqueKey*);
 
     // For wrapped resources, 'fConfig', 'fWidth', 'fHeight', and 'fOrigin; will always be filled in
     // from the wrapped resource.
diff --git a/include/private/GrTextureProxy.h b/include/private/GrTextureProxy.h
index 9db2bb2..3634ce4 100644
--- a/include/private/GrTextureProxy.h
+++ b/include/private/GrTextureProxy.h
@@ -12,6 +12,7 @@
 #include "GrSurfaceProxy.h"
 
 class GrCaps;
+class GrResourceCache;
 class GrResourceProvider;
 class GrTextureOpList;
 
@@ -38,6 +39,31 @@
 
     bool isMipMapped() const { return fIsMipMapped; }
 
+    /**
+     * Return the texture proxy's unique key. It will be invalid if the proxy doesn't have one.
+     */
+    const GrUniqueKey& getUniqueKey() const {
+#ifdef SK_DEBUG
+        if (fTarget && fUniqueKey.isValid()) {
+            SkASSERT(fTarget->getUniqueKey().isValid());
+            // It is possible for a non-keyed proxy to have a uniquely keyed resource assigned to
+            // it. This just means that a future user of the resource will be filling it with unique
+            // data. However, if the proxy has a unique key its attached resource should also
+            // have that key.
+            SkASSERT(fUniqueKey == fTarget->getUniqueKey());
+        }
+#endif
+
+        return fUniqueKey;
+    }
+
+    /**
+     * Internal-only helper class used for manipulations of the resource by the cache.
+     */
+    class CacheAccess;
+    inline CacheAccess cacheAccess();
+    inline const CacheAccess cacheAccess() const;
+
 protected:
     friend class GrSurfaceProxy; // for ctors
 
@@ -47,6 +73,8 @@
     // Wrapped version
     GrTextureProxy(sk_sp<GrSurface>, GrSurfaceOrigin);
 
+    ~GrTextureProxy() override;
+
     SkDestinationSurfaceColorMode mipColorMode() const { return fMipColorMode;  }
 
     sk_sp<GrSurface> createSurface(GrResourceProvider*) const override;
@@ -55,8 +83,15 @@
     bool fIsMipMapped;
     SkDestinationSurfaceColorMode fMipColorMode;
 
+    GrUniqueKey fUniqueKey;
+    GrResourceCache* fCache; // only set when fUniqueKey is valid
+
     size_t onUninstantiatedGpuMemorySize() const override;
 
+    // Methods made available via GrTextureProxy::CacheAccess
+    void setUniqueKey(GrResourceCache*, const GrUniqueKey&);
+    void clearUniqueKey();
+
     // For wrapped proxies the GrTexture pointer is stored in GrIORefProxy.
     // For deferred proxies that pointer will be filled in when we need to instantiate
     // the deferred resource
diff --git a/src/gpu/GrBitmapTextureMaker.cpp b/src/gpu/GrBitmapTextureMaker.cpp
index 85e791e..2bf3afe 100644
--- a/src/gpu/GrBitmapTextureMaker.cpp
+++ b/src/gpu/GrBitmapTextureMaker.cpp
@@ -53,7 +53,6 @@
     if (proxy && fOriginalKey.isValid()) {
         SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
         this->context()->resourceProvider()->assignUniqueKeyToProxy(fOriginalKey, proxy.get());
-        // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
         GrInstallBitmapUniqueKeyInvalidator(fOriginalKey, fBitmap.pixelRef());
     }
     return proxy;
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index 165b442..69d81a0 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -420,7 +420,6 @@
 
     SkASSERT(result->origin() == kBottomLeft_GrSurfaceOrigin);
     resourceProvider->assignUniqueKeyToProxy(key, result.get());
-    // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
     add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
 
     return result;
@@ -559,7 +558,6 @@
 
     SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
     context->resourceProvider()->assignUniqueKeyToProxy(key, proxy.get());
-    // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
     add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
     return proxy;
 }
diff --git a/src/gpu/GrRenderTargetProxy.cpp b/src/gpu/GrRenderTargetProxy.cpp
index fff3d0b..111ecf6 100644
--- a/src/gpu/GrRenderTargetProxy.cpp
+++ b/src/gpu/GrRenderTargetProxy.cpp
@@ -53,7 +53,7 @@
 
     if (!this->instantiateImpl(resourceProvider, fSampleCnt, fNeedsStencil, kFlags,
                                /* isMipped = */ false,
-                               SkDestinationSurfaceColorMode::kLegacy)) {
+                               SkDestinationSurfaceColorMode::kLegacy, nullptr)) {
         return false;
     }
     SkASSERT(fTarget->asRenderTarget());
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index f6170c0..c207b44 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -10,6 +10,8 @@
 
 #include "GrCaps.h"
 #include "GrGpuResourceCacheAccess.h"
+#include "GrTexture.h"
+#include "GrTextureProxyCacheAccess.h"
 #include "GrTracing.h"
 #include "SkGr.h"
 #include "SkMessageBus.h"
@@ -578,6 +580,8 @@
 void GrResourceCache::processInvalidUniqueKeys(
     const SkTArray<GrUniqueKeyInvalidatedMessage>& msgs) {
     for (int i = 0; i < msgs.count(); ++i) {
+        this->processInvalidProxyUniqueKey(msgs[i].key());
+
         GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
         if (resource) {
             resource->resourcePriv().removeUniqueKey();
@@ -847,3 +851,63 @@
 }
 
 #endif
+
+void GrResourceCache::assignUniqueKeyToProxy(const GrUniqueKey& key, GrTextureProxy* proxy) {
+    SkASSERT(key.isValid());
+    SkASSERT(proxy);
+
+    // If there is already a GrResource with this key then the caller has violated the normal
+    // usage pattern of uniquely keyed resources (e.g., they have created one w/o first seeing
+    // if it already existed in the cache).
+    SkASSERT(!this->findAndRefUniqueResource(key));
+
+    // Uncached resources can never have a unique key, unless they're wrapped resources. Wrapped
+    // resources are a special case: the unique keys give us a weak ref so that we can reuse the
+    // same resource (rather than re-wrapping). When a wrapped resource is no longer referenced,
+    // it will always be released - it is never converted to a scratch resource.
+    if (SkBudgeted::kNo == proxy->isBudgeted() &&
+                    (!proxy->priv().isInstantiated() ||
+                     !proxy->priv().peekSurface()->resourcePriv().refsWrappedObjects())) {
+        return;
+    }
+
+    SkASSERT(!fUniquelyKeyedProxies.find(key));     // multiple proxies can't get the same key
+
+    proxy->cacheAccess().setUniqueKey(this, key);
+    SkASSERT(proxy->getUniqueKey() == key);
+    fUniquelyKeyedProxies.add(proxy);
+}
+
+sk_sp<GrTextureProxy> GrResourceCache::findProxyByUniqueKey(const GrUniqueKey& key,
+                                                            GrSurfaceOrigin origin) {
+
+    sk_sp<GrTextureProxy> result = sk_ref_sp(fUniquelyKeyedProxies.find(key));
+    if (result) {
+        SkASSERT(result->origin() == origin);
+        return result;
+    }
+
+    GrGpuResource* resource = findAndRefUniqueResource(key);
+    if (!resource) {
+        return nullptr;
+    }
+
+    sk_sp<GrTexture> texture(static_cast<GrSurface*>(resource)->asTexture());
+    SkASSERT(texture);
+
+    result = GrSurfaceProxy::MakeWrapped(std::move(texture), origin);
+    SkASSERT(result->getUniqueKey() == key);
+    fUniquelyKeyedProxies.add(result.get());
+    return result;
+}
+
+void GrResourceCache::processInvalidProxyUniqueKey(const GrUniqueKey& key) {
+    // Note: this method is called for the whole variety of GrGpuResources so often 'key'
+    // will not be in 'fUniquelyKeyedProxies'.
+    GrTextureProxy* proxy = fUniquelyKeyedProxies.find(key);
+    if (proxy) {
+        fUniquelyKeyedProxies.remove(key);
+        proxy->cacheAccess().clearUniqueKey();
+    }
+}
+
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 5cdc563..96323be 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -13,6 +13,7 @@
 #include "GrGpuResourcePriv.h"
 #include "GrResourceCache.h"
 #include "GrResourceKey.h"
+#include "GrTextureProxy.h"
 #include "SkMessageBus.h"
 #include "SkRefCnt.h"
 #include "SkTArray.h"
@@ -156,6 +157,48 @@
         return resource;
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // TextureProxies & GrUniqueKeys
+    //
+    // The two GrResourceCache methods assignUniqueKeyToProxy and findProxyByUniqueKey drive
+    // the behavior of uniqueKeys on proxies.
+    //
+    // assignUniqueKeyToProxy does the following:
+    //    if the proxy is wrapped, it sets the texture & proxy keys & adds the proxy to the hash
+    //    if the proxy is deferred, it just set the unique key on the proxy & adds it to the hash
+    //
+    //    Note that when a deferred proxy with a unique key is instantiated, its unique key will be
+    //    pushed to the backing resource.
+    //
+    //    Futher note, a proxy can only receive a unique key once. It can be removed if Ganesh
+    //    determines that the key will never be used again but, in that case, the proxy should
+    //    never receive another key.
+    //
+    // findProxyByUniqueKey does the following:
+    //    first looks in the UniqueKeyProxy hash table to see if there is already a proxy w/ the key
+    //    failing that it looks in the ResourceCache to see there is a texture with that key
+    //       if so, it will wrap the texture in a proxy, add the proxy to the hash and return it
+    //    failing that it will return null
+
+    /*
+     * Associate the provided proxy with the provided unique key.
+     */
+    void assignUniqueKeyToProxy(const GrUniqueKey&, GrTextureProxy*);
+
+    /**
+     * Find a texture proxy that is associated with the provided unique key.
+     */
+    sk_sp<GrTextureProxy> findProxyByUniqueKey(const GrUniqueKey&, GrSurfaceOrigin);
+
+    /**
+     * Either the proxy attached to the unique key is being deleted (in which case we
+     * don't want it cluttering up the hash table) or the client has indicated that
+     * it will never refer to the unique key again. In either case, remove the key
+     * from the hash table.
+     * Note: this does not, by itself, alter unique key attached to the underlying GrTexture.
+     */
+    void processInvalidProxyUniqueKey(const GrUniqueKey&);
+
     /**
      * Query whether a unique key exists in the cache.
      */
@@ -249,6 +292,8 @@
     // Enumerates all cached resources and dumps their details to traceMemoryDump.
     void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const;
 
+    int numUniqueKeyProxies_TestOnly() const;
+
 private:
     ///////////////////////////////////////////////////////////////////////////
     /// @name Methods accessible via ResourceAccess
@@ -303,6 +348,13 @@
     };
     typedef SkTDynamicHash<GrGpuResource, GrUniqueKey, UniqueHashTraits> UniqueHash;
 
+    struct UniquelyKeyedProxyHashTraits {
+        static const GrUniqueKey& GetKey(const GrTextureProxy& p) { return p.getUniqueKey(); }
+
+        static uint32_t Hash(const GrUniqueKey& key) { return key.hash(); }
+    };
+    typedef SkTDynamicHash<GrTextureProxy, GrUniqueKey, UniquelyKeyedProxyHashTraits> UniquelyKeyedProxyHash;
+
     static bool CompareTimestamp(GrGpuResource* const& a, GrGpuResource* const& b) {
         return a->cacheAccess().timestamp() < b->cacheAccess().timestamp();
     }
@@ -327,6 +379,9 @@
     ScratchMap                          fScratchMap;
     // This holds all resources that have unique keys.
     UniqueHash                          fUniqueHash;
+    // This holds the texture proxies that have unique keys. The resourceCache does not get a ref
+    // on these proxies but they must send a message to the resourceCache when they are deleted.
+    UniquelyKeyedProxyHash              fUniquelyKeyedProxies;
 
     // our budget, used in purgeAsNeeded()
     int                                 fMaxCount;
diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp
index 8dce549..0182baa 100644
--- a/src/gpu/GrResourceProvider.cpp
+++ b/src/gpu/GrResourceProvider.cpp
@@ -292,7 +292,6 @@
     this->assignUniqueKeyToResource(key, texture);
 }
 
-// MDB TODO (caching): this side-steps the issue of texture proxies with unique IDs
 void GrResourceProvider::assignUniqueKeyToProxy(const GrUniqueKey& key, GrTextureProxy* proxy) {
     ASSERT_SINGLE_OWNER
     SkASSERT(key.isValid());
@@ -300,25 +299,13 @@
         return;
     }
 
-    if (!proxy->instantiate(this)) {
-        return;
-    }
-    GrTexture* texture = proxy->priv().peekTexture();
-
-    this->assignUniqueKeyToResource(key, texture);
+    fCache->assignUniqueKeyToProxy(key, proxy);
 }
 
-// MDB TODO (caching): this side-steps the issue of texture proxies with unique IDs
 sk_sp<GrTextureProxy> GrResourceProvider::findProxyByUniqueKey(const GrUniqueKey& key,
                                                                GrSurfaceOrigin origin) {
     ASSERT_SINGLE_OWNER
-
-    sk_sp<GrTexture> texture(this->findAndRefTextureByUniqueKey(key));
-    if (!texture) {
-        return nullptr;
-    }
-
-    return GrSurfaceProxy::MakeWrapped(std::move(texture), origin);
+    return this->isAbandoned() ? nullptr : fCache->findProxyByUniqueKey(key, origin);
 }
 
 const GrBuffer* GrResourceProvider::createPatternedIndexBuffer(const uint16_t* pattern,
diff --git a/src/gpu/GrResourceProvider.h b/src/gpu/GrResourceProvider.h
index 1ab1ec4..7be8ec1 100644
--- a/src/gpu/GrResourceProvider.h
+++ b/src/gpu/GrResourceProvider.h
@@ -45,18 +45,20 @@
         return static_cast<T*>(this->findAndRefResourceByUniqueKey(key));
     }
 
+    /*
+     * Assigns a unique key to a proxy. The proxy will be findable via this key using
+     * findProxyByUniqueKey(). It is an error if an existing proxy already has a key.
+     */
+    void assignUniqueKeyToProxy(const GrUniqueKey&, GrTextureProxy*);
+
+    /*
+     * Finds a proxy by unique key.
+     */
+    sk_sp<GrTextureProxy> findProxyByUniqueKey(const GrUniqueKey&, GrSurfaceOrigin);
+
     ///////////////////////////////////////////////////////////////////////////
     // Textures
 
-    /** Assigns a unique key to the texture. The texture will be findable via this key using
-    findTextureByUniqueKey(). If an existing texture has this key, it's key will be removed. */
-    void assignUniqueKeyToProxy(const GrUniqueKey& key, GrTextureProxy*);
-
-    /** Finds a texture by unique key. If the texture is found it is ref'ed and returned. */
-    // MDB TODO (caching): If this were actually caching proxies (rather than shallowly 
-    // wrapping GrSurface caching) we would not need the origin parameter.
-    sk_sp<GrTextureProxy> findProxyByUniqueKey(const GrUniqueKey& key, GrSurfaceOrigin);
-
     /**
      * Finds a texture that approximately matches the descriptor. Will be at least as large in width
      * and height as desc specifies. If desc specifies that the texture should be a render target
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index 5918830..433c230 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -109,8 +109,12 @@
 
 bool GrSurfaceProxy::instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt,
                                      bool needsStencil, GrSurfaceFlags flags, bool isMipMapped,
-                                     SkDestinationSurfaceColorMode mipColorMode) {
+                                     SkDestinationSurfaceColorMode mipColorMode,
+                                     const GrUniqueKey* uniqueKey) {
     if (fTarget) {
+        if (uniqueKey) {
+            SkASSERT(fTarget->getUniqueKey() == *uniqueKey);
+        }
         return attach_stencil_if_needed(resourceProvider, fTarget, needsStencil);
     }
 
@@ -120,6 +124,10 @@
         return false;
     }
 
+    if (uniqueKey) {
+        resourceProvider->assignUniqueKeyToResource(*uniqueKey, surface.get());
+    }
+
     this->assign(std::move(surface));
     return true;
 }
diff --git a/src/gpu/GrTextureProxy.cpp b/src/gpu/GrTextureProxy.cpp
index 3cb87b3..208ef78 100644
--- a/src/gpu/GrTextureProxy.cpp
+++ b/src/gpu/GrTextureProxy.cpp
@@ -7,25 +7,46 @@
 
 #include "GrTextureProxy.h"
 
+#include "GrContext.h"
+#include "GrResourceCache.h"
+
 #include "GrTexturePriv.h"
 
 GrTextureProxy::GrTextureProxy(const GrSurfaceDesc& srcDesc, SkBackingFit fit, SkBudgeted budgeted,
                                const void* srcData, size_t /*rowBytes*/, uint32_t flags)
         : INHERITED(srcDesc, fit, budgeted, flags)
         , fIsMipMapped(srcDesc.fIsMipMapped)
-        , fMipColorMode(SkDestinationSurfaceColorMode::kLegacy) {
+        , fMipColorMode(SkDestinationSurfaceColorMode::kLegacy)
+        , fCache(nullptr) {
     SkASSERT(!srcData);  // currently handled in Make()
 }
 
 GrTextureProxy::GrTextureProxy(sk_sp<GrSurface> surf, GrSurfaceOrigin origin)
         : INHERITED(std::move(surf), origin, SkBackingFit::kExact)
         , fIsMipMapped(fTarget->asTexture()->texturePriv().hasMipMaps())
-        , fMipColorMode(fTarget->asTexture()->texturePriv().mipColorMode()) {
+        , fMipColorMode(fTarget->asTexture()->texturePriv().mipColorMode())
+        , fCache(nullptr) {
+    if (fTarget->getUniqueKey().isValid()) {
+        fUniqueKey = fTarget->getUniqueKey();
+        fCache = fTarget->asTexture()->getContext()->getResourceCache();
+    }
+}
+
+GrTextureProxy::~GrTextureProxy() {
+    // Due to the order of cleanup the GrSurface this proxy may have wrapped may have gone away
+    // at this point. Zero out the pointer so the cache invalidation code doesn't try to use it.
+    fTarget = nullptr;
+    if (fUniqueKey.isValid()) {
+        fCache->processInvalidProxyUniqueKey(fUniqueKey);
+    } else {
+        SkASSERT(!fCache);
+    }
 }
 
 bool GrTextureProxy::instantiate(GrResourceProvider* resourceProvider) {
     if (!this->instantiateImpl(resourceProvider, 0, /* needsStencil = */ false,
-                               kNone_GrSurfaceFlags, fIsMipMapped, fMipColorMode)) {
+                               kNone_GrSurfaceFlags, fIsMipMapped, fMipColorMode,
+                               fUniqueKey.isValid() ? &fUniqueKey : nullptr)) {
         return false;
     }
 
@@ -80,3 +101,23 @@
     return GrSurface::ComputeSize(fConfig, fWidth, fHeight, 1, kHasMipMaps,
                                   SkBackingFit::kApprox == fFit);
 }
+
+void GrTextureProxy::setUniqueKey(GrResourceCache* cache, const GrUniqueKey& key) {
+    SkASSERT(key.isValid());
+    SkASSERT(!fUniqueKey.isValid()); // proxies can only ever get one uniqueKey
+
+    if (fTarget) {
+        SkASSERT(!fTarget->getUniqueKey().isValid());
+        fTarget->resourcePriv().setUniqueKey(key);
+        SkASSERT(fTarget->getUniqueKey() == key);
+    }
+
+    fUniqueKey = key;
+    fCache = cache;
+}
+
+void GrTextureProxy::clearUniqueKey() {
+    fUniqueKey.reset();
+    fCache = nullptr;
+}
+
diff --git a/src/gpu/GrTextureProxyCacheAccess.h b/src/gpu/GrTextureProxyCacheAccess.h
new file mode 100644
index 0000000..79cf845
--- /dev/null
+++ b/src/gpu/GrTextureProxyCacheAccess.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTextureProxyCacheAccess_DEFINED
+#define GrTextureProxyCacheAccess_DEFINED
+
+#include "GrTextureProxy.h"
+
+/**
+ * This class allows GrResourceCache increased privileged access to GrTextureProxy objects.
+ */
+class GrTextureProxy::CacheAccess {
+private:
+    void setUniqueKey(GrResourceCache* cache, const GrUniqueKey& key) {
+        fTextureProxy->setUniqueKey(cache, key);
+    }
+
+    void clearUniqueKey() {
+        fTextureProxy->clearUniqueKey();
+    }
+
+    explicit CacheAccess(GrTextureProxy* textureProxy) : fTextureProxy(textureProxy) {}
+    CacheAccess(const CacheAccess&) {} // unimpl
+    CacheAccess& operator=(const CacheAccess&); // unimpl
+
+    // No taking addresses of this type.
+    const CacheAccess* operator&() const;
+    CacheAccess* operator&();
+
+    GrTextureProxy* fTextureProxy;
+
+    friend class GrTextureProxy;  // to construct/copy this type.
+    friend class GrResourceCache; // to use this type
+};
+
+inline GrTextureProxy::CacheAccess GrTextureProxy::cacheAccess() { return CacheAccess(this); }
+
+inline const GrTextureProxy::CacheAccess GrTextureProxy::cacheAccess() const {
+    return CacheAccess(const_cast<GrTextureProxy*>(this));
+}
+
+#endif
diff --git a/src/gpu/GrTextureRenderTargetProxy.cpp b/src/gpu/GrTextureRenderTargetProxy.cpp
index 4a4d79c..7515c3d 100644
--- a/src/gpu/GrTextureRenderTargetProxy.cpp
+++ b/src/gpu/GrTextureRenderTargetProxy.cpp
@@ -48,10 +48,17 @@
 bool GrTextureRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) {
     static constexpr GrSurfaceFlags kFlags = kRenderTarget_GrSurfaceFlag;
 
+    const GrUniqueKey& key = this->getUniqueKey();
+
     if (!this->instantiateImpl(resourceProvider, this->numStencilSamples(), this->needsStencil(),
-                               kFlags, this->isMipMapped(), this->mipColorMode())) {
+                               kFlags, this->isMipMapped(), this->mipColorMode(),
+                               key.isValid() ? &key : nullptr)) {
         return false;
     }
+    if (key.isValid()) {
+        SkASSERT(key == this->getUniqueKey());
+    }
+
     SkASSERT(fTarget->asRenderTarget());
     SkASSERT(fTarget->asTexture());
 
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 4943804..b9f75e0 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -266,7 +266,6 @@
         if (proxy && originalKey.isValid()) {
             SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
             resourceProvider->assignUniqueKeyToProxy(originalKey, proxy.get());
-            // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching
             GrInstallBitmapUniqueKeyInvalidator(originalKey, bitmap.pixelRef());
         }
     }
diff --git a/tests/TextureProxyTest.cpp b/tests/TextureProxyTest.cpp
new file mode 100644
index 0000000..58c29fc
--- /dev/null
+++ b/tests/TextureProxyTest.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// This is a GPU-backend specific test.
+
+#include "Test.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrBackendSurface.h"
+#include "GrContextPriv.h"
+#include "GrResourceCache.h"
+#include "GrResourceProvider.h"
+#include "GrTest.h"
+#include "GrTexture.h"
+#include "GrTextureProxy.h"
+
+#include "SkGr.h"
+#include "SkImage.h"
+
+int GrResourceCache::numUniqueKeyProxies_TestOnly() const {
+    return fUniquelyKeyedProxies.count();
+}
+
+static GrSurfaceDesc make_desc(GrSurfaceFlags flags) {
+    GrSurfaceDesc desc;
+    desc.fFlags = flags;
+    desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
+    desc.fWidth = 64;
+    desc.fHeight = 64;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    desc.fSampleCnt = 0;
+
+    return desc;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Basic test
+
+static sk_sp<GrTextureProxy> deferred_tex(GrResourceProvider* provider, SkBackingFit fit) {
+    GrSurfaceDesc desc = make_desc(kNone_GrSurfaceFlags);
+
+    // Only budgeted & wrapped external proxies get to carry uniqueKeys
+    return GrSurfaceProxy::MakeDeferred(provider, desc, fit, SkBudgeted::kYes);
+}
+
+static sk_sp<GrTextureProxy> deferred_texRT(GrResourceProvider* provider, SkBackingFit fit) {
+    GrSurfaceDesc desc = make_desc(kRenderTarget_GrSurfaceFlag);
+
+    // Only budgeted & wrapped external proxies get to carry uniqueKeys
+    return GrSurfaceProxy::MakeDeferred(provider, desc, fit, SkBudgeted::kYes);
+}
+
+static sk_sp<GrTextureProxy> wrapped(GrResourceProvider* provider, SkBackingFit fit) {
+    GrSurfaceDesc desc = make_desc(kNone_GrSurfaceFlags);
+
+    sk_sp<GrTexture> tex;
+    if (SkBackingFit::kApprox == fit) {
+        tex = sk_sp<GrTexture>(provider->createApproxTexture(desc, 0));
+    } else {
+        // Only budgeted & wrapped external proxies get to carry uniqueKeys
+        tex = provider->createTexture(desc, SkBudgeted::kYes);
+    }
+
+    return GrSurfaceProxy::MakeWrapped(std::move(tex), kBottomLeft_GrSurfaceOrigin);
+}
+
+static sk_sp<GrTextureProxy> create_wrapped_backend(GrContext* context, SkBackingFit fit,
+                                                    sk_sp<GrTexture>* backingSurface) {
+    GrResourceProvider* provider = context->resourceProvider();
+
+    GrSurfaceDesc desc = make_desc(kNone_GrSurfaceFlags);
+
+    *backingSurface = provider->createTexture(desc, SkBudgeted::kNo);
+    if (!(*backingSurface)) {
+        return nullptr;
+    }
+
+    GrBackendTexture backendTex =
+            GrTest::CreateBackendTexture(context->contextPriv().getBackend(),
+                                         64, 64,
+                                         kRGBA_8888_GrPixelConfig,
+                                         (*backingSurface)->getTextureHandle());
+
+    return GrSurfaceProxy::MakeWrappedBackend(context, backendTex, kBottomLeft_GrSurfaceOrigin);
+}
+
+
+// This tests the basic capabilities of the uniquely keyed texture proxies. Does assigning
+// and looking them up work, etc.
+static void basic_test(GrContext* context,
+                       skiatest::Reporter* reporter,
+                       sk_sp<GrTextureProxy> proxy, bool proxyIsCached) {
+    static int id = 1;
+
+    GrResourceProvider* provider = context->resourceProvider();
+    GrResourceCache* cache = context->getResourceCache();
+
+    int startCacheCount = cache->getResourceCount();
+
+    REPORTER_ASSERT(reporter, !proxy->getUniqueKey().isValid());
+
+    GrUniqueKey key;
+    GrMakeKeyFromImageID(&key, id, SkIRect::MakeWH(64, 64));
+    ++id;
+
+    // Assigning the uniqueKey adds the proxy to the hash but doesn't force instantiation
+    REPORTER_ASSERT(reporter, !cache->numUniqueKeyProxies_TestOnly());
+    provider->assignUniqueKeyToProxy(key, proxy.get());
+    REPORTER_ASSERT(reporter, 1 == cache->numUniqueKeyProxies_TestOnly());
+    REPORTER_ASSERT(reporter, startCacheCount == cache->getResourceCount());
+
+    // setUniqueKey had better stick
+    REPORTER_ASSERT(reporter, key == proxy->getUniqueKey());
+
+    // We just added it, surely we can find it
+    REPORTER_ASSERT(reporter, provider->findProxyByUniqueKey(key, kBottomLeft_GrSurfaceOrigin));
+    REPORTER_ASSERT(reporter, 1 == cache->numUniqueKeyProxies_TestOnly());
+
+    // Once instantiated, the backing resource should have the same key
+    SkAssertResult(proxy->instantiate(provider));
+    const GrUniqueKey& texKey = proxy->priv().peekSurface()->getUniqueKey();
+    REPORTER_ASSERT(reporter, texKey.isValid());
+    REPORTER_ASSERT(reporter, key == texKey);
+    if (proxyIsCached) {
+        REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+    }
+
+    // deleting the proxy should delete it from the hash but not the cache
+    proxy = nullptr;
+    REPORTER_ASSERT(reporter, 0 == cache->numUniqueKeyProxies_TestOnly());
+    REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+
+    // If the proxy was cached refinding it should bring it back to life
+    proxy = provider->findProxyByUniqueKey(key, kBottomLeft_GrSurfaceOrigin);
+    if (proxyIsCached) {
+        REPORTER_ASSERT(reporter, proxy);
+        REPORTER_ASSERT(reporter, 1 == cache->numUniqueKeyProxies_TestOnly());
+    } else {
+        REPORTER_ASSERT(reporter, !proxy);
+        REPORTER_ASSERT(reporter, 0 == cache->numUniqueKeyProxies_TestOnly());
+    }
+    REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+
+    // Mega-purging it should remove it from both the hash and the cache
+    proxy = nullptr;
+    cache->purgeAllUnlocked();
+    if (proxyIsCached) {
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+    } else {
+        REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+    }
+
+    // We can bring neither the texture nor proxy back from perma-death
+    proxy = provider->findProxyByUniqueKey(key, kBottomLeft_GrSurfaceOrigin);
+    REPORTER_ASSERT(reporter, !proxy);
+    if (proxyIsCached) {
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+    } else {
+        REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Invalidation test
+
+// Test if invalidating unique ids operates as expected for texture proxies.
+static void invalidation_test(GrContext* context, skiatest::Reporter* reporter) {
+
+    GrResourceCache* cache = context->getResourceCache();
+    REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+
+    sk_sp<SkImage> rasterImg;
+
+    {
+        SkImageInfo ii = SkImageInfo::Make(64, 64, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
+
+        SkBitmap bm;
+        bm.allocPixels(ii);
+
+        rasterImg = SkImage::MakeFromBitmap(bm);
+        REPORTER_ASSERT(reporter, 0 == cache->numUniqueKeyProxies_TestOnly());
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+    }
+
+    sk_sp<SkImage> textureImg = rasterImg->makeTextureImage(context, nullptr);
+    REPORTER_ASSERT(reporter, 1 == cache->numUniqueKeyProxies_TestOnly());
+    REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+
+    rasterImg = nullptr;        // this invalidates the uniqueKey
+
+    // this forces the cache to respond to the inval msg
+    int maxNum;
+    size_t maxBytes;
+    context->getResourceCacheLimits(&maxNum, &maxBytes);
+    context->setResourceCacheLimits(maxNum-1, maxBytes);
+
+    REPORTER_ASSERT(reporter, 0 == cache->numUniqueKeyProxies_TestOnly());
+    REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
+
+    textureImg = nullptr;
+    context->purgeAllUnlockedResources();
+
+    REPORTER_ASSERT(reporter, 0 == cache->numUniqueKeyProxies_TestOnly());
+    REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TextureProxyTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    GrResourceProvider* provider = context->resourceProvider();
+    GrResourceCache* cache = context->getResourceCache();
+
+    REPORTER_ASSERT(reporter, !cache->numUniqueKeyProxies_TestOnly());
+    REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+
+    for (auto fit : { SkBackingFit::kExact, SkBackingFit::kApprox }) {
+        for (auto create : { deferred_tex, deferred_texRT, wrapped }) {
+            REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+            basic_test(context, reporter, create(provider, fit), true);
+        }
+
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+        sk_sp<GrTexture> backingTex;
+        sk_sp<GrTextureProxy> proxy = create_wrapped_backend(context, fit, &backingTex);
+        basic_test(context, reporter, std::move(proxy), false);
+
+        backingTex = nullptr;
+        cache->purgeAllUnlocked();
+    }
+
+    invalidation_test(context, reporter);
+}
+
+#endif