Uses a single pre-baked set of paths for drawing nvpr text of a given

typeface. Loads the paths using the driver's glyph loading routines.

Refactors GrPathRange to accept a PathGenerator class that it uses to
lazily initialize its paths. The client code is no longer expected to
initialize the paths in a GrPathRange; instead it must provide a
PathGenerator* instance to createPathRange().

Adds a new createGlyphs() method to GrPathRendering that creates a
range of glyph paths, indexed by glyph id. GrPathRendering implements
createGlyphs() with a PathGenerator that loads glyph paths using the
skia frameworks. GrGLPathRendering uses glMemoryGlyphIndexArrayNV()
instead, when possible, to load the glyph paths.

Removes all GlyphPathRange logic from GrStencilAndCoverTextContext.
It instead uses createGlyphs().

BUG=skia:2939
R=bsalomon@google.com, jvanverth@google.com

Author: cdalton@nvidia.com

Review URL: https://codereview.chromium.org/563283004
This commit is contained in:
cdalton 2014-09-18 13:51:53 -07:00 committed by Commit bot
parent abfaf63bd0
commit 855d83ff79
16 changed files with 510 additions and 207 deletions

View File

@ -42,3 +42,13 @@ multipicturedraw_pathclip_tiled
# rileya - https://codereview.chromium.org/516463005/ will rebaseline after bots cycle
yuv_to_rgb_effect
# cdalton - https://codereview.chromium.org/563283004/
typefacestyles_kerning
textblob
stroketext
stroke-fill
shadertext3
shadertext2
glyph_pos_align
getpostextpath
fontmgr_iter

View File

@ -103,10 +103,12 @@
'<(skia_src_path)/gpu/GrPaint.cpp',
'<(skia_src_path)/gpu/GrPath.cpp',
'<(skia_src_path)/gpu/GrPath.h',
'<(skia_src_path)/gpu/GrPathRange.cpp',
'<(skia_src_path)/gpu/GrPathRange.h',
'<(skia_src_path)/gpu/GrPathRendererChain.cpp',
'<(skia_src_path)/gpu/GrPathRenderer.cpp',
'<(skia_src_path)/gpu/GrPathRenderer.h',
'<(skia_src_path)/gpu/GrPathRendering.cpp',
'<(skia_src_path)/gpu/GrPathRendering.h',
'<(skia_src_path)/gpu/GrPathUtils.cpp',
'<(skia_src_path)/gpu/GrPathUtils.h',

View File

@ -1127,6 +1127,8 @@ private:
friend class GrBitmapTextContext;
friend class GrDistanceFieldTextContext;
friend class GrStencilAndCoverTextContext;
friend class GrPathRendering;
friend class GrGLPathRendering;
friend class SkTextToPathIter;
friend class SkCanonicalizePaint;

View File

@ -328,6 +328,8 @@ private:
friend class SkGTypeface;
friend class SkPDFFont;
friend class SkPDFCIDFont;
friend class GrPathRendering;
friend class GrGLPathRendering;
/** Retrieve detailed typeface metrics. Used by the PDF backend.
@param perGlyphInfo Indicate what glyph specific information (advances,

View File

@ -378,6 +378,7 @@ void GrGpu::onDrawPaths(const GrPathRange* pathRange,
return;
}
pathRange->willDrawPaths(indices, count);
this->pathRendering()->drawPaths(pathRange, indices, count, transforms, transformsType, fill);
}

70
src/gpu/GrPathRange.cpp Normal file
View File

@ -0,0 +1,70 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrPathRange.h"
#include "SkPath.h"
enum {
kPathsPerGroup = 16 // Paths get tracked in groups of 16 for lazy loading.
};
GrPathRange::GrPathRange(GrGpu* gpu,
PathGenerator* pathGenerator,
const SkStrokeRec& stroke)
: INHERITED(gpu, kIsWrapped),
fPathGenerator(SkRef(pathGenerator)),
fNumPaths(fPathGenerator->getNumPaths()),
fStroke(stroke) {
const int numGroups = (fNumPaths + kPathsPerGroup - 1) / kPathsPerGroup;
fGeneratedPaths.reset((numGroups + 7) / 8); // 1 bit per path group.
memset(&fGeneratedPaths.front(), 0, fGeneratedPaths.count());
}
GrPathRange::GrPathRange(GrGpu* gpu,
int numPaths,
const SkStrokeRec& stroke)
: INHERITED(gpu, kIsWrapped),
fNumPaths(numPaths),
fStroke(stroke) {
}
void GrPathRange::willDrawPaths(const uint32_t indices[], int count) const {
if (NULL == fPathGenerator.get()) {
return;
}
bool didLoadPaths = false;
for (int i = 0; i < count; ++i) {
SkASSERT(indices[i] < static_cast<uint32_t>(fNumPaths));
const int groupIndex = indices[i] / kPathsPerGroup;
const int groupByte = groupIndex / 8;
const uint8_t groupBit = 1 << (groupIndex % 8);
const bool hasPath = SkToBool(fGeneratedPaths[groupByte] & groupBit);
if (!hasPath) {
// We track which paths are loaded in groups of kPathsPerGroup. To
// mark a path as loaded we need to load the entire group.
const int groupFirstPath = groupIndex * kPathsPerGroup;
const int groupLastPath = SkTMin(groupFirstPath + kPathsPerGroup, fNumPaths) - 1;
SkPath path;
for (int pathIdx = groupFirstPath; pathIdx <= groupLastPath; ++pathIdx) {
fPathGenerator->generatePath(pathIdx, &path);
this->onInitPath(pathIdx, path);
}
fGeneratedPaths[groupByte] |= groupBit;
didLoadPaths = true;
}
}
if (didLoadPaths) {
this->didChangeGpuMemorySize();
}
}

View File

@ -10,51 +10,80 @@
#include "GrGpuResource.h"
#include "GrResourceCache.h"
#include "SkRefCnt.h"
#include "SkStrokeRec.h"
#include "SkTArray.h"
class SkPath;
class SkDescriptor;
/**
* Represents a contiguous range of GPU path objects with a common stroke. The
* path range is immutable with the exception that individual paths can be
* initialized lazily. Unititialized paths are silently ignored by drawing
* functions.
* Represents a contiguous range of GPU path objects, all with a common stroke.
* This object is immutable with the exception that individual paths may be
* initialized lazily.
*/
class GrPathRange : public GrGpuResource {
public:
SK_DECLARE_INST_COUNT(GrPathRange);
static const bool kIsWrapped = false;
/**
* Return the resourceType intended for cache lookups involving GrPathRange.
*/
static GrResourceKey::ResourceType resourceType() {
static const GrResourceKey::ResourceType type = GrResourceKey::GenerateResourceType();
return type;
}
/**
* Initialize to a range with a fixed size and stroke. Stroke must not be hairline.
* Class that generates the paths for a specific range.
*/
GrPathRange(GrGpu* gpu, size_t size, const SkStrokeRec& stroke)
: INHERITED(gpu, kIsWrapped),
fSize(size),
fStroke(stroke) {
this->registerWithCache();
}
size_t getSize() const { return fSize; }
const SkStrokeRec& getStroke() const { return fStroke; }
class PathGenerator : public SkRefCnt {
public:
virtual int getNumPaths() = 0;
virtual void generatePath(int index, SkPath* out) = 0;
virtual bool isEqualTo(const SkDescriptor&) const { return false; }
virtual ~PathGenerator() {}
};
/**
* Initialize a path in the range. It is invalid to call this method for a
* path that has already been initialized.
* Initialize a lazy-loaded path range. This class will generate an SkPath and call
* onInitPath() for each path within the range before it is drawn for the first time.
*/
virtual void initAt(size_t index, const SkPath&) = 0;
GrPathRange(GrGpu*, PathGenerator*, const SkStrokeRec& stroke);
/**
* Initialize an eager-loaded path range. The subclass is responsible for ensuring all
* the paths are initialized up front.
*/
GrPathRange(GrGpu*, int numPaths, const SkStrokeRec& stroke);
virtual bool isEqualTo(const SkDescriptor& desc) const {
return NULL != fPathGenerator.get() && fPathGenerator->isEqualTo(desc);
}
int getNumPaths() const { return fNumPaths; }
const SkStrokeRec& getStroke() const { return fStroke; }
const PathGenerator* getPathGenerator() const { return fPathGenerator.get(); }
protected:
size_t fSize;
SkStrokeRec fStroke;
// Initialize a path in the range before drawing. This is only called when
// fPathGenerator is non-null. The child class need not call didChangeGpuMemorySize(),
// GrPathRange will take care of that after the call is complete.
virtual void onInitPath(int index, const SkPath&) const = 0;
private:
// Notify when paths will be drawn in case this is a lazy-loaded path range.
friend class GrGpu;
void willDrawPaths(const uint32_t indices[], int count) const;
mutable SkAutoTUnref<PathGenerator> fPathGenerator;
mutable SkTArray<uint8_t, true /*MEM_COPY*/> fGeneratedPaths;
const int fNumPaths;
const SkStrokeRec fStroke;
typedef GrGpuResource INHERITED;
};

View File

@ -0,0 +1,79 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrPathRendering.h"
#include "SkDescriptor.h"
#include "SkGlyph.h"
#include "SkMatrix.h"
#include "SkTypeface.h"
#include "GrPathRange.h"
class GlyphGenerator : public GrPathRange::PathGenerator {
public:
GlyphGenerator(const SkTypeface& typeface, const SkDescriptor& desc)
: fDesc(desc.copy()),
fScalerContext(typeface.createScalerContext(fDesc)) {
fFlipMatrix.setScale(1, -1);
}
virtual ~GlyphGenerator() {
SkDescriptor::Free(fDesc);
}
virtual int getNumPaths() {
return fScalerContext->getGlyphCount();
}
virtual void generatePath(int glyphID, SkPath* out) {
SkGlyph skGlyph;
skGlyph.init(SkGlyph::MakeID(glyphID));
fScalerContext->getMetrics(&skGlyph);
fScalerContext->getPath(skGlyph, out);
out->transform(fFlipMatrix); // Load glyphs with the inverted y-direction.
}
virtual bool isEqualTo(const SkDescriptor& desc) const {
return fDesc->equals(desc);
}
private:
SkDescriptor* const fDesc;
const SkAutoTDelete<SkScalerContext> fScalerContext;
SkMatrix fFlipMatrix;
};
GrPathRange* GrPathRendering::createGlyphs(const SkTypeface* typeface,
const SkDescriptor* desc,
const SkStrokeRec& stroke) {
if (NULL == typeface) {
typeface = SkTypeface::GetDefaultTypeface();
SkASSERT(NULL != typeface);
}
if (desc) {
SkAutoTUnref<GlyphGenerator> generator(SkNEW_ARGS(GlyphGenerator, (*typeface, *desc)));
return this->createPathRange(generator, stroke);
}
SkScalerContextRec rec;
memset(&rec, 0, sizeof(rec));
rec.fFontID = typeface->uniqueID();
rec.fTextSize = SkPaint::kCanonicalTextSizeForPaths;
rec.fPreScaleX = rec.fPost2x2[0][0] = rec.fPost2x2[1][1] = SK_Scalar1;
// Don't bake stroke information into the glyphs, we'll let the GPU do the stroking.
SkAutoDescriptor ad(sizeof(rec) + SkDescriptor::ComputeOverhead(1));
SkDescriptor* genericDesc = ad.getDesc();
genericDesc->init();
genericDesc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
genericDesc->computeChecksum();
SkAutoTUnref<GlyphGenerator> generator(SkNEW_ARGS(GlyphGenerator, (*typeface, *genericDesc)));
return this->createPathRange(generator, stroke);
}

View File

@ -9,10 +9,12 @@
#define GrPathRendering_DEFINED
#include "SkPath.h"
#include "GrPathRange.h"
class SkStrokeRec;
class SkDescriptor;
class SkTypeface;
class GrPath;
class GrPathRange;
class GrGpu;
/**
@ -77,7 +79,37 @@ public:
* @param SkStrokeRec the common stroke applied to each path in the range.
* @return a new path range.
*/
virtual GrPathRange* createPathRange(size_t size, const SkStrokeRec&) = 0;
virtual GrPathRange* createPathRange(GrPathRange::PathGenerator*, const SkStrokeRec&) = 0;
/**
* Creates a range of glyph paths, indexed by glyph id. The glyphs will have an
* inverted y-direction in order to match the raw font path data. The caller owns
* a ref on the returned path range which must be balanced by a call to unref.
*
* @param SkTypeface Typeface that defines the glyphs.
* If null, the default typeface will be used.
*
* @param SkDescriptor Additional font configuration that specifies the font's size,
* stroke, and other flags. This will generally come from an
* SkGlyphCache.
*
* It is recommended to leave this value null when possible, in
* which case the glyphs will be loaded directly from the font's
* raw path data and sized at SkPaint::kCanonicalTextSizeForPaths.
* This will result in less memory usage and more efficient paths.
*
* If non-null, the glyph paths will match the font descriptor,
* including with the stroke information baked directly into
* the outlines.
*
* @param SkStrokeRec Common stroke that the GPU will apply to every path. Note that
* if the glyph outlines contain baked-in strokes from the font
* descriptor, the GPU stroke will be applied on top of those
* outlines.
*
* @return a new path range populated with glyphs.
*/
virtual GrPathRange* createGlyphs(const SkTypeface*, const SkDescriptor*, const SkStrokeRec&) = 0;
virtual void stencilPath(const GrPath*, SkPath::FillType) = 0;
virtual void drawPath(const GrPath*, SkPath::FillType) = 0;

View File

@ -17,97 +17,11 @@
#include "SkGpuDevice.h"
#include "SkPath.h"
#include "SkTextMapStateProc.h"
class GrStencilAndCoverTextContext::GlyphPathRange : public GrGpuResource {
static const int kMaxGlyphCount = 1 << 16; // Glyph IDs are uint16_t's
static const int kGlyphGroupSize = 16; // Glyphs get tracked in groups of 16
public:
static GlyphPathRange* Create(GrContext* context,
SkGlyphCache* cache,
const SkStrokeRec& stroke) {
static const GrCacheID::Domain gGlyphPathRangeDomain = GrCacheID::GenerateDomain();
GrCacheID::Key key;
key.fData32[0] = cache->getDescriptor().getChecksum();
key.fData32[1] = cache->getScalerContext()->getTypeface()->uniqueID();
key.fData64[1] = GrPath::ComputeStrokeKey(stroke);
GrResourceKey resourceKey(GrCacheID(gGlyphPathRangeDomain, key),
GrPathRange::resourceType(), 0);
SkAutoTUnref<GlyphPathRange> glyphs(
static_cast<GlyphPathRange*>(context->findAndRefCachedResource(resourceKey)));
if (NULL == glyphs ||
!glyphs->fDesc->equals(cache->getDescriptor() /*checksum collision*/)) {
glyphs.reset(SkNEW_ARGS(GlyphPathRange, (context, cache->getDescriptor(), stroke)));
glyphs->registerWithCache();
context->addResourceToCache(resourceKey, glyphs);
}
return glyphs.detach();
}
const GrPathRange* pathRange() const { return fPathRange.get(); }
void preloadGlyph(uint16_t glyphID, SkGlyphCache* cache) {
const uint16_t groupIndex = glyphID / kGlyphGroupSize;
const uint16_t groupByte = groupIndex >> 3;
const uint8_t groupBit = 1 << (groupIndex & 7);
const bool hasGlyph = 0 != (fLoadedGlyphs[groupByte] & groupBit);
if (hasGlyph) {
return;
}
// We track which glyphs are loaded in groups of kGlyphGroupSize. To
// mark a glyph loaded we need to load the entire group.
const uint16_t groupFirstID = groupIndex * kGlyphGroupSize;
const uint16_t groupLastID = groupFirstID + kGlyphGroupSize - 1;
SkPath skPath;
for (int id = groupFirstID; id <= groupLastID; ++id) {
const SkGlyph& skGlyph = cache->getGlyphIDMetrics(id);
if (const SkPath* skPath = cache->findPath(skGlyph)) {
fPathRange->initAt(id, *skPath);
} // GrGpu::drawPaths will silently ignore undefined paths.
}
fLoadedGlyphs[groupByte] |= groupBit;
this->didChangeGpuMemorySize();
}
// GrGpuResource overrides
virtual size_t gpuMemorySize() const SK_OVERRIDE { return fPathRange->gpuMemorySize(); }
private:
GlyphPathRange(GrContext* context, const SkDescriptor& desc, const SkStrokeRec& stroke)
: INHERITED(context->getGpu(), false)
, fDesc(desc.copy())
// We reserve a range of kMaxGlyphCount paths because of fallbacks fonts. We
// can't know exactly how many glyphs we might need without preloading every
// fallback, which we don't want to do at this point.
, fPathRange(context->getGpu()->pathRendering()->createPathRange(kMaxGlyphCount, stroke)) {
memset(fLoadedGlyphs, 0, sizeof(fLoadedGlyphs));
}
~GlyphPathRange() {
this->release();
SkDescriptor::Free(fDesc);
}
static const int kMaxGroupCount = (kMaxGlyphCount + (kGlyphGroupSize - 1)) / kGlyphGroupSize;
SkDescriptor* const fDesc;
uint8_t fLoadedGlyphs[(kMaxGroupCount + 7) >> 3]; // One bit per glyph group
SkAutoTUnref<GrPathRange> fPathRange;
typedef GrGpuResource INHERITED;
};
#include "SkTextFormatParams.h"
GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
GrContext* context, const SkDeviceProperties& properties)
: GrTextContext(context, properties)
, fStroke(SkStrokeRec::kFill_InitStyle)
, fPendingGlyphCount(0) {
}
@ -140,22 +54,18 @@ void GrStencilAndCoverTextContext::drawText(const GrPaint& paint,
// will turn off the use of device-space glyphs when perspective transforms
// are in use.
this->init(paint, skPaint, byteLength, kUseIfNeeded_DeviceSpaceGlyphsBehavior);
this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode);
SkMatrix* glyphCacheTransform = NULL;
// Transform our starting point.
if (fNeedsDeviceSpaceGlyphs) {
SkPoint loc;
fContextInitialMatrix.mapXY(x, y, &loc);
x = loc.fX;
y = loc.fY;
glyphCacheTransform = &fContextInitialMatrix;
}
SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform);
fGlyphCache = autoCache.getCache();
fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke);
fTransformType = GrPathRendering::kTranslate_PathTransformType;
const char* stop = text + byteLength;
@ -232,14 +142,10 @@ void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint,
// same glyphs as what were measured.
const float textTranslateY = (1 == scalarsPerPosition ? constY : 0);
this->init(paint, skPaint, byteLength, kDoNotUse_DeviceSpaceGlyphsBehavior, textTranslateY);
this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode, textTranslateY);
SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
fGlyphCache = autoCache.getCache();
fGlyphs = GlyphPathRange::Create(fContext, fGlyphCache, fStroke);
const char* stop = text + byteLength;
if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
@ -308,76 +214,145 @@ bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
return rec.getFormat() != SkMask::kARGB32_Format;
}
static GrPathRange* get_gr_glyphs(GrContext* ctx,
const SkTypeface* typeface,
const SkDescriptor* desc,
const SkStrokeRec& stroke) {
static const GrCacheID::Domain gGlyphsDomain = GrCacheID::GenerateDomain();
GrCacheID::Key key;
uint64_t* keyData = key.fData64;
keyData[0] = desc ? desc->getChecksum() : 0;
keyData[0] = (keyData[0] << 32) | (typeface ? typeface->uniqueID() : 0);
keyData[1] = GrPath::ComputeStrokeKey(stroke);
GrResourceKey resourceKey = GrResourceKey(GrCacheID(gGlyphsDomain, key),
GrPathRange::resourceType(), 0);
SkAutoTUnref<GrPathRange> glyphs(
static_cast<GrPathRange*>(ctx->findAndRefCachedResource(resourceKey)));
if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) {
glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke));
ctx->addResourceToCache(resourceKey, glyphs);
}
return glyphs.detach();
}
void GrStencilAndCoverTextContext::init(const GrPaint& paint,
const SkPaint& skPaint,
size_t textByteLength,
DeviceSpaceGlyphsBehavior deviceSpaceGlyphsBehavior,
RenderMode renderMode,
SkScalar textTranslateY) {
GrTextContext::init(paint, skPaint);
fContextInitialMatrix = fContext->getMatrix();
bool otherBackendsWillDrawAsPaths =
const bool otherBackendsWillDrawAsPaths =
SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
if (otherBackendsWillDrawAsPaths) {
// This is to reproduce SkDraw::drawText_asPaths glyph positions.
fSkPaint.setLinearText(true);
fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
if (fSkPaint.getStyle() != SkPaint::kFill_Style) {
// Compensate the glyphs being scaled up by fTextRatio by scaling the
// stroke down.
fSkPaint.setStrokeWidth(fSkPaint.getStrokeWidth() / fTextRatio);
}
fNeedsDeviceSpaceGlyphs = false;
} else {
fTextRatio = fTextInverseRatio = 1.0f;
fNeedsDeviceSpaceGlyphs =
kUseIfNeeded_DeviceSpaceGlyphsBehavior == deviceSpaceGlyphsBehavior &&
(fContextInitialMatrix.getType() &
(SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0;
// SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
SkASSERT(!fContextInitialMatrix.hasPerspective());
}
fStroke = SkStrokeRec(fSkPaint);
fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
kMaxAccuracy_RenderMode == renderMode &&
SkToBool(fContextInitialMatrix.getType() &
(SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
if (fNeedsDeviceSpaceGlyphs) {
SkASSERT(1.0f == fTextRatio);
SkASSERT(0.0f == textTranslateY);
fPaint.localCoordChangeInverse(fContextInitialMatrix);
fContext->setIdentityMatrix();
// SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
SkASSERT(!fContextInitialMatrix.hasPerspective());
SkASSERT(0 == textTranslateY); // TODO: Handle textTranslateY in device-space usecase.
// The whole shape is baked into the glyph. Make NVPR just fill the
// baked shape.
fStroke.setStrokeStyle(-1, false);
fTextRatio = fTextInverseRatio = 1.0f;
// Glyphs loaded by GPU path rendering have an inverted y-direction.
SkMatrix m;
m.setScale(1, -1);
fContext->setMatrix(m);
// Post-flip the initial matrix so we're left with just the flip after
// the paint preConcats the inverse.
m = fContextInitialMatrix;
m.postScale(1, -1);
fPaint.localCoordChangeInverse(m);
// The whole shape (including stroke) will be baked into the glyph outlines. Make
// NVPR just fill the baked shapes.
fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false);
fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
&fGlyphCache->getDescriptor(),
SkStrokeRec(SkStrokeRec::kFill_InitStyle));
} else {
if (1.0f != fTextRatio || 0.0f != textTranslateY) {
SkMatrix textMatrix;
textMatrix.setTranslate(0, textTranslateY);
textMatrix.preScale(fTextRatio, fTextRatio);
fPaint.localCoordChange(textMatrix);
fContext->concatMatrix(textMatrix);
}
if (fSkPaint.getStrokeWidth() == 0.0f) {
if (fSkPaint.getStyle() == SkPaint::kStrokeAndFill_Style) {
fStroke.setStrokeStyle(-1, false);
} else if (fSkPaint.getStyle() == SkPaint::kStroke_Style) {
// Approximate hairline stroke.
const SkMatrix& ctm = fContext->getMatrix();
SkScalar strokeWidth = SK_Scalar1 /
(SkVector::Make(ctm.getScaleX(), ctm.getSkewY()).length());
fStroke.setStrokeStyle(strokeWidth, false);
}
}
// Make glyph cache produce paths geometry for fill. We will stroke them
// by passing fStroke to drawPath. This is the fast path.
// Don't bake strokes into the glyph outlines. We will stroke the glyphs
// using the GPU instead. This is the fast path.
SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint);
fSkPaint.setStyle(SkPaint::kFill_Style);
if (gpuStroke.isHairlineStyle()) {
// Approximate hairline stroke.
SkScalar strokeWidth = SK_Scalar1 /
(SkVector::Make(fContextInitialMatrix.getScaleX(),
fContextInitialMatrix.getSkewY()).length());
gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/);
} else if (fSkPaint.isFakeBoldText() &&
#ifdef SK_USE_FREETYPE_EMBOLDEN
kMaxPerformance_RenderMode == renderMode &&
#endif
SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) {
// Instead of baking fake bold into the glyph outlines, do it with the GPU stroke.
SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(),
kStdFakeBoldInterpKeys,
kStdFakeBoldInterpValues,
kStdFakeBoldInterpLength);
SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale);
gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra,
true /*strokeAndFill*/);
fSkPaint.setFakeBoldText(false);
}
bool canUseRawPaths;
if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) {
// We can draw the glyphs from canonically sized paths.
fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
// Compensate for the glyphs being scaled by fTextRatio.
if (!gpuStroke.isFillStyle()) {
gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio,
SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle());
}
fSkPaint.setLinearText(true);
fSkPaint.setLCDRenderText(false);
fSkPaint.setAutohinted(false);
fSkPaint.setHinting(SkPaint::kNo_Hinting);
fSkPaint.setSubpixelText(true);
fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() &&
0 == fSkPaint.getTextSkewX() &&
!fSkPaint.isFakeBoldText() &&
!fSkPaint.isVerticalText();
} else {
fTextRatio = fTextInverseRatio = 1.0f;
canUseRawPaths = false;
}
SkMatrix textMatrix;
textMatrix.setTranslate(0, textTranslateY);
// Glyphs loaded by GPU path rendering have an inverted y-direction.
textMatrix.preScale(fTextRatio, -fTextRatio);
fPaint.localCoordChange(textMatrix);
fContext->concatMatrix(textMatrix);
fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false);
fGlyphs = canUseRawPaths ?
get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) :
get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
&fGlyphCache->getDescriptor(), gpuStroke);
}
fStateRestore.set(fDrawTarget->drawState());
fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(),
@ -403,8 +378,6 @@ inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x)
this->flush();
}
fGlyphs->preloadGlyph(glyphID, fGlyphCache);
fIndexBuffer[fPendingGlyphCount] = glyphID;
fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x;
@ -418,11 +391,9 @@ inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x,
this->flush();
}
fGlyphs->preloadGlyph(glyphID, fGlyphCache);
fIndexBuffer[fPendingGlyphCount] = glyphID;
fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
fTransformBuffer[2 * fPendingGlyphCount + 1] = fTextInverseRatio * y;
fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y;
++fPendingGlyphCount;
}
@ -432,7 +403,7 @@ void GrStencilAndCoverTextContext::flush() {
return;
}
fDrawTarget->drawPaths(fGlyphs->pathRange(), fIndexBuffer, fPendingGlyphCount,
fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount,
fTransformBuffer, fTransformType, SkPath::kWinding_FillType);
fPendingGlyphCount = 0;
@ -441,8 +412,10 @@ void GrStencilAndCoverTextContext::flush() {
void GrStencilAndCoverTextContext::finish() {
this->flush();
SkSafeUnref(fGlyphs);
fGlyphs->unref();
fGlyphs = NULL;
SkGlyphCache::AttachCache(fGlyphCache);
fGlyphCache = NULL;
fDrawTarget->drawState()->stencil()->setDisabled();

View File

@ -15,6 +15,7 @@
class GrTextStrike;
class GrPath;
class GrPathRange;
/*
* This class implements text rendering using stencil and cover path rendering
@ -37,15 +38,31 @@ public:
virtual bool canDraw(const SkPaint& paint) SK_OVERRIDE;
private:
class GlyphPathRange;
static const int kGlyphBufferSize = 1024;
enum DeviceSpaceGlyphsBehavior {
kUseIfNeeded_DeviceSpaceGlyphsBehavior,
kDoNotUse_DeviceSpaceGlyphsBehavior,
enum RenderMode {
/**
* This is the render mode used by drawText(), which is mainly used by
* the Skia unit tests. It tries match the other text backends exactly,
* with the exception of not implementing LCD text, and doing anti-
* aliasing with the built-in MSAA.
*/
kMaxAccuracy_RenderMode,
/**
* This is the render mode used by drawPosText(). It ignores hinting and
* LCD text, even if the client provided positions for hinted glyphs,
* and renders from a canonically-sized, generic set of paths for the
* given typeface. In the future we should work out a system for the
* client to know it should not provide hinted glyph positions. This
* render mode also tries to use GPU stroking for fake bold, even when
* SK_USE_FREETYPE_EMBOLDEN is set.
*/
kMaxPerformance_RenderMode,
};
void init(const GrPaint&, const SkPaint&, size_t textByteLength,
DeviceSpaceGlyphsBehavior, SkScalar textTranslateY = 0);
RenderMode, SkScalar textTranslateY = 0);
void initGlyphs(SkGlyphCache* cache);
void appendGlyph(uint16_t glyphID, float x);
void appendGlyph(uint16_t glyphID, float x, float y);
@ -55,9 +72,8 @@ private:
GrDrawState::AutoRestoreEffects fStateRestore;
SkScalar fTextRatio;
float fTextInverseRatio;
SkStrokeRec fStroke;
SkGlyphCache* fGlyphCache;
GlyphPathRange* fGlyphs;
GrPathRange* fGlyphs;
uint32_t fIndexBuffer[kGlyphBufferSize];
float fTransformBuffer[2 * kGlyphBufferSize];
GrDrawTarget::PathTransformType fTransformType;

View File

@ -109,6 +109,8 @@ void GrGLPath::InitPathObject(GrGpuGL* gpu,
GR_GL_CALL(gpu->glInterface(),
PathCommands(pathID, verbCnt, &pathCommands[0],
2 * pointCnt, GR_GL_FLOAT, &pathPoints[0]));
} else {
GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, NULL, 0, GR_GL_FLOAT, NULL));
}
if (stroke.needToApply()) {

View File

@ -11,17 +11,29 @@
#include "GrGLPathRendering.h"
#include "GrGpuGL.h"
GrGLPathRange::GrGLPathRange(GrGpuGL* gpu, size_t size, const SkStrokeRec& stroke)
: INHERITED(gpu, size, stroke),
fBasePathID(gpu->glPathRendering()->genPaths(fSize)),
fNumDefinedPaths(0) {
GrGLPathRange::GrGLPathRange(GrGpuGL* gpu, PathGenerator* pathGenerator, const SkStrokeRec& stroke)
: INHERITED(gpu, pathGenerator, stroke),
fBasePathID(gpu->glPathRendering()->genPaths(this->getNumPaths())),
fGpuMemorySize(0) {
this->registerWithCache();
}
GrGLPathRange::GrGLPathRange(GrGpuGL* gpu,
GrGLuint basePathID,
int numPaths,
size_t gpuMemorySize,
const SkStrokeRec& stroke)
: INHERITED(gpu, numPaths, stroke),
fBasePathID(basePathID),
fGpuMemorySize(gpuMemorySize) {
this->registerWithCache();
}
GrGLPathRange::~GrGLPathRange() {
this->release();
}
void GrGLPathRange::initAt(size_t index, const SkPath& skPath) {
void GrGLPathRange::onInitPath(int index, const SkPath& skPath) const {
GrGpuGL* gpu = static_cast<GrGpuGL*>(this->getGpu());
if (NULL == gpu) {
return;
@ -33,16 +45,18 @@ void GrGLPathRange::initAt(size_t index, const SkPath& skPath) {
GR_GL_CALL_RET(gpu->glInterface(), isPath, IsPath(fBasePathID + index)));
SkASSERT(GR_GL_FALSE == isPath);
GrGLPath::InitPathObject(gpu, fBasePathID + index, skPath, fStroke);
++fNumDefinedPaths;
this->didChangeGpuMemorySize();
GrGLPath::InitPathObject(gpu, fBasePathID + index, skPath, this->getStroke());
// TODO: Use a better approximation for the individual path sizes.
fGpuMemorySize += 100;
}
void GrGLPathRange::onRelease() {
SkASSERT(this->getGpu());
if (0 != fBasePathID && !this->isWrapped()) {
static_cast<GrGpuGL*>(this->getGpu())->glPathRendering()->deletePaths(fBasePathID, fSize);
static_cast<GrGpuGL*>(this->getGpu())->glPathRendering()->deletePaths(fBasePathID,
this->getNumPaths());
fBasePathID = 0;
}

View File

@ -22,25 +22,38 @@ class GrGpuGL;
class GrGLPathRange : public GrPathRange {
public:
GrGLPathRange(GrGpuGL*, size_t size, const SkStrokeRec&);
/**
* Initialize a GL path range from a PathGenerator. This class will allocate
* the GPU path objects and initialize them lazily.
*/
GrGLPathRange(GrGpuGL*, PathGenerator*, const SkStrokeRec&);
/**
* Initialize a GL path range from an existing range of pre-initialized GPU
* path objects. This class assumes ownership of the GPU path objects and
* will delete them when done.
*/
GrGLPathRange(GrGpuGL*,
GrGLuint basePathID,
int numPaths,
size_t gpuMemorySize,
const SkStrokeRec&);
virtual ~GrGLPathRange();
GrGLuint basePathID() const { return fBasePathID; }
virtual void initAt(size_t index, const SkPath&);
// TODO: Use a better approximation for the individual path sizes.
virtual size_t gpuMemorySize() const SK_OVERRIDE {
return 100 * fNumDefinedPaths;
}
virtual size_t gpuMemorySize() const SK_OVERRIDE { return fGpuMemorySize; }
protected:
virtual void onInitPath(int index, const SkPath&) const;
virtual void onRelease() SK_OVERRIDE;
virtual void onAbandon() SK_OVERRIDE;
private:
GrGLuint fBasePathID;
size_t fNumDefinedPaths;
mutable size_t fGpuMemorySize;
typedef GrPathRange INHERITED;
};

View File

@ -14,6 +14,9 @@
#include "GrGLPathRange.h"
#include "GrGLPathRendering.h"
#include "SkStream.h"
#include "SkTypeface.h"
#define GL_CALL(X) GR_GL_CALL(fGpu->glInterface(), X)
#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(fGpu->glInterface(), RET, X)
@ -93,8 +96,59 @@ GrPath* GrGLPathRendering::createPath(const SkPath& inPath, const SkStrokeRec& s
return SkNEW_ARGS(GrGLPath, (fGpu, inPath, stroke));
}
GrPathRange* GrGLPathRendering::createPathRange(size_t size, const SkStrokeRec& stroke) {
return SkNEW_ARGS(GrGLPathRange, (fGpu, size, stroke));
GrPathRange* GrGLPathRendering::createPathRange(GrPathRange::PathGenerator* pathGenerator,
const SkStrokeRec& stroke) {
return SkNEW_ARGS(GrGLPathRange, (fGpu, pathGenerator, stroke));
}
GrPathRange* GrGLPathRendering::createGlyphs(const SkTypeface* typeface,
const SkDescriptor* desc,
const SkStrokeRec& stroke) {
if (NULL != desc || !caps().glyphLoadingSupport) {
return GrPathRendering::createGlyphs(typeface, desc, stroke);
}
if (NULL == typeface) {
typeface = SkTypeface::GetDefaultTypeface();
SkASSERT(NULL != typeface);
}
int faceIndex;
SkAutoTUnref<SkStream> fontStream(typeface->openStream(&faceIndex));
const size_t fontDataLength = fontStream->getLength();
if (0 == fontDataLength) {
return GrPathRendering::createGlyphs(typeface, NULL, stroke);
}
SkTArray<uint8_t> fontTempBuffer;
const void* fontData = fontStream->getMemoryBase();
if (NULL == fontData) {
// TODO: Find a more efficient way to pass the font data (e.g. open file descriptor).
fontTempBuffer.reset(fontDataLength);
fontStream->read(&fontTempBuffer.front(), fontDataLength);
fontData = &fontTempBuffer.front();
}
const size_t numPaths = typeface->countGlyphs();
const GrGLuint basePathID = this->genPaths(numPaths);
GrGLenum status;
GL_CALL_RET(status, PathMemoryGlyphIndexArray(basePathID, GR_GL_STANDARD_FONT_FORMAT,
fontDataLength, fontData, faceIndex, 0, numPaths,
GrGLPath(fGpu, SkPath(), stroke).pathID(),
SkPaint::kCanonicalTextSizeForPaths));
if (GR_GL_FONT_GLYPHS_AVAILABLE != status) {
this->deletePaths(basePathID, numPaths);
return GrPathRendering::createGlyphs(typeface, NULL, stroke);
}
// This is a crude approximation. We may want to consider giving this class
// a pseudo PathGenerator whose sole purpose is to track the approximate gpu
// memory size.
const size_t gpuMemorySize = fontDataLength / 4;
return SkNEW_ARGS(GrGLPathRange, (fGpu, basePathID, numPaths, gpuMemorySize, stroke));
}
void GrGLPathRendering::stencilPath(const GrPath* path, SkPath::FillType fill) {

View File

@ -34,7 +34,11 @@ public:
// GrPathRendering implementations.
virtual GrPath* createPath(const SkPath&, const SkStrokeRec&) SK_OVERRIDE;
virtual GrPathRange* createPathRange(size_t size, const SkStrokeRec&) SK_OVERRIDE;
virtual GrPathRange* createPathRange(GrPathRange::PathGenerator*,
const SkStrokeRec&) SK_OVERRIDE;
virtual GrPathRange* createGlyphs(const SkTypeface*,
const SkDescriptor*,
const SkStrokeRec&) SK_OVERRIDE;
virtual void stencilPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
virtual void drawPath(const GrPath*, SkPath::FillType) SK_OVERRIDE;
virtual void drawPaths(const GrPathRange*, const uint32_t indices[], int count,