BitmapTextBatch and BitmapTextBlob
BUG=skia: Review URL: https://codereview.chromium.org/1011403004
This commit is contained in:
parent
2a3c8493af
commit
eed1dae049
@ -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 "";
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
|
@ -91,6 +91,7 @@ private:
|
||||
|
||||
static unsigned ScalarsPerGlyph(GlyphPositioning pos);
|
||||
|
||||
friend class GrBitmapTextContextB;
|
||||
friend class GrTextContext;
|
||||
friend class SkBaseDevice;
|
||||
friend class SkTextBlobBuilder;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -226,6 +226,7 @@ GrPlot* GrAtlas::addToAtlas(ClientPlotUsage* usage,
|
||||
desc.fConfig = fPixelConfig;
|
||||
|
||||
fTexture = fGpu->createTexture(desc, true, NULL, 0);
|
||||
|
||||
if (NULL == fTexture) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
309
src/gpu/GrBatchFontCache.cpp
Normal file
309
src/gpu/GrBatchFontCache.cpp
Normal file
@ -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;
|
||||
}
|
137
src/gpu/GrBatchFontCache.h
Normal file
137
src/gpu/GrBatchFontCache.h
Normal file
@ -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
|
@ -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) {
|
||||
@ -638,4 +1560,3 @@ inline void GrBitmapTextContext::finish() {
|
||||
|
||||
GrTextContext::finish();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -219,6 +219,7 @@ private:
|
||||
static GrRenderTarget* CreateRenderTarget(GrContext*, SkSurface::Budgeted, const SkImageInfo&,
|
||||
int sampleCount);
|
||||
|
||||
friend class GrBitmapTextContextB;
|
||||
friend class GrTextContext;
|
||||
typedef SkBaseDevice INHERITED;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user