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