Make GrTextureCache into a generic GrResource cache. Also some GrContext texture interface cleanup.

http://codereview.appspot.com/4815055/


git-svn-id: http://skia.googlecode.com/svn/trunk@1965 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/src/GrContext.cpp b/gpu/src/GrContext.cpp
index b829323..2f34e5a 100644
--- a/gpu/src/GrContext.cpp
+++ b/gpu/src/GrContext.cpp
@@ -22,7 +22,7 @@
 #include "GrInOrderDrawBuffer.h"
 #include "GrPathRenderer.h"
 #include "GrPathUtils.h"
-#include "GrTextureCache.h"
+#include "GrResourceCache.h"
 #include "GrTextStrike.h"
 #include "SkTrace.h"
 
@@ -135,41 +135,66 @@
 enum {
     kNPOTBit    = 0x1,
     kFilterBit  = 0x2,
-    kKeylessBit = 0x4,
+    kScratchBit = 0x4,
 };
 
-bool GrContext::finalizeTextureKey(GrTextureKey* key,
-                                   const GrSamplerState& sampler,
-                                   bool keyless) const {
-    uint32_t bits = 0;
-    uint16_t width = key->width();
-    uint16_t height = key->height();
+GrTexture* GrContext::TextureCacheEntry::texture() const {
+    if (NULL == fEntry) {
+        return NULL; 
+    } else {
+        return (GrTexture*) fEntry->resource();
+    }
+}
 
-    if (!fGpu->npotTextureTileSupport()) {
+namespace {
+// returns true if this is a "special" texture because of gpu NPOT limitations
+bool gen_texture_key_values(const GrGpu* gpu,
+                            const GrSamplerState& sampler,
+                            GrContext::TextureKey clientKey,
+                            int width,
+                            int height,
+                            bool scratch,
+                            uint32_t v[4]) {
+    GR_STATIC_ASSERT(sizeof(GrContext::TextureKey) == sizeof(uint64_t));
+    // we assume we only need 16 bits of width and height
+    // assert that texture creation will fail anyway if this assumption
+    // would cause key collisions.
+    GrAssert(gpu->maxTextureSize() <= SK_MaxU16);
+    v[0] = clientKey & 0xffffffffUL;
+    v[1] = (clientKey >> 32) & 0xffffffffUL;
+    v[2] = width | (height << 16);
+
+    v[3] = 0;
+    if (!gpu->npotTextureTileSupport()) {
         bool isPow2 = GrIsPow2(width) && GrIsPow2(height);
 
         bool tiled = (sampler.getWrapX() != GrSamplerState::kClamp_WrapMode) ||
                      (sampler.getWrapY() != GrSamplerState::kClamp_WrapMode);
 
         if (tiled && !isPow2) {
-            bits |= kNPOTBit;
+            v[3] |= kNPOTBit;
             if (GrSamplerState::kNearest_Filter != sampler.getFilter()) {
-                bits |= kFilterBit;
+                v[3] |= kFilterBit;
             }
         }
     }
 
-    if (keyless) {
-        bits |= kKeylessBit;
+    if (scratch) {
+        v[3] |= kScratchBit;
     }
-    key->finalize(bits);
-    return 0 != bits;
+
+    return v[3] & kNPOTBit;
+}
 }
 
-GrTextureEntry* GrContext::findAndLockTexture(GrTextureKey* key,
-                                              const GrSamplerState& sampler) {
-    finalizeTextureKey(key, sampler, false);
-    return fTextureCache->findAndLock(*key);
+GrContext::TextureCacheEntry GrContext::findAndLockTexture(TextureKey key,
+                                                           int width,
+                                                           int height,
+                                                const GrSamplerState& sampler) {
+    uint32_t v[4];
+    gen_texture_key_values(fGpu, sampler, key, width, height, false, v);
+    GrResourceKey resourceKey(v);
+    return TextureCacheEntry(fTextureCache->findAndLock(resourceKey));
 }
 
 static void stretchImage(void* dst,
@@ -199,32 +224,34 @@
     }
 }
 
-GrTextureEntry* GrContext::createAndLockTexture(GrTextureKey* key,
+GrContext::TextureCacheEntry GrContext::createAndLockTexture(TextureKey key,
                                                 const GrSamplerState& sampler,
                                                 const GrTextureDesc& desc,
                                                 void* srcData, size_t rowBytes) {
     SK_TRACE_EVENT0("GrContext::createAndLockTexture");
-    GrAssert(key->width() == desc.fWidth);
-    GrAssert(key->height() == desc.fHeight);
 
 #if GR_DUMP_TEXTURE_UPLOAD
     GrPrintf("GrContext::createAndLockTexture [%d %d]\n", desc.fWidth, desc.fHeight);
 #endif
 
-    GrTextureEntry* entry = NULL;
-    bool special = finalizeTextureKey(key, sampler, false);
-    if (special) {
-        GrTextureEntry* clampEntry;
-        GrTextureKey clampKey(*key);
-        clampEntry = findAndLockTexture(&clampKey, GrSamplerState::ClampNoFilter());
+    TextureCacheEntry entry;
+    uint32_t v[4];
+    bool special = gen_texture_key_values(fGpu, sampler, key,
+                                          desc.fWidth, desc.fHeight, false, v);
+    GrResourceKey resourceKey(v);
 
-        if (NULL == clampEntry) {
-            clampEntry = createAndLockTexture(&clampKey,
+    if (special) {
+        TextureCacheEntry clampEntry = 
+                            findAndLockTexture(key, desc.fWidth, desc.fHeight,
+                                               GrSamplerState::ClampNoFilter());
+
+        if (NULL == clampEntry.texture()) {
+            clampEntry = createAndLockTexture(key,
                                               GrSamplerState::ClampNoFilter(),
                                               desc, srcData, rowBytes);
-            GrAssert(NULL != clampEntry);
-            if (NULL == clampEntry) {
-                return NULL;
+            GrAssert(NULL != clampEntry.texture());
+            if (NULL == clampEntry.texture()) {
+                return entry;
             }
         }
         GrTextureDesc rtDesc = desc;
@@ -241,7 +268,7 @@
         if (NULL != texture) {
             GrDrawTarget::AutoStateRestore asr(fGpu);
             fGpu->setRenderTarget(texture->asRenderTarget());
-            fGpu->setTexture(0, clampEntry->texture());
+            fGpu->setTexture(0, clampEntry.texture());
             fGpu->disableStencil();
             fGpu->setViewMatrix(GrMatrix::I());
             fGpu->setAlpha(0xff);
@@ -276,7 +303,7 @@
                 verts[1].setIRectFan(0, 0, 1, 1, 2*sizeof(GrPoint));
                 fGpu->drawNonIndexed(kTriangleFan_PrimitiveType,
                                      0, 4);
-                entry = fTextureCache->createAndLock(*key, texture);
+                entry.set(fTextureCache->createAndLock(resourceKey, texture));
             }
             texture->releaseRenderTarget();
         } else {
@@ -302,69 +329,64 @@
                                                      stretchedPixels.get(),
                                                      stretchedRowBytes);
             GrAssert(NULL != texture);
-            entry = fTextureCache->createAndLock(*key, texture);
+            entry.set(fTextureCache->createAndLock(resourceKey, texture));
         }
-        fTextureCache->unlock(clampEntry);
+        fTextureCache->unlock(clampEntry.cacheEntry());
 
     } else {
         GrTexture* texture = fGpu->createTexture(desc, srcData, rowBytes);
         if (NULL != texture) {
-            entry = fTextureCache->createAndLock(*key, texture);
-        } else {
-            entry = NULL;
+            entry.set(fTextureCache->createAndLock(resourceKey, texture));
         }
     }
     return entry;
 }
 
-GrTextureEntry* GrContext::lockKeylessTexture(const GrTextureDesc& desc) {
-    uint32_t p0 = desc.fFormat;
-    uint32_t p1 = (desc.fAALevel << 16) | desc.fFlags;
-    GrTextureKey key(p0, p1, desc.fWidth, desc.fHeight);
-    this->finalizeTextureKey(&key, GrSamplerState::ClampNoFilter(), true);
-    
-    GrTextureEntry* entry = fTextureCache->findAndLock(key);
-    if (NULL == entry) {
-        GrTexture* texture = fGpu->createTexture(desc, NULL, 0);
-        if (NULL != texture) {
-            entry = fTextureCache->createAndLock(key, texture);
-        }
-    }
-    // If the caller gives us the same desc/sampler twice we don't want
-    // to return the same texture the second time (unless it was previously
-    // released). So we detach the entry from the cache and reattach at release.
-    if (NULL != entry) {
-        fTextureCache->detach(entry);
-    }
-    return entry;
+namespace {
+inline void gen_scratch_tex_key_values(const GrGpu* gpu, 
+                                       const GrTextureDesc& desc,
+                                       uint32_t v[4]) {
+    // Instead of a client-provided key of the texture contents
+    // we create a key of from the descriptor.
+    GrContext::TextureKey descKey = desc.fAALevel |
+                                    (desc.fFlags << 8) |
+                                    ((uint64_t) desc.fFormat << 32);
+    // this code path isn't friendly to tiling with NPOT restricitons
+    // We just pass ClampNoFilter()
+    gen_texture_key_values(gpu, GrSamplerState::ClampNoFilter(), descKey,
+                            desc.fWidth, desc.fHeight, true, v);
+}
 }
 
-GrTextureEntry* GrContext::findApproximateKeylessTexture(
-                                                    const GrTextureDesc& inDesc) {
+GrContext::TextureCacheEntry GrContext::lockScratchTexture(
+                                                const GrTextureDesc& inDesc,
+                                                ScratchTexMatch match) {
+
     GrTextureDesc desc = inDesc;
-    // bin by pow2 with a reasonable min
-    static const int MIN_SIZE = 256;
-    desc.fWidth  = GrMax(MIN_SIZE, GrNextPow2(desc.fWidth));
-    desc.fHeight = GrMax(MIN_SIZE, GrNextPow2(desc.fHeight));
+    if (kExact_ScratchTexMatch != match) {
+        // bin by pow2 with a reasonable min
+        static const int MIN_SIZE = 256;
+        desc.fWidth  = GrMax(MIN_SIZE, GrNextPow2(desc.fWidth));
+        desc.fHeight = GrMax(MIN_SIZE, GrNextPow2(desc.fHeight));
+    }
 
     uint32_t p0 = desc.fFormat;
     uint32_t p1 = (desc.fAALevel << 16) | desc.fFlags;
     
-    GrTextureEntry* entry;
-    bool keepTrying = true;
+    GrResourceEntry* entry;
     int origWidth = desc.fWidth;
     int origHeight = desc.fHeight;
     bool doubledW = false;
     bool doubledH = false;
 
     do {
-        GrTextureKey key(p0, p1, desc.fWidth, desc.fHeight);
-        this->finalizeTextureKey(&key, GrSamplerState::ClampNoFilter(), true);
+        uint32_t v[4];
+        gen_scratch_tex_key_values(fGpu, desc, v);
+        GrResourceKey key(v);
         entry = fTextureCache->findAndLock(key);
-
         // if we miss, relax the fit of the flags...
         // then try doubling width... then height.
-        if (NULL != entry) {
+        if (NULL != entry || kExact_ScratchTexMatch == match) {
             break;
         }
         if (!(desc.fFlags & kRenderTarget_GrTextureFlagBit)) {
@@ -392,9 +414,9 @@
         desc.fHeight = origHeight;
         GrTexture* texture = fGpu->createTexture(desc, NULL, 0);
         if (NULL != texture) {
-            GrTextureKey key(p0, p1, desc.fWidth, desc.fHeight);
-            this->finalizeTextureKey(&key, GrSamplerState::ClampNoFilter(), 
-                                     true);
+            uint32_t v[4];
+            gen_scratch_tex_key_values(fGpu, desc, v);
+            GrResourceKey key(v);
             entry = fTextureCache->createAndLock(key, texture);
         }
     }
@@ -405,14 +427,17 @@
     if (NULL != entry) {
         fTextureCache->detach(entry);
     }
-    return entry;
+    return TextureCacheEntry(entry);
 }
 
-void GrContext::unlockTexture(GrTextureEntry* entry) {
-    if (kKeylessBit & entry->key().getPrivateBits()) {
-        fTextureCache->reattachAndUnlock(entry);
+void GrContext::unlockTexture(TextureCacheEntry entry) {
+    // If this is a scratch texture we detached it from the cache
+    // while it was locked (to avoid two callers simultaneously getting
+    // the same texture).
+    if (kScratchBit & entry.cacheEntry()->key().getValue32(3)) {
+        fTextureCache->reattachAndUnlock(entry.cacheEntry());
     } else {
-        fTextureCache->unlock(entry);
+        fTextureCache->unlock(entry.cacheEntry());
     }
 }
 
@@ -529,9 +554,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 struct GrContext::OffscreenRecord {
-    OffscreenRecord() { fEntry0 = NULL; fEntry1 = NULL; }
-    ~OffscreenRecord() { GrAssert(NULL == fEntry0 && NULL == fEntry1); }
-
     enum Downsample {
         k4x4TwoPass_Downsample,
         k4x4SinglePass_Downsample,
@@ -542,8 +564,8 @@
     int                            fTileCountX;
     int                            fTileCountY;
     int                            fScale;
-    GrTextureEntry*                fEntry0;
-    GrTextureEntry*                fEntry1;
+    GrAutoScratchTexture           fOffscreen0;
+    GrAutoScratchTexture           fOffscreen1;
     GrDrawTarget::SavedDrawState   fSavedState;
     GrClip                         fClip;
 };
@@ -585,8 +607,8 @@
 
     GrAssert(GR_USE_OFFSCREEN_AA);
 
-    GrAssert(NULL == record->fEntry0);
-    GrAssert(NULL == record->fEntry1);
+    GrAssert(NULL == record->fOffscreen0.texture());
+    GrAssert(NULL == record->fOffscreen1.texture());
     GrAssert(!boundRect.isEmpty());
 
     int boundW = boundRect.width();
@@ -627,31 +649,28 @@
     
     desc.fWidth *= record->fScale;
     desc.fHeight *= record->fScale;
-
-    record->fEntry0 = this->findApproximateKeylessTexture(desc);
-    if (NULL == record->fEntry0) {
+    record->fOffscreen0.set(this, desc);
+    if (NULL == record->fOffscreen0.texture()) {
         return false;
     }
     // the approximate lookup might have given us some slop space, might as well
     // use it when computing the tiles size.
     // these are scale values, will adjust after considering
     // the possible second offscreen.
-    record->fTileSizeX = record->fEntry0->texture()->width();
-    record->fTileSizeY = record->fEntry0->texture()->height();
+    record->fTileSizeX = record->fOffscreen0.texture()->width();
+    record->fTileSizeY = record->fOffscreen0.texture()->height();
 
     if (OffscreenRecord::k4x4TwoPass_Downsample == record->fDownsample) {
         desc.fWidth /= 2;
         desc.fHeight /= 2;
-        record->fEntry1 = this->findApproximateKeylessTexture(desc);
-        if (NULL == record->fEntry1) {
-            this->unlockTexture(record->fEntry0);
-            record->fEntry0 = NULL;
+        record->fOffscreen1.set(this, desc);
+        if (NULL == record->fOffscreen1.texture()) {
             return false;
         }
         record->fTileSizeX = GrMin(record->fTileSizeX, 
-                                   2 * record->fEntry0->texture()->width());
+                                   2 * record->fOffscreen0.texture()->width());
         record->fTileSizeY = GrMin(record->fTileSizeY, 
-                                   2 * record->fEntry0->texture()->height());
+                                   2 * record->fOffscreen0.texture()->height());
     }
     record->fTileSizeX /= record->fScale;
     record->fTileSizeY /= record->fScale;
@@ -670,7 +689,7 @@
                                       int tileX, int tileY,
                                       OffscreenRecord* record) {
 
-    GrRenderTarget* offRT0 = record->fEntry0->texture()->asRenderTarget();
+    GrRenderTarget* offRT0 = record->fOffscreen0.texture()->asRenderTarget();
     GrAssert(NULL != offRT0);
 
     GrPaint tempPaint;
@@ -715,7 +734,7 @@
                                  int tileX, int tileY,
                                  OffscreenRecord* record) {
     SK_TRACE_EVENT0("GrContext::doOffscreenAAPass2");
-    GrAssert(NULL != record->fEntry0);
+    GrAssert(NULL != record->fOffscreen0.texture());
     GrDrawTarget::AutoGeometryPush agp(target);
     GrIRect tileRect;
     tileRect.fLeft = boundRect.fLeft + tileX * record->fTileSizeX;
@@ -738,7 +757,7 @@
     GrSamplerState sampler(GrSamplerState::kClamp_WrapMode, 
                            GrSamplerState::kClamp_WrapMode, filter);
 
-    GrTexture* src = record->fEntry0->texture();
+    GrTexture* src = record->fOffscreen0.texture();
     int scale;
 
     enum {
@@ -746,9 +765,9 @@
     };
 
     if (OffscreenRecord::k4x4TwoPass_Downsample == record->fDownsample) {
-        GrAssert(NULL != record->fEntry1);
+        GrAssert(NULL != record->fOffscreen1.texture());
         scale = 2;
-        GrRenderTarget* dst = record->fEntry1->texture()->asRenderTarget();
+        GrRenderTarget* dst = record->fOffscreen1.texture()->asRenderTarget();
         
         // Do 2x2 downsample from first to second
         target->setTexture(kOffscreenStage, src);
@@ -762,7 +781,7 @@
                                      scale * tileRect.height());
         target->drawSimpleRect(rect, NULL, 1 << kOffscreenStage);
         
-        src = record->fEntry1->texture();
+        src = record->fOffscreen1.texture();
     } else if (OffscreenRecord::kFSAA_Downsample == record->fDownsample) {
         scale = 1;
         GrIRect rect = SkIRect::MakeWH(tileRect.width(), tileRect.height());
@@ -811,16 +830,10 @@
 void GrContext::cleanupOffscreenAA(GrDrawTarget* target,
                                    GrPathRenderer* pr,
                                    OffscreenRecord* record) {
-    this->unlockTexture(record->fEntry0);
-    record->fEntry0 = NULL;
     if (pr) {
         // Counterpart of scale() in prepareForOffscreenAA()
         //pr->scaleCurveTolerance(SkScalarInvert(SkIntToScalar(record->fScale)));
     }
-    if (NULL != record->fEntry1) {
-        this->unlockTexture(record->fEntry1);
-        record->fEntry1 = NULL;
-    }
     target->restoreDrawState(record->fSavedState);
 }
 
@@ -1471,9 +1484,8 @@
     const GrTextureDesc desc = {
         kNone_GrTextureFlags, kNone_GrAALevel, width, height, config
     };
-    GrAutoUnlockTextureEntry aute(this,
-                                    this->findApproximateKeylessTexture(desc));
-    GrTexture* texture = aute.texture();
+    GrAutoScratchTexture ast(this, desc);
+    GrTexture* texture = ast.texture();
     if (NULL == texture) {
         return;
     }
@@ -1630,8 +1642,8 @@
     fCustomPathRenderer = GrPathRenderer::CreatePathRenderer();
     fGpu->setClipPathRenderer(fCustomPathRenderer);
 
-    fTextureCache = new GrTextureCache(MAX_TEXTURE_CACHE_COUNT,
-                                       MAX_TEXTURE_CACHE_BYTES);
+    fTextureCache = new GrResourceCache(MAX_TEXTURE_CACHE_COUNT,
+                                        MAX_TEXTURE_CACHE_BYTES);
     fFontCache = new GrFontCache(fGpu);
 
     fLastDrawCategory = kUnbuffered_DrawCategory;
diff --git a/gpu/src/GrGpu.cpp b/gpu/src/GrGpu.cpp
index 007ea31..10307e3 100644
--- a/gpu/src/GrGpu.cpp
+++ b/gpu/src/GrGpu.cpp
@@ -16,7 +16,6 @@
 
 #include "GrGpu.h"
 #include "GrTextStrike.h"
-#include "GrTextureCache.h"
 #include "GrClipIterator.h"
 #include "GrIndexBuffer.h"
 #include "GrVertexBuffer.h"
diff --git a/gpu/src/GrTextureCache.cpp b/gpu/src/GrResourceCache.cpp
similarity index 62%
rename from gpu/src/GrTextureCache.cpp
rename to gpu/src/GrResourceCache.cpp
index c3a61ac..15f9817 100644
--- a/gpu/src/GrTextureCache.cpp
+++ b/gpu/src/GrResourceCache.cpp
@@ -15,33 +15,33 @@
  */
 
 
-#include "GrTextureCache.h"
-#include "GrTexture.h"
+#include "GrResourceCache.h"
+#include "GrResource.h"
 
-GrTextureEntry::GrTextureEntry(const GrTextureKey& key, GrTexture* texture)
-        : fKey(key), fTexture(texture) {
+GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource)
+        : fKey(key), fResource(resource) {
     fLockCount = 0;
     fPrev = fNext = NULL;
 
-    // we assume ownership of the texture, and will unref it when we die
-    GrAssert(texture);
+    // we assume ownership of the resource, and will unref it when we die
+    GrAssert(resource);
 }
 
-GrTextureEntry::~GrTextureEntry() {
-    fTexture->unref();
+GrResourceEntry::~GrResourceEntry() {
+    fResource->unref();
 }
 
 #if GR_DEBUG
-void GrTextureEntry::validate() const {
+void GrResourceEntry::validate() const {
     GrAssert(fLockCount >= 0);
-    GrAssert(fTexture);
-    fTexture->validate();
+    GrAssert(fResource);
+    fResource->validate();
 }
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrTextureCache::GrTextureCache(int maxCount, size_t maxBytes) :
+GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
         fMaxCount(maxCount),
         fMaxBytes(maxBytes) {
     fEntryCount          = 0;
@@ -52,36 +52,36 @@
     fHead = fTail = NULL;
 }
 
-GrTextureCache::~GrTextureCache() {
-    GrAutoTextureCacheValidate atcv(this);
+GrResourceCache::~GrResourceCache() {
+    GrAutoResourceCacheValidate atcv(this);
 
     this->removeAll();
 }
 
-void GrTextureCache::getLimits(int* maxTextures, size_t* maxTextureBytes) const{
-    if (maxTextures) {
-        *maxTextures = fMaxCount;
+void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{
+    if (maxResources) {
+        *maxResources = fMaxCount;
     }
-    if (maxTextureBytes) {
-        *maxTextureBytes = fMaxBytes;
+    if (maxResourceBytes) {
+        *maxResourceBytes = fMaxBytes;
     }
 }
 
-void GrTextureCache::setLimits(int maxTextures, size_t maxTextureBytes) {
-    bool smaller = (maxTextures < fMaxCount) || (maxTextureBytes < fMaxBytes);
+void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
+    bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes);
 
-    fMaxCount = maxTextures;
-    fMaxBytes = maxTextureBytes;
+    fMaxCount = maxResources;
+    fMaxBytes = maxResourceBytes;
 
     if (smaller) {
         this->purgeAsNeeded();
     }
 }
 
-void GrTextureCache::internalDetach(GrTextureEntry* entry,
+void GrResourceCache::internalDetach(GrResourceEntry* entry,
                                     bool clientDetach) {
-    GrTextureEntry* prev = entry->fPrev;
-    GrTextureEntry* next = entry->fNext;
+    GrResourceEntry* prev = entry->fPrev;
+    GrResourceEntry* next = entry->fNext;
 
     if (prev) {
         prev->fNext = next;
@@ -97,14 +97,14 @@
     // update our stats
     if (clientDetach) {
         fClientDetachedCount += 1;
-        fClientDetachedBytes += entry->texture()->sizeInBytes();
+        fClientDetachedBytes += entry->resource()->sizeInBytes();
     } else {
         fEntryCount -= 1;
-        fEntryBytes -= entry->texture()->sizeInBytes();
+        fEntryBytes -= entry->resource()->sizeInBytes();
     }
 }
 
-void GrTextureCache::attachToHead(GrTextureEntry* entry,
+void GrResourceCache::attachToHead(GrResourceEntry* entry,
                                   bool clientReattach) {
     entry->fPrev = NULL;
     entry->fNext = fHead;
@@ -119,19 +119,19 @@
     // update our stats
     if (clientReattach) {
         fClientDetachedCount -= 1;
-        fClientDetachedBytes -= entry->texture()->sizeInBytes();
+        fClientDetachedBytes -= entry->resource()->sizeInBytes();
     } else {
         fEntryCount += 1;
-        fEntryBytes += entry->texture()->sizeInBytes();
+        fEntryBytes += entry->resource()->sizeInBytes();
     }
 }
 
-class GrTextureCache::Key {
-    typedef GrTextureEntry T;
+class GrResourceCache::Key {
+    typedef GrResourceEntry T;
 
-    const GrTextureKey& fKey;
+    const GrResourceKey& fKey;
 public:
-    Key(const GrTextureKey& key) : fKey(key) {}
+    Key(const GrResourceKey& key) : fKey(key) {}
 
     uint32_t getHash() const { return fKey.hashIndex(); }
 
@@ -154,10 +154,10 @@
 #endif
 };
 
-GrTextureEntry* GrTextureCache::findAndLock(const GrTextureKey& key) {
-    GrAutoTextureCacheValidate atcv(this);
+GrResourceEntry* GrResourceCache::findAndLock(const GrResourceKey& key) {
+    GrAutoResourceCacheValidate atcv(this);
 
-    GrTextureEntry* entry = fCache.find(key);
+    GrResourceEntry* entry = fCache.find(key);
     if (entry) {
         this->internalDetach(entry, false);
         this->attachToHead(entry, false);
@@ -167,18 +167,18 @@
     return entry;
 }
 
-GrTextureEntry* GrTextureCache::createAndLock(const GrTextureKey& key,
-                                              GrTexture* texture) {
-    GrAutoTextureCacheValidate atcv(this);
+GrResourceEntry* GrResourceCache::createAndLock(const GrResourceKey& key,
+                                              GrResource* resource) {
+    GrAutoResourceCacheValidate atcv(this);
 
-    GrTextureEntry* entry = new GrTextureEntry(key, texture);
+    GrResourceEntry* entry = new GrResourceEntry(key, resource);
 
     this->attachToHead(entry, false);
     fCache.insert(key, entry);
 
 #if GR_DUMP_TEXTURE_UPLOAD
-    GrPrintf("--- add texture to cache %p, count=%d bytes= %d %d\n",
-             entry, fEntryCount, texture->sizeInBytes(), fEntryBytes);
+    GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n",
+             entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
 #endif
 
     // mark the entry as "busy" so it doesn't get purged
@@ -187,19 +187,19 @@
     return entry;
 }
 
-void GrTextureCache::detach(GrTextureEntry* entry) {
+void GrResourceCache::detach(GrResourceEntry* entry) {
     internalDetach(entry, true);
     fCache.remove(entry->fKey, entry);
 }
 
-void GrTextureCache::reattachAndUnlock(GrTextureEntry* entry) {
+void GrResourceCache::reattachAndUnlock(GrResourceEntry* entry) {
     attachToHead(entry, true);
     fCache.insert(entry->key(), entry);
     unlock(entry);
 }
 
-void GrTextureCache::unlock(GrTextureEntry* entry) {
-    GrAutoTextureCacheValidate atcv(this);
+void GrResourceCache::unlock(GrResourceEntry* entry) {
+    GrAutoResourceCacheValidate atcv(this);
 
     GrAssert(entry);
     GrAssert(entry->isLocked());
@@ -209,16 +209,16 @@
     this->purgeAsNeeded();
 }
 
-void GrTextureCache::purgeAsNeeded() {
-    GrAutoTextureCacheValidate atcv(this);
+void GrResourceCache::purgeAsNeeded() {
+    GrAutoResourceCacheValidate atcv(this);
 
-    GrTextureEntry* entry = fTail;
+    GrResourceEntry* entry = fTail;
     while (entry) {
         if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
             break;
         }
 
-        GrTextureEntry* prev = entry->fPrev;
+        GrResourceEntry* prev = entry->fPrev;
         if (!entry->isLocked()) {
             // remove from our cache
             fCache.remove(entry->fKey, entry);
@@ -227,9 +227,9 @@
             this->internalDetach(entry, false);
 
 #if GR_DUMP_TEXTURE_UPLOAD
-            GrPrintf("--- ~texture from cache %p [%d %d]\n", entry->texture(),
-                     entry->texture()->width(),
-                     entry->texture()->height());
+            GrPrintf("--- ~resource from cache %p [%d %d]\n", entry->resource(),
+                     entry->resource()->width(),
+                     entry->resource()->height());
 #endif
             delete entry;
         }
@@ -237,15 +237,15 @@
     }
 }
 
-void GrTextureCache::removeAll() {
+void GrResourceCache::removeAll() {
     GrAssert(!fClientDetachedCount);
     GrAssert(!fClientDetachedBytes);
 
-    GrTextureEntry* entry = fHead;
+    GrResourceEntry* entry = fHead;
     while (entry) {
         GrAssert(!entry->isLocked());
 
-        GrTextureEntry* next = entry->fNext;
+        GrResourceEntry* next = entry->fNext;
         delete entry;
         entry = next;
     }
@@ -259,8 +259,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 #if GR_DEBUG
-static int countMatches(const GrTextureEntry* head, const GrTextureEntry* target) {
-    const GrTextureEntry* entry = head;
+static int countMatches(const GrResourceEntry* head, const GrResourceEntry* target) {
+    const GrResourceEntry* entry = head;
     int count = 0;
     while (entry) {
         if (target == entry) {
@@ -277,7 +277,7 @@
 }
 #endif
 
-void GrTextureCache::validate() const {
+void GrResourceCache::validate() const {
     GrAssert(!fHead == !fTail);
     GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
     GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
@@ -287,14 +287,14 @@
 
     fCache.validate();
 
-    GrTextureEntry* entry = fHead;
+    GrResourceEntry* entry = fHead;
     int count = 0;
     size_t bytes = 0;
     while (entry) {
         entry->validate();
         GrAssert(fCache.find(entry->key()));
         count += 1;
-        bytes += entry->texture()->sizeInBytes();
+        bytes += entry->resource()->sizeInBytes();
         entry = entry->fNext;
     }
     GrAssert(count == fEntryCount - fClientDetachedCount);
diff --git a/gpu/src/GrResourceCache.h b/gpu/src/GrResourceCache.h
new file mode 100644
index 0000000..73d2924
--- /dev/null
+++ b/gpu/src/GrResourceCache.h
@@ -0,0 +1,307 @@
+/*
+    Copyright 2011 Google Inc.
+
+    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.
+ */
+
+
+#ifndef GrResourceCache_DEFINED
+#define GrResourceCache_DEFINED
+
+#include "GrTypes.h"
+#include "GrTHashCache.h"
+
+class GrResource;
+
+// return true if a<b, or false if b<a
+//
+#define RET_IF_LT_OR_GT(a, b)   \
+    do {                        \
+        if ((a) < (b)) {        \
+            return true;        \
+        }                       \
+        if ((b) < (a)) {        \
+            return false;       \
+        }                       \
+    } while (0)
+
+/**
+ *  Helper class for GrResourceCache, the Key is used to identify src data for
+ *  a resource. It is identified by 2 32bit data fields which can hold any
+ *  data (uninterpreted by the cache) and a width/height.
+ */
+class GrResourceKey {
+public:
+    enum {
+        kHashBits   = 7,
+        kHashCount  = 1 << kHashBits,
+        kHashMask   = kHashCount - 1
+    };
+
+    GrResourceKey(uint32_t p0, uint32_t p1, uint32_t p2, uint32_t p3) {
+        fP[0] = p0;
+        fP[1] = p1;
+        fP[2] = p2;
+        fP[3] = p3;
+        this->computeHashIndex();
+    }
+
+    GrResourceKey(uint32_t v[4]) {
+        memcpy(fP, v, 4 * sizeof(uint32_t));
+        this->computeHashIndex();
+    }
+
+    GrResourceKey(const GrResourceKey& src) {
+        memcpy(fP, src.fP, 4 * sizeof(uint32_t));
+#if GR_DEBUG
+        this->computeHashIndex();
+        GrAssert(fHashIndex == src.fHashIndex);
+#endif
+        fHashIndex = src.fHashIndex;
+    }
+
+    //!< returns hash value [0..kHashMask] for the key
+    int hashIndex() const { return fHashIndex; }
+
+    friend bool operator==(const GrResourceKey& a, const GrResourceKey& b) {
+        GR_DEBUGASSERT(-1 != a.fHashIndex && -1 != b.fHashIndex);
+        return 0 == memcmp(a.fP, b.fP, 4 * sizeof(uint32_t));
+    }
+
+    friend bool operator!=(const GrResourceKey& a, const GrResourceKey& b) {
+        GR_DEBUGASSERT(-1 != a.fHashIndex && -1 != b.fHashIndex);
+        return !(a == b);
+    }
+
+    friend bool operator<(const GrResourceKey& a, const GrResourceKey& b) {
+        RET_IF_LT_OR_GT(a.fP[0], b.fP[0]);
+        RET_IF_LT_OR_GT(a.fP[1], b.fP[1]);
+        RET_IF_LT_OR_GT(a.fP[2], b.fP[2]);
+        return a.fP[3] < b.fP[3];
+    }
+
+    uint32_t getValue32(int i) const {
+        GrAssert(i >=0 && i < 4);
+        return fP[i];
+    }
+private:
+
+    static uint32_t rol(uint32_t x) {
+        return (x >> 24) | (x << 8);
+    }
+    static uint32_t ror(uint32_t x) {
+        return (x >> 8) | (x << 24);
+    }
+    static uint32_t rohalf(uint32_t x) {
+        return (x >> 16) | (x << 16);
+    }
+
+    void computeHashIndex() {
+        uint32_t hash = fP[0] ^ rol(fP[1]) ^ ror(fP[2]) ^ rohalf(fP[3]);
+        // this way to mix and reduce hash to its index may have to change
+        // depending on how many bits we allocate to the index
+        hash ^= hash >> 16;
+        hash ^= hash >> 8;
+        fHashIndex = hash & kHashMask;
+    }
+
+    uint32_t    fP[4];
+
+    // this is computed from the fP... fields
+    int         fHashIndex;
+
+    friend class GrContext;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrResourceEntry {
+public:
+    GrResource* resource() const { return fResource; }
+    const GrResourceKey& key() const { return fKey; }
+
+#if GR_DEBUG
+    GrResourceEntry* next() const { return fNext; }
+    GrResourceEntry* prev() const { return fPrev; }
+#endif
+
+#if GR_DEBUG
+    void validate() const;
+#else
+    void validate() const {}
+#endif
+
+private:
+    GrResourceEntry(const GrResourceKey& key, GrResource* resource);
+    ~GrResourceEntry();
+
+    bool isLocked() const { return fLockCount != 0; }
+    void lock() { ++fLockCount; }
+    void unlock() {
+        GrAssert(fLockCount > 0);
+        --fLockCount;
+    }
+
+    GrResourceKey    fKey;
+    GrResource*      fResource;
+
+    // track if we're in use, used when we need to purge
+    // we only purge unlocked entries
+    int fLockCount;
+
+    // we're a dlinklist
+    GrResourceEntry* fPrev;
+    GrResourceEntry* fNext;
+
+    friend class GrResourceCache;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "GrTHashCache.h"
+
+/**
+ *  Cache of GrResource objects.
+ *
+ *  These have a corresponding GrResourceKey, built from 128bits identifying the
+ *  resource.
+ *
+ *  The cache stores the entries in a double-linked list, which is its LRU.
+ *  When an entry is "locked" (i.e. given to the caller), it is moved to the
+ *  head of the list. If/when we must purge some of the entries, we walk the
+ *  list backwards from the tail, since those are the least recently used.
+ *
+ *  For fast searches, we maintain a sorted array (based on the GrResourceKey)
+ *  which we can bsearch. When a new entry is added, it is inserted into this
+ *  array.
+ *
+ *  For even faster searches, a hash is computed from the Key. If there is
+ *  a collision between two keys with the same hash, we fall back on the
+ *  bsearch, and update the hash to reflect the most recent Key requested.
+ */
+class GrResourceCache {
+public:
+    GrResourceCache(int maxCount, size_t maxBytes);
+    ~GrResourceCache();
+
+    /**
+     *  Return the current resource cache limits.
+     *
+     *  @param maxResource If non-null, returns maximum number of resources 
+     *                     that can be held in the cache.
+     *  @param maxBytes    If non-null, returns maximum number of bytes of
+     *                         gpu memory that can be held in the cache.
+     */
+    void getLimits(int* maxResources, size_t* maxBytes) const;
+
+    /**
+     *  Specify the resource cache limits. If the current cache exceeds either
+     *  of these, it will be purged (LRU) to keep the cache within these limits.
+     *
+     *  @param maxResources The maximum number of resources that can be held in
+     *                      the cache.
+     *  @param maxBytes     The maximum number of bytes of resource memory that
+     *                      can be held in the cache.
+     */
+    void setLimits(int maxResource, size_t maxResourceBytes);
+
+    /**
+     *  Search for an entry with the same Key. If found, "lock" it and return it.
+     *  If not found, return null.
+     */
+    GrResourceEntry* findAndLock(const GrResourceKey&);
+
+    /**
+     *  Create a new entry, based on the specified key and resource, and return
+     *  its "locked" entry.
+     *
+     *  Ownership of the resource is transferred to the Entry, which will unref()
+     *  it when we are purged or deleted.
+     */
+    GrResourceEntry* createAndLock(const GrResourceKey&, GrResource*);
+
+    /**
+     * Detach removes an entry from the cache. This prevents the entry from
+     * being found by a subsequent findAndLock() until it is reattached. The
+     * entry still counts against the cache's budget and should be reattached
+     * when exclusive access is no longer needed.
+     */
+    void detach(GrResourceEntry*);
+
+    /**
+     * Reattaches a resource to the cache and unlocks it. Allows it to be found
+     * by a subsequent findAndLock or be purged (provided its lock count is
+     * now 0.)
+     */
+    void reattachAndUnlock(GrResourceEntry*);
+
+    /**
+     *  When done with an entry, call unlock(entry) on it, which returns it to
+     *  a purgable state.
+     */
+    void unlock(GrResourceEntry*);
+
+    void removeAll();
+
+#if GR_DEBUG
+    void validate() const;
+#else
+    void validate() const {}
+#endif
+
+private:
+    void internalDetach(GrResourceEntry*, bool);
+    void attachToHead(GrResourceEntry*, bool);
+    void purgeAsNeeded();   // uses kFreeResource_DeleteMode
+
+    class Key;
+    GrTHashTable<GrResourceEntry, Key, 8> fCache;
+
+    // manage the dlink list
+    GrResourceEntry* fHead;
+    GrResourceEntry* fTail;
+
+    // our budget, used in purgeAsNeeded()
+    int fMaxCount;
+    size_t fMaxBytes;
+
+    // our current stats, related to our budget
+    int fEntryCount;
+    size_t fEntryBytes;
+    int fClientDetachedCount;
+    size_t fClientDetachedBytes;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_DEBUG
+    class GrAutoResourceCacheValidate {
+    public:
+        GrAutoResourceCacheValidate(GrResourceCache* cache) : fCache(cache) {
+            cache->validate();
+        }
+        ~GrAutoResourceCacheValidate() {
+            fCache->validate();
+        }
+    private:
+        GrResourceCache* fCache;
+    };
+#else
+    class GrAutoResourceCacheValidate {
+    public:
+        GrAutoResourceCacheValidate(GrResourceCache*) {}
+    };
+#endif
+
+#endif
+