diff options
-rw-r--r-- | src/atlastext/SkAtlasTextTarget.cpp | 13 | ||||
-rw-r--r-- | src/gpu/GrDrawOpAtlas.cpp | 107 | ||||
-rw-r--r-- | src/gpu/GrDrawOpAtlas.h | 37 | ||||
-rw-r--r-- | src/gpu/ops/GrAtlasTextOp.cpp | 17 | ||||
-rw-r--r-- | src/gpu/ops/GrSmallPathRenderer.cpp | 168 | ||||
-rw-r--r-- | src/gpu/ops/GrSmallPathRenderer.h | 105 | ||||
-rw-r--r-- | src/gpu/text/GrAtlasManager.cpp | 5 | ||||
-rw-r--r-- | src/gpu/text/GrAtlasManager.h | 4 | ||||
-rw-r--r-- | src/gpu/text/GrAtlasTextBlob.h | 4 | ||||
-rw-r--r-- | src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp | 68 | ||||
-rw-r--r-- | src/gpu/text/GrAtlasTextContext.cpp | 51 | ||||
-rw-r--r-- | src/gpu/text/GrAtlasTextContext.h | 5 | ||||
-rw-r--r-- | src/gpu/text/GrGlyphCache.cpp | 14 | ||||
-rw-r--r-- | src/gpu/text/GrGlyphCache.h | 7 | ||||
-rw-r--r-- | tests/DrawOpAtlasTest.cpp | 94 | ||||
-rw-r--r-- | tools/gpu/GrTest.cpp | 3 | ||||
-rw-r--r-- | tools/gpu/GrTest.h | 3 |
17 files changed, 450 insertions, 255 deletions
diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp index 9da5fd1e3d..4513952781 100644 --- a/src/atlastext/SkAtlasTextTarget.cpp +++ b/src/atlastext/SkAtlasTextTarget.cpp @@ -192,15 +192,20 @@ void GrAtlasTextOp::executeForTextTarget(SkAtlasTextTarget* target) { 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 bbb87f78ab..629d91b3b0 100644 --- a/src/gpu/GrDrawOpAtlas.cpp +++ b/src/gpu/GrDrawOpAtlas.cpp @@ -186,7 +186,7 @@ GrDrawOpAtlas::GrDrawOpAtlas(GrProxyProvider* proxyProvider, , 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 @@ inline bool GrDrawOpAtlas::updatePlot(GrDeferredUploadTarget* target, AtlasID* i 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 @@ inline bool GrDrawOpAtlas::updatePlot(GrDeferredUploadTarget* target, AtlasID* i // 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 @@ bool GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider, // 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; } - } - - // 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()); + } 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; + } - 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); + 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; } + } - // 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 @@ bool GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider, // 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 @@ bool GrDrawOpAtlas::addToAtlas(GrResourceProvider* resourceProvider, *id = newPlot->id(); - return true; + return ErrorCode::kSucceeded; } void GrDrawOpAtlas::compact(GrDeferredUploadToken startTokenForNextFlush) { @@ -537,9 +550,7 @@ bool GrDrawOpAtlas::createPages(GrProxyProvider* proxyProvider) { 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 b849d9e067..6106c0c64b 100644 --- a/src/gpu/GrDrawOpAtlas.h +++ b/src/gpu/GrDrawOpAtlas.h @@ -98,19 +98,30 @@ public: 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 @@ public: 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 @@ private: // 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 @@ private: // 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 9e144ed278..07e114196b 100644 --- a/src/gpu/ops/GrAtlasTextOp.cpp +++ b/src/gpu/ops/GrAtlasTextOp.cpp @@ -292,9 +292,14 @@ void GrAtlasTextOp::onPrepareDraws(Target* target) { 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 @@ void GrAtlasTextOp::onPrepareDraws(Target* target) { 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 1607069118..73f0da567e 100644 --- a/src/gpu/ops/GrSmallPathRenderer.cpp +++ b/src/gpu/ops/GrSmallPathRenderer.cpp @@ -43,6 +43,91 @@ static const SkScalar kMaxDim = 73; 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 @@ private: 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 @@ private: 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 @@ private: } } 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 @@ private: 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 @@ private: // 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 @@ private: 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 @@ private: // 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 @@ struct GrSmallPathRenderer::PathTestStruct { 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 @@ GR_DRAW_OP_TEST_DEFINE(SmallPathOp) { // 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 ef83c771a6..ab58bdf107 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 @@ public: } 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 @@ private: 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 c6a60567e6..b688c1cce5 100644 --- a/src/gpu/text/GrAtlasManager.cpp +++ b/src/gpu/text/GrAtlasManager.cpp @@ -94,14 +94,15 @@ bool GrAtlasManager::hasGlyph(GrGlyph* glyph) { } // 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 eb9c113b7e..924119397e 100644 --- a/src/gpu/text/GrAtlasManager.h +++ b/src/gpu/text/GrAtlasManager.h @@ -100,7 +100,8 @@ public: } // 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 @@ public: #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 b4a11a496d..a01041f8dc 100644 --- a/src/gpu/text/GrAtlasTextBlob.h +++ b/src/gpu/text/GrAtlasTextBlob.h @@ -595,11 +595,11 @@ public: 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 296df22f51..19b0959f58 100644 --- a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp +++ b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp @@ -230,7 +230,7 @@ Regenerator::VertexRegenerator(GrResourceProvider* resourceProvider, GrAtlasText } 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 @@ Regenerator::Result Regenerator::doRegen() { } 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 @@ Regenerator::Result Regenerator::doRegen() { 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 @@ Regenerator::Result Regenerator::doRegen() { fSubRun->drawAsDistanceFields(), fTransX, fTransY, fColor); currVertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph; - ++result.fGlyphsRegenerated; + ++result->fGlyphsRegenerated; ++fCurrGlyph; } @@ -309,10 +315,10 @@ Regenerator::Result Regenerator::doRegen() { ? 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 @@ Regenerator::Result Regenerator::regenerate() { 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 @@ Regenerator::Result Regenerator::regenerate() { 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 ad708d7d7f..ef71fe0613 100644 --- a/src/gpu/text/GrAtlasTextContext.cpp +++ b/src/gpu/text/GrAtlasTextContext.cpp @@ -930,6 +930,37 @@ void GrAtlasTextContext::FallbackTextHelper::drawText(GrAtlasTextBlob* blob, int #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 @@ GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) { 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 @@ GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) { 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 2f95d9a0c0..1342ee7ac8 100644 --- a/src/gpu/text/GrAtlasTextContext.h +++ b/src/gpu/text/GrAtlasTextContext.h @@ -55,6 +55,11 @@ public: 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 2372af815c..49e24b8b8c 100644 --- a/src/gpu/text/GrGlyphCache.cpp +++ b/src/gpu/text/GrGlyphCache.cpp @@ -292,7 +292,8 @@ void GrTextStrike::removeID(GrDrawOpAtlas::AtlasID id) { } } -bool GrTextStrike::addGlyphToAtlas(GrResourceProvider* resourceProvider, +GrDrawOpAtlas::ErrorCode GrTextStrike::addGlyphToAtlas( + GrResourceProvider* resourceProvider, GrDeferredUploadTarget* target, GrGlyphCache* glyphCache, GrAtlasManager* fullAtlasManager, @@ -325,7 +326,7 @@ bool GrTextStrike::addGlyphToAtlas(GrResourceProvider* resourceProvider, 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 @@ bool GrTextStrike::addGlyphToAtlas(GrResourceProvider* resourceProvider, 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 @@ bool GrTextStrike::addGlyphToAtlas(GrResourceProvider* resourceProvider, 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 7e6056e6f5..bbc199fac1 100644 --- a/src/gpu/text/GrGlyphCache.h +++ b/src/gpu/text/GrGlyphCache.h @@ -63,9 +63,10 @@ public: // 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 7e35a07726..a579063f2b 100644 --- a/tests/DrawOpAtlasTest.cpp +++ b/tests/DrawOpAtlasTest.cpp @@ -28,6 +28,20 @@ int GrDrawOpAtlas::numAllocated_TestingOnly() const { 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 @@ class TestingUploadTarget : public GrDeferredUploadTarget { 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 @@ static bool fill_plot(GrDrawOpAtlas* atlas, 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 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DrawOpAtlas, reporter, ctxInfo) { 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 3e9f73a6cb..aa2b07e232 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 5d988c7b04..6666ab1a20 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, |