Move GrTextureStripAtlas to its own file

This is a purely mechanical change and intended to make the diff of
https://skia-review.googlesource.com/c/skia/+/144305(Update GrTextureStripAtlas for DDLs) comprehensible.

Change-Id: I85fe54dd26aef6c86536702c2e19bd3226d44ba2
Reviewed-on: https://skia-review.googlesource.com/144788
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index de9d8b8..1c25078 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -362,6 +362,8 @@
   "$_src/gpu/effects/GrDisableColorXP.h",
   "$_src/gpu/effects/GrDistanceFieldGeoProc.cpp",
   "$_src/gpu/effects/GrDistanceFieldGeoProc.h",
+  "$_src/gpu/effects/GrDynamicTextureStripAtlas.cpp",
+  "$_src/gpu/effects/GrDynamicTextureStripAtlas.h",
   "$_src/gpu/effects/GrEllipseEffect.cpp",
   "$_src/gpu/effects/GrEllipseEffect.h",
   "$_src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp",
diff --git a/src/gpu/GrTextureStripAtlas.h b/src/gpu/GrTextureStripAtlas.h
index b948566..af972b5 100644
--- a/src/gpu/GrTextureStripAtlas.h
+++ b/src/gpu/GrTextureStripAtlas.h
@@ -8,162 +8,17 @@
 #ifndef GrTextureStripAtlas_DEFINED
 #define GrTextureStripAtlas_DEFINED
 
-#include "SkBitmap.h"
-#include "SkGr.h"
+#include "effects/GrDynamicTextureStripAtlas.h"
+
+#include "SkNoncopyable.h"
 #include "SkOpts.h"
 #include "SkRefCnt.h"
-#include "SkTDArray.h"
 #include "SkTDynamicHash.h"
-#include "SkTypes.h"
 
-class GrSurfaceContext;
+class GrContext;
+class GrProxyProvider;
 class GrTextureProxy;
-
-/**
- * Maintains a single large texture whose rows store many textures of a small fixed height,
- * stored in rows across the x-axis such that we can safely wrap/repeat them horizontally.
- */
-class GrTextureStripAtlas : public SkRefCnt {
-public:
-    /**
-     * Descriptor struct which we'll use as a hash table key
-     */
-    struct Desc {
-        Desc() { sk_bzero(this, sizeof(*this)); }
-        GrPixelConfig fConfig;
-        uint16_t fWidth, fHeight, fRowHeight;
-        uint16_t fUnusedPadding;
-        bool operator==(const Desc& other) const {
-            return 0 == memcmp(this, &other, sizeof(Desc));
-        }
-    };
-
-    ~GrTextureStripAtlas();
-
-    /**
-     * Add a texture to the atlas
-     *  @param data Bitmap data to copy into the row
-     *  @return The row index we inserted into, or -1 if we failed to find an open row. The caller
-     *      is responsible for calling unlockRow() with this row index when it's done with it.
-     */
-    int lockRow(GrContext*, const SkBitmap&);
-
-    /**
-     * This is intended to be used when cloning a processor that already holds a lock. It is
-     * assumed that the row already has at least one lock.
-     */
-    void lockRow(int row);
-    void unlockRow(int row);
-
-    /**
-     * These functions help turn an integer row index in [0, 1, 2, ... numRows] into a scalar y
-     * texture coordinate in [0, 1] that we can use in a shader.
-     *
-     * If a regular texture access without using the atlas looks like:
-     *
-     *      texture2D(sampler, float2(x, y))
-     *
-     * Then when using the atlas we'd replace it with:
-     *
-     *       texture2D(sampler, float2(x, yOffset + y * scaleFactor))
-     *
-     * Where yOffset, returned by getYOffset(), is the offset to the start of the row within the
-     * atlas and scaleFactor, returned by getNormalizedTexelHeight, is the normalized height of
-     * one texel row.
-     */
-    SkScalar getYOffset(int row) const { return SkIntToScalar(row) / fNumRows; }
-    SkScalar getNormalizedTexelHeight() const { return fNormalizedYHeight; }
-
-    sk_sp<GrTextureProxy> asTextureProxyRef() const;
-
-private:
-    friend class GrTextureStripAtlasManager; // for ctor
-
-    static uint32_t CreateUniqueID();
-
-    // Key to indicate an atlas row without any meaningful data stored in it
-    const static uint32_t kEmptyAtlasRowKey = 0xffffffff;
-
-    /**
-     * The state of a single row in our cache, next/prev pointers allow these to be chained
-     * together to represent LRU status
-     */
-    struct AtlasRow : ::SkNoncopyable {
-        AtlasRow() : fKey(kEmptyAtlasRowKey), fLocks(0), fNext(nullptr), fPrev(nullptr) { }
-        // GenerationID of the bitmap that is represented by this row, 0xffffffff means "empty"
-        uint32_t fKey;
-        // How many times this has been locked (0 == unlocked)
-        int32_t fLocks;
-        // We maintain an LRU linked list between unlocked nodes with these pointers
-        AtlasRow* fNext;
-        AtlasRow* fPrev;
-    };
-
-    /**
-     * Only the GrTextureStripAtlasManager is allowed to create GrTextureStripAtlases
-     */
-    GrTextureStripAtlas(const Desc& desc);
-
-    void lockTexture(GrContext*);
-    void unlockTexture();
-
-    /**
-     * Initialize our LRU list (if one already exists, clear it and start anew)
-     */
-    void initLRU();
-
-    /**
-     * Grabs the least recently used free row out of the LRU list, returns nullptr if no rows
-     * are free.
-     */
-    AtlasRow* getLRU();
-
-    void appendLRU(AtlasRow* row);
-    void removeFromLRU(AtlasRow* row);
-
-    /**
-     * Searches the key table for a key and returns the index if found; if not found, it returns
-     * the bitwise not of the index at which we could insert the key to maintain a sorted list.
-     **/
-    int searchByKey(uint32_t key);
-
-    /**
-     * Compare two atlas rows by key, so we can sort/search by key
-     */
-    static bool KeyLess(const AtlasRow& lhs, const AtlasRow& rhs) {
-        return lhs.fKey < rhs.fKey;
-    }
-
-#ifdef SK_DEBUG
-    void validate();
-#endif
-
-    // A unique ID for this atlas, so we can be sure that if we
-    // get a texture back from the texture cache, that it's the same one we last used.
-    const uint32_t fCacheKey;
-
-    // Total locks on all rows (when this reaches zero, we can unlock our texture)
-    int32_t fLockedRows;
-
-    const Desc fDesc;
-    const uint16_t fNumRows;
-    sk_sp<GrSurfaceContext> fTexContext;
-
-    SkScalar fNormalizedYHeight;
-
-    // Array of AtlasRows which store the state of all our rows. Stored in a contiguous array, in
-    // order that they appear in our texture, this means we can subtract this pointer from a row
-    // pointer to get its index in the texture, and can save storing a row number in AtlasRow.
-    AtlasRow* fRows;
-
-    // Head and tail for linked list of least-recently-used rows (front = least recently used).
-    // Note that when a texture is locked, it gets removed from this list until it is unlocked.
-    AtlasRow* fLRUFront;
-    AtlasRow* fLRUBack;
-
-    // A list of pointers to AtlasRows that currently contain cached images, sorted by key
-    SkTDArray<AtlasRow*> fKeyTable;
-};
+class SkBitmap;
 
 class GrTextureStripAtlasManager {
 public:
diff --git a/src/gpu/effects/GrDynamicTextureStripAtlas.cpp b/src/gpu/effects/GrDynamicTextureStripAtlas.cpp
new file mode 100644
index 0000000..ede6809
--- /dev/null
+++ b/src/gpu/effects/GrDynamicTextureStripAtlas.cpp
@@ -0,0 +1,334 @@
+/*
+ * 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 "GrDynamicTextureStripAtlas.h"
+
+#include "GrContextPriv.h"
+#include "SkAtomics.h"
+#include "SkGr.h"
+#include "SkTSearch.h"
+
+#ifdef SK_DEBUG
+    #define VALIDATE this->validate()
+#else
+    #define VALIDATE
+#endif
+
+uint32_t GrTextureStripAtlas::CreateUniqueID() {
+    static int32_t gUniqueID = SK_InvalidUniqueID;
+    uint32_t id;
+    // Loop in case our global wraps around, as we never want to return a 0.
+    do {
+        id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
+    } while (id == SK_InvalidUniqueID);
+    return id;
+}
+
+GrTextureStripAtlas::GrTextureStripAtlas(const Desc& desc)
+    : fCacheKey(CreateUniqueID())
+    , fLockedRows(0)
+    , fDesc(desc)
+    , fNumRows(desc.fHeight / desc.fRowHeight)
+    , fRows(new AtlasRow[fNumRows])
+    , fLRUFront(nullptr)
+    , fLRUBack(nullptr) {
+    SkASSERT(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
+    this->initLRU();
+    fNormalizedYHeight = SK_Scalar1 / fDesc.fHeight;
+    VALIDATE;
+}
+
+GrTextureStripAtlas::~GrTextureStripAtlas() { delete[] fRows; }
+
+void GrTextureStripAtlas::lockRow(int row) {
+    // This should only be called on a row that is already locked.
+    SkASSERT(fRows[row].fLocks);
+    fRows[row].fLocks++;
+    ++fLockedRows;
+}
+
+int GrTextureStripAtlas::lockRow(GrContext* context, const SkBitmap& bitmap) {
+    VALIDATE;
+
+    if (!context->contextPriv().resourceProvider()) {
+        // DDL TODO: For DDL we need to schedule inline & ASAP uploads. However these systems
+        // currently use the flushState which we can't use for the opList-based DDL phase.
+        // For the opList-based solution every texture strip will get its own texture proxy.
+        // We will revisit this for the flushState-based solution.
+        return -1;
+    }
+
+    if (0 == fLockedRows) {
+        this->lockTexture(context);
+        if (!fTexContext) {
+            return -1;
+        }
+    }
+
+    int key = bitmap.getGenerationID();
+    int rowNumber = -1;
+    int index = this->searchByKey(key);
+
+    if (index >= 0) {
+        // We already have the data in a row, so we can just return that row
+        AtlasRow* row = fKeyTable[index];
+        if (0 == row->fLocks) {
+            this->removeFromLRU(row);
+        }
+        ++row->fLocks;
+        ++fLockedRows;
+
+        // Since all the rows are always stored in a contiguous array, we can save the memory
+        // required for storing row numbers and just compute it with some pointer arithmetic
+        rowNumber = static_cast<int>(row - fRows);
+    } else {
+        // ~index is the index where we will insert the new key to keep things sorted
+        index = ~index;
+
+        // We don't have this data cached, so pick the least recently used row to copy into
+        AtlasRow* row = this->getLRU();
+
+        ++fLockedRows;
+
+        if (nullptr == row) {
+            // force a flush, which should unlock all the rows; then try again
+            context->contextPriv().flush(nullptr); // tighten this up?
+            row = this->getLRU();
+            if (nullptr == row) {
+                --fLockedRows;
+                return -1;
+            }
+        }
+
+        this->removeFromLRU(row);
+
+        uint32_t oldKey = row->fKey;
+
+        // If we are writing into a row that already held bitmap data, we need to remove the
+        // reference to that genID which is stored in our sorted table of key values.
+        if (oldKey != kEmptyAtlasRowKey) {
+
+            // Find the entry in the list; if it's before the index where we plan on adding the new
+            // entry, we decrement since it will shift elements ahead of it back by one.
+            int oldIndex = this->searchByKey(oldKey);
+            if (oldIndex < index) {
+                --index;
+            }
+
+            fKeyTable.remove(oldIndex);
+        }
+
+        row->fKey = key;
+        row->fLocks = 1;
+        fKeyTable.insert(index, 1, &row);
+        rowNumber = static_cast<int>(row - fRows);
+
+        SkASSERT(bitmap.width() == fDesc.fWidth);
+        SkASSERT(bitmap.height() == fDesc.fRowHeight);
+
+        // Pass in the kDontFlush flag, since we know we're writing to a part of this texture
+        // that is not currently in use
+        fTexContext->writePixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(),
+                                 0, rowNumber * fDesc.fRowHeight,
+                                 GrContextPriv::kDontFlush_PixelOpsFlag);
+    }
+
+    SkASSERT(rowNumber >= 0);
+    VALIDATE;
+    return rowNumber;
+}
+
+sk_sp<GrTextureProxy> GrTextureStripAtlas::asTextureProxyRef() const {
+    return fTexContext->asTextureProxyRef();
+}
+
+void GrTextureStripAtlas::unlockRow(int row) {
+    VALIDATE;
+    --fRows[row].fLocks;
+    --fLockedRows;
+    SkASSERT(fRows[row].fLocks >= 0 && fLockedRows >= 0);
+    if (0 == fRows[row].fLocks) {
+        this->appendLRU(fRows + row);
+    }
+    if (0 == fLockedRows) {
+        this->unlockTexture();
+    }
+    VALIDATE;
+}
+
+GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
+    // Front is least-recently-used
+    AtlasRow* row = fLRUFront;
+    return row;
+}
+
+void GrTextureStripAtlas::lockTexture(GrContext* context) {
+
+    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+    GrUniqueKey key;
+    GrUniqueKey::Builder builder(&key, kDomain, 1);
+    builder[0] = static_cast<uint32_t>(fCacheKey);
+    builder.finish();
+
+    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
+
+    sk_sp<GrTextureProxy> proxy = proxyProvider->findOrCreateProxyByUniqueKey(
+                                                                key, kTopLeft_GrSurfaceOrigin);
+    if (!proxy) {
+        GrSurfaceDesc texDesc;
+        texDesc.fWidth  = fDesc.fWidth;
+        texDesc.fHeight = fDesc.fHeight;
+        texDesc.fConfig = fDesc.fConfig;
+
+        proxy = proxyProvider->createProxy(texDesc, kTopLeft_GrSurfaceOrigin, SkBackingFit::kExact,
+                                           SkBudgeted::kYes, GrInternalSurfaceFlags::kNoPendingIO);
+        if (!proxy) {
+            return;
+        }
+
+        SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
+        proxyProvider->assignUniqueKeyToProxy(key, proxy.get());
+        // This is a new texture, so all of our cache info is now invalid
+        this->initLRU();
+        fKeyTable.rewind();
+    }
+    SkASSERT(proxy);
+    fTexContext = context->contextPriv().makeWrappedSurfaceContext(std::move(proxy));
+}
+
+void GrTextureStripAtlas::unlockTexture() {
+    SkASSERT(fTexContext && 0 == fLockedRows);
+    fTexContext.reset();
+}
+
+void GrTextureStripAtlas::initLRU() {
+    fLRUFront = nullptr;
+    fLRUBack = nullptr;
+    // Initially all the rows are in the LRU list
+    for (int i = 0; i < fNumRows; ++i) {
+        fRows[i].fKey = kEmptyAtlasRowKey;
+        fRows[i].fNext = nullptr;
+        fRows[i].fPrev = nullptr;
+        this->appendLRU(fRows + i);
+    }
+    SkASSERT(nullptr == fLRUFront || nullptr == fLRUFront->fPrev);
+    SkASSERT(nullptr == fLRUBack || nullptr == fLRUBack->fNext);
+}
+
+void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
+    SkASSERT(nullptr == row->fPrev && nullptr == row->fNext);
+    if (nullptr == fLRUFront && nullptr == fLRUBack) {
+        fLRUFront = row;
+        fLRUBack = row;
+    } else {
+        row->fPrev = fLRUBack;
+        fLRUBack->fNext = row;
+        fLRUBack = row;
+    }
+}
+
+void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
+    SkASSERT(row);
+    if (row->fNext && row->fPrev) {
+        row->fPrev->fNext = row->fNext;
+        row->fNext->fPrev = row->fPrev;
+    } else {
+        if (nullptr == row->fNext) {
+            SkASSERT(row == fLRUBack);
+            fLRUBack = row->fPrev;
+            if (fLRUBack) {
+                fLRUBack->fNext = nullptr;
+            }
+        }
+        if (nullptr == row->fPrev) {
+            SkASSERT(row == fLRUFront);
+            fLRUFront = row->fNext;
+            if (fLRUFront) {
+                fLRUFront->fPrev = nullptr;
+            }
+        }
+    }
+    row->fNext = nullptr;
+    row->fPrev = nullptr;
+}
+
+int GrTextureStripAtlas::searchByKey(uint32_t key) {
+    AtlasRow target;
+    target.fKey = key;
+    return SkTSearch<const AtlasRow,
+                     GrTextureStripAtlas::KeyLess>((const AtlasRow**)fKeyTable.begin(),
+                                                   fKeyTable.count(),
+                                                   &target,
+                                                   sizeof(AtlasRow*));
+}
+
+#ifdef SK_DEBUG
+void GrTextureStripAtlas::validate() {
+
+    // Our key table should be sorted
+    uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
+    for (int i = 1; i < fKeyTable.count(); ++i) {
+        SkASSERT(prev < fKeyTable[i]->fKey);
+        SkASSERT(fKeyTable[i]->fKey != kEmptyAtlasRowKey);
+        prev = fKeyTable[i]->fKey;
+    }
+
+    int lruCount = 0;
+    // Validate LRU pointers, and count LRU entries
+    SkASSERT(nullptr == fLRUFront || nullptr == fLRUFront->fPrev);
+    SkASSERT(nullptr == fLRUBack  || nullptr == fLRUBack->fNext);
+    for (AtlasRow* r = fLRUFront; r != nullptr; r = r->fNext) {
+        if (nullptr == r->fNext) {
+            SkASSERT(r == fLRUBack);
+        } else {
+            SkASSERT(r->fNext->fPrev == r);
+        }
+        ++lruCount;
+    }
+
+    int rowLocks = 0;
+    int freeRows = 0;
+
+    for (int i = 0; i < fNumRows; ++i) {
+        rowLocks += fRows[i].fLocks;
+        if (0 == fRows[i].fLocks) {
+            ++freeRows;
+            bool inLRU = false;
+            // Step through the LRU and make sure it's present
+            for (AtlasRow* r = fLRUFront; r != nullptr; r = r->fNext) {
+                if (r == &fRows[i]) {
+                    inLRU = true;
+                    break;
+                }
+            }
+            SkASSERT(inLRU);
+        } else {
+            // If we are locked, we should have a key
+            SkASSERT(kEmptyAtlasRowKey != fRows[i].fKey);
+        }
+
+        // If we have a key != kEmptyAtlasRowKey, it should be in the key table
+        SkASSERT(fRows[i].fKey == kEmptyAtlasRowKey || this->searchByKey(fRows[i].fKey) >= 0);
+    }
+
+    // Our count of locks should equal the sum of row locks, unless we ran out of rows and flushed,
+    // in which case we'll have one more lock than recorded in the rows (to represent the pending
+    // lock of a row; which ensures we don't unlock the texture prematurely).
+    SkASSERT(rowLocks == fLockedRows || rowLocks + 1 == fLockedRows);
+
+    // We should have one lru entry for each free row
+    SkASSERT(freeRows == lruCount);
+
+    // If we have locked rows, we should have a locked texture, otherwise
+    // it should be unlocked
+    if (fLockedRows == 0) {
+        SkASSERT(!fTexContext);
+    } else {
+        SkASSERT(fTexContext);
+    }
+}
+#endif
diff --git a/src/gpu/effects/GrDynamicTextureStripAtlas.h b/src/gpu/effects/GrDynamicTextureStripAtlas.h
new file mode 100644
index 0000000..b404f57
--- /dev/null
+++ b/src/gpu/effects/GrDynamicTextureStripAtlas.h
@@ -0,0 +1,170 @@
+/*
+ * 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 GrDynamicTextureStripAtlas_DEFINED
+#define GrDynamicTextureStripAtlas_DEFINED
+
+#include "GrTypesPriv.h"
+
+#include "SkNoncopyable.h"
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkTDArray.h"
+
+class GrContext;
+class GrSurfaceContext;
+class GrTextureProxy;
+
+class SkBitmap;
+
+/**
+ * Maintains a single large texture whose rows store many textures of a small fixed height,
+ * stored in rows across the x-axis such that we can safely wrap/repeat them horizontally.
+ */
+class GrTextureStripAtlas : public SkRefCnt {
+public:
+    /**
+     * Descriptor struct which we'll use as a hash table key
+     */
+    struct Desc {
+        Desc() { sk_bzero(this, sizeof(*this)); }
+        GrPixelConfig fConfig;
+        uint16_t fWidth, fHeight, fRowHeight;
+        uint16_t fUnusedPadding;
+        bool operator==(const Desc& other) const {
+            return 0 == memcmp(this, &other, sizeof(Desc));
+        }
+    };
+
+    ~GrTextureStripAtlas();
+
+    /**
+     * Add a texture to the atlas
+     *  @param data Bitmap data to copy into the row
+     *  @return The row index we inserted into, or -1 if we failed to find an open row. The caller
+     *      is responsible for calling unlockRow() with this row index when it's done with it.
+     */
+    int lockRow(GrContext*, const SkBitmap&);
+
+    /**
+     * This is intended to be used when cloning a processor that already holds a lock. It is
+     * assumed that the row already has at least one lock.
+     */
+    void lockRow(int row);
+    void unlockRow(int row);
+
+    /**
+     * These functions help turn an integer row index in [0, 1, 2, ... numRows] into a scalar y
+     * texture coordinate in [0, 1] that we can use in a shader.
+     *
+     * If a regular texture access without using the atlas looks like:
+     *
+     *      texture2D(sampler, float2(x, y))
+     *
+     * Then when using the atlas we'd replace it with:
+     *
+     *       texture2D(sampler, float2(x, yOffset + y * scaleFactor))
+     *
+     * Where yOffset, returned by getYOffset(), is the offset to the start of the row within the
+     * atlas and scaleFactor, returned by getNormalizedTexelHeight, is the normalized height of
+     * one texel row.
+     */
+    SkScalar getYOffset(int row) const { return SkIntToScalar(row) / fNumRows; }
+    SkScalar getNormalizedTexelHeight() const { return fNormalizedYHeight; }
+
+    sk_sp<GrTextureProxy> asTextureProxyRef() const;
+
+private:
+    friend class GrTextureStripAtlasManager; // for ctor
+
+    static uint32_t CreateUniqueID();
+
+    // Key to indicate an atlas row without any meaningful data stored in it
+    const static uint32_t kEmptyAtlasRowKey = 0xffffffff;
+
+    /**
+     * The state of a single row in our cache, next/prev pointers allow these to be chained
+     * together to represent LRU status
+     */
+    struct AtlasRow : ::SkNoncopyable {
+        AtlasRow() : fKey(kEmptyAtlasRowKey), fLocks(0), fNext(nullptr), fPrev(nullptr) { }
+        // GenerationID of the bitmap that is represented by this row, 0xffffffff means "empty"
+        uint32_t fKey;
+        // How many times this has been locked (0 == unlocked)
+        int32_t fLocks;
+        // We maintain an LRU linked list between unlocked nodes with these pointers
+        AtlasRow* fNext;
+        AtlasRow* fPrev;
+    };
+
+    /**
+     * Only the GrTextureStripAtlasManager is allowed to create GrTextureStripAtlases
+     */
+    GrTextureStripAtlas(const Desc& desc);
+
+    void lockTexture(GrContext*);
+    void unlockTexture();
+
+    /**
+     * Initialize our LRU list (if one already exists, clear it and start anew)
+     */
+    void initLRU();
+
+    /**
+     * Grabs the least recently used free row out of the LRU list, returns nullptr if no rows
+     * are free.
+     */
+    AtlasRow* getLRU();
+
+    void appendLRU(AtlasRow* row);
+    void removeFromLRU(AtlasRow* row);
+
+    /**
+     * Searches the key table for a key and returns the index if found; if not found, it returns
+     * the bitwise not of the index at which we could insert the key to maintain a sorted list.
+     **/
+    int searchByKey(uint32_t key);
+
+    /**
+     * Compare two atlas rows by key, so we can sort/search by key
+     */
+    static bool KeyLess(const AtlasRow& lhs, const AtlasRow& rhs) {
+        return lhs.fKey < rhs.fKey;
+    }
+
+#ifdef SK_DEBUG
+    void validate();
+#endif
+
+    // A unique ID for this atlas, so we can be sure that if we
+    // get a texture back from the texture cache, that it's the same one we last used.
+    const uint32_t fCacheKey;
+
+    // Total locks on all rows (when this reaches zero, we can unlock our texture)
+    int32_t fLockedRows;
+
+    const Desc fDesc;
+    const uint16_t fNumRows;
+    sk_sp<GrSurfaceContext> fTexContext;
+
+    SkScalar fNormalizedYHeight;
+
+    // Array of AtlasRows which store the state of all our rows. Stored in a contiguous array, in
+    // order that they appear in our texture, this means we can subtract this pointer from a row
+    // pointer to get its index in the texture, and can save storing a row number in AtlasRow.
+    AtlasRow* fRows;
+
+    // Head and tail for linked list of least-recently-used rows (front = least recently used).
+    // Note that when a texture is locked, it gets removed from this list until it is unlocked.
+    AtlasRow* fLRUFront;
+    AtlasRow* fLRUBack;
+
+    // A list of pointers to AtlasRows that currently contain cached images, sorted by key
+    SkTDArray<AtlasRow*> fKeyTable;
+};
+
+#endif
diff --git a/src/gpu/effects/GrTextureStripAtlas.cpp b/src/gpu/effects/GrTextureStripAtlas.cpp
index fa2e6fc..eecaadf 100644
--- a/src/gpu/effects/GrTextureStripAtlas.cpp
+++ b/src/gpu/effects/GrTextureStripAtlas.cpp
@@ -8,17 +8,8 @@
 #include "GrTextureStripAtlas.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
-#include "GrProxyProvider.h"
-#include "GrSurfaceContext.h"
-#include "SkGr.h"
-#include "SkPixelRef.h"
-#include "SkTSearch.h"
-
-#ifdef SK_DEBUG
-    #define VALIDATE this->validate()
-#else
-    #define VALIDATE
-#endif
+#include "GrDynamicTextureStripAtlas.h"
+#include "SkBitmap.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 GrTextureStripAtlasManager::~GrTextureStripAtlasManager() {
@@ -51,319 +42,3 @@
 
     return entry->fAtlas;
 }
-
-////////////////////////////////////////////////////////////////////////////////
-uint32_t GrTextureStripAtlas::CreateUniqueID() {
-    static int32_t gUniqueID = SK_InvalidUniqueID;
-    uint32_t id;
-    // Loop in case our global wraps around, as we never want to return a 0.
-    do {
-        id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
-    } while (id == SK_InvalidUniqueID);
-    return id;
-}
-
-GrTextureStripAtlas::GrTextureStripAtlas(const Desc& desc)
-    : fCacheKey(CreateUniqueID())
-    , fLockedRows(0)
-    , fDesc(desc)
-    , fNumRows(desc.fHeight / desc.fRowHeight)
-    , fRows(new AtlasRow[fNumRows])
-    , fLRUFront(nullptr)
-    , fLRUBack(nullptr) {
-    SkASSERT(fNumRows * fDesc.fRowHeight == fDesc.fHeight);
-    this->initLRU();
-    fNormalizedYHeight = SK_Scalar1 / fDesc.fHeight;
-    VALIDATE;
-}
-
-GrTextureStripAtlas::~GrTextureStripAtlas() { delete[] fRows; }
-
-void GrTextureStripAtlas::lockRow(int row) {
-    // This should only be called on a row that is already locked.
-    SkASSERT(fRows[row].fLocks);
-    fRows[row].fLocks++;
-    ++fLockedRows;
-}
-
-int GrTextureStripAtlas::lockRow(GrContext* context, const SkBitmap& bitmap) {
-    VALIDATE;
-
-    if (!context->contextPriv().resourceProvider()) {
-        // DDL TODO: For DDL we need to schedule inline & ASAP uploads. However these systems
-        // currently use the flushState which we can't use for the opList-based DDL phase.
-        // For the opList-based solution every texture strip will get its own texture proxy.
-        // We will revisit this for the flushState-based solution.
-        return -1;
-    }
-
-    if (0 == fLockedRows) {
-        this->lockTexture(context);
-        if (!fTexContext) {
-            return -1;
-        }
-    }
-
-    int key = bitmap.getGenerationID();
-    int rowNumber = -1;
-    int index = this->searchByKey(key);
-
-    if (index >= 0) {
-        // We already have the data in a row, so we can just return that row
-        AtlasRow* row = fKeyTable[index];
-        if (0 == row->fLocks) {
-            this->removeFromLRU(row);
-        }
-        ++row->fLocks;
-        ++fLockedRows;
-
-        // Since all the rows are always stored in a contiguous array, we can save the memory
-        // required for storing row numbers and just compute it with some pointer arithmetic
-        rowNumber = static_cast<int>(row - fRows);
-    } else {
-        // ~index is the index where we will insert the new key to keep things sorted
-        index = ~index;
-
-        // We don't have this data cached, so pick the least recently used row to copy into
-        AtlasRow* row = this->getLRU();
-
-        ++fLockedRows;
-
-        if (nullptr == row) {
-            // force a flush, which should unlock all the rows; then try again
-            context->contextPriv().flush(nullptr); // tighten this up?
-            row = this->getLRU();
-            if (nullptr == row) {
-                --fLockedRows;
-                return -1;
-            }
-        }
-
-        this->removeFromLRU(row);
-
-        uint32_t oldKey = row->fKey;
-
-        // If we are writing into a row that already held bitmap data, we need to remove the
-        // reference to that genID which is stored in our sorted table of key values.
-        if (oldKey != kEmptyAtlasRowKey) {
-
-            // Find the entry in the list; if it's before the index where we plan on adding the new
-            // entry, we decrement since it will shift elements ahead of it back by one.
-            int oldIndex = this->searchByKey(oldKey);
-            if (oldIndex < index) {
-                --index;
-            }
-
-            fKeyTable.remove(oldIndex);
-        }
-
-        row->fKey = key;
-        row->fLocks = 1;
-        fKeyTable.insert(index, 1, &row);
-        rowNumber = static_cast<int>(row - fRows);
-
-        SkASSERT(bitmap.width() == fDesc.fWidth);
-        SkASSERT(bitmap.height() == fDesc.fRowHeight);
-
-        // Pass in the kDontFlush flag, since we know we're writing to a part of this texture
-        // that is not currently in use
-        fTexContext->writePixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(),
-                                 0, rowNumber * fDesc.fRowHeight,
-                                 GrContextPriv::kDontFlush_PixelOpsFlag);
-    }
-
-    SkASSERT(rowNumber >= 0);
-    VALIDATE;
-    return rowNumber;
-}
-
-sk_sp<GrTextureProxy> GrTextureStripAtlas::asTextureProxyRef() const {
-    return fTexContext->asTextureProxyRef();
-}
-
-void GrTextureStripAtlas::unlockRow(int row) {
-    VALIDATE;
-    --fRows[row].fLocks;
-    --fLockedRows;
-    SkASSERT(fRows[row].fLocks >= 0 && fLockedRows >= 0);
-    if (0 == fRows[row].fLocks) {
-        this->appendLRU(fRows + row);
-    }
-    if (0 == fLockedRows) {
-        this->unlockTexture();
-    }
-    VALIDATE;
-}
-
-GrTextureStripAtlas::AtlasRow* GrTextureStripAtlas::getLRU() {
-    // Front is least-recently-used
-    AtlasRow* row = fLRUFront;
-    return row;
-}
-
-void GrTextureStripAtlas::lockTexture(GrContext* context) {
-
-    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-    GrUniqueKey key;
-    GrUniqueKey::Builder builder(&key, kDomain, 1);
-    builder[0] = static_cast<uint32_t>(fCacheKey);
-    builder.finish();
-
-    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
-
-    sk_sp<GrTextureProxy> proxy = proxyProvider->findOrCreateProxyByUniqueKey(
-                                                                key, kTopLeft_GrSurfaceOrigin);
-    if (!proxy) {
-        GrSurfaceDesc texDesc;
-        texDesc.fWidth  = fDesc.fWidth;
-        texDesc.fHeight = fDesc.fHeight;
-        texDesc.fConfig = fDesc.fConfig;
-
-        proxy = proxyProvider->createProxy(texDesc, kTopLeft_GrSurfaceOrigin, SkBackingFit::kExact,
-                                           SkBudgeted::kYes, GrInternalSurfaceFlags::kNoPendingIO);
-        if (!proxy) {
-            return;
-        }
-
-        SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
-        proxyProvider->assignUniqueKeyToProxy(key, proxy.get());
-        // This is a new texture, so all of our cache info is now invalid
-        this->initLRU();
-        fKeyTable.rewind();
-    }
-    SkASSERT(proxy);
-    fTexContext = context->contextPriv().makeWrappedSurfaceContext(std::move(proxy));
-}
-
-void GrTextureStripAtlas::unlockTexture() {
-    SkASSERT(fTexContext && 0 == fLockedRows);
-    fTexContext.reset();
-}
-
-void GrTextureStripAtlas::initLRU() {
-    fLRUFront = nullptr;
-    fLRUBack = nullptr;
-    // Initially all the rows are in the LRU list
-    for (int i = 0; i < fNumRows; ++i) {
-        fRows[i].fKey = kEmptyAtlasRowKey;
-        fRows[i].fNext = nullptr;
-        fRows[i].fPrev = nullptr;
-        this->appendLRU(fRows + i);
-    }
-    SkASSERT(nullptr == fLRUFront || nullptr == fLRUFront->fPrev);
-    SkASSERT(nullptr == fLRUBack || nullptr == fLRUBack->fNext);
-}
-
-void GrTextureStripAtlas::appendLRU(AtlasRow* row) {
-    SkASSERT(nullptr == row->fPrev && nullptr == row->fNext);
-    if (nullptr == fLRUFront && nullptr == fLRUBack) {
-        fLRUFront = row;
-        fLRUBack = row;
-    } else {
-        row->fPrev = fLRUBack;
-        fLRUBack->fNext = row;
-        fLRUBack = row;
-    }
-}
-
-void GrTextureStripAtlas::removeFromLRU(AtlasRow* row) {
-    SkASSERT(row);
-    if (row->fNext && row->fPrev) {
-        row->fPrev->fNext = row->fNext;
-        row->fNext->fPrev = row->fPrev;
-    } else {
-        if (nullptr == row->fNext) {
-            SkASSERT(row == fLRUBack);
-            fLRUBack = row->fPrev;
-            if (fLRUBack) {
-                fLRUBack->fNext = nullptr;
-            }
-        }
-        if (nullptr == row->fPrev) {
-            SkASSERT(row == fLRUFront);
-            fLRUFront = row->fNext;
-            if (fLRUFront) {
-                fLRUFront->fPrev = nullptr;
-            }
-        }
-    }
-    row->fNext = nullptr;
-    row->fPrev = nullptr;
-}
-
-int GrTextureStripAtlas::searchByKey(uint32_t key) {
-    AtlasRow target;
-    target.fKey = key;
-    return SkTSearch<const AtlasRow,
-                     GrTextureStripAtlas::KeyLess>((const AtlasRow**)fKeyTable.begin(),
-                                                   fKeyTable.count(),
-                                                   &target,
-                                                   sizeof(AtlasRow*));
-}
-
-#ifdef SK_DEBUG
-void GrTextureStripAtlas::validate() {
-
-    // Our key table should be sorted
-    uint32_t prev = 1 > fKeyTable.count() ? 0 : fKeyTable[0]->fKey;
-    for (int i = 1; i < fKeyTable.count(); ++i) {
-        SkASSERT(prev < fKeyTable[i]->fKey);
-        SkASSERT(fKeyTable[i]->fKey != kEmptyAtlasRowKey);
-        prev = fKeyTable[i]->fKey;
-    }
-
-    int lruCount = 0;
-    // Validate LRU pointers, and count LRU entries
-    SkASSERT(nullptr == fLRUFront || nullptr == fLRUFront->fPrev);
-    SkASSERT(nullptr == fLRUBack  || nullptr == fLRUBack->fNext);
-    for (AtlasRow* r = fLRUFront; r != nullptr; r = r->fNext) {
-        if (nullptr == r->fNext) {
-            SkASSERT(r == fLRUBack);
-        } else {
-            SkASSERT(r->fNext->fPrev == r);
-        }
-        ++lruCount;
-    }
-
-    int rowLocks = 0;
-    int freeRows = 0;
-
-    for (int i = 0; i < fNumRows; ++i) {
-        rowLocks += fRows[i].fLocks;
-        if (0 == fRows[i].fLocks) {
-            ++freeRows;
-            bool inLRU = false;
-            // Step through the LRU and make sure it's present
-            for (AtlasRow* r = fLRUFront; r != nullptr; r = r->fNext) {
-                if (r == &fRows[i]) {
-                    inLRU = true;
-                    break;
-                }
-            }
-            SkASSERT(inLRU);
-        } else {
-            // If we are locked, we should have a key
-            SkASSERT(kEmptyAtlasRowKey != fRows[i].fKey);
-        }
-
-        // If we have a key != kEmptyAtlasRowKey, it should be in the key table
-        SkASSERT(fRows[i].fKey == kEmptyAtlasRowKey || this->searchByKey(fRows[i].fKey) >= 0);
-    }
-
-    // Our count of locks should equal the sum of row locks, unless we ran out of rows and flushed,
-    // in which case we'll have one more lock than recorded in the rows (to represent the pending
-    // lock of a row; which ensures we don't unlock the texture prematurely).
-    SkASSERT(rowLocks == fLockedRows || rowLocks + 1 == fLockedRows);
-
-    // We should have one lru entry for each free row
-    SkASSERT(freeRows == lruCount);
-
-    // If we have locked rows, we should have a locked texture, otherwise
-    // it should be unlocked
-    if (fLockedRows == 0) {
-        SkASSERT(!fTexContext);
-    } else {
-        SkASSERT(fTexContext);
-    }
-}
-#endif