Revise Text & Small Path Atlas so instantiation failure is handled at flush time
This paves the way to having the AtlasTextOps not need the RestrictedAtlasManager at op creation time.
Change-Id: I1028faba730d50d3d3349a4c0809465d036ed611
Reviewed-on: https://skia-review.googlesource.com/111807
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp
index 9da5fd1..4513952 100644
--- a/src/atlastext/SkAtlasTextTarget.cpp
+++ b/src/atlastext/SkAtlasTextTarget.cpp
@@ -192,15 +192,20 @@
resourceProvider, fGeoData[i].fBlob, fGeoData[i].fRun, fGeoData[i].fSubRun,
fGeoData[i].fViewMatrix, fGeoData[i].fX, fGeoData[i].fY, fGeoData[i].fColor,
&context, glyphCache, fullAtlasManager, &autoGlyphCache);
- GrAtlasTextBlob::VertexRegenerator::Result result;
- do {
- result = regenerator.regenerate();
+ bool done = false;
+ while (!done) {
+ GrAtlasTextBlob::VertexRegenerator::Result result;
+ if (!regenerator.regenerate(&result)) {
+ break;
+ }
+ done = result.fFinished;
+
context.recordDraw(result.fFirstVertex, result.fGlyphsRegenerated,
fGeoData[i].fViewMatrix, target->handle());
if (!result.fFinished) {
// Make space in the atlas so we can continue generating vertices.
context.flush();
}
- } while (!result.fFinished);
+ }
}
}
diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp
index bbb87f7..629d91b 100644
--- a/src/gpu/GrDrawOpAtlas.cpp
+++ b/src/gpu/GrDrawOpAtlas.cpp
@@ -186,7 +186,7 @@
, fTextureHeight(height)
, fAtlasGeneration(kInvalidAtlasGeneration + 1)
, fPrevFlushToken(GrDeferredUploadToken::AlreadyFlushedToken())
- , fAllowMultitexturing(allowMultitexturing)
+ , fMaxPages(AllowMultitexturing::kYes == allowMultitexturing ? kMaxMultitexturePages : 1)
, fNumActivePages(0) {
fPlotWidth = fTextureWidth / numPlotsX;
fPlotHeight = fTextureHeight / numPlotsY;
@@ -230,6 +230,25 @@
return true;
}
+bool GrDrawOpAtlas::uploadToPage(unsigned int pageIdx, AtlasID* id, GrDeferredUploadTarget* target,
+ int width, int height, const void* image, SkIPoint16* loc) {
+ SkASSERT(fProxies[pageIdx] && fProxies[pageIdx]->priv().isInstantiated());
+
+ // look through all allocated plots for one we can share, in Most Recently Refed order
+ PlotList::Iter plotIter;
+ plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
+
+ for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
+ SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
+
+ if (plot->addSubImage(width, height, image, loc)) {
+ return this->updatePlot(target, id, plot);
+ }
+ }
+
+ return false;
+}
+
// Number of atlas-related flushes beyond which we consider a plot to no longer be in use.
//
// This value is somewhat arbitrary -- the idea is to keep it low enough that
@@ -238,28 +257,20 @@
// are rare; i.e., we are not continually refreshing the frame.
static constexpr auto kRecentlyUsedCount = 256;
-bool GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider,
- AtlasID* id, GrDeferredUploadTarget* target,
- int width, int height, const void* image, SkIPoint16* loc) {
+GrDrawOpAtlas::ErrorCode GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider,
+ AtlasID* id, GrDeferredUploadTarget* target,
+ int width, int height,
+ const void* image, SkIPoint16* loc) {
if (width > fPlotWidth || height > fPlotHeight) {
- return false;
+ return ErrorCode::kError;
}
// Look through each page to see if we can upload without having to flush
// We prioritize this upload to the first pages, not the most recently used, to make it easier
// to remove unused pages in reverse page order.
for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
- SkASSERT(fProxies[pageIdx]);
- // look through all allocated plots for one we can share, in Most Recently Refed order
- PlotList::Iter plotIter;
- plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
- Plot* plot;
- while ((plot = plotIter.get())) {
- SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
- if (plot->addSubImage(width, height, image, loc)) {
- return this->updatePlot(target, id, plot);
- }
- plotIter.next();
+ if (this->uploadToPage(pageIdx, id, target, width, height, image, loc)) {
+ return ErrorCode::kSucceeded;
}
}
@@ -268,37 +279,39 @@
// We wait until we've grown to the full number of pages to begin evicting already flushed
// plots so that we can maximize the opportunity for reuse.
// As before we prioritize this upload to the first pages, not the most recently used.
- for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
- Plot* plot = fPages[pageIdx].fPlotList.tail();
- SkASSERT(plot);
- if ((fNumActivePages == this->maxPages() &&
- plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush()) ||
- plot->flushesSinceLastUsed() >= kRecentlyUsedCount) {
- this->processEvictionAndResetRects(plot);
- SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
- SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc);
- SkASSERT(verify);
- if (!this->updatePlot(target, id, plot)) {
- return false;
+ if (fNumActivePages == this->maxPages()) {
+ for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
+ Plot* plot = fPages[pageIdx].fPlotList.tail();
+ SkASSERT(plot);
+ if (plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush() ||
+ plot->flushesSinceLastUsed() >= kRecentlyUsedCount) {
+ this->processEvictionAndResetRects(plot);
+ SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
+ SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc);
+ SkASSERT(verify);
+ if (!this->updatePlot(target, id, plot)) {
+ return ErrorCode::kError;
+ }
+ return ErrorCode::kSucceeded;
}
- return true;
+ }
+ } else {
+ // If we haven't activated all the available pages, try to create a new one and add to it
+ if (!this->activateNewPage(resourceProvider)) {
+ return ErrorCode::kError;
+ }
+
+ if (this->uploadToPage(fNumActivePages-1, id, target, width, height, image, loc)) {
+ return ErrorCode::kSucceeded;
+ } else {
+ // If we fail to upload to a newly activated page then something has gone terribly
+ // wrong - return an error
+ return ErrorCode::kError;
}
}
- // If the simple cases fail, try to create a new page and add to it
- if (this->activateNewPage(resourceProvider)) {
- unsigned int pageIdx = fNumActivePages-1;
- SkASSERT(fProxies[pageIdx] && fProxies[pageIdx]->priv().isInstantiated());
-
- Plot* plot = fPages[pageIdx].fPlotList.head();
- SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
- if (plot->addSubImage(width, height, image, loc)) {
- return this->updatePlot(target, id, plot);
- }
-
- // we shouldn't get here -- if so, something has gone terribly wrong
- SkASSERT(false);
- return false;
+ if (!fNumActivePages) {
+ return ErrorCode::kError;
}
// Try to find a plot that we can perform an inline upload to.
@@ -316,9 +329,9 @@
// we have to fail. This gives the op a chance to enqueue the draw, and call back into this
// function. When that draw is enqueued, the draw token advances, and the subsequent call will
// continue past this branch and prepare an inline upload that will occur after the enqueued
- //draw which references the plot's pre-upload content.
+ // draw which references the plot's pre-upload content.
if (!plot) {
- return false;
+ return ErrorCode::kTryAgain;
}
this->processEviction(plot->id());
@@ -348,7 +361,7 @@
*id = newPlot->id();
- return true;
+ return ErrorCode::kSucceeded;
}
void GrDrawOpAtlas::compact(GrDeferredUploadToken startTokenForNextFlush) {
@@ -537,9 +550,7 @@
bool GrDrawOpAtlas::activateNewPage(GrResourceProvider* resourceProvider) {
- if (fNumActivePages >= this->maxPages()) {
- return false;
- }
+ SkASSERT(fNumActivePages < this->maxPages());
if (!fProxies[fNumActivePages]->instantiate(resourceProvider)) {
return false;
diff --git a/src/gpu/GrDrawOpAtlas.h b/src/gpu/GrDrawOpAtlas.h
index b849d9e..6106c0c 100644
--- a/src/gpu/GrDrawOpAtlas.h
+++ b/src/gpu/GrDrawOpAtlas.h
@@ -98,19 +98,30 @@
GrDrawOpAtlas::EvictionFunc func, void* data);
/**
- * Adds a width x height subimage to the atlas. Upon success it returns an ID and the subimage's
- * coordinates in the backing texture. False is returned if the subimage cannot fit in the
- * atlas without overwriting texels that will be read in the current draw. This indicates that
- * the op should end its current draw and begin another before adding more data. Upon success,
- * an upload of the provided image data will have been added to the GrDrawOp::Target, in "asap"
- * mode if possible, otherwise in "inline" mode. Successive uploads in either mode may be
- * consolidated.
+ * Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns
+ * the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if
+ * the subimage cannot fit in the atlas without overwriting texels that will be read in the
+ * current draw. This indicates that the op should end its current draw and begin another
+ * before adding more data. Upon success, an upload of the provided image data will have
+ * been added to the GrDrawOp::Target, in "asap" mode if possible, otherwise in "inline" mode.
+ * Successive uploads in either mode may be consolidated.
+ * 'kError' will be returned when some unrecoverable error was encountered while trying to
+ * add the subimage. In this case the op being created should be discarded.
+ *
* NOTE: When the GrDrawOp prepares a draw that reads from the atlas, it must immediately call
* 'setUseToken' with the currentToken from the GrDrawOp::Target, otherwise the next call to
* addToAtlas might cause the previous data to be overwritten before it has been read.
*/
- bool addToAtlas(GrResourceProvider*, AtlasID*, GrDeferredUploadTarget*, int width, int height,
- const void* image, SkIPoint16* loc);
+
+ enum class ErrorCode {
+ kError,
+ kSucceeded,
+ kTryAgain
+ };
+
+ ErrorCode addToAtlas(GrResourceProvider*, AtlasID*, GrDeferredUploadTarget*,
+ int width, int height,
+ const void* image, SkIPoint16* loc);
const sk_sp<GrTextureProxy>* getProxies() const { return fProxies; }
@@ -229,10 +240,11 @@
void instantiate(GrOnFlushResourceProvider*);
uint32_t maxPages() const {
- return AllowMultitexturing::kYes == fAllowMultitexturing ? kMaxMultitexturePages : 1;
+ return fMaxPages;
}
int numAllocated_TestingOnly() const;
+ void setMaxPages_TestingOnly(uint32_t maxPages);
private:
GrDrawOpAtlas(GrProxyProvider*, GrPixelConfig, int width, int height, int numPlotsX,
@@ -359,6 +371,9 @@
// the front and remove from the back there is no need for MRU.
}
+ bool uploadToPage(unsigned int pageIdx, AtlasID* id, GrDeferredUploadTarget* target,
+ int width, int height, const void* image, SkIPoint16* loc);
+
bool createPages(GrProxyProvider*);
bool activateNewPage(GrResourceProvider*);
void deactivateLastPage();
@@ -396,7 +411,7 @@
// proxies kept separate to make it easier to pass them up to client
sk_sp<GrTextureProxy> fProxies[kMaxMultitexturePages];
Page fPages[kMaxMultitexturePages];
- AllowMultitexturing fAllowMultitexturing;
+ uint32_t fMaxPages;
uint32_t fNumActivePages;
};
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 9e144ed..07e1141 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -292,9 +292,14 @@
resourceProvider, blob, args.fRun, args.fSubRun, args.fViewMatrix, args.fX, args.fY,
args.fColor, target->deferredUploadTarget(), glyphCache, fullAtlasManager,
&autoGlyphCache);
- GrAtlasTextBlob::VertexRegenerator::Result result;
- do {
- result = regenerator.regenerate();
+ bool done = false;
+ while (!done) {
+ GrAtlasTextBlob::VertexRegenerator::Result result;
+ if (!regenerator.regenerate(&result)) {
+ break;
+ }
+ done = result.fFinished;
+
// Copy regenerated vertices from the blob to our vertex buffer.
size_t vertexBytes = result.fGlyphsRegenerated * kVerticesPerGlyph * vertexStride;
if (args.fClipRect.isEmpty()) {
@@ -325,12 +330,16 @@
this->flush(target, &flushInfo);
}
currVertex += vertexBytes;
- } while (!result.fFinished);
+ }
}
this->flush(target, &flushInfo);
}
void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
+ if (!flushInfo->fGlyphsToFlush) {
+ return;
+ }
+
auto fullAtlasManager = target->fullAtlasManager();
SkASSERT(fRestrictedAtlasManager == fullAtlasManager);
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index 1607069..73f0da5 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -43,6 +43,91 @@
static const SkScalar kMinSize = SK_ScalarHalf;
static const SkScalar kMaxSize = 2*kMaxMIP;
+class ShapeDataKey {
+public:
+ ShapeDataKey() {}
+ ShapeDataKey(const ShapeDataKey& that) { *this = that; }
+ ShapeDataKey(const GrShape& shape, uint32_t dim) { this->set(shape, dim); }
+ ShapeDataKey(const GrShape& shape, const SkMatrix& ctm) { this->set(shape, ctm); }
+
+ ShapeDataKey& operator=(const ShapeDataKey& that) {
+ fKey.reset(that.fKey.count());
+ memcpy(fKey.get(), that.fKey.get(), fKey.count() * sizeof(uint32_t));
+ return *this;
+ }
+
+ // for SDF paths
+ void set(const GrShape& shape, uint32_t dim) {
+ // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any
+ // relevant styling information.
+ SkASSERT(shape.style().isSimpleFill());
+ SkASSERT(shape.hasUnstyledKey());
+ int shapeKeySize = shape.unstyledKeySize();
+ fKey.reset(1 + shapeKeySize);
+ fKey[0] = dim;
+ shape.writeUnstyledKey(&fKey[1]);
+ }
+
+ // for bitmap paths
+ void set(const GrShape& shape, const SkMatrix& ctm) {
+ // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any
+ // relevant styling information.
+ SkASSERT(shape.style().isSimpleFill());
+ SkASSERT(shape.hasUnstyledKey());
+ // We require the upper left 2x2 of the matrix to match exactly for a cache hit.
+ SkScalar sx = ctm.get(SkMatrix::kMScaleX);
+ SkScalar sy = ctm.get(SkMatrix::kMScaleY);
+ SkScalar kx = ctm.get(SkMatrix::kMSkewX);
+ SkScalar ky = ctm.get(SkMatrix::kMSkewY);
+ SkScalar tx = ctm.get(SkMatrix::kMTransX);
+ SkScalar ty = ctm.get(SkMatrix::kMTransY);
+ // Allow 8 bits each in x and y of subpixel positioning.
+ SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00;
+ SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00;
+ int shapeKeySize = shape.unstyledKeySize();
+ fKey.reset(5 + shapeKeySize);
+ fKey[0] = SkFloat2Bits(sx);
+ fKey[1] = SkFloat2Bits(sy);
+ fKey[2] = SkFloat2Bits(kx);
+ fKey[3] = SkFloat2Bits(ky);
+ fKey[4] = fracX | (fracY >> 8);
+ shape.writeUnstyledKey(&fKey[5]);
+ }
+
+ bool operator==(const ShapeDataKey& that) const {
+ return fKey.count() == that.fKey.count() &&
+ 0 == memcmp(fKey.get(), that.fKey.get(), sizeof(uint32_t) * fKey.count());
+ }
+
+ int count32() const { return fKey.count(); }
+ const uint32_t* data() const { return fKey.get(); }
+
+private:
+ // The key is composed of the GrShape's key, and either the dimensions of the DF
+ // generated for the path (32x32 max, 64x64 max, 128x128 max) if an SDF image or
+ // the matrix for the path with only fractional translation.
+ SkAutoSTArray<24, uint32_t> fKey;
+};
+
+class ShapeData {
+public:
+ ShapeDataKey fKey;
+ GrDrawOpAtlas::AtlasID fID;
+ SkRect fBounds;
+ GrIRect16 fTextureCoords;
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeData);
+
+ static inline const ShapeDataKey& GetKey(const ShapeData& data) {
+ return data.fKey;
+ }
+
+ static inline uint32_t Hash(ShapeDataKey key) {
+ return SkOpts::hash(key.data(), sizeof(uint32_t) * key.count32());
+ }
+};
+
+
+
// Callback to clear out internal path cache when eviction occurs
void GrSmallPathRenderer::HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
GrSmallPathRenderer* dfpr = (GrSmallPathRenderer*)pr;
@@ -134,8 +219,7 @@
public:
DEFINE_OP_CLASS_ID
- using ShapeData = GrSmallPathRenderer::ShapeData;
- using ShapeCache = SkTDynamicHash<ShapeData, ShapeData::Key>;
+ using ShapeCache = SkTDynamicHash<ShapeData, ShapeDataKey>;
using ShapeDataList = GrSmallPathRenderer::ShapeDataList;
static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const GrShape& shape,
@@ -330,7 +414,7 @@
SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
// check to see if df path is cached
- ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
+ ShapeDataKey key(args.fShape, SkScalarCeilToInt(desiredDimension));
shapeData = fShapeCache->find(key);
if (nullptr == shapeData || !fAtlas->hasID(shapeData->fID)) {
// Remove the stale cache entry
@@ -355,7 +439,7 @@
}
} else {
// check to see if bitmap path is cached
- ShapeData::Key key(args.fShape, args.fViewMatrix);
+ ShapeDataKey key(args.fShape, args.fViewMatrix);
shapeData = fShapeCache->find(key);
if (nullptr == shapeData || !fAtlas->hasID(shapeData->fID)) {
// Remove the stale cache entry
@@ -394,10 +478,32 @@
this->flush(target, &flushInfo);
}
+ bool addToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas,
+ int width, int height, const void* image,
+ GrDrawOpAtlas::AtlasID* id, SkIPoint16* atlasLocation) const {
+ auto resourceProvider = target->resourceProvider();
+ auto uploadTarget = target->deferredUploadTarget();
+
+ GrDrawOpAtlas::ErrorCode code = atlas->addToAtlas(resourceProvider, id,
+ uploadTarget, width, height,
+ image, atlasLocation);
+ if (GrDrawOpAtlas::ErrorCode::kError == code) {
+ return false;
+ }
+
+ if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) {
+ this->flush(target, flushInfo);
+
+ code = atlas->addToAtlas(resourceProvider, id, uploadTarget, width, height,
+ image, atlasLocation);
+ }
+
+ return GrDrawOpAtlas::ErrorCode::kSucceeded == code;
+ }
+
bool addDFPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo,
GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape,
uint32_t dimension, SkScalar scale) const {
- auto resourceProvider = target->resourceProvider();
const SkRect& bounds = shape.bounds();
@@ -486,14 +592,10 @@
// add to atlas
SkIPoint16 atlasLocation;
GrDrawOpAtlas::AtlasID id;
- auto uploadTarget = target->deferredUploadTarget();
- if (!atlas->addToAtlas(resourceProvider, &id, uploadTarget, width, height,
- dfStorage.get(), &atlasLocation)) {
- this->flush(target, flushInfo);
- if (!atlas->addToAtlas(resourceProvider, &id, uploadTarget, width, height,
- dfStorage.get(), &atlasLocation)) {
- return false;
- }
+
+ if (!this->addToAtlas(target, flushInfo, atlas,
+ width, height, dfStorage.get(), &id, &atlasLocation)) {
+ return false;
}
// add to cache
@@ -530,8 +632,6 @@
bool addBMPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo,
GrDrawOpAtlas* atlas, ShapeData* shapeData, const GrShape& shape,
const SkMatrix& ctm) const {
- auto resourceProvider = target->resourceProvider();
-
const SkRect& bounds = shape.bounds();
if (bounds.isEmpty()) {
return false;
@@ -591,14 +691,10 @@
// add to atlas
SkIPoint16 atlasLocation;
GrDrawOpAtlas::AtlasID id;
- auto uploadTarget = target->deferredUploadTarget();
- if (!atlas->addToAtlas(resourceProvider, &id, uploadTarget, dst.width(), dst.height(),
- dst.addr(), &atlasLocation)) {
- this->flush(target, flushInfo);
- if (!atlas->addToAtlas(resourceProvider, &id, uploadTarget, dst.width(), dst.height(),
- dst.addr(), &atlasLocation)) {
- return false;
- }
+
+ if (!this->addToAtlas(target, flushInfo, atlas,
+ dst.width(), dst.height(), dst.addr(), &id, &atlasLocation)) {
+ return false;
}
// add to cache
@@ -853,6 +949,21 @@
ShapeDataList fShapeList;
};
+std::unique_ptr<GrDrawOp> GrSmallPathRenderer::createOp_TestingOnly(
+ GrPaint&& paint,
+ const GrShape& shape,
+ const SkMatrix& viewMatrix,
+ GrDrawOpAtlas* atlas,
+ ShapeCache* shapeCache,
+ ShapeDataList* shapeList,
+ bool gammaCorrect,
+ const GrUserStencilSettings* stencil) {
+
+ return GrSmallPathRenderer::SmallPathOp::Make(std::move(paint), shape, viewMatrix, atlas,
+ shapeCache, shapeList, gammaCorrect, stencil);
+
+}
+
GR_DRAW_OP_TEST_DEFINE(SmallPathOp) {
using PathTestStruct = GrSmallPathRenderer::PathTestStruct;
static PathTestStruct gTestStruct;
@@ -874,10 +985,13 @@
// This path renderer only allows fill styles.
GrShape shape(GrTest::TestPath(random), GrStyle::SimpleFill());
-
- return GrSmallPathRenderer::SmallPathOp::Make(
- std::move(paint), shape, viewMatrix, gTestStruct.fAtlas.get(), &gTestStruct.fShapeCache,
- &gTestStruct.fShapeList, gammaCorrect, GrGetRandomStencil(random, context));
+ return GrSmallPathRenderer::createOp_TestingOnly(
+ std::move(paint), shape, viewMatrix,
+ gTestStruct.fAtlas.get(),
+ &gTestStruct.fShapeCache,
+ &gTestStruct.fShapeList,
+ gammaCorrect,
+ GrGetRandomStencil(random, context));
}
#endif
diff --git a/src/gpu/ops/GrSmallPathRenderer.h b/src/gpu/ops/GrSmallPathRenderer.h
index ef83c77..ab58bdf 100644
--- a/src/gpu/ops/GrSmallPathRenderer.h
+++ b/src/gpu/ops/GrSmallPathRenderer.h
@@ -19,14 +19,14 @@
class GrContext;
+class ShapeData;
+class ShapeDataKey;
+
class GrSmallPathRenderer : public GrPathRenderer, public GrOnFlushCallbackObject {
public:
GrSmallPathRenderer();
~GrSmallPathRenderer() override;
- class SmallPathOp;
- struct PathTestStruct;
-
// GrOnFlushCallbackObject overrides
//
// Note: because this class is associated with a path renderer we want it to be removed from
@@ -41,13 +41,26 @@
}
void postFlush(GrDeferredUploadToken startTokenForNextFlush,
- const uint32_t* opListIDs, int numOpListIDs) override {
+ const uint32_t* /*opListIDs*/, int /*numOpListIDs*/) override {
if (fAtlas) {
fAtlas->compact(startTokenForNextFlush);
}
}
+ using ShapeCache = SkTDynamicHash<ShapeData, ShapeDataKey>;
+ typedef SkTInternalLList<ShapeData> ShapeDataList;
+
+ static std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrPaint&&, const GrShape&,
+ const SkMatrix& viewMatrix,
+ GrDrawOpAtlas* atlas,
+ ShapeCache*, ShapeDataList*,
+ bool gammaCorrect,
+ const GrUserStencilSettings*);
+ struct PathTestStruct;
+
private:
+ class SmallPathOp;
+
StencilSupport onGetStencilSupport(const GrShape&) const override {
return GrPathRenderer::kNoSupport_StencilSupport;
}
@@ -56,92 +69,8 @@
bool onDrawPath(const DrawPathArgs&) override;
- struct ShapeData {
- class Key {
- public:
- Key() {}
- Key(const Key& that) { *this = that; }
- Key(const GrShape& shape, uint32_t dim) { this->set(shape, dim); }
- Key(const GrShape& shape, const SkMatrix& ctm) { this->set(shape, ctm); }
-
- Key& operator=(const Key& that) {
- fKey.reset(that.fKey.count());
- memcpy(fKey.get(), that.fKey.get(), fKey.count() * sizeof(uint32_t));
- return *this;
- }
-
- // for SDF paths
- void set(const GrShape& shape, uint32_t dim) {
- // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any
- // relevant styling information.
- SkASSERT(shape.style().isSimpleFill());
- SkASSERT(shape.hasUnstyledKey());
- int shapeKeySize = shape.unstyledKeySize();
- fKey.reset(1 + shapeKeySize);
- fKey[0] = dim;
- shape.writeUnstyledKey(&fKey[1]);
- }
-
- // for bitmap paths
- void set(const GrShape& shape, const SkMatrix& ctm) {
- // Shapes' keys are for their pre-style geometry, but by now we shouldn't have any
- // relevant styling information.
- SkASSERT(shape.style().isSimpleFill());
- SkASSERT(shape.hasUnstyledKey());
- // We require the upper left 2x2 of the matrix to match exactly for a cache hit.
- SkScalar sx = ctm.get(SkMatrix::kMScaleX);
- SkScalar sy = ctm.get(SkMatrix::kMScaleY);
- SkScalar kx = ctm.get(SkMatrix::kMSkewX);
- SkScalar ky = ctm.get(SkMatrix::kMSkewY);
- SkScalar tx = ctm.get(SkMatrix::kMTransX);
- SkScalar ty = ctm.get(SkMatrix::kMTransY);
- // Allow 8 bits each in x and y of subpixel positioning.
- SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00;
- SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00;
- int shapeKeySize = shape.unstyledKeySize();
- fKey.reset(5 + shapeKeySize);
- fKey[0] = SkFloat2Bits(sx);
- fKey[1] = SkFloat2Bits(sy);
- fKey[2] = SkFloat2Bits(kx);
- fKey[3] = SkFloat2Bits(ky);
- fKey[4] = fracX | (fracY >> 8);
- shape.writeUnstyledKey(&fKey[5]);
- }
-
- bool operator==(const Key& that) const {
- return fKey.count() == that.fKey.count() &&
- 0 == memcmp(fKey.get(), that.fKey.get(), sizeof(uint32_t) * fKey.count());
- }
-
- int count32() const { return fKey.count(); }
- const uint32_t* data() const { return fKey.get(); }
-
- private:
- // The key is composed of the GrShape's key, and either the dimensions of the DF
- // generated for the path (32x32 max, 64x64 max, 128x128 max) if an SDF image or
- // the matrix for the path with only fractional translation.
- SkAutoSTArray<24, uint32_t> fKey;
- };
- Key fKey;
- GrDrawOpAtlas::AtlasID fID;
- SkRect fBounds;
- GrIRect16 fTextureCoords;
- SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeData);
-
- static inline const Key& GetKey(const ShapeData& data) {
- return data.fKey;
- }
-
- static inline uint32_t Hash(Key key) {
- return SkOpts::hash(key.data(), sizeof(uint32_t) * key.count32());
- }
- };
-
static void HandleEviction(GrDrawOpAtlas::AtlasID, void*);
- typedef SkTDynamicHash<ShapeData, ShapeData::Key> ShapeCache;
- typedef SkTInternalLList<ShapeData> ShapeDataList;
-
std::unique_ptr<GrDrawOpAtlas> fAtlas;
ShapeCache fShapeCache;
ShapeDataList fShapeList;
diff --git a/src/gpu/text/GrAtlasManager.cpp b/src/gpu/text/GrAtlasManager.cpp
index c6a6056..b688c1c 100644
--- a/src/gpu/text/GrAtlasManager.cpp
+++ b/src/gpu/text/GrAtlasManager.cpp
@@ -94,14 +94,15 @@
}
// add to texture atlas that matches this format
-bool GrAtlasManager::addToAtlas(GrResourceProvider* resourceProvider,
+GrDrawOpAtlas::ErrorCode GrAtlasManager::addToAtlas(
+ GrResourceProvider* resourceProvider,
GrGlyphCache* glyphCache,
GrTextStrike* strike, GrDrawOpAtlas::AtlasID* id,
GrDeferredUploadTarget* target, GrMaskFormat format,
int width, int height, const void* image, SkIPoint16* loc) {
glyphCache->setStrikeToPreserve(strike);
return this->getAtlas(format)->addToAtlas(resourceProvider, id, target, width, height,
- image, loc);
+ image, loc);
}
void GrAtlasManager::addGlyphToBulkAndSetUseToken(GrDrawOpAtlas::BulkUseTokenUpdater* updater,
diff --git a/src/gpu/text/GrAtlasManager.h b/src/gpu/text/GrAtlasManager.h
index eb9c113..9241193 100644
--- a/src/gpu/text/GrAtlasManager.h
+++ b/src/gpu/text/GrAtlasManager.h
@@ -100,7 +100,8 @@
}
// add to texture atlas that matches this format
- bool addToAtlas(GrResourceProvider*, GrGlyphCache*, GrTextStrike*,
+ GrDrawOpAtlas::ErrorCode addToAtlas(
+ GrResourceProvider*, GrGlyphCache*, GrTextStrike*,
GrDrawOpAtlas::AtlasID*, GrDeferredUploadTarget*, GrMaskFormat,
int width, int height, const void* image, SkIPoint16* loc);
@@ -142,6 +143,7 @@
#endif
void setAtlasSizes_ForTesting(const GrDrawOpAtlasConfig configs[3]);
+ void setMaxPages_TestingOnly(uint32_t maxPages);
private:
bool initAtlas(GrMaskFormat) override;
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
index b4a11a4..a01041f 100644
--- a/src/gpu/text/GrAtlasTextBlob.h
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -595,11 +595,11 @@
const char* fFirstVertex;
};
- Result regenerate();
+ bool regenerate(Result*);
private:
template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
- Result doRegen();
+ bool doRegen(Result*);
GrResourceProvider* fResourceProvider;
const SkMatrix& fViewMatrix;
diff --git a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
index 296df22..19b0959 100644
--- a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
+++ b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
@@ -230,7 +230,7 @@
}
template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
-Regenerator::Result Regenerator::doRegen() {
+bool Regenerator::doRegen(Regenerator::Result* result) {
static_assert(!regenGlyphs || regenTexCoords, "must regenTexCoords along regenGlyphs");
sk_sp<GrTextStrike> strike;
if (regenTexCoords) {
@@ -255,11 +255,10 @@
}
bool hasW = fSubRun->hasWCoord();
- Result result;
auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
char* currVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
fCurrGlyph * kVerticesPerGlyph * vertexStride;
- result.fFirstVertex = currVertex;
+ result->fFirstVertex = currVertex;
for (int glyphIdx = fCurrGlyph; glyphIdx < (int)fSubRun->glyphCount(); glyphIdx++) {
GrGlyph* glyph = nullptr;
@@ -277,14 +276,21 @@
glyph = fBlob->fGlyphs[glyphOffset];
SkASSERT(glyph && glyph->fMaskFormat == fSubRun->maskFormat());
- if (!fFullAtlasManager->hasGlyph(glyph) &&
- !strike->addGlyphToAtlas(fResourceProvider, fUploadTarget, fGlyphCache,
- fFullAtlasManager, glyph,
- fLazyCache->get(), fSubRun->maskFormat(),
- fSubRun->hasScaledGlyphs())) {
- fBrokenRun = glyphIdx > 0;
- result.fFinished = false;
- return result;
+ if (!fFullAtlasManager->hasGlyph(glyph)) {
+ GrDrawOpAtlas::ErrorCode code;
+ code = strike->addGlyphToAtlas(fResourceProvider, fUploadTarget, fGlyphCache,
+ fFullAtlasManager, glyph,
+ fLazyCache->get(), fSubRun->maskFormat(),
+ fSubRun->hasScaledGlyphs());
+ if (GrDrawOpAtlas::ErrorCode::kError == code) {
+ // Something horrible has happened - drop the op
+ return false;
+ }
+ else if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) {
+ fBrokenRun = glyphIdx > 0;
+ result->fFinished = false;
+ return true;
+ }
}
auto tokenTracker = fUploadTarget->tokenTracker();
fFullAtlasManager->addGlyphToBulkAndSetUseToken(fSubRun->bulkUseToken(), glyph,
@@ -295,7 +301,7 @@
fSubRun->drawAsDistanceFields(), fTransX,
fTransY, fColor);
currVertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph;
- ++result.fGlyphsRegenerated;
+ ++result->fGlyphsRegenerated;
++fCurrGlyph;
}
@@ -309,10 +315,10 @@
? GrDrawOpAtlas::kInvalidAtlasGeneration
: fFullAtlasManager->atlasGeneration(fSubRun->maskFormat()));
}
- return result;
+ return true;
}
-Regenerator::Result Regenerator::regenerate() {
+bool Regenerator::regenerate(Regenerator::Result* result) {
uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
// If regenerate() is called multiple times then the atlas gen may have changed. So we check
// this each time.
@@ -322,36 +328,36 @@
switch (static_cast<RegenMask>(fRegenFlags)) {
case kRegenPos:
- return this->doRegen<true, false, false, false>();
+ return this->doRegen<true, false, false, false>(result);
case kRegenCol:
- return this->doRegen<false, true, false, false>();
+ return this->doRegen<false, true, false, false>(result);
case kRegenTex:
- return this->doRegen<false, false, true, false>();
+ return this->doRegen<false, false, true, false>(result);
case kRegenGlyph:
- return this->doRegen<false, false, true, true>();
+ return this->doRegen<false, false, true, true>(result);
// combinations
case kRegenPosCol:
- return this->doRegen<true, true, false, false>();
+ return this->doRegen<true, true, false, false>(result);
case kRegenPosTex:
- return this->doRegen<true, false, true, false>();
+ return this->doRegen<true, false, true, false>(result);
case kRegenPosTexGlyph:
- return this->doRegen<true, false, true, true>();
+ return this->doRegen<true, false, true, true>(result);
case kRegenPosColTex:
- return this->doRegen<true, true, true, false>();
+ return this->doRegen<true, true, true, false>(result);
case kRegenPosColTexGlyph:
- return this->doRegen<true, true, true, true>();
+ return this->doRegen<true, true, true, true>(result);
case kRegenColTex:
- return this->doRegen<false, true, true, false>();
+ return this->doRegen<false, true, true, false>(result);
case kRegenColTexGlyph:
- return this->doRegen<false, true, true, true>();
+ return this->doRegen<false, true, true, true>(result);
case kNoRegen: {
- Result result;
bool hasW = fSubRun->hasWCoord();
auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
- result.fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
- result.fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
- fCurrGlyph * kVerticesPerGlyph * vertexStride;
+ result->fFinished = true;
+ result->fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
+ result->fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
+ fCurrGlyph * kVerticesPerGlyph * vertexStride;
fCurrGlyph = fSubRun->glyphCount();
// set use tokens for all of the glyphs in our subrun. This is only valid if we
@@ -359,9 +365,9 @@
fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
fUploadTarget->tokenTracker()->nextDrawToken(),
fSubRun->maskFormat());
- return result;
+ return true;
}
}
SK_ABORT("Should not get here");
- return Result();
+ return false;
}
diff --git a/src/gpu/text/GrAtlasTextContext.cpp b/src/gpu/text/GrAtlasTextContext.cpp
index ad708d7..ef71fe0 100644
--- a/src/gpu/text/GrAtlasTextContext.cpp
+++ b/src/gpu/text/GrAtlasTextContext.cpp
@@ -930,6 +930,37 @@
#include "GrRenderTargetContext.h"
+std::unique_ptr<GrDrawOp> GrAtlasTextContext::createOp_TestingOnly(
+ GrContext* context,
+ GrAtlasTextContext* textContext,
+ GrRenderTargetContext* rtc,
+ const SkPaint& skPaint,
+ const SkMatrix& viewMatrix,
+ const char* text, int x, int y) {
+ auto glyphCache = context->contextPriv().getGlyphCache();
+ auto restrictedAtlasManager = context->contextPriv().getRestrictedAtlasManager();
+
+ static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
+
+ size_t textLen = (int)strlen(text);
+
+ GrTextUtils::Paint utilsPaint(&skPaint, &rtc->colorSpaceInfo());
+
+ // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to
+ // test the text op with this unit test, that is okay.
+ sk_sp<GrAtlasTextBlob> blob(textContext->makeDrawTextBlob(
+ context->contextPriv().getTextBlobCache(), glyphCache,
+ *context->caps()->shaderCaps(), utilsPaint,
+ GrAtlasTextContext::kTextBlobOpScalerContextFlags,
+ viewMatrix, surfaceProps, text,
+ static_cast<size_t>(textLen),
+ SkIntToScalar(x), SkIntToScalar(y)));
+
+ return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, utilsPaint, surfaceProps,
+ textContext->dfAdjustTable(), restrictedAtlasManager,
+ rtc->textTarget());
+}
+
GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) {
static uint32_t gContextID = SK_InvalidGenID;
static std::unique_ptr<GrAtlasTextContext> gTextContext;
@@ -953,10 +984,8 @@
skPaint.setLCDRenderText(random->nextBool());
skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
skPaint.setSubpixelText(random->nextBool());
- GrTextUtils::Paint utilsPaint(&skPaint, &rtc->colorSpaceInfo());
const char* text = "The quick brown fox jumps over the lazy dog.";
- int textLen = (int)strlen(text);
// create some random x/y offsets, including negative offsets
static const int kMaxTrans = 1024;
@@ -964,23 +993,9 @@
int yPos = (random->nextU() % 2) * 2 - 1;
int xInt = (random->nextU() % kMaxTrans) * xPos;
int yInt = (random->nextU() % kMaxTrans) * yPos;
- SkScalar x = SkIntToScalar(xInt);
- SkScalar y = SkIntToScalar(yInt);
- auto glyphCache = context->contextPriv().getGlyphCache();
- auto restrictedAtlasManager = context->contextPriv().getRestrictedAtlasManager();
-
- // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to
- // test the text op with this unit test, that is okay.
- sk_sp<GrAtlasTextBlob> blob(gTextContext->makeDrawTextBlob(
- context->contextPriv().getTextBlobCache(), glyphCache,
- *context->caps()->shaderCaps(), utilsPaint,
- GrAtlasTextContext::kTextBlobOpScalerContextFlags, viewMatrix, gSurfaceProps, text,
- static_cast<size_t>(textLen), x, y));
-
- return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, utilsPaint, gSurfaceProps,
- gTextContext->dfAdjustTable(), restrictedAtlasManager,
- rtc->textTarget());
+ return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(),
+ skPaint, viewMatrix, text, xInt, yInt);
}
#endif
diff --git a/src/gpu/text/GrAtlasTextContext.h b/src/gpu/text/GrAtlasTextContext.h
index 2f95d9a..1342ee7 100644
--- a/src/gpu/text/GrAtlasTextContext.h
+++ b/src/gpu/text/GrAtlasTextContext.h
@@ -55,6 +55,11 @@
const SkMatrix& viewMatrix, const SkSurfaceProps&, const SkTextBlob*,
SkScalar x, SkScalar y, SkDrawFilter*, const SkIRect& clipBounds);
+ std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrContext*, GrAtlasTextContext*,
+ GrRenderTargetContext*, const SkPaint&,
+ const SkMatrix& viewMatrix, const char* text,
+ int x, int y);
+
private:
GrAtlasTextContext(const Options& options);
diff --git a/src/gpu/text/GrGlyphCache.cpp b/src/gpu/text/GrGlyphCache.cpp
index 2372af8..49e24b8 100644
--- a/src/gpu/text/GrGlyphCache.cpp
+++ b/src/gpu/text/GrGlyphCache.cpp
@@ -292,7 +292,8 @@
}
}
-bool GrTextStrike::addGlyphToAtlas(GrResourceProvider* resourceProvider,
+GrDrawOpAtlas::ErrorCode GrTextStrike::addGlyphToAtlas(
+ GrResourceProvider* resourceProvider,
GrDeferredUploadTarget* target,
GrGlyphCache* glyphCache,
GrAtlasManager* fullAtlasManager,
@@ -325,7 +326,7 @@
if (isSDFGlyph) {
if (!get_packed_glyph_df_image(cache, skGlyph, width, height,
storage.get())) {
- return false;
+ return GrDrawOpAtlas::ErrorCode::kError;
}
} else {
void* dataPtr = storage.get();
@@ -336,15 +337,16 @@
if (!get_packed_glyph_image(cache, skGlyph, glyph->width(), glyph->height(),
rowBytes, expectedMaskFormat,
dataPtr)) {
- return false;
+ return GrDrawOpAtlas::ErrorCode::kError;
}
}
- bool success = fullAtlasManager->addToAtlas(resourceProvider, glyphCache, this,
+ GrDrawOpAtlas::ErrorCode result = fullAtlasManager->addToAtlas(
+ resourceProvider, glyphCache, this,
&glyph->fID, target, expectedMaskFormat,
width, height,
storage.get(), &glyph->fAtlasLocation);
- if (success) {
+ if (GrDrawOpAtlas::ErrorCode::kSucceeded == result) {
if (addPad) {
glyph->fAtlasLocation.fX += 1;
glyph->fAtlasLocation.fY += 1;
@@ -352,5 +354,5 @@
SkASSERT(GrDrawOpAtlas::kInvalidAtlasID != glyph->fID);
fAtlasedGlyphs++;
}
- return success;
+ return result;
}
diff --git a/src/gpu/text/GrGlyphCache.h b/src/gpu/text/GrGlyphCache.h
index 7e6056e..bbc199f 100644
--- a/src/gpu/text/GrGlyphCache.h
+++ b/src/gpu/text/GrGlyphCache.h
@@ -63,9 +63,10 @@
// happen.
// TODO we can handle some of these cases if we really want to, but the long term solution is to
// get the actual glyph image itself when we get the glyph metrics.
- bool addGlyphToAtlas(GrResourceProvider*, GrDeferredUploadTarget*, GrGlyphCache*,
- GrAtlasManager*, GrGlyph*,
- SkGlyphCache*, GrMaskFormat expectedMaskFormat, bool isScaledGlyph);
+ GrDrawOpAtlas::ErrorCode addGlyphToAtlas(GrResourceProvider*, GrDeferredUploadTarget*,
+ GrGlyphCache*, GrAtlasManager*, GrGlyph*,
+ SkGlyphCache*, GrMaskFormat expectedMaskFormat,
+ bool isScaledGlyph);
// testing
int countGlyphs() const { return fCache.count(); }
diff --git a/tests/DrawOpAtlasTest.cpp b/tests/DrawOpAtlasTest.cpp
index 7e35a07..a579063 100644
--- a/tests/DrawOpAtlasTest.cpp
+++ b/tests/DrawOpAtlasTest.cpp
@@ -28,6 +28,20 @@
return count;
}
+void GrAtlasManager::setMaxPages_TestingOnly(uint32_t numPages) {
+ for (int i = 0; i < kMaskFormatCount; i++) {
+ if (fAtlases[i]) {
+ fAtlases[i]->setMaxPages_TestingOnly(numPages);
+ }
+ }
+}
+
+void GrDrawOpAtlas::setMaxPages_TestingOnly(uint32_t maxPages) {
+ SkASSERT(!fNumActivePages);
+
+ fMaxPages = maxPages;
+}
+
void EvictionFunc(GrDrawOpAtlas::AtlasID atlasID, void*) {
SkASSERT(0); // The unit test shouldn't exercise this code path
}
@@ -43,9 +57,8 @@
public:
TestingUploadTarget() { }
- const GrTokenTracker* tokenTracker() final {
- return &fTokenTracker;
- }
+ const GrTokenTracker* tokenTracker() final { return &fTokenTracker; }
+ GrTokenTracker* writeableTokenTracker() { return &fTokenTracker; }
GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) final {
SkASSERT(0); // this test shouldn't invoke this code path
@@ -77,13 +90,16 @@
data.eraseARGB(alpha, 0, 0, 0);
SkIPoint16 loc;
- bool result = atlas->addToAtlas(resourceProvider, atlasID, target, kPlotSize, kPlotSize,
- data.getAddr(0, 0), &loc);
- return result;
+ GrDrawOpAtlas::ErrorCode code;
+ code = atlas->addToAtlas(resourceProvider, atlasID, target, kPlotSize, kPlotSize,
+ data.getAddr(0, 0), &loc);
+ return GrDrawOpAtlas::ErrorCode::kSucceeded == code;
}
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DrawOpAtlas, reporter, ctxInfo) {
+// This is a basic DrawOpAtlas test. It simply verifies that multitexture atlases correctly
+// add and remove pages. Note that this is simulating flush-time behavior.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(BasicDrawOpAtlas, reporter, ctxInfo) {
auto context = ctxInfo.grContext();
auto proxyProvider = context->contextPriv().proxyProvider();
auto resourceProvider = context->contextPriv().resourceProvider();
@@ -129,4 +145,68 @@
check(reporter, atlas.get(), 1, 4, 1);
}
+#include "GrTest.h"
+
+#include "GrDrawingManager.h"
+#include "GrOpFlushState.h"
+#include "GrProxyProvider.h"
+
+#include "effects/GrConstColorProcessor.h"
+#include "ops/GrAtlasTextOp.h"
+#include "text/GrAtlasTextContext.h"
+
+// This test verifies that the GrAtlasTextOp::onPrepare method correctly handles a failure
+// when allocating an atlas page.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrAtlasTextOpPreparation, reporter, ctxInfo) {
+
+ auto context = ctxInfo.grContext();
+
+ auto gpu = context->contextPriv().getGpu();
+ auto resourceProvider = context->contextPriv().resourceProvider();
+ auto drawingManager = context->contextPriv().drawingManager();
+ auto textContext = drawingManager->getAtlasTextContext();
+
+ auto rtc = context->contextPriv().makeDeferredRenderTargetContext(SkBackingFit::kApprox,
+ 32, 32,
+ kRGBA_8888_GrPixelConfig,
+ nullptr);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ paint.setLCDRenderText(false);
+ paint.setAntiAlias(false);
+ paint.setSubpixelText(false);
+ GrTextUtils::Paint utilsPaint(&paint, &rtc->colorSpaceInfo());
+
+ const char* text = "a";
+
+ std::unique_ptr<GrDrawOp> op = textContext->createOp_TestingOnly(context, textContext,
+ rtc.get(), paint,
+ SkMatrix::I(), text,
+ 16, 16);
+ op->finalize(*context->caps(), nullptr, GrPixelConfigIsClamped::kNo);
+
+ TestingUploadTarget uploadTarget;
+
+ GrOpFlushState flushState(gpu, resourceProvider, uploadTarget.writeableTokenTracker());
+ GrOpFlushState::OpArgs opArgs = {
+ op.get(),
+ rtc->asRenderTargetProxy(),
+ nullptr,
+ GrXferProcessor::DstProxy(nullptr, SkIPoint::Make(0, 0))
+ };
+
+ // Cripple the atlas manager so it can't allocate any pages. This will force a failure
+ // in the preparation of the text op
+ auto atlasManager = context->contextPriv().getFullAtlasManager();
+ unsigned int numProxies;
+ atlasManager->getProxies(kA8_GrMaskFormat, &numProxies);
+ atlasManager->setMaxPages_TestingOnly(0);
+
+ flushState.setOpArgs(&opArgs);
+ op->prepare(&flushState);
+ flushState.setOpArgs(nullptr);
+}
+
+
#endif
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 3e9f73a..aa2b07e 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -31,10 +31,9 @@
namespace GrTest {
-void SetupAlwaysEvictAtlas(GrContext* context) {
+void SetupAlwaysEvictAtlas(GrContext* context, int dim) {
// These sizes were selected because they allow each atlas to hold a single plot and will thus
// stress the atlas
- int dim = GrDrawOpAtlas::kGlyphMaxDim;
GrDrawOpAtlasConfig configs[3];
configs[kA8_GrMaskFormat].fWidth = dim;
configs[kA8_GrMaskFormat].fHeight = dim;
diff --git a/tools/gpu/GrTest.h b/tools/gpu/GrTest.h
index 5d988c7..6666ab1 100644
--- a/tools/gpu/GrTest.h
+++ b/tools/gpu/GrTest.h
@@ -10,13 +10,14 @@
#include "GrBackendSurface.h"
#include "GrContext.h"
+#include "GrDrawOpAtlas.h"
namespace GrTest {
/**
* Forces the GrContext to use a small atlas which only has room for one plot and will thus
* constantly be evicting entries
*/
- void SetupAlwaysEvictAtlas(GrContext*);
+ void SetupAlwaysEvictAtlas(GrContext*, int dim = GrDrawOpAtlas::kGlyphMaxDim);
// TODO: remove this. It is only used in the SurfaceSemaphores Test.
GrBackendTexture CreateBackendTexture(GrBackend, int width, int height,