diff options
-rw-r--r-- | dm/DMSrcSink.cpp | 13 | ||||
-rw-r--r-- | gyp/gpu.gypi | 2 | ||||
-rw-r--r-- | include/core/SkPaint.h | 1 | ||||
-rw-r--r-- | include/core/SkTextBlob.h | 1 | ||||
-rw-r--r-- | include/gpu/GrContext.h | 3 | ||||
-rwxr-xr-x | src/gpu/GrAADistanceFieldPathRenderer.cpp | 8 | ||||
-rw-r--r-- | src/gpu/GrAtlas.cpp | 1 | ||||
-rw-r--r-- | src/gpu/GrBatchAtlas.cpp | 7 | ||||
-rw-r--r-- | src/gpu/GrBatchAtlas.h | 4 | ||||
-rw-r--r-- | src/gpu/GrBatchFontCache.cpp | 309 | ||||
-rw-r--r-- | src/gpu/GrBatchFontCache.h | 137 | ||||
-rwxr-xr-x | src/gpu/GrBitmapTextContext.cpp | 959 | ||||
-rw-r--r-- | src/gpu/GrBitmapTextContext.h | 121 | ||||
-rwxr-xr-x | src/gpu/GrContext.cpp | 14 | ||||
-rwxr-xr-x | src/gpu/GrDistanceFieldTextContext.cpp | 4 | ||||
-rw-r--r-- | src/gpu/GrFontAtlasSizes.h | 26 | ||||
-rw-r--r-- | src/gpu/GrGlyph.h | 16 | ||||
-rw-r--r-- | src/gpu/GrStencilAndCoverTextContext.cpp | 4 | ||||
-rw-r--r-- | src/gpu/GrTextContext.h | 11 | ||||
-rw-r--r-- | src/gpu/SkGpuDevice.h | 1 |
20 files changed, 1605 insertions, 37 deletions
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 8bb1b25cbd..929ff0556b 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -243,8 +243,21 @@ Error SKPSrc::draw(SkCanvas* canvas) const { return SkStringPrintf("Couldn't decode %s as a picture.", fPath.c_str()); } stream.reset((SkStream*)NULL); // Might as well drop this when we're done with it. + canvas->clipRect(kSKPViewport); + // Testing TextBlob batching requires that we see individual text blobs more than once + // TODO remove this and add a flag to DM so we can run skps multiple times +//#define DOUBLE_LOOP +#ifdef DOUBLE_LOOP + { + SkAutoCanvasRestore acr(canvas, true); +#endif + canvas->drawPicture(pic); +#ifdef DOUBLE_LOOP + } + canvas->clear(0); canvas->drawPicture(pic); +#endif return ""; } diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi index 98c9175a50..0d9c4fe195 100644 --- a/gyp/gpu.gypi +++ b/gyp/gpu.gypi @@ -65,6 +65,8 @@ '<(skia_src_path)/gpu/GrBatch.h', '<(skia_src_path)/gpu/GrBatchAtlas.cpp', '<(skia_src_path)/gpu/GrBatchAtlas.h', + '<(skia_src_path)/gpu/GrBatchFontCache.cpp', + '<(skia_src_path)/gpu/GrBatchFontCache.h', '<(skia_src_path)/gpu/GrBatchTarget.cpp', '<(skia_src_path)/gpu/GrBatchTarget.h', '<(skia_src_path)/gpu/GrBitmapTextContext.cpp', diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h index 4bf8c059ac..5015c49d69 100644 --- a/include/core/SkPaint.h +++ b/include/core/SkPaint.h @@ -1122,6 +1122,7 @@ private: friend class SkGraphics; // So Term() can be called. friend class SkPDFDevice; friend class GrBitmapTextContext; + friend class GrBitmapTextContextB; friend class GrDistanceFieldTextContext; friend class GrStencilAndCoverTextContext; friend class GrPathRendering; diff --git a/include/core/SkTextBlob.h b/include/core/SkTextBlob.h index d31ec5c760..4ec9c5fb4d 100644 --- a/include/core/SkTextBlob.h +++ b/include/core/SkTextBlob.h @@ -91,6 +91,7 @@ private: static unsigned ScalarsPerGlyph(GlyphPositioning pos); + friend class GrBitmapTextContextB; friend class GrTextContext; friend class SkBaseDevice; friend class SkTextBlobBuilder; diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h index 335b4169cc..f631f56baa 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -19,6 +19,7 @@ #include "SkTypes.h" class GrAARectRenderer; +class GrBatchFontCache; class GrDrawTarget; class GrFontCache; class GrFragmentProcessor; @@ -654,6 +655,7 @@ public: // Functions intended for internal use only. GrGpu* getGpu() { return fGpu; } const GrGpu* getGpu() const { return fGpu; } + GrBatchFontCache* getBatchFontCache() { return fBatchFontCache; } GrFontCache* getFontCache() { return fFontCache; } GrLayerCache* getLayerCache() { return fLayerCache.get(); } GrDrawTarget* getTextTarget(); @@ -695,6 +697,7 @@ private: GrGpu* fGpu; GrResourceCache* fResourceCache; + GrBatchFontCache* fBatchFontCache; GrFontCache* fFontCache; SkAutoTDelete<GrLayerCache> fLayerCache; diff --git a/src/gpu/GrAADistanceFieldPathRenderer.cpp b/src/gpu/GrAADistanceFieldPathRenderer.cpp index 8b119f0b23..789406bdbc 100755 --- a/src/gpu/GrAADistanceFieldPathRenderer.cpp +++ b/src/gpu/GrAADistanceFieldPathRenderer.cpp @@ -293,8 +293,7 @@ public: instancesToFlush++; } - this->flush(batchTarget, dfProcessor, pipeline, &drawInfo, instancesToFlush, - maxInstancesPerDraw); + this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw); } SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } @@ -421,8 +420,7 @@ private: bool success = atlas->addToAtlas(&id, batchTarget, width, height, dfStorage.get(), &atlasLocation); if (!success) { - this->flush(batchTarget, dfProcessor, pipeline, drawInfo, *instancesToFlush, - maxInstancesPerDraw); + this->flush(batchTarget, drawInfo, *instancesToFlush, maxInstancesPerDraw); this->initDraw(batchTarget, dfProcessor, pipeline); *instancesToFlush = 0; @@ -516,8 +514,6 @@ private: } void flush(GrBatchTarget* batchTarget, - const GrGeometryProcessor* dfProcessor, - const GrPipeline* pipeline, GrDrawTarget::DrawInfo* drawInfo, int instanceCount, int maxInstancesPerDraw) { diff --git a/src/gpu/GrAtlas.cpp b/src/gpu/GrAtlas.cpp index 7ebdf6eb12..e18b157b83 100644 --- a/src/gpu/GrAtlas.cpp +++ b/src/gpu/GrAtlas.cpp @@ -226,6 +226,7 @@ GrPlot* GrAtlas::addToAtlas(ClientPlotUsage* usage, desc.fConfig = fPixelConfig; fTexture = fGpu->createTexture(desc, true, NULL, 0); + if (NULL == fTexture) { return NULL; } diff --git a/src/gpu/GrBatchAtlas.cpp b/src/gpu/GrBatchAtlas.cpp index 566cd5d922..3374e00a9f 100644 --- a/src/gpu/GrBatchAtlas.cpp +++ b/src/gpu/GrBatchAtlas.cpp @@ -227,7 +227,8 @@ GrBatchAtlas::GrBatchAtlas(GrTexture* texture, int numPlotsX, int numPlotsY) , fNumPlotsX(numPlotsX) , fNumPlotsY(numPlotsY) , fPlotWidth(texture->width() / numPlotsX) - , fPlotHeight(texture->height() / numPlotsY) { + , fPlotHeight(texture->height() / numPlotsY) + , fAtlasGeneration(kInvalidAtlasGeneration + 1) { SkASSERT(fPlotWidth * fNumPlotsX == texture->width()); SkASSERT(fPlotHeight * fNumPlotsY == texture->height()); @@ -243,7 +244,7 @@ GrBatchAtlas::GrBatchAtlas(GrTexture* texture, int numPlotsX, int numPlotsY) for (int x = fNumPlotsX - 1, c = 0; x >= 0; --x, ++c) { int id = r * fNumPlotsX + c; currPlot->reset(SkNEW(BatchPlot)); - (*currPlot)->init(this, texture, id, 0, x, y, fPlotWidth, fPlotHeight, fBPP); + (*currPlot)->init(this, texture, id, 1, x, y, fPlotWidth, fPlotHeight, fBPP); // build LRU list fPlotList.addToHead(currPlot->get()); @@ -318,6 +319,7 @@ bool GrBatchAtlas::addToAtlas(AtlasID* id, GrBatchTarget* batchTarget, SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc, fBPP * width); SkASSERT(verify); this->updatePlot(batchTarget, id, plot); + fAtlasGeneration++; return true; } @@ -352,6 +354,7 @@ bool GrBatchAtlas::addToAtlas(AtlasID* id, GrBatchTarget* batchTarget, batchTarget->upload(uploader); *id = newPlot->id(); plot->unref(); + fAtlasGeneration++; return true; } diff --git a/src/gpu/GrBatchAtlas.h b/src/gpu/GrBatchAtlas.h index b514b9d74f..cd8123f140 100644 --- a/src/gpu/GrBatchAtlas.h +++ b/src/gpu/GrBatchAtlas.h @@ -25,6 +25,8 @@ public: // An AtlasID is an opaque handle which callers can use to determine if the atlas contains // a specific piece of data typedef uint32_t AtlasID; + static const uint32_t kInvalidAtlasID = 0; + static const uint64_t kInvalidAtlasGeneration = 0; // A function pointer for use as a callback during eviction. Whenever GrBatchAtlas evicts a // specific AtlasID, it will call all of the registered listeners so they can optionally process @@ -43,6 +45,7 @@ public: GrTexture* getTexture() const { return fTexture; } + uint64_t atlasGeneration() const { return fAtlasGeneration; } bool hasID(AtlasID id); void setLastRefToken(AtlasID id, BatchToken batchToken); void registerEvictionCallback(EvictionFunc func, void* userData) { @@ -72,6 +75,7 @@ private: int fPlotWidth; int fPlotHeight; size_t fBPP; + uint64_t fAtlasGeneration; struct EvictionData { EvictionFunc fFunc; diff --git a/src/gpu/GrBatchFontCache.cpp b/src/gpu/GrBatchFontCache.cpp new file mode 100644 index 0000000000..bfa9a2e129 --- /dev/null +++ b/src/gpu/GrBatchFontCache.cpp @@ -0,0 +1,309 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrBatchFontCache.h" +#include "GrFontAtlasSizes.h" +#include "GrGpu.h" +#include "GrRectanizer.h" +#include "GrSurfacePriv.h" +#include "SkString.h" + +#include "SkDistanceFieldGen.h" + +/////////////////////////////////////////////////////////////////////////////// + +static GrBatchAtlas* make_atlas(GrContext* context, GrPixelConfig config, + int textureWidth, int textureHeight, + int numPlotsX, int numPlotsY) { + GrSurfaceDesc desc; + desc.fFlags = kNone_GrSurfaceFlags; + desc.fWidth = textureWidth; + desc.fHeight = textureHeight; + desc.fConfig = config; + + // We don't want to flush the context so we claim we're in the middle of flushing so as to + // guarantee we do not recieve a texture with pending IO + GrTexture* texture = context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch, true); + return SkNEW_ARGS(GrBatchAtlas, (texture, numPlotsX, numPlotsY)); +} + +int GrBatchFontCache::MaskFormatToAtlasIndex(GrMaskFormat format) { + static const int sAtlasIndices[] = { + kA8_GrMaskFormat, + kA565_GrMaskFormat, + kARGB_GrMaskFormat, + }; + SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, array_size_mismatch); + + SkASSERT(sAtlasIndices[format] < kMaskFormatCount); + return sAtlasIndices[format]; +} + +GrMaskFormat GrBatchFontCache::AtlasIndexToMaskFormat(int atlasIndex) { + static GrMaskFormat sMaskFormats[] = { + kA8_GrMaskFormat, + kA565_GrMaskFormat, + kARGB_GrMaskFormat, + }; + SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sMaskFormats) == kMaskFormatCount, array_size_mismatch); + + SkASSERT(sMaskFormats[atlasIndex] < kMaskFormatCount); + return sMaskFormats[atlasIndex]; +} + +GrBatchFontCache::GrBatchFontCache() + : fPreserveStrike(NULL) { +} + +void GrBatchFontCache::init(GrContext* context) { + for (int i = 0; i < kMaskFormatCount; i++) { + GrMaskFormat format = AtlasIndexToMaskFormat(i); + GrPixelConfig config = this->getPixelConfig(format); + + if (kA8_GrMaskFormat == format) { + fAtlases[i] = make_atlas(context, config, + GR_FONT_ATLAS_A8_TEXTURE_WIDTH, + GR_FONT_ATLAS_TEXTURE_HEIGHT, + GR_FONT_ATLAS_A8_NUM_PLOTS_X, + GR_FONT_ATLAS_NUM_PLOTS_Y); + } else { + fAtlases[i] = make_atlas(context, config, + GR_FONT_ATLAS_TEXTURE_WIDTH, + GR_FONT_ATLAS_TEXTURE_HEIGHT, + GR_FONT_ATLAS_NUM_PLOTS_X, + GR_FONT_ATLAS_NUM_PLOTS_Y); + } + + fAtlases[i]->registerEvictionCallback(&GrBatchFontCache::HandleEviction, (void*)this); + } +} + +GrBatchFontCache::~GrBatchFontCache() { + SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache); + while (!iter.done()) { + SkDELETE(&(*iter)); + ++iter; + } + for (int i = 0; i < kMaskFormatCount; ++i) { + SkDELETE(fAtlases[i]); + } +} + +GrBatchTextStrike* GrBatchFontCache::generateStrike(GrFontScaler* scaler) { + GrBatchTextStrike* strike = SkNEW_ARGS(GrBatchTextStrike, (this, scaler->getKey())); + fCache.add(strike); + return strike; +} + +void GrBatchFontCache::freeAll() { + SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache); + while (!iter.done()) { + SkDELETE(&(*iter)); + ++iter; + } + fCache.rewind(); + for (int i = 0; i < kMaskFormatCount; ++i) { + SkDELETE(fAtlases[i]); + fAtlases[i] = NULL; + } +} + +inline GrBatchAtlas* GrBatchFontCache::getAtlas(GrMaskFormat format) const { + int atlasIndex = MaskFormatToAtlasIndex(format); + SkASSERT(fAtlases[atlasIndex]); + return fAtlases[atlasIndex]; +} + +bool GrBatchFontCache::hasGlyph(GrGlyph* glyph) { + SkASSERT(glyph); + return this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID); +} + +void GrBatchFontCache::setGlyphRefToken(GrGlyph* glyph, GrBatchAtlas::BatchToken batchToken) { + SkASSERT(glyph); + SkASSERT(this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID)); + this->getAtlas(glyph->fMaskFormat)->setLastRefToken(glyph->fID, batchToken); +} + +bool GrBatchFontCache::addToAtlas(GrBatchTextStrike* strike, GrBatchAtlas::AtlasID* id, + GrBatchTarget* batchTarget, + GrMaskFormat format, int width, int height, const void* image, + SkIPoint16* loc) { + fPreserveStrike = strike; + return this->getAtlas(format)->addToAtlas(id, batchTarget, width, height, image, loc); +} + +uint64_t GrBatchFontCache::atlasGeneration(GrMaskFormat format) const { + return this->getAtlas(format)->atlasGeneration(); +} + +GrTexture* GrBatchFontCache::getTexture(GrMaskFormat format) { + int atlasIndex = MaskFormatToAtlasIndex(format); + SkASSERT(fAtlases[atlasIndex]); + return fAtlases[atlasIndex]->getTexture(); +} + +GrPixelConfig GrBatchFontCache::getPixelConfig(GrMaskFormat format) const { + static const GrPixelConfig kPixelConfigs[] = { + kAlpha_8_GrPixelConfig, + kRGB_565_GrPixelConfig, + kSkia8888_GrPixelConfig + }; + SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kPixelConfigs) == kMaskFormatCount, array_size_mismatch); + + return kPixelConfigs[format]; +} + +void GrBatchFontCache::HandleEviction(GrBatchAtlas::AtlasID id, void* ptr) { + GrBatchFontCache* fontCache = reinterpret_cast<GrBatchFontCache*>(ptr); + + SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fontCache->fCache); + for (; !iter.done(); ++iter) { + GrBatchTextStrike* strike = &*iter; + strike->removeID(id); + + // clear out any empty strikes. We will preserve the strike whose call to addToAtlas + // triggered the eviction + if (strike != fontCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) { + fontCache->fCache.remove(*(strike->fFontScalerKey)); + SkDELETE(strike); + } + } +} + +void GrBatchFontCache::dump() const { + static int gDumpCount = 0; + for (int i = 0; i < kMaskFormatCount; ++i) { + if (fAtlases[i]) { + GrTexture* texture = fAtlases[i]->getTexture(); + if (texture) { + SkString filename; +#ifdef SK_BUILD_FOR_ANDROID + filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i); +#else + filename.printf("fontcache_%d%d.png", gDumpCount, i); +#endif + texture->surfacePriv().savePixels(filename.c_str()); + } + } + } + ++gDumpCount; +} + +/////////////////////////////////////////////////////////////////////////////// + +/* + The text strike is specific to a given font/style/matrix setup, which is + represented by the GrHostFontScaler object we are given in getGlyph(). + + We map a 32bit glyphID to a GrGlyph record, which in turn points to a + atlas and a position within that texture. + */ + +GrBatchTextStrike::GrBatchTextStrike(GrBatchFontCache* cache, const GrFontDescKey* key) + : fFontScalerKey(SkRef(key)) + , fPool(9/*start allocations at 512 bytes*/) + , fAtlasedGlyphs(0) { + + fBatchFontCache = cache; // no need to ref, it won't go away before we do +} + +GrBatchTextStrike::~GrBatchTextStrike() { + SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache); + while (!iter.done()) { + (*iter).free(); + ++iter; + } +} + +GrGlyph* GrBatchTextStrike::generateGlyph(GrGlyph::PackedID packed, + GrFontScaler* scaler) { + SkIRect bounds; + if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(packed)) { + if (!scaler->getPackedGlyphDFBounds(packed, &bounds)) { + return NULL; + } + } else { + if (!scaler->getPackedGlyphBounds(packed, &bounds)) { + return NULL; + } + } + GrMaskFormat format = scaler->getPackedGlyphMaskFormat(packed); + + GrGlyph* glyph = (GrGlyph*)fPool.alloc(sizeof(GrGlyph), SK_MALLOC_THROW); + glyph->init(packed, bounds, format); + fCache.add(glyph); + return glyph; +} + +void GrBatchTextStrike::removeID(GrBatchAtlas::AtlasID id) { + SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache); + while (!iter.done()) { + if (id == (*iter).fID) { + (*iter).fID = GrBatchAtlas::kInvalidAtlasID; + fAtlasedGlyphs--; + SkASSERT(fAtlasedGlyphs >= 0); + } + ++iter; + } +} + +bool GrBatchTextStrike::glyphTooLargeForAtlas(GrGlyph* glyph) { + int width = glyph->fBounds.width(); + int height = glyph->fBounds.height(); + bool useDistanceField = + (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)); + int pad = useDistanceField ? 2 * SK_DistanceFieldPad : 0; + int plotWidth = (kA8_GrMaskFormat == glyph->fMaskFormat) ? GR_FONT_ATLAS_A8_PLOT_WIDTH + : GR_FONT_ATLAS_PLOT_WIDTH; + if (width + pad > plotWidth) { + return true; + } + if (height + pad > GR_FONT_ATLAS_PLOT_HEIGHT) { + return true; + } + + return false; +} + +bool GrBatchTextStrike::addGlyphToAtlas(GrBatchTarget* batchTarget, GrGlyph* glyph, + GrFontScaler* scaler) { + SkASSERT(glyph); + SkASSERT(scaler); + SkASSERT(fCache.find(glyph->fPackedID)); + SkASSERT(NULL == glyph->fPlot); + + SkAutoUnref ar(SkSafeRef(scaler)); + + int bytesPerPixel = GrMaskFormatBytesPerPixel(glyph->fMaskFormat); + + size_t size = glyph->fBounds.area() * bytesPerPixel; + GrAutoMalloc<1024> storage(size); + + if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)) { + if (!scaler->getPackedGlyphDFImage(glyph->fPackedID, glyph->width(), + glyph->height(), + storage.get())) { + return false; + } + } else { + if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(), + glyph->height(), + glyph->width() * bytesPerPixel, + storage.get())) { + return false; + } + } + + bool success = fBatchFontCache->addToAtlas(this, &glyph->fID, batchTarget, glyph->fMaskFormat, + glyph->width(), glyph->height(), + storage.get(), &glyph->fAtlasLocation); + if (success) { + fAtlasedGlyphs++; + } + return success; +} diff --git a/src/gpu/GrBatchFontCache.h b/src/gpu/GrBatchFontCache.h new file mode 100644 index 0000000000..6300fbe212 --- /dev/null +++ b/src/gpu/GrBatchFontCache.h @@ -0,0 +1,137 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrBatchFontCache_DEFINED +#define GrBatchFontCache_DEFINED + +#include "GrBatchAtlas.h" +#include "GrDrawTarget.h" +#include "GrFontScaler.h" +#include "GrGlyph.h" +#include "SkTDynamicHash.h" +#include "SkVarAlloc.h" + +class GrBatchFontCache; +class GrBatchTarget; +class GrGpu; + +/** + * The GrBatchTextStrike manages a pool of CPU backing memory for Glyph Masks. This backing memory + * is abstracted by GrGlyph, and indexed by a PackedID and GrFontScaler. The GrFontScaler is what + * actually creates the mask. + */ +class GrBatchTextStrike { +public: + GrBatchTextStrike(GrBatchFontCache*, const GrFontDescKey* fontScalerKey); + ~GrBatchTextStrike(); + + const GrFontDescKey* getFontScalerKey() const { return fFontScalerKey; } + GrBatchFontCache* getBatchFontCache() const { return fBatchFontCache; } + + inline GrGlyph* getGlyph(GrGlyph::PackedID packed, GrFontScaler* scaler) { + GrGlyph* glyph = fCache.find(packed); + if (NULL == glyph) { + glyph = this->generateGlyph(packed, scaler); + } + return glyph; + } + + // returns true if glyph (or glyph+padding for distance field) + // is too large to ever fit in texture atlas subregions (GrPlots) + bool glyphTooLargeForAtlas(GrGlyph*); + // returns true if glyph successfully added to texture atlas, false otherwise + bool addGlyphToAtlas(GrBatchTarget*, GrGlyph*, GrFontScaler*); + + // testing + int countGlyphs() const { return fCache.count(); } + + // remove any references to this plot + void removeID(GrBatchAtlas::AtlasID); + + static const GrFontDescKey& GetKey(const GrBatchTextStrike& ts) { + return *(ts.fFontScalerKey); + } + static uint32_t Hash(const GrFontDescKey& key) { + return key.getHash(); + } + +private: + SkTDynamicHash<GrGlyph, GrGlyph::PackedID> fCache; + SkAutoTUnref<const GrFontDescKey> fFontScalerKey; + SkVarAlloc fPool; + + GrBatchFontCache* fBatchFontCache; + int fAtlasedGlyphs; + + GrGlyph* generateGlyph(GrGlyph::PackedID packed, GrFontScaler* scaler); + + friend class GrBatchFontCache; +}; + +/* + * GrBatchFontCache manages strikes which are indexed by a GrFontScaler. These strikes can then be + * used to individual Glyph Masks. The GrBatchFontCache also manages GrBatchAtlases, though this is + * more or less transparent to the client(aside from atlasGeneration, described below) + */ +class GrBatchFontCache { +public: + GrBatchFontCache(); + ~GrBatchFontCache(); + + // Initializes the GrBatchFontCache on the owning GrContext + void init(GrContext*); + + inline GrBatchTextStrike* getStrike(GrFontScaler* scaler) { + + GrBatchTextStrike* strike = fCache.find(*(scaler->getKey())); + if (NULL == strike) { + strike = this->generateStrike(scaler); + } + return strike; + } + + bool hasGlyph(GrGlyph* glyph); + + // To ensure the GrBatchAtlas does not evict the Glyph Mask from its texture backing store, + // the client must pass in the currentToken from the GrBatchTarget along with the GrGlyph + void setGlyphRefToken(GrGlyph*, GrBatchAtlas::BatchToken); + + // add to texture atlas that matches this format + bool addToAtlas(GrBatchTextStrike*, GrBatchAtlas::AtlasID*, GrBatchTarget*, + GrMaskFormat, int width, int height, const void* image, + SkIPoint16* loc); + + // Some clients may wish to verify the integrity of the texture backing store of the + // GrBatchAtlas. The atlasGeneration returned below is a monitonically increasing number which + // changes everytime something is removed from the texture backing store. + uint64_t atlasGeneration(GrMaskFormat) const; + + void freeAll(); + + GrTexture* getTexture(GrMaskFormat); + GrPixelConfig getPixelConfig(GrMaskFormat) const; + + void dump() const; + +private: + // There is a 1:1 mapping between GrMaskFormats and atlas indices + static int MaskFormatToAtlasIndex(GrMaskFormat); + static GrMaskFormat AtlasIndexToMaskFormat(int atlasIndex); + + GrBatchTextStrike* generateStrike(GrFontScaler*); + + inline GrBatchAtlas* getAtlas(GrMaskFormat) const; + + static void HandleEviction(GrBatchAtlas::AtlasID, void*); + + SkTDynamicHash<GrBatchTextStrike, GrFontDescKey> fCache; + + GrBatchAtlas* fAtlases[kMaskFormatCount]; + GrBatchTextStrike* fPreserveStrike; +}; + +#endif diff --git a/src/gpu/GrBitmapTextContext.cpp b/src/gpu/GrBitmapTextContext.cpp index dd01e7d2b8..b21d7ea906 100755 --- a/src/gpu/GrBitmapTextContext.cpp +++ b/src/gpu/GrBitmapTextContext.cpp @@ -4,9 +4,12 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ - #include "GrBitmapTextContext.h" + #include "GrAtlas.h" +#include "GrBatch.h" +#include "GrBatchFontCache.h" +#include "GrBatchTarget.h" #include "GrDefaultGeoProcFactory.h" #include "GrDrawTarget.h" #include "GrFontCache.h" @@ -18,6 +21,7 @@ #include "SkAutoKern.h" #include "SkColorPriv.h" #include "SkDraw.h" +#include "SkDrawFilter.h" #include "SkDrawProcs.h" #include "SkGlyphCache.h" #include "SkGpuDevice.h" @@ -25,6 +29,7 @@ #include "SkPath.h" #include "SkRTConf.h" #include "SkStrokeRec.h" +#include "SkTextBlob.h" #include "SkTextMapStateProc.h" #include "effects/GrBitmapTextGeoProc.h" @@ -45,6 +50,934 @@ static const int kVerticesPerGlyph = 4; static const int kIndicesPerGlyph = 6; }; +// TODO +// More tests +// move to SkCache +// handle textblobs where the whole run is larger than the cache size +// TODO implement micro speedy hash map for fast refing of glyphs + +GrBitmapTextContextB::GrBitmapTextContextB(GrContext* context, + SkGpuDevice* gpuDevice, + const SkDeviceProperties& properties) + : INHERITED(context, gpuDevice, properties) { + fCurrStrike = NULL; +} + +void GrBitmapTextContextB::ClearCacheEntry(uint32_t key, BitmapTextBlob** blob) { + (*blob)->unref(); +} + +GrBitmapTextContextB::~GrBitmapTextContextB() { + fCache.foreach(&GrBitmapTextContextB::ClearCacheEntry); +} + +GrBitmapTextContextB* GrBitmapTextContextB::Create(GrContext* context, + SkGpuDevice* gpuDevice, + const SkDeviceProperties& props) { + return SkNEW_ARGS(GrBitmapTextContextB, (context, gpuDevice, props)); +} + +bool GrBitmapTextContextB::canDraw(const GrRenderTarget*, + const GrClip&, + const GrPaint&, + const SkPaint& skPaint, + const SkMatrix& viewMatrix) { + return !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix); +} + +inline void GrBitmapTextContextB::init(GrRenderTarget* rt, const GrClip& clip, + const GrPaint& paint, const SkPaint& skPaint, + const SkIRect& regionClipBounds) { + INHERITED::init(rt, clip, paint, skPaint, regionClipBounds); + + fCurrStrike = NULL; +} + +bool GrBitmapTextContextB::MustRegenerateBlob(const BitmapTextBlob& blob, const SkPaint& paint, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { + // We always regenerate blobs with patheffects or mask filters we could cache these + // TODO find some way to cache the maskfilter / patheffects on the textblob + return !blob.fViewMatrix.cheapEqualTo(viewMatrix) || blob.fX != x || blob.fY != y || + paint.getMaskFilter() || paint.getPathEffect() || paint.getStyle() != blob.fStyle; +} + +void GrBitmapTextContextB::drawTextBlob(GrRenderTarget* rt, const GrClip& clip, + const SkPaint& skPaint, const SkMatrix& viewMatrix, + const SkTextBlob* blob, SkScalar x, SkScalar y, + SkDrawFilter* drawFilter, const SkIRect& clipBounds) { + BitmapTextBlob* cacheBlob; + BitmapTextBlob** foundBlob = fCache.find(blob->uniqueID()); + + SkIRect clipRect; + clip.getConservativeBounds(rt->width(), rt->height(), &clipRect); + + if (foundBlob) { + cacheBlob = *foundBlob; + if (MustRegenerateBlob(*cacheBlob, skPaint, viewMatrix, x, y)) { + // We can get away with reusing the blob if there are no outstanding refs on it. + // However, we still have to reset all of the runs. + if (!cacheBlob->unique()) { + cacheBlob->unref(); + cacheBlob = SkNEW(BitmapTextBlob); + fCache.set(blob->uniqueID(), cacheBlob); + } + this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, + clipRect); + } + } else { + cacheBlob = SkNEW(BitmapTextBlob); + fCache.set(blob->uniqueID(), cacheBlob); + this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, clipRect); + } + + // Though for the time being runs in the textblob can override the paint, they only touch font + // info. + GrPaint grPaint; + SkPaint2GrPaintShader(fContext, rt, skPaint, viewMatrix, true, &grPaint); + + this->flush(fContext->getTextTarget(), cacheBlob, rt, grPaint, clip, viewMatrix, + fSkPaint.getAlpha()); +} + +void GrBitmapTextContextB::regenerateTextBlob(BitmapTextBlob* cacheBlob, + const SkPaint& skPaint, const SkMatrix& viewMatrix, + const SkTextBlob* blob, SkScalar x, SkScalar y, + SkDrawFilter* drawFilter, const SkIRect& clipRect) { + cacheBlob->fViewMatrix = viewMatrix; + cacheBlob->fX = x; + cacheBlob->fY = y; + cacheBlob->fStyle = skPaint.getStyle(); + cacheBlob->fRuns.reset(blob->fRunCount); + + // Regenerate textblob + SkPaint runPaint = skPaint; + SkTextBlob::RunIterator it(blob); + for (int run = 0; !it.done(); it.next(), run++) { + size_t textLen = it.glyphCount() * sizeof(uint16_t); + const SkPoint& offset = it.offset(); + // applyFontToPaint() always overwrites the exact same attributes, + // so it is safe to not re-seed the paint for this reason. + it.applyFontToPaint(&runPaint); + + if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) { + // A false return from filter() means we should abort the current draw. + runPaint = skPaint; + continue; + } + + runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint)); + + switch (it.positioning()) { + case SkTextBlob::kDefault_Positioning: + this->internalDrawText(cacheBlob, run, runPaint, viewMatrix, + (const char *)it.glyphs(), textLen, + x + offset.x(), y + offset.y(), clipRect); + break; + case SkTextBlob::kHorizontal_Positioning: + this->internalDrawPosText(cacheBlob, run, runPaint, viewMatrix, + (const char*)it.glyphs(), textLen, it.pos(), 1, + SkPoint::Make(x, y + offset.y()), clipRect); + break; + case SkTextBlob::kFull_Positioning: + this->internalDrawPosText(cacheBlob, run, runPaint, viewMatrix, + (const char*)it.glyphs(), textLen, it.pos(), 2, + SkPoint::Make(x, y), clipRect); + break; + } + + if (drawFilter) { + // A draw filter may change the paint arbitrarily, so we must re-seed in this case. + runPaint = skPaint; + } + } +} + +void GrBitmapTextContextB::onDrawText(GrRenderTarget* rt, const GrClip& clip, + const GrPaint& paint, const SkPaint& skPaint, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, + SkScalar x, SkScalar y, const SkIRect& regionClipBounds) { + SkAutoTUnref<BitmapTextBlob> blob(SkNEW(BitmapTextBlob)); + blob->fViewMatrix = viewMatrix; + blob->fX = x; + blob->fY = y; + blob->fStyle = skPaint.getStyle(); + blob->fRuns.push_back(); + + SkIRect clipRect; + clip.getConservativeBounds(rt->width(), rt->height(), &clipRect); + this->internalDrawText(blob, 0, skPaint, viewMatrix, text, byteLength, x, y, clipRect); + this->flush(fContext->getTextTarget(), blob, rt, paint, clip, viewMatrix, skPaint.getAlpha()); +} + +void GrBitmapTextContextB::internalDrawText(BitmapTextBlob* blob, int runIndex, + const SkPaint& skPaint, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, + SkScalar x, SkScalar y, const SkIRect& clipRect) { + SkASSERT(byteLength == 0 || text != NULL); + + // nothing to draw + if (text == NULL || byteLength == 0) { + return; + } + + fCurrStrike = NULL; + SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc(); + + // Get GrFontScaler from cache + BitmapTextBlob::Run& run = blob->fRuns[runIndex]; + run.fDescriptor.reset(skPaint.getScalerContextDescriptor(&fDeviceProperties, &viewMatrix, + false)); + run.fTypeface.reset(SkSafeRef(skPaint.getTypeface())); + const SkDescriptor* desc = reinterpret_cast<const SkDescriptor*>(run.fDescriptor->data()); + SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface, desc); + GrFontScaler* fontScaler = GetGrFontScaler(cache); + + // transform our starting point + { + SkPoint loc; + viewMatrix.mapXY(x, y, &loc); + x = loc.fX; + y = loc.fY; + } + + // need to measure first + if (skPaint.getTextAlign() != SkPaint::kLeft_Align) { + SkVector stopVector; + MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector); + + SkScalar stopX = stopVector.fX; + SkScalar stopY = stopVector.fY; + + if (skPaint.getTextAlign() == SkPaint::kCenter_Align) { + stopX = SkScalarHalf(stopX); + stopY = SkScalarHalf(stopY); + } + x -= stopX; + y -= stopY; + } + + const char* stop = text + byteLength; + + SkAutoKern autokern; + + SkFixed fxMask = ~0; + SkFixed fyMask = ~0; + SkScalar halfSampleX, halfSampleY; + if (cache->isSubpixel()) { + halfSampleX = halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound); + SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix); + if (kX_SkAxisAlignment == baseline) { + fyMask = 0; + halfSampleY = SK_ScalarHalf; + } else if (kY_SkAxisAlignment == baseline) { + fxMask = 0; + halfSampleX = SK_ScalarHalf; + } + } else { + halfSampleX = halfSampleY = SK_ScalarHalf; + } + + Sk48Dot16 fx = SkScalarTo48Dot16(x + halfSampleX); + Sk48Dot16 fy = SkScalarTo48Dot16(y + halfSampleY); + + while (text < stop) { + const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask); + + fx += autokern.adjust(glyph); + + if (glyph.fWidth) { + this->appendGlyph(blob, + runIndex, + GrGlyph::Pack(glyph.getGlyphID(), + glyph.getSubXFixed(), + glyph.getSubYFixed(), + GrGlyph::kCoverage_MaskStyle), + Sk48Dot16FloorToInt(fx), + Sk48Dot16FloorToInt(fy), + fontScaler, + clipRect); + } + + fx += glyph.fAdvanceX; + fy += glyph.fAdvanceY; + } + + SkGlyphCache::AttachCache(cache); +} + +void GrBitmapTextContextB::onDrawPosText(GrRenderTarget* rt, const GrClip& clip, + const GrPaint& paint, const SkPaint& skPaint, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, + const SkScalar pos[], int scalarsPerPosition, + const SkPoint& offset, const SkIRect& regionClipBounds) { + SkAutoTUnref<BitmapTextBlob> blob(SkNEW(BitmapTextBlob)); + blob->fStyle = skPaint.getStyle(); + blob->fRuns.push_back(); + blob->fViewMatrix = viewMatrix; + + SkIRect clipRect; + clip.getConservativeBounds(rt->width(), rt->height(), &clipRect); + this->internalDrawPosText(blob, 0, skPaint, viewMatrix, text, byteLength, pos, + scalarsPerPosition, offset, clipRect); + this->flush(fContext->getTextTarget(), blob, rt, paint, clip, viewMatrix, fSkPaint.getAlpha()); +} + +void GrBitmapTextContextB::internalDrawPosText(BitmapTextBlob* blob, int runIndex, + const SkPaint& skPaint, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, + const SkScalar pos[], int scalarsPerPosition, + const SkPoint& offset, const SkIRect& clipRect) { + SkASSERT(byteLength == 0 || text != NULL); + SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); + + // nothing to draw + if (text == NULL || byteLength == 0) { + return; + } + + fCurrStrike = NULL; + SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc(); + + // Get GrFontScaler from cache + BitmapTextBlob::Run& run = blob->fRuns[runIndex]; + run.fDescriptor.reset(skPaint.getScalerContextDescriptor(&fDeviceProperties, &viewMatrix, + false)); + run.fTypeface.reset(SkSafeRef(skPaint.getTypeface())); + const SkDescriptor* desc = reinterpret_cast<const SkDescriptor*>(run.fDescriptor->data()); + SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface, desc); + GrFontScaler* fontScaler = GetGrFontScaler(cache); + + const char* stop = text + byteLength; + SkTextAlignProc alignProc(skPaint.getTextAlign()); + SkTextMapStateProc tmsProc(viewMatrix, offset, scalarsPerPosition); + SkScalar halfSampleX = 0, halfSampleY = 0; + + if (cache->isSubpixel()) { + // maybe we should skip the rounding if linearText is set + SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix); + + SkFixed fxMask = ~0; + SkFixed fyMask = ~0; + if (kX_SkAxisAlignment == baseline) { + fyMask = 0; + halfSampleY = SK_ScalarHalf; + } else if (kY_SkAxisAlignment == baseline) { + fxMask = 0; + halfSampleX = SK_ScalarHalf; + } + + if (SkPaint::kLeft_Align == skPaint.getTextAlign()) { + while (text < stop) { + SkPoint tmsLoc; + tmsProc(pos, &tmsLoc); + Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + halfSampleX); + Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + halfSampleY); + + const SkGlyph& glyph = glyphCacheProc(cache, &text, + fx & fxMask, fy & fyMask); + + if (glyph.fWidth) { + this->appendGlyph(blob, + runIndex, + GrGlyph::Pack(glyph.getGlyphID(), + glyph.getSubXFixed(), + glyph.getSubYFixed(), + GrGlyph::kCoverage_MaskStyle), + Sk48Dot16FloorToInt(fx), + Sk48Dot16FloorToInt(fy), + fontScaler, + clipRect); + } + pos += scalarsPerPosition; + } + } else { + while (text < stop) { + const char* currentText = text; + const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0); + + if (metricGlyph.fWidth) { + SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;) + SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;) + SkPoint tmsLoc; + tmsProc(pos, &tmsLoc); + SkPoint alignLoc; + alignProc(tmsLoc, metricGlyph, &alignLoc); + + Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + halfSampleX); + Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + halfSampleY); + + // have to call again, now that we've been "aligned" + const SkGlyph& glyph = glyphCacheProc(cache, ¤tText, + fx & fxMask, fy & fyMask); + // the assumption is that the metrics haven't changed + SkASSERT(prevAdvX == glyph.fAdvanceX); + SkASSERT(prevAdvY == glyph.fAdvanceY); + SkASSERT(glyph.fWidth); + + this->appendGlyph(blob, + runIndex, + GrGlyph::Pack(glyph.getGlyphID(), + glyph.getSubXFixed(), + glyph.getSubYFixed(), + GrGlyph::kCoverage_MaskStyle), + Sk48Dot16FloorToInt(fx), + Sk48Dot16FloorToInt(fy), + fontScaler, + clipRect); + } + pos += scalarsPerPosition; + } + } + } else { // not subpixel + + if (SkPaint::kLeft_Align == skPaint.getTextAlign()) { + while (text < stop) { + // the last 2 parameters are ignored + const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); + + if (glyph.fWidth) { + SkPoint tmsLoc; + tmsProc(pos, &tmsLoc); + + Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + SK_ScalarHalf); //halfSampleX; + Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + SK_ScalarHalf); //halfSampleY; + this->appendGlyph(blob, + runIndex, + GrGlyph::Pack(glyph.getGlyphID(), + glyph.getSubXFixed(), + glyph.getSubYFixed(), + GrGlyph::kCoverage_MaskStyle), + Sk48Dot16FloorToInt(fx), + Sk48Dot16FloorToInt(fy), + fontScaler, + clipRect); + } + pos += scalarsPerPosition; + } + } else { + while (text < stop) { + // the last 2 parameters are ignored + const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); + + if (glyph.fWidth) { + SkPoint tmsLoc; + tmsProc(pos, &tmsLoc); + + SkPoint alignLoc; + alignProc(tmsLoc, glyph, &alignLoc); + + Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + SK_ScalarHalf); //halfSampleX; + Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + SK_ScalarHalf); //halfSampleY; + this->appendGlyph(blob, + runIndex, + GrGlyph::Pack(glyph.getGlyphID(), + glyph.getSubXFixed(), + glyph.getSubYFixed(), + GrGlyph::kCoverage_MaskStyle), + Sk48Dot16FloorToInt(fx), + Sk48Dot16FloorToInt(fy), + fontScaler, + clipRect); + } + pos += scalarsPerPosition; + } + } + } + SkGlyphCache::AttachCache(cache); +} + +static size_t get_vertex_stride(GrMaskFormat maskFormat) { + switch (maskFormat) { + case kA8_GrMaskFormat: + return kGrayTextVASize; + case kARGB_GrMaskFormat: + return kColorTextVASize; + default: + return kLCDTextVASize; + } +} + +void GrBitmapTextContextB::appendGlyph(BitmapTextBlob* blob, int runIndex, GrGlyph::PackedID packed, + int vx, int vy, GrFontScaler* scaler, + const SkIRect& clipRect) { + if (NULL == fCurrStrike) { + fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler); + } + + GrGlyph* glyph = fCurrStrike->getGlyph(packed, scaler); + if (NULL == glyph || glyph->fBounds.isEmpty()) { + return; + } + + int x = vx + glyph->fBounds.fLeft; + int y = vy + glyph->fBounds.fTop; + + // keep them as ints until we've done the clip-test + int width = glyph->fBounds.width(); + int height = glyph->fBounds.height(); + + // check if we clipped out + if (clipRect.quickReject(x, y, x + width, y + height)) { + return; + } + + // If the glyph is too large we fall back to paths + if (fCurrStrike->glyphTooLargeForAtlas(glyph)) { + if (NULL == glyph->fPath) { + SkPath* path = SkNEW(SkPath); + if (!scaler->getGlyphPath(glyph->glyphID(), path)) { + // flag the glyph as being dead? + SkDELETE(path); + return; + } + glyph->fPath = path; + } + SkASSERT(glyph->fPath); + blob->fBigGlyphs.push_back(BitmapTextBlob::BigGlyph(*glyph->fPath, vx, vy)); + return; + } + GrMaskFormat format = glyph->fMaskFormat; + size_t vertexStride = get_vertex_stride(format); + + BitmapTextBlob::Run& run = blob->fRuns[runIndex]; + int glyphIdx = run.fInfos[format].fGlyphIDs.count(); + *run.fInfos[format].fGlyphIDs.append() = packed; + run.fInfos[format].fVertices.append(static_cast<int>(vertexStride * kVerticesPerGlyph)); + + SkRect r; + r.fLeft = SkIntToScalar(x); + r.fTop = SkIntToScalar(y); + r.fRight = r.fLeft + SkIntToScalar(width); + r.fBottom = r.fTop + SkIntToScalar(height); + + run.fVertexBounds.joinNonEmptyArg(r); + GrColor color = fPaint.getColor(); + run.fColor = color; + + intptr_t vertex = reinterpret_cast<intptr_t>(run.fInfos[format].fVertices.begin()); + vertex += vertexStride * glyphIdx * kVerticesPerGlyph; + + // V0 + SkPoint* position = reinterpret_cast<SkPoint*>(vertex); + position->set(r.fLeft, r.fTop); + if (kA8_GrMaskFormat == format) { + SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint)); + *colorPtr = color; + } + vertex += vertexStride; + + // V1 + position = reinterpret_cast<SkPoint*>(vertex); + position->set(r.fLeft, r.fBottom); + if (kA8_GrMaskFormat == format) { + SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint)); + *colorPtr = color; + } + vertex += vertexStride; + + // V2 + position = reinterpret_cast<SkPoint*>(vertex); + position->set(r.fRight, r.fBottom); + if (kA8_GrMaskFormat == format) { + SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint)); + *colorPtr = color; + } + vertex += vertexStride; + + // V3 + position = reinterpret_cast<SkPoint*>(vertex); + position->set(r.fRight, r.fTop); + if (kA8_GrMaskFormat == format) { + SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint)); + *colorPtr = color; + } +} + +class BitmapTextBatch : public GrBatch { +public: + typedef GrBitmapTextContextB::BitmapTextBlob Blob; + typedef Blob::Run Run; + typedef Run::PerFormatInfo TextInfo; + struct Geometry { + Geometry() {} + Geometry(const Geometry& geometry) + : fBlob(SkRef(geometry.fBlob.get())) + , fRun(geometry.fRun) + , fColor(geometry.fColor) {} + SkAutoTUnref<Blob> fBlob; + int fRun; + GrColor fColor; + }; + + static GrBatch* Create(const Geometry& geometry, GrColor color, GrMaskFormat maskFormat, + GrBatchFontCache* fontCache) { + return SkNEW_ARGS(BitmapTextBatch, (geometry, color, maskFormat, fontCache)); + } + + const char* name() const override { return "BitmapTextBatch"; } + + void getInvariantOutputColor(GrInitInvariantOutput* out) const override { + if (kARGB_GrMaskFormat == fMaskFormat) { + out->setUnknownFourComponents(); + } else { + out->setKnownFourComponents(fBatch.fColor); + } + } + + void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override { + if (kARGB_GrMaskFormat != fMaskFormat) { + if (GrPixelConfigIsAlphaOnly(fPixelConfig)) { + out->setUnknownSingleComponent(); + } else if (GrPixelConfigIsOpaque(fPixelConfig)) { + out->setUnknownOpaqueFourComponents(); + out->setUsingLCDCoverage(); + } else { + out->setUnknownFourComponents(); + out->setUsingLCDCoverage(); + } + } else { + out->setKnownSingleComponent(0xff); + } + } + + void initBatchTracker(const GrPipelineInfo& init) override { + // Handle any color overrides + if (init.fColorIgnored) { + fBatch.fColor = GrColor_ILLEGAL; + } else if (GrColor_ILLEGAL != init.fOverrideColor) { + fBatch.fColor = init.fOverrideColor; + } + + // setup batch properties + fBatch.fColorIgnored = init.fColorIgnored; + fBatch.fUsesLocalCoords = init.fUsesLocalCoords; + fBatch.fCoverageIgnored = init.fCoverageIgnored; + } + + void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override { + // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix. + // TODO actually only invert if we don't have RGBA + SkMatrix localMatrix; + if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) { + SkDebugf("Cannot invert viewmatrix\n"); + return; + } + + GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode); + // This will be ignored in the non A8 case + bool opaqueVertexColors = GrColorIsOpaque(this->color()); + SkAutoTUnref<const GrGeometryProcessor> gp( + GrBitmapTextGeoProc::Create(this->color(), + fFontCache->getTexture(fMaskFormat), + params, + fMaskFormat, + opaqueVertexColors, + localMatrix)); + + size_t vertexStride = gp->getVertexStride(); + SkASSERT(vertexStride == get_vertex_stride(fMaskFormat)); + + this->initDraw(batchTarget, gp, pipeline); + + int glyphCount = this->numGlyphs(); + int instanceCount = fGeoData.count(); + const GrVertexBuffer* vertexBuffer; + int firstVertex; + + void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride, + glyphCount * kVerticesPerGlyph, + &vertexBuffer, + &firstVertex); + if (!vertices) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices); + + // setup drawinfo + const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer(); + int maxInstancesPerDraw = quadIndexBuffer->maxQuads(); + + GrDrawTarget::DrawInfo drawInfo; + drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType); + drawInfo.setStartVertex(0); + drawInfo.setStartIndex(0); + drawInfo.setVerticesPerInstance(kVerticesPerGlyph); + drawInfo.setIndicesPerInstance(kIndicesPerGlyph); + drawInfo.adjustStartVertex(firstVertex); + drawInfo.setVertexBuffer(vertexBuffer); + drawInfo.setIndexBuffer(quadIndexBuffer); + + int instancesToFlush = 0; + for (int i = 0; i < instanceCount; i++) { + Geometry& args = fGeoData[i]; + Blob* blob = args.fBlob; + Run& run = blob->fRuns[args.fRun]; + TextInfo& info = run.fInfos[fMaskFormat]; + + uint64_t currentAtlasGen = fFontCache->atlasGeneration(fMaskFormat); + bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen; + bool regenerateColors = kA8_GrMaskFormat == fMaskFormat && run.fColor != args.fColor; + int glyphCount = info.fGlyphIDs.count(); + + // We regenerate both texture coords and colors in the blob itself, and update the + // atlas generation. If we don't end up purging any unused plots, we can avoid + // regenerating the coords. We could take a finer grained approach to updating texture + // coords but its not clear if the extra bookkeeping would offset any gains. + // To avoid looping over the glyphs twice, we do one loop and conditionally update color + // or coords as needed. One final note, if we have to break a run for an atlas eviction + // then we can't really trust the atlas has all of the correct data. Atlas evictions + // should be pretty rare, so we just always regenerate in those cases + if (regenerateTextureCoords || regenerateColors) { + // first regenerate texture coordinates / colors if need be + const SkDescriptor* desc = NULL; + SkGlyphCache* cache = NULL; + GrFontScaler* scaler = NULL; + GrBatchTextStrike* strike = NULL; + bool brokenRun = false; + if (regenerateTextureCoords) { + desc = reinterpret_cast<const SkDescriptor*>(run.fDescriptor->data()); + cache = SkGlyphCache::DetachCache(run.fTypeface, desc); + scaler = GrTextContext::GetGrFontScaler(cache); + strike = fFontCache->getStrike(scaler); + } + for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) { + GrGlyph::PackedID glyphID = info.fGlyphIDs[glyphIdx]; + + if (regenerateTextureCoords) { + // Upload the glyph only if needed + GrGlyph* glyph = strike->getGlyph(glyphID, scaler); + SkASSERT(glyph); + + if (!fFontCache->hasGlyph(glyph) && + !strike->addGlyphToAtlas(batchTarget, glyph, scaler)) { + this->flush(batchTarget, &drawInfo, instancesToFlush, + maxInstancesPerDraw); + this->initDraw(batchTarget, gp, pipeline); + instancesToFlush = 0; + brokenRun = glyphIdx > 0; + + SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(batchTarget, glyph, + scaler); + SkASSERT(success); + } + + fFontCache->setGlyphRefToken(glyph, batchTarget->currentToken()); + + // Texture coords are the last vertex attribute so we get a pointer to the + // first one and then map with stride in regenerateTextureCoords + intptr_t vertex = reinterpret_cast<intptr_t>(info.fVertices.begin()); + vertex += vertexStride * glyphIdx * kVerticesPerGlyph; + vertex += vertexStride - sizeof(SkIPoint16); + + this->regenerateTextureCoords(glyph, vertex, vertexStride); + } + + if (regenerateColors) { + intptr_t vertex = reinterpret_cast<intptr_t>(info.fVertices.begin()); + vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint); + this->regenerateColors(vertex, vertexStride, args.fColor); + } + + instancesToFlush++; + } + + if (regenerateTextureCoords) { + SkGlyphCache::AttachCache(cache); + info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration : + fFontCache->atlasGeneration(fMaskFormat); + } + } else { + instancesToFlush += glyphCount; + } + + // now copy all vertices + int byteCount = info.fVertices.count(); + memcpy(currVertex, info.fVertices.begin(), byteCount); + + currVertex += byteCount; + } + + this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw); + } + + SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } + +private: + BitmapTextBatch(const Geometry& geometry, GrColor color, GrMaskFormat maskFormat, + GrBatchFontCache* fontCache) + : fMaskFormat(maskFormat) + , fPixelConfig(fontCache->getPixelConfig(maskFormat)) + , fFontCache(fontCache) { + this->initClassID<BitmapTextBatch>(); + fGeoData.push_back(geometry); + fBatch.fColor = color; + fBatch.fViewMatrix = geometry.fBlob->fViewMatrix; + int numGlyphs = geometry.fBlob->fRuns[geometry.fRun].fInfos[maskFormat].fGlyphIDs.count(); + fBatch.fNumGlyphs = numGlyphs; + } + + void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride) { + int width = glyph->fBounds.width(); + int height = glyph->fBounds.height(); + int u0 = glyph->fAtlasLocation.fX; + int v0 = glyph->fAtlasLocation.fY; + int u1 = u0 + width; + int v1 = v0 + height; + + // we assume texture coords are the last vertex attribute, this is a bit fragile. + // TODO pass in this offset or something + SkIPoint16* textureCoords; + // V0 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u0, v0); + vertex += vertexStride; + + // V1 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u0, v1); + vertex += vertexStride; + + // V2 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u1, v1); + vertex += vertexStride; + + // V3 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u1, v0); + } + + void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) { + for (int i = 0; i < kVerticesPerGlyph; i++) { + SkColor* vcolor = reinterpret_cast<SkColor*>(vertex); + *vcolor = color; + vertex += vertexStride; + } + } + + void initDraw(GrBatchTarget* batchTarget, + const GrGeometryProcessor* gp, + const GrPipeline* pipeline) { + batchTarget->initDraw(gp, pipeline); + + // TODO remove this when batch is everywhere + GrPipelineInfo init; + init.fColorIgnored = fBatch.fColorIgnored; + init.fOverrideColor = GrColor_ILLEGAL; + init.fCoverageIgnored = fBatch.fCoverageIgnored; + init.fUsesLocalCoords = this->usesLocalCoords(); + gp->initBatchTracker(batchTarget->currentBatchTracker(), init); + } + + void flush(GrBatchTarget* batchTarget, + GrDrawTarget::DrawInfo* drawInfo, + int instanceCount, + int maxInstancesPerDraw) { + while (instanceCount) { + drawInfo->setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw)); + drawInfo->setVertexCount(drawInfo->instanceCount() * drawInfo->verticesPerInstance()); + drawInfo->setIndexCount(drawInfo->instanceCount() * drawInfo->indicesPerInstance()); + + batchTarget->draw(*drawInfo); + + drawInfo->setStartVertex(drawInfo->startVertex() + drawInfo->vertexCount()); + instanceCount -= drawInfo->instanceCount(); + } + } + + GrColor color() const { return fBatch.fColor; } + const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; } + bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } + int numGlyphs() const { return fBatch.fNumGlyphs; } + + bool onCombineIfPossible(GrBatch* t) override { + BitmapTextBatch* that = t->cast<BitmapTextBatch>(); + + if (this->fMaskFormat != that->fMaskFormat) { + return false; + } + + if (this->fMaskFormat != kA8_GrMaskFormat && this->color() != that->color()) { + return false; + } + + if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return false; + } + + fBatch.fNumGlyphs += that->numGlyphs(); + fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin()); + return true; + } + + struct BatchTracker { + GrColor fColor; + SkMatrix fViewMatrix; + bool fUsesLocalCoords; + bool fColorIgnored; + bool fCoverageIgnored; + int fNumGlyphs; + }; + + BatchTracker fBatch; + SkSTArray<1, Geometry, true> fGeoData; + GrMaskFormat fMaskFormat; + GrPixelConfig fPixelConfig; + GrBatchFontCache* fFontCache; +}; + +void GrBitmapTextContextB::flushSubRun(GrDrawTarget* target, BitmapTextBlob* blob, int i, + GrPipelineBuilder* pipelineBuilder, GrMaskFormat format, + GrColor color, int paintAlpha) { + if (0 == blob->fRuns[i].fInfos[format].fGlyphIDs.count()) { + return; + } + + if (kARGB_GrMaskFormat == format) { + color = SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha); + } + + BitmapTextBatch::Geometry geometry; + geometry.fBlob.reset(SkRef(blob)); + geometry.fRun = i; + geometry.fColor = color; + SkAutoTUnref<GrBatch> batch(BitmapTextBatch::Create(geometry, color, format, + fContext->getBatchFontCache())); + + target->drawBatch(pipelineBuilder, batch, &blob->fRuns[i].fVertexBounds); +} + +void GrBitmapTextContextB::flush(GrDrawTarget* target, BitmapTextBlob* blob, GrRenderTarget* rt, + const GrPaint& paint, const GrClip& clip, + const SkMatrix& viewMatrix, int paintAlpha) { + GrPipelineBuilder pipelineBuilder; + pipelineBuilder.setFromPaint(paint, rt, clip); + + GrColor color = paint.getColor(); + for (int i = 0; i < blob->fRuns.count(); i++) { + this->flushSubRun(target, blob, i, &pipelineBuilder, kA8_GrMaskFormat, color, paintAlpha); + this->flushSubRun(target, blob, i, &pipelineBuilder, kA565_GrMaskFormat, color, paintAlpha); + this->flushSubRun(target, blob, i, &pipelineBuilder, kARGB_GrMaskFormat, color, paintAlpha); + } + + // Now flush big glyphs + for (int i = 0; i < blob->fBigGlyphs.count(); i++) { + BitmapTextBlob::BigGlyph& bigGlyph = blob->fBigGlyphs[i]; + SkMatrix translate; + translate.setTranslate(SkIntToScalar(bigGlyph.fVx), SkIntToScalar(bigGlyph.fVy)); + SkPath tmpPath(bigGlyph.fPath); + tmpPath.transform(translate); + GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle); + fContext->drawPath(rt, clip, paint, SkMatrix::I(), tmpPath, strokeInfo); + } +} + GrBitmapTextContext::GrBitmapTextContext(GrContext* context, SkGpuDevice* gpuDevice, const SkDeviceProperties& properties) @@ -352,17 +1285,6 @@ void GrBitmapTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip, this->finish(); } -static size_t get_vertex_stride(GrMaskFormat maskFormat) { - switch (maskFormat) { - case kA8_GrMaskFormat: - return kGrayTextVASize; - case kARGB_GrMaskFormat: - return kColorTextVASize; - default: - return kLCDTextVASize; - } -} - static void* alloc_vertices(GrDrawTarget* drawTarget, int numVertices, GrMaskFormat maskFormat) { @@ -386,33 +1308,33 @@ inline bool GrBitmapTextContext::uploadGlyph(GrGlyph* glyph, GrFontScaler* scale if (fStrike->addGlyphToAtlas(glyph, scaler)) { return true; } - + // try to clear out an unused plot before we flush if (fContext->getFontCache()->freeUnusedPlot(fStrike, glyph) && fStrike->addGlyphToAtlas(glyph, scaler)) { return true; } - + if (c_DumpFontCache) { #ifdef SK_DEVELOPER fContext->getFontCache()->dump(); #endif } - + // before we purge the cache, we must flush any accumulated draws this->flush(); fContext->flush(); - + // we should have an unused plot now if (fContext->getFontCache()->freeUnusedPlot(fStrike, glyph) && fStrike->addGlyphToAtlas(glyph, scaler)) { return true; } - + // we should never get here SkASSERT(false); } - + return false; } @@ -638,4 +1560,3 @@ inline void GrBitmapTextContext::finish() { GrTextContext::finish(); } - diff --git a/src/gpu/GrBitmapTextContext.h b/src/gpu/GrBitmapTextContext.h index f843fc76c2..3bf3a941c6 100644 --- a/src/gpu/GrBitmapTextContext.h +++ b/src/gpu/GrBitmapTextContext.h @@ -11,6 +11,127 @@ #include "GrTextContext.h" #include "GrGeometryProcessor.h" +#include "SkTHash.h" + +class GrBatchTextStrike; +class GrPipelineBuilder; + +/* + * This class implements GrTextContext using standard bitmap fonts, and can also process textblobs. + * TODO replace GrBitmapTextContext + */ +class GrBitmapTextContextB : public GrTextContext { +public: + static GrBitmapTextContextB* Create(GrContext*, SkGpuDevice*, const SkDeviceProperties&); + + virtual ~GrBitmapTextContextB(); + +private: + GrBitmapTextContextB(GrContext*, SkGpuDevice*, const SkDeviceProperties&); + + bool canDraw(const GrRenderTarget*, const GrClip&, const GrPaint&, + const SkPaint&, const SkMatrix& viewMatrix) override; + + void onDrawText(GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&, + const SkMatrix& viewMatrix, const char text[], size_t byteLength, + SkScalar x, SkScalar y, const SkIRect& regionClipBounds) override; + void onDrawPosText(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; + void drawTextBlob(GrRenderTarget*, const GrClip&, const SkPaint&, + const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y, + SkDrawFilter*, const SkIRect& clipBounds) override; + + void init(GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&, + const SkIRect& regionClipBounds); + + /* + * A BitmapTextBlob contains a fully processed SkTextBlob, suitable for nearly immediate drawing + * on the GPU. These are initially created with valid positions and colors, but invalid + * texture coordinates. The BitmapTextBlob itself has a few Blob-wide properties, and also + * consists of a number of runs. Runs inside a blob are flushed individually so they can be + * reordered. + * + * The only thing(aside from a memcopy) required to flush a BitmapTextBlob is to ensure that + * the GrAtlas will not evict anything the Blob needs. + * TODO this is currently a bug + */ + struct BitmapTextBlob : public SkRefCnt { + // Each Run inside of the blob can have its texture coordinates regenerated if required. + // To determine if regeneration is necessary, fAtlasGeneration is used. If there have been + // any evictions inside of the atlas, then we will simply regenerate Runs. We could track + // this at a more fine grained level, but its not clear if this is worth it, as evictions + // should be fairly rare. + // One additional point, each run can contain glyphs with any of the three mask formats. + // We maintain separate arrays for each format type, and flush them separately. In practice + // most of the time a run will have the same format type + struct Run { + Run() : fColor(GrColor_ILLEGAL) { fVertexBounds.setLargestInverted(); } + struct PerFormatInfo { + PerFormatInfo() : fAtlasGeneration(GrBatchAtlas::kInvalidAtlasGeneration) {} + SkTDArray<unsigned char> fVertices; + SkTDArray<GrGlyph::PackedID> fGlyphIDs; + uint64_t fAtlasGeneration; + }; + SkAutoTUnref<const SkData> fDescriptor; + SkAutoTUnref<SkTypeface> fTypeface; + PerFormatInfo fInfos[kMaskFormatCount]; + SkRect fVertexBounds; + GrColor fColor; + }; + SkSTArray<1, Run, true> fRuns; + struct BigGlyph { + BigGlyph(const SkPath& path, int vx, int vy) : fPath(path), fVx(vx), fVy(vy) {} + SkPath fPath; + int fVx; + int fVy; + }; + SkTArray<BigGlyph> fBigGlyphs; + SkTextBlob* fBlob; + SkMatrix fViewMatrix; + SkScalar fX; + SkScalar fY; + SkPaint::Style fStyle; + + static uint32_t Hash(const uint32_t& key) { + return SkChecksum::Mix(key); + } + }; + + void appendGlyph(BitmapTextBlob*, int runIndex, GrGlyph::PackedID, int left, int top, + GrFontScaler*, const SkIRect& clipRect); + void flushSubRun(GrDrawTarget*, BitmapTextBlob*, int i, GrPipelineBuilder*, GrMaskFormat, + GrColor color, int paintAlpha); + void flush(GrDrawTarget*, BitmapTextBlob*, GrRenderTarget*, const GrPaint&, const GrClip&, + const SkMatrix& viewMatrix, int paintAlpha); + + void internalDrawText(BitmapTextBlob*, int runIndex, const SkPaint&, + const SkMatrix& viewMatrix, const char text[], size_t byteLength, + SkScalar x, SkScalar y, const SkIRect& clipRect); + void internalDrawPosText(BitmapTextBlob*, int runIndex, const SkPaint&, + const SkMatrix& viewMatrix, + const char text[], size_t byteLength, + const SkScalar pos[], int scalarsPerPosition, + const SkPoint& offset, const SkIRect& clipRect); + + static inline bool MustRegenerateBlob(const BitmapTextBlob&, const SkPaint&, + const SkMatrix& viewMatrix, SkScalar x, SkScalar y); + void regenerateTextBlob(BitmapTextBlob* bmp, const SkPaint& skPaint, const SkMatrix& viewMatrix, + const SkTextBlob* blob, SkScalar x, SkScalar y, + SkDrawFilter* drawFilter, const SkIRect& clipRect); + + GrBatchTextStrike* fCurrStrike; + + // TODO use real cache + static void ClearCacheEntry(uint32_t key, BitmapTextBlob**); + SkTHashMap<uint32_t, BitmapTextBlob*, BitmapTextBlob::Hash> fCache; + + friend class BitmapTextBatch; + + typedef GrTextContext INHERITED; +}; class GrTextStrike; diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 4c7f3eabe0..a1f1c253a7 100755 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -10,7 +10,9 @@ #include "GrAARectRenderer.h" #include "GrBatch.h" +#include "GrBatchFontCache.h" #include "GrBatchTarget.h" +#include "GrBitmapTextContext.h" #include "GrBufferAllocPool.h" #include "GrDefaultGeoProcFactory.h" #include "GrFontCache.h" @@ -94,6 +96,7 @@ GrContext::GrContext(const Options& opts) : fOptions(opts) { fPathRendererChain = NULL; fSoftwarePathRenderer = NULL; fResourceCache = NULL; + fBatchFontCache = NULL; fFontCache = NULL; fDrawBuffer = NULL; fDrawBufferVBAllocPool = NULL; @@ -129,6 +132,10 @@ void GrContext::initCommon() { fDidTestPMConversions = false; this->setupDrawBuffer(); + + // GrBatchFontCache will eventually replace GrFontCache + fBatchFontCache = SkNEW(GrBatchFontCache); + fBatchFontCache->init(this); } GrContext::~GrContext() { @@ -143,6 +150,7 @@ GrContext::~GrContext() { } SkDELETE(fResourceCache); + SkDELETE(fBatchFontCache); SkDELETE(fFontCache); SkDELETE(fDrawBuffer); SkDELETE(fDrawBufferVBAllocPool); @@ -180,6 +188,7 @@ void GrContext::abandonContext() { fAARectRenderer->reset(); fOvalRenderer->reset(); + fBatchFontCache->freeAll(); fFontCache->freeAll(); fLayerCache->freeAll(); } @@ -198,6 +207,7 @@ void GrContext::freeGpuResources() { fAARectRenderer->reset(); fOvalRenderer->reset(); + fBatchFontCache->freeAll(); fFontCache->freeAll(); fLayerCache->freeAll(); // a path renderer may be holding onto resources @@ -226,8 +236,12 @@ GrTextContext* GrContext::createTextContext(GrRenderTarget* renderTarget, } } +#ifdef USE_BITMAP_TEXTBLOBS + return GrBitmapTextContextB::Create(this, gpuDevice, leakyProperties); +#else return GrDistanceFieldTextContext::Create(this, gpuDevice, leakyProperties, enableDistanceFieldFonts); +#endif } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/gpu/GrDistanceFieldTextContext.cpp b/src/gpu/GrDistanceFieldTextContext.cpp index f0915a7555..acab0cf768 100755 --- a/src/gpu/GrDistanceFieldTextContext.cpp +++ b/src/gpu/GrDistanceFieldTextContext.cpp @@ -74,7 +74,11 @@ GrDistanceFieldTextContext* GrDistanceFieldTextContext::Create(GrContext* contex bool enable) { GrDistanceFieldTextContext* textContext = SkNEW_ARGS(GrDistanceFieldTextContext, (context, gpuDevice, props, enable)); +#ifdef USE_BITMAP_TEXTBLOBS + textContext->fFallbackTextContext = GrBitmapTextContextB::Create(context, gpuDevice, props); +#else textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, gpuDevice, props); +#endif return textContext; } diff --git a/src/gpu/GrFontAtlasSizes.h b/src/gpu/GrFontAtlasSizes.h index d5c5e287e1..8a3091c6c7 100644 --- a/src/gpu/GrFontAtlasSizes.h +++ b/src/gpu/GrFontAtlasSizes.h @@ -9,6 +9,30 @@ #ifndef GrFontAtlasSizes_DEFINED #define GrFontAtlasSizes_DEFINED +// For debugging atlas which evict all of the time +//#define DEBUG_CONSTANT_EVICT +#ifdef DEBUG_CONSTANT_EVICT +#define GR_FONT_ATLAS_TEXTURE_WIDTH 256//1024 +#define GR_FONT_ATLAS_A8_TEXTURE_WIDTH 256//2048 +#define GR_FONT_ATLAS_TEXTURE_HEIGHT 256//2048 + +#define GR_FONT_ATLAS_PLOT_WIDTH 256 +#define GR_FONT_ATLAS_A8_PLOT_WIDTH 256//512 +#define GR_FONT_ATLAS_PLOT_HEIGHT 256 + +#define GR_FONT_ATLAS_NUM_PLOTS_X (GR_FONT_ATLAS_TEXTURE_WIDTH / GR_FONT_ATLAS_PLOT_WIDTH) +#define GR_FONT_ATLAS_A8_NUM_PLOTS_X (GR_FONT_ATLAS_A8_TEXTURE_WIDTH / GR_FONT_ATLAS_A8_PLOT_WIDTH) +#define GR_FONT_ATLAS_NUM_PLOTS_Y (GR_FONT_ATLAS_TEXTURE_HEIGHT / GR_FONT_ATLAS_PLOT_HEIGHT) + +// one over width and height +#define GR_FONT_ATLAS_RECIP_WIDTH "0.00390625"//"0.0009765625" +#define GR_FONT_ATLAS_A8_RECIP_WIDTH "0.00390625"//"0.00048828125" +#define GR_FONT_ATLAS_RECIP_HEIGHT "0.00390625"//"0.00048828125" + +// 1/(3*width) +// only used for distance fields, which are A8 +#define GR_FONT_ATLAS_LCD_DELTA "0.001302083"//"0.000162760417" +#else #define GR_FONT_ATLAS_TEXTURE_WIDTH 1024 #define GR_FONT_ATLAS_A8_TEXTURE_WIDTH 2048 #define GR_FONT_ATLAS_TEXTURE_HEIGHT 2048 @@ -29,5 +53,5 @@ // 1/(3*width) // only used for distance fields, which are A8 #define GR_FONT_ATLAS_LCD_DELTA "0.000162760417" - +#endif #endif diff --git a/src/gpu/GrGlyph.h b/src/gpu/GrGlyph.h index 108f2f0fe7..2d3e945ddb 100644 --- a/src/gpu/GrGlyph.h +++ b/src/gpu/GrGlyph.h @@ -8,6 +8,7 @@ #ifndef GrGlyph_DEFINED #define GrGlyph_DEFINED +#include "GrBatchAtlas.h" #include "GrRect.h" #include "GrTypes.h" @@ -30,14 +31,17 @@ struct GrGlyph { typedef uint32_t PackedID; - GrPlot* fPlot; - SkPath* fPath; - PackedID fPackedID; - GrMaskFormat fMaskFormat; - GrIRect16 fBounds; - SkIPoint16 fAtlasLocation; + // TODO either plot or AtlasID will be valid, not both + GrBatchAtlas::AtlasID fID; + GrPlot* fPlot; + SkPath* fPath; + PackedID fPackedID; + GrMaskFormat fMaskFormat; + GrIRect16 fBounds; + SkIPoint16 fAtlasLocation; void init(GrGlyph::PackedID packed, const SkIRect& bounds, GrMaskFormat format) { + fID = GrBatchAtlas::kInvalidAtlasID; fPlot = NULL; fPath = NULL; fPackedID = packed; diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp index 172a856eb1..bf47abd731 100644 --- a/src/gpu/GrStencilAndCoverTextContext.cpp +++ b/src/gpu/GrStencilAndCoverTextContext.cpp @@ -34,7 +34,11 @@ GrStencilAndCoverTextContext::Create(GrContext* context, SkGpuDevice* gpuDevice, const SkDeviceProperties& props) { GrStencilAndCoverTextContext* textContext = SkNEW_ARGS(GrStencilAndCoverTextContext, (context, gpuDevice, props)); +#ifdef USE_BITMAP_TEXTBLOBS + textContext->fFallbackTextContext = GrBitmapTextContextB::Create(context, gpuDevice, props); +#else textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, gpuDevice, props); +#endif return textContext; } diff --git a/src/gpu/GrTextContext.h b/src/gpu/GrTextContext.h index 0a4beae5ff..54b3efd19e 100644 --- a/src/gpu/GrTextContext.h +++ b/src/gpu/GrTextContext.h @@ -23,6 +23,9 @@ class SkDrawFilter; class SkGpuDevice; class SkTextBlob; +// For testing textblobs on GPU. +//#define USE_BITMAP_TEXTBLOBS + /* * This class wraps the state for a single text render */ @@ -38,9 +41,9 @@ public: const char text[], size_t byteLength, const SkScalar pos[], int scalarsPerPosition, const SkPoint& offset, const SkIRect& clipBounds); - void drawTextBlob(GrRenderTarget*, const GrClip&, const SkPaint&, - const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y, - SkDrawFilter*, const SkIRect& clipBounds); + virtual void drawTextBlob(GrRenderTarget*, const GrClip&, const SkPaint&, + const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y, + SkDrawFilter*, const SkIRect& clipBounds); protected: GrTextContext* fFallbackTextContext; @@ -90,6 +93,8 @@ protected: // sets extent in stopVector and returns glyph count static int MeasureText(SkGlyphCache* cache, SkDrawCacheProc glyphCacheProc, const char text[], size_t byteLength, SkVector* stopVector); + + friend class BitmapTextBatch; }; #endif diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h index 522cba80a1..8c3414b1db 100644 --- a/src/gpu/SkGpuDevice.h +++ b/src/gpu/SkGpuDevice.h @@ -219,6 +219,7 @@ private: static GrRenderTarget* CreateRenderTarget(GrContext*, SkSurface::Budgeted, const SkImageInfo&, int sampleCount); + friend class GrBitmapTextContextB; friend class GrTextContext; typedef SkBaseDevice INHERITED; }; |