#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<typename Key, typename Val> static void delete_hash_map_entry(const Key&, Val* val) {
+ SkASSERT(*val);
+ delete *val;
+}
+
+template<typename T> 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*
}
GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
+ fBlobIdCache.foreach(delete_hash_map_entry<uint32_t, TextBlob*>);
+ fBlobKeyCache.foreach(delete_hash_table_entry<TextBlob*>);
}
-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;
}
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,
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<const char*>(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();
+ }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
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
}
// When drawing from canonically sized paths, the actual local coords are fTextRatio * coords.
- fLocalMatrix.setScale(fTextRatio, fTextRatio);
+ fLocalMatrixTemplate.setScale(fTextRatio, fTextRatio);
}
GrStencilAndCoverTextContext::TextRun::~TextRun() {
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();
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();
}
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,
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);
}
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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#include "GrTextContext.h"
#include "GrDrawTarget.h"
#include "GrStrokeInfo.h"
+#include "SkTHash.h"
+#include "SkTInternalLList.h"
+#include "SkTLList.h"
class GrTextStrike;
class GrPath;
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;
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*);
SkPaint fFont;
SkScalar fTextRatio;
float fTextInverseRatio;
- SkMatrix fLocalMatrix;
bool fUsingRawGlyphPaths;
+ int fTotalGlyphCount;
SkAutoTUnref<GrPathRangeDraw> fDraw;
SkAutoTUnref<const SkTextBlob> fFallbackTextBlob;
+ mutable SkMatrix fLocalMatrixTemplate;
+ };
+
+ // Text blobs/caches.
+
+ class TextBlob : public SkTLList<TextRun> {
+ public:
+ typedef SkTArray<uint32_t, true> 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<uint32_t, TextBlob*> fBlobIdCache;
+ SkTHashTable<TextBlob*, const TextBlob::Key&, TextBlob> fBlobKeyCache;
+ SkTInternalLList<TextBlob> fLRUList;
+ int fCacheSize;
+
typedef GrTextContext INHERITED;
};