From cdd7907a3d7daf195f76e33af5dab20c83fe715f Mon Sep 17 00:00:00 2001 From: cdalton Date: Mon, 5 Oct 2015 15:37:35 -0700 Subject: [PATCH] Implement cached nvpr text blobs BUG=skia: Review URL: https://codereview.chromium.org/1381073002 --- include/core/SkTextBlob.h | 1 + src/gpu/GrStencilAndCoverTextContext.cpp | 209 ++++++++++++++++++++--- src/gpu/GrStencilAndCoverTextContext.h | 73 +++++++- 3 files changed, 250 insertions(+), 33 deletions(-) diff --git a/include/core/SkTextBlob.h b/include/core/SkTextBlob.h index b1ae456689..a39f378abc 100644 --- a/include/core/SkTextBlob.h +++ b/include/core/SkTextBlob.h @@ -93,6 +93,7 @@ private: static unsigned ScalarsPerGlyph(GlyphPositioning pos); friend class GrAtlasTextContext; + friend class GrStencilAndCoverTextContext; friend class GrTextBlobCache; friend class GrTextContext; friend class SkBaseDevice; diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp index 61f7f864d7..c3c49f92bf 100644 --- a/src/gpu/GrStencilAndCoverTextContext.cpp +++ b/src/gpu/GrStencilAndCoverTextContext.cpp @@ -17,15 +17,27 @@ #include "SkDrawProcs.h" #include "SkGlyphCache.h" #include "SkGpuDevice.h" +#include "SkGrPriv.h" #include "SkPath.h" #include "SkTextMapStateProc.h" #include "SkTextFormatParams.h" #include "batches/GrDrawPathBatch.h" +template static void delete_hash_map_entry(const Key&, Val* val) { + SkASSERT(*val); + delete *val; +} + +template static void delete_hash_table_entry(T* val) { + SkASSERT(*val); + delete *val; +} + GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(GrContext* context, const SkSurfaceProps& surfaceProps) - : INHERITED(context, surfaceProps) { + : INHERITED(context, surfaceProps), + fCacheSize(0) { } GrStencilAndCoverTextContext* @@ -38,13 +50,11 @@ GrStencilAndCoverTextContext::Create(GrContext* context, const SkSurfaceProps& s } GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() { + fBlobIdCache.foreach(delete_hash_map_entry); + fBlobKeyCache.foreach(delete_hash_table_entry); } -bool GrStencilAndCoverTextContext::canDraw(const GrRenderTarget* rt, - const GrClip& clip, - const GrPaint& paint, - const SkPaint& skPaint, - const SkMatrix& viewMatrix) { +bool GrStencilAndCoverTextContext::internalCanDraw(const SkPaint& skPaint) { if (skPaint.getRasterizer()) { return false; } @@ -68,10 +78,12 @@ void GrStencilAndCoverTextContext::onDrawText(GrDrawContext* dc, GrRenderTarget* const char text[], size_t byteLength, SkScalar x, SkScalar y, - const SkIRect& regionClipBounds) { + const SkIRect& clipBounds) { TextRun run(skPaint); + GrPipelineBuilder pipelineBuilder(paint, rt, clip); run.setText(text, byteLength, x, y, fContext, &fSurfaceProps); - run.draw(dc, rt, clip, paint, viewMatrix, regionClipBounds, fFallbackTextContext, skPaint); + run.draw(dc, &pipelineBuilder, paint.getColor(), viewMatrix, 0, 0, clipBounds, + fFallbackTextContext, skPaint); } void GrStencilAndCoverTextContext::onDrawPosText(GrDrawContext* dc, GrRenderTarget* rt, @@ -84,10 +96,137 @@ void GrStencilAndCoverTextContext::onDrawPosText(GrDrawContext* dc, GrRenderTarg const SkScalar pos[], int scalarsPerPosition, const SkPoint& offset, - const SkIRect& regionClipBounds) { + const SkIRect& clipBounds) { TextRun run(skPaint); + GrPipelineBuilder pipelineBuilder(paint, rt, clip); run.setPosText(text, byteLength, pos, scalarsPerPosition, offset, fContext, &fSurfaceProps); - run.draw(dc, rt, clip, paint, viewMatrix, regionClipBounds, fFallbackTextContext, skPaint); + run.draw(dc, &pipelineBuilder, paint.getColor(), viewMatrix, 0, 0, clipBounds, + fFallbackTextContext, skPaint); +} + +void GrStencilAndCoverTextContext::drawTextBlob(GrDrawContext* dc, GrRenderTarget* rt, + const GrClip& clip, const SkPaint& skPaint, + const SkMatrix& viewMatrix, + const SkTextBlob* skBlob, SkScalar x, SkScalar y, + SkDrawFilter* drawFilter, + const SkIRect& clipBounds) { + if (!this->internalCanDraw(skPaint)) { + fFallbackTextContext->drawTextBlob(dc, rt, clip, skPaint, viewMatrix, skBlob, x, y, + drawFilter, clipBounds); + return; + } + + if (drawFilter || skPaint.getPathEffect()) { + // This draw can't be cached. + INHERITED::drawTextBlob(dc, rt, clip, skPaint, viewMatrix, skBlob, x, y, drawFilter, + clipBounds); + return; + } + + if (fContext->abandoned()) { + return; + } + + GrPaint paint; + if (!SkPaintToGrPaint(fContext, skPaint, viewMatrix, &paint)) { + return; + } + + const TextBlob& blob = this->findOrCreateTextBlob(skBlob, skPaint); + GrPipelineBuilder pipelineBuilder(paint, rt, clip); + + TextBlob::Iter iter(blob); + for (TextRun* run = iter.get(); run; run = iter.next()) { + run->draw(dc, &pipelineBuilder, paint.getColor(), viewMatrix, x, y, clipBounds, + fFallbackTextContext, skPaint); + } +} + +const GrStencilAndCoverTextContext::TextBlob& +GrStencilAndCoverTextContext::findOrCreateTextBlob(const SkTextBlob* skBlob, + const SkPaint& skPaint) { + // The font-related parameters are baked into the text blob and will override this skPaint, so + // the only remaining properties that can affect a TextBlob are the ones related to stroke. + if (SkPaint::kFill_Style == skPaint.getStyle()) { // Fast path. + if (TextBlob** found = fBlobIdCache.find(skBlob->uniqueID())) { + fLRUList.remove(*found); + fLRUList.addToTail(*found); + return **found; + } + TextBlob* blob = new TextBlob(skBlob->uniqueID(), skBlob, skPaint, fContext, + &fSurfaceProps); + this->purgeToFit(*blob); + fBlobIdCache.set(skBlob->uniqueID(), blob); + fLRUList.addToTail(blob); + fCacheSize += blob->cpuMemorySize(); + return *blob; + } else { + GrStrokeInfo stroke(skPaint); + SkSTArray<4, uint32_t, true> key; + key.reset(1 + stroke.computeUniqueKeyFragmentData32Cnt()); + key[0] = skBlob->uniqueID(); + stroke.asUniqueKeyFragment(&key[1]); + if (TextBlob** found = fBlobKeyCache.find(key)) { + fLRUList.remove(*found); + fLRUList.addToTail(*found); + return **found; + } + TextBlob* blob = new TextBlob(key, skBlob, skPaint, fContext, &fSurfaceProps); + this->purgeToFit(*blob); + fBlobKeyCache.set(blob); + fLRUList.addToTail(blob); + fCacheSize += blob->cpuMemorySize(); + return *blob; + } +} + +void GrStencilAndCoverTextContext::purgeToFit(const TextBlob& blob) { + static const int maxCacheSize = 4 * 1024 * 1024; // Allow up to 4 MB for caching text blobs. + + int maxSizeForNewBlob = maxCacheSize - blob.cpuMemorySize(); + while (fCacheSize && fCacheSize > maxSizeForNewBlob) { + TextBlob* lru = fLRUList.head(); + if (1 == lru->key().count()) { + // 1-length keys are unterstood to be the blob id. + fBlobIdCache.remove(lru->key()[0]); + } else { + fBlobKeyCache.remove(lru->key()); + } + fLRUList.remove(lru); + fCacheSize -= lru->cpuMemorySize(); + delete lru; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void GrStencilAndCoverTextContext::TextBlob::init(const SkTextBlob* skBlob, const SkPaint& skPaint, + GrContext* ctx, const SkSurfaceProps* props) { + fCpuMemorySize = sizeof(TextBlob); + SkPaint runPaint(skPaint); + for (SkTextBlob::RunIterator iter(skBlob); !iter.done(); iter.next()) { + iter.applyFontToPaint(&runPaint); // No need to re-seed the paint. + TextRun* run = SkNEW_INSERT_AT_LLIST_TAIL(this, TextRun, (runPaint)); + + const char* text = reinterpret_cast(iter.glyphs()); + size_t byteLength = sizeof(uint16_t) * iter.glyphCount(); + const SkPoint& runOffset = iter.offset(); + + switch (iter.positioning()) { + case SkTextBlob::kDefault_Positioning: + run->setText(text, byteLength, runOffset.fX, runOffset.fY, ctx, props); + break; + case SkTextBlob::kHorizontal_Positioning: + run->setPosText(text, byteLength, iter.pos(), 1, SkPoint::Make(0, runOffset.fY), + ctx, props); + break; + case SkTextBlob::kFull_Positioning: + run->setPosText(text, byteLength, iter.pos(), 2, SkPoint::Make(0, 0), ctx, props); + break; + } + + fCpuMemorySize += run->cpuMemorySize(); + } } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -120,7 +259,8 @@ private: GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke) : fStroke(fontAndStroke), - fFont(fontAndStroke) { + fFont(fontAndStroke), + fTotalGlyphCount(0) { SkASSERT(!fStroke.isHairlineStyle()); // Hairlines are not supported. // Setting to "fill" ensures that no strokes get baked into font outlines. (We use the GPU path @@ -168,7 +308,7 @@ GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke) } // When drawing from canonically sized paths, the actual local coords are fTextRatio * coords. - fLocalMatrix.setScale(fTextRatio, fTextRatio); + fLocalMatrixTemplate.setScale(fTextRatio, fTextRatio); } GrStencilAndCoverTextContext::TextRun::~TextRun() { @@ -182,9 +322,10 @@ void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t by SkAutoGlyphCacheNoGamma autoGlyphCache(fFont, surfaceProps, nullptr); SkGlyphCache* glyphCache = autoGlyphCache.getCache(); + fTotalGlyphCount = fFont.countText(text, byteLength); fDraw.reset(GrPathRangeDraw::Create(this->createGlyphs(ctx, glyphCache), GrPathRendering::kTranslate_PathTransformType, - fFont.countText(text, byteLength))); + fTotalGlyphCount)); SkDrawCacheProc glyphCacheProc = fFont.getDrawCacheProc(); @@ -250,9 +391,10 @@ void GrStencilAndCoverTextContext::TextRun::setPosText(const char text[], size_t SkAutoGlyphCacheNoGamma autoGlyphCache(fFont, surfaceProps, nullptr); SkGlyphCache* glyphCache = autoGlyphCache.getCache(); + fTotalGlyphCount = fFont.countText(text, byteLength); fDraw.reset(GrPathRangeDraw::Create(this->createGlyphs(ctx, glyphCache), GrPathRendering::kTranslate_PathTransformType, - fFont.countText(text, byteLength))); + fTotalGlyphCount)); SkDrawCacheProc glyphCacheProc = fFont.getDrawCacheProc(); @@ -323,19 +465,19 @@ inline void GrStencilAndCoverTextContext::TextRun::appendGlyph(const SkGlyph& gl } void GrStencilAndCoverTextContext::TextRun::draw(GrDrawContext* dc, - GrRenderTarget* rt, - const GrClip& clip, - const GrPaint& paint, + GrPipelineBuilder* pipelineBuilder, + GrColor color, const SkMatrix& viewMatrix, - const SkIRect& regionClipBounds, + SkScalar x, SkScalar y, + const SkIRect& clipBounds, GrTextContext* fallbackTextContext, const SkPaint& originalSkPaint) const { SkASSERT(fDraw); + SkASSERT(pipelineBuilder->getRenderTarget()->isStencilBufferMultisampled() || + !fFont.isAntiAlias()); if (fDraw->count()) { - GrPipelineBuilder pipelineBuilder(paint, rt, clip); - SkASSERT(rt->isStencilBufferMultisampled() || !paint.isAntiAlias()); - pipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, paint.isAntiAlias()); + pipelineBuilder->setState(GrPipelineBuilder::kHWAntialias_Flag, fFont.isAntiAlias()); GR_STATIC_CONST_SAME_STENCIL(kStencilPass, kZero_StencilOp, @@ -345,12 +487,17 @@ void GrStencilAndCoverTextContext::TextRun::draw(GrDrawContext* dc, 0x0000, 0xffff); - *pipelineBuilder.stencil() = kStencilPass; + *pipelineBuilder->stencil() = kStencilPass; SkMatrix drawMatrix(viewMatrix); + drawMatrix.preTranslate(x, y); drawMatrix.preScale(fTextRatio, fTextRatio); - dc->drawPathsFromRange(&pipelineBuilder, drawMatrix, fLocalMatrix, paint.getColor(), fDraw, + SkMatrix& localMatrix = fLocalMatrixTemplate; + localMatrix.setTranslateX(x); + localMatrix.setTranslateY(y); + + dc->drawPathsFromRange(pipelineBuilder, drawMatrix, localMatrix, color, fDraw, GrPathRendering::kWinding_FillType); } @@ -361,11 +508,23 @@ void GrStencilAndCoverTextContext::TextRun::draw(GrDrawContext* dc, fallbackSkPaint.setStrokeWidth(fStroke.getWidth() * fTextRatio); } - fallbackTextContext->drawTextBlob(dc, rt, clip, fallbackSkPaint, viewMatrix, - fFallbackTextBlob, 0, 0, nullptr, regionClipBounds); + fallbackTextContext->drawTextBlob(dc, pipelineBuilder->getRenderTarget(), + pipelineBuilder->clip(), fallbackSkPaint, viewMatrix, + fFallbackTextBlob, x, y, nullptr, clipBounds); } } +int GrStencilAndCoverTextContext::TextRun::cpuMemorySize() const { + int size = sizeof(TextRun) + fTotalGlyphCount * (sizeof(uint16_t) + 2 * sizeof(float)); + if (fDraw) { + size += sizeof(GrPathRangeDraw); + } + if (fFallbackTextBlob) { + size += sizeof(SkTextBlob); + } + return size; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void GrStencilAndCoverTextContext::FallbackBlobBuilder::init(const SkPaint& font, diff --git a/src/gpu/GrStencilAndCoverTextContext.h b/src/gpu/GrStencilAndCoverTextContext.h index 9e85e89c88..008f61ee5f 100644 --- a/src/gpu/GrStencilAndCoverTextContext.h +++ b/src/gpu/GrStencilAndCoverTextContext.h @@ -11,6 +11,9 @@ #include "GrTextContext.h" #include "GrDrawTarget.h" #include "GrStrokeInfo.h" +#include "SkTHash.h" +#include "SkTInternalLList.h" +#include "SkTLList.h" class GrTextStrike; class GrPath; @@ -31,19 +34,24 @@ public: private: GrStencilAndCoverTextContext(GrContext*, const SkSurfaceProps&); - bool canDraw(const GrRenderTarget*, const GrClip&, const GrPaint&, - const SkPaint&, const SkMatrix& viewMatrix) override; + bool canDraw(const GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint& skPaint, + const SkMatrix&) override { return this->internalCanDraw(skPaint); } + + bool internalCanDraw(const SkPaint&); void onDrawText(GrDrawContext*, GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&, const SkMatrix& viewMatrix, const char text[], size_t byteLength, - SkScalar x, SkScalar y, const SkIRect& regionClipBounds) override; + SkScalar x, SkScalar y, const SkIRect& clipBounds) override; void onDrawPosText(GrDrawContext*, GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&, const SkMatrix& viewMatrix, const char text[], size_t byteLength, const SkScalar pos[], int scalarsPerPosition, - const SkPoint& offset, const SkIRect& regionClipBounds) override; + const SkPoint& offset, const SkIRect& clipBounds) override; + void drawTextBlob(GrDrawContext*, GrRenderTarget*, const GrClip&, const SkPaint&, + const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y, + SkDrawFilter*, const SkIRect& clipBounds) override; class FallbackBlobBuilder; @@ -59,9 +67,11 @@ private: const SkScalar pos[], int scalarsPerPosition, const SkPoint& offset, GrContext*, const SkSurfaceProps*); - void draw(GrDrawContext*, GrRenderTarget*, const GrClip&, const GrPaint&, const SkMatrix&, - const SkIRect& regionClipBounds, GrTextContext* fallbackTextContext, - const SkPaint& originalSkPaint) const; + void draw(GrDrawContext*, GrPipelineBuilder*, GrColor, const SkMatrix&, + SkScalar x, SkScalar y, const SkIRect& clipBounds, + GrTextContext* fallbackTextContext, const SkPaint& originalSkPaint) const; + + int cpuMemorySize() const; private: GrPathRange* createGlyphs(GrContext*, SkGlyphCache*); @@ -72,12 +82,59 @@ private: SkPaint fFont; SkScalar fTextRatio; float fTextInverseRatio; - SkMatrix fLocalMatrix; bool fUsingRawGlyphPaths; + int fTotalGlyphCount; SkAutoTUnref fDraw; SkAutoTUnref fFallbackTextBlob; + mutable SkMatrix fLocalMatrixTemplate; }; + // Text blobs/caches. + + class TextBlob : public SkTLList { + public: + typedef SkTArray Key; + + static const Key& GetKey(const TextBlob* blob) { return blob->key(); } + + static uint32_t Hash(const Key& key) { + SkASSERT(key.count() > 1); // 1-length keys should be using the blob-id hash map. + return SkChecksum::Murmur3(key.begin(), sizeof(uint32_t) * key.count()); + } + + TextBlob(uint32_t blobId, const SkTextBlob* skBlob, const SkPaint& skPaint, + GrContext* ctx, const SkSurfaceProps* props) + : fKey(&blobId, 1) { this->init(skBlob, skPaint, ctx, props); } + + TextBlob(const Key& key, const SkTextBlob* skBlob, const SkPaint& skPaint, + GrContext* ctx, const SkSurfaceProps* props) + : fKey(key) { + // 1-length keys are unterstood to be the blob id and must use the other constructor. + SkASSERT(fKey.count() > 1); + this->init(skBlob, skPaint, ctx, props); + } + + const Key& key() const { return fKey; } + + int cpuMemorySize() const { return fCpuMemorySize; } + + private: + void init(const SkTextBlob*, const SkPaint&, GrContext*, const SkSurfaceProps*); + + const SkSTArray<1, uint32_t, true> fKey; + int fCpuMemorySize; + + SK_DECLARE_INTERNAL_LLIST_INTERFACE(TextBlob); + }; + + const TextBlob& findOrCreateTextBlob(const SkTextBlob*, const SkPaint&); + void purgeToFit(const TextBlob&); + + SkTHashMap fBlobIdCache; + SkTHashTable fBlobKeyCache; + SkTInternalLList fLRUList; + int fCacheSize; + typedef GrTextContext INHERITED; };