Implement text rendering with NVPR

Use path rendering to render the text from outlines if supported by the
GPU. Implement this in GrStencilAndCoverTextContext by copying chunks of code
from GrBitmapTextContext.

The drawing is implemented with "instanced" path drawing functions.

Moves the creation of the "main" text context from SkGpuDevice to the
GrContext::createTextContext. This is done because the decision of which text
renderer is optimal can be made only with the internal implementation-specific
information of the context.

Remove a windows assertion from SkScalerContext_GDI::getGDIGlyphPath.  The
GetGlyphOutlineW fails in fontmgr_match for the initial space char in the string
" [700] ...". According to MSDN, this is a known problem.  Just return that the
glyph has no path data in these cases.

R=jvanverth@google.com, bsalomon@google.com, mtklein@google.com

Author: kkinnunen@nvidia.com

Review URL: https://codereview.chromium.org/196133014
This commit is contained in:
kkinnunen 2014-06-24 00:12:27 -07:00 committed by Commit bot
parent 6fc763e3c2
commit c6cb56f36c
9 changed files with 463 additions and 6 deletions

View File

@ -117,6 +117,8 @@
'<(skia_src_path)/gpu/GrStencil.h', '<(skia_src_path)/gpu/GrStencil.h',
'<(skia_src_path)/gpu/GrStencilAndCoverPathRenderer.cpp', '<(skia_src_path)/gpu/GrStencilAndCoverPathRenderer.cpp',
'<(skia_src_path)/gpu/GrStencilAndCoverPathRenderer.h', '<(skia_src_path)/gpu/GrStencilAndCoverPathRenderer.h',
'<(skia_src_path)/gpu/GrStencilAndCoverTextContext.cpp',
'<(skia_src_path)/gpu/GrStencilAndCoverTextContext.h',
'<(skia_src_path)/gpu/GrStencilBuffer.cpp', '<(skia_src_path)/gpu/GrStencilBuffer.cpp',
'<(skia_src_path)/gpu/GrStencilBuffer.h', '<(skia_src_path)/gpu/GrStencilBuffer.h',
'<(skia_src_path)/gpu/GrStrokeInfo.h', '<(skia_src_path)/gpu/GrStrokeInfo.h',

View File

@ -1136,6 +1136,7 @@ private:
friend class SkPDFDevice; friend class SkPDFDevice;
friend class GrBitmapTextContext; friend class GrBitmapTextContext;
friend class GrDistanceFieldTextContext; friend class GrDistanceFieldTextContext;
friend class GrStencilAndCoverTextContext;
friend class SkTextToPathIter; friend class SkTextToPathIter;
friend class SkCanonicalizePaint; friend class SkCanonicalizePaint;

View File

@ -38,6 +38,7 @@ class GrResourceEntry;
class GrResourceCache; class GrResourceCache;
class GrStencilBuffer; class GrStencilBuffer;
class GrTestTarget; class GrTestTarget;
class GrTextContext;
class GrTextureParams; class GrTextureParams;
class GrVertexBuffer; class GrVertexBuffer;
class GrVertexBufferAllocPool; class GrVertexBufferAllocPool;
@ -194,6 +195,16 @@ public:
*/ */
GrCacheable* findAndRefCachedResource(const GrResourceKey&); GrCacheable* findAndRefCachedResource(const GrResourceKey&);
/**
* Creates a new text rendering context that is optimal for the
* render target and the context. Caller assumes the ownership
* of the returned object. The returned object must be deleted
* before the context is destroyed.
*/
GrTextContext* createTextContext(GrRenderTarget*,
const SkDeviceProperties&,
bool enableDistanceFieldFonts);
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Textures // Textures
@ -1018,6 +1029,7 @@ private:
// addExistingTextureToCache // addExistingTextureToCache
friend class GrTexture; friend class GrTexture;
friend class GrStencilAndCoverPathRenderer; friend class GrStencilAndCoverPathRenderer;
friend class GrStencilAndCoverTextContext;
// Add an existing texture to the texture cache. This is intended solely // Add an existing texture to the texture cache. This is intended solely
// for use with textures released from an GrAutoScratchTexture. // for use with textures released from an GrAutoScratchTexture.

View File

@ -214,6 +214,7 @@ private:
} }
friend class GrContext; // To access above two functions friend class GrContext; // To access above two functions
friend class GrStencilAndCoverTextContext; // To access above two functions
SkSTArray<4, GrEffectStage> fColorStages; SkSTArray<4, GrEffectStage> fColorStages;
SkSTArray<2, GrEffectStage> fCoverageStages; SkSTArray<2, GrEffectStage> fCoverageStages;

15
src/gpu/GrContext.cpp Normal file → Executable file
View File

@ -16,6 +16,7 @@
#include "GrAARectRenderer.h" #include "GrAARectRenderer.h"
#include "GrBufferAllocPool.h" #include "GrBufferAllocPool.h"
#include "GrGpu.h" #include "GrGpu.h"
#include "GrDistanceFieldTextContext.h"
#include "GrDrawTargetCaps.h" #include "GrDrawTargetCaps.h"
#include "GrIndexBuffer.h" #include "GrIndexBuffer.h"
#include "GrInOrderDrawBuffer.h" #include "GrInOrderDrawBuffer.h"
@ -26,6 +27,7 @@
#include "GrResourceCache.h" #include "GrResourceCache.h"
#include "GrSoftwarePathRenderer.h" #include "GrSoftwarePathRenderer.h"
#include "GrStencilBuffer.h" #include "GrStencilBuffer.h"
#include "GrStencilAndCoverTextContext.h"
#include "GrStrokeInfo.h" #include "GrStrokeInfo.h"
#include "GrTextStrike.h" #include "GrTextStrike.h"
#include "GrTraceMarker.h" #include "GrTraceMarker.h"
@ -236,6 +238,19 @@ void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes)
} }
} }
GrTextContext* GrContext::createTextContext(GrRenderTarget* renderTarget,
const SkDeviceProperties&
leakyProperties,
bool enableDistanceFieldFonts) {
if (fGpu->caps()->pathRenderingSupport()) {
if (renderTarget->getStencilBuffer() && renderTarget->isMultisampled()) {
return SkNEW_ARGS(GrStencilAndCoverTextContext, (this, leakyProperties));
}
}
return SkNEW_ARGS(GrDistanceFieldTextContext, (this, leakyProperties,
enableDistanceFieldFonts));
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
GrTexture* GrContext::findAndRefTexture(const GrTextureDesc& desc, GrTexture* GrContext::findAndRefTexture(const GrTextureDesc& desc,

View File

@ -0,0 +1,372 @@
/*
* 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 "GrStencilAndCoverTextContext.h"
#include "GrDrawTarget.h"
#include "GrFontScaler.h"
#include "GrGpu.h"
#include "GrPath.h"
#include "GrTextStrike.h"
#include "GrTextStrike_impl.h"
#include "SkAutoKern.h"
#include "SkDraw.h"
#include "SkDrawProcs.h"
#include "SkGlyphCache.h"
#include "SkGpuDevice.h"
#include "SkPath.h"
#include "SkTextMapStateProc.h"
static const int kMaxReservedGlyphs = 64;
GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
GrContext* context, const SkDeviceProperties& properties)
: GrTextContext(context, properties)
, fStroke(SkStrokeRec::kFill_InitStyle) {
}
GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
}
void GrStencilAndCoverTextContext::drawText(const GrPaint& paint,
const SkPaint& skPaint,
const char text[],
size_t byteLength,
SkScalar x, SkScalar y) {
SkASSERT(byteLength == 0 || text != NULL);
if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
return;
}
// This is the slow path, mainly used by Skia unit tests. The other
// backends (8888, gpu, ...) use device-space dependent glyph caches. In
// order to match the glyph positions that the other code paths produce, we
// must also use device-space dependent glyph cache. This has the
// side-effect that the glyph shape outline will be in device-space,
// too. This in turn has the side-effect that NVPR can not stroke the paths,
// as the stroke in NVPR is defined in object-space.
// NOTE: here we have following coincidence that works at the moment:
// - When using the device-space glyphs, the transforms we pass to NVPR
// instanced drawing are the global transforms, and the view transform is
// identity. NVPR can not use non-affine transforms in the instanced
// drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it
// will turn off the use of device-space glyphs when perspective transforms
// are in use.
fGlyphTransform = fContext->getMatrix();
this->init(paint, skPaint, byteLength);
SkMatrix* glyphCacheTransform = NULL;
// Transform our starting point.
if (fNeedsDeviceSpaceGlyphs) {
SkPoint loc;
fGlyphTransform.mapXY(x, y, &loc);
x = loc.fX;
y = loc.fY;
glyphCacheTransform = &fGlyphTransform;
}
SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform);
SkGlyphCache* cache = autoCache.getCache();
GrFontScaler* scaler = GetGrFontScaler(cache);
GrTextStrike* strike =
fContext->getFontCache()->getStrike(scaler, true);
const char* stop = text + byteLength;
// Measure first if needed.
if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
SkFixed stopX = 0;
SkFixed stopY = 0;
const char* textPtr = text;
while (textPtr < stop) {
// We don't need x, y here, since all subpixel variants will have the
// same advance.
const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
stopX += glyph.fAdvanceX;
stopY += glyph.fAdvanceY;
}
SkASSERT(textPtr == stop);
SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
alignX = SkScalarHalf(alignX);
alignY = SkScalarHalf(alignY);
}
x -= alignX;
y -= alignY;
}
SkAutoKern autokern;
SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
SkFixed fx = SkScalarToFixed(x);
SkFixed fy = SkScalarToFixed(y);
while (text < stop) {
const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
if (glyph.fWidth) {
this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
glyph.getSubXFixed(),
glyph.getSubYFixed()),
SkPoint::Make(
SkFixedToScalar(fx),
SkFixedToScalar(fy)),
strike,
scaler);
}
fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio);
}
this->finish();
}
void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint,
const SkPaint& skPaint,
const char text[],
size_t byteLength,
const SkScalar pos[],
SkScalar constY,
int scalarsPerPosition) {
SkASSERT(byteLength == 0 || text != NULL);
SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
// nothing to draw
if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
return;
}
// This is the fast path. Here we do not bake in the device-transform to
// the glyph outline or the advances. This is because we do not need to
// position the glyphs at all, since the caller has done the positioning.
// The positioning is based on SkPaint::measureText of individual
// glyphs. That already uses glyph cache without device transforms. Device
// transform is not part of SkPaint::measureText API, and thus we use the
// same glyphs as what were measured.
fGlyphTransform.reset();
this->init(paint, skPaint, byteLength);
SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
SkGlyphCache* cache = autoCache.getCache();
GrFontScaler* scaler = GetGrFontScaler(cache);
GrTextStrike* strike =
fContext->getFontCache()->getStrike(scaler, true);
const char* stop = text + byteLength;
SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
SkTextMapStateProc tmsProc(SkMatrix::I(), constY, scalarsPerPosition);
if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
while (text < stop) {
SkPoint loc;
tmsProc(pos, &loc);
const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
if (glyph.fWidth) {
this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
glyph.getSubXFixed(),
glyph.getSubYFixed()),
loc,
strike,
scaler);
}
pos += scalarsPerPosition;
}
} else {
while (text < stop) {
const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
if (glyph.fWidth) {
SkPoint tmsLoc;
tmsProc(pos, &tmsLoc);
SkPoint loc;
alignProc(tmsLoc, glyph, &loc);
this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
glyph.getSubXFixed(),
glyph.getSubYFixed()),
loc,
strike,
scaler);
}
pos += scalarsPerPosition;
}
}
this->finish();
}
bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
if (paint.getRasterizer()) {
return false;
}
if (paint.getMaskFilter()) {
return false;
}
if (paint.getPathEffect()) {
return false;
}
// No hairlines unless we can map the 1 px width to the object space.
if (paint.getStyle() == SkPaint::kStroke_Style
&& paint.getStrokeWidth() == 0
&& fContext->getMatrix().hasPerspective()) {
return false;
}
// No color bitmap fonts.
SkScalerContext::Rec rec;
SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
return rec.getFormat() != SkMask::kARGB32_Format;
}
void GrStencilAndCoverTextContext::init(const GrPaint& paint,
const SkPaint& skPaint,
size_t textByteLength) {
GrTextContext::init(paint, skPaint);
bool otherBackendsWillDrawAsPaths =
SkDraw::ShouldDrawTextAsPaths(skPaint, fContext->getMatrix());
if (otherBackendsWillDrawAsPaths) {
// This is to reproduce SkDraw::drawText_asPaths glyph positions.
fSkPaint.setLinearText(true);
fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
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 = 1.0f;
fNeedsDeviceSpaceGlyphs = (fGlyphTransform.getType() &
(SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0;
// SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
SkASSERT(!fGlyphTransform.hasPerspective());
if (fNeedsDeviceSpaceGlyphs) {
fPaint.localCoordChangeInverse(fGlyphTransform);
fContext->setIdentityMatrix();
}
}
fStroke = SkStrokeRec(fSkPaint);
if (fNeedsDeviceSpaceGlyphs) {
// The whole shape is baked into the glyph. Make NVPR just fill the
// baked shape.
fStroke.setStrokeStyle(-1, false);
} else {
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 /
(fTextRatio * 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.
fSkPaint.setStyle(SkPaint::kFill_Style);
}
fStateRestore.set(fDrawTarget->drawState());
fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(),
fContext->getRenderTarget());
GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
kZero_StencilOp,
kZero_StencilOp,
kNotEqual_StencilFunc,
0xffff,
0x0000,
0xffff);
*fDrawTarget->drawState()->stencil() = kStencilPass;
size_t reserveAmount;
switch (skPaint.getTextEncoding()) {
default:
SkASSERT(false);
case SkPaint::kUTF8_TextEncoding:
reserveAmount = textByteLength;
break;
case SkPaint::kUTF16_TextEncoding:
reserveAmount = textByteLength / 2;
break;
case SkPaint::kUTF32_TextEncoding:
case SkPaint::kGlyphID_TextEncoding:
reserveAmount = textByteLength / 4;
break;
}
fPaths.setReserve(reserveAmount);
fTransforms.setReserve(reserveAmount);
}
inline void GrStencilAndCoverTextContext::appendGlyph(GrGlyph::PackedID glyphID,
const SkPoint& pos,
GrTextStrike* strike,
GrFontScaler* scaler) {
GrGlyph* glyph = strike->getGlyph(glyphID, scaler);
if (NULL == glyph || glyph->fBounds.isEmpty()) {
return;
}
if (scaler->getGlyphPath(glyph->glyphID(), &fTmpPath)) {
if (!fTmpPath.isEmpty()) {
*fPaths.append() = fContext->createPath(fTmpPath, fStroke);
SkMatrix* t = fTransforms.append();
t->setTranslate(pos.fX, pos.fY);
t->preScale(fTextRatio, fTextRatio);
}
}
}
void GrStencilAndCoverTextContext::finish() {
if (fPaths.count() > 0) {
fDrawTarget->drawPaths(static_cast<size_t>(fPaths.count()),
fPaths.begin(), fTransforms.begin(),
SkPath::kWinding_FillType, fStroke.getStyle());
for (int i = 0; i < fPaths.count(); ++i) {
fPaths[i]->unref();
}
if (fPaths.count() > kMaxReservedGlyphs) {
fPaths.reset();
fTransforms.reset();
} else {
fPaths.rewind();
fTransforms.rewind();
}
}
fTmpPath.reset();
fDrawTarget->drawState()->stencil()->setDisabled();
fStateRestore.set(NULL);
if (fNeedsDeviceSpaceGlyphs) {
fContext->setMatrix(fGlyphTransform);
}
GrTextContext::finish();
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrStencilAndCoverTextContext_DEFINED
#define GrStencilAndCoverTextContext_DEFINED
#include "GrTextContext.h"
#include "GrDrawState.h"
#include "SkStrokeRec.h"
class GrTextStrike;
class GrPath;
/*
* This class implements text rendering using stencil and cover path rendering
* (by the means of GrDrawTarget::drawPath).
* This class exposes the functionality through GrTextContext interface.
*/
class GrStencilAndCoverTextContext : public GrTextContext {
public:
GrStencilAndCoverTextContext(GrContext*, const SkDeviceProperties&);
virtual ~GrStencilAndCoverTextContext();
virtual void drawText(const GrPaint&, const SkPaint&, const char text[],
size_t byteLength,
SkScalar x, SkScalar y) SK_OVERRIDE;
virtual void drawPosText(const GrPaint&, const SkPaint&,
const char text[], size_t byteLength,
const SkScalar pos[], SkScalar constY,
int scalarsPerPosition) SK_OVERRIDE;
virtual bool canDraw(const SkPaint& paint) SK_OVERRIDE;
private:
void init(const GrPaint&, const SkPaint&, size_t textByteLength);
void appendGlyph(GrGlyph::PackedID, const SkPoint&,
GrTextStrike*, GrFontScaler*);
void finish();
GrDrawState::AutoRestoreEffects fStateRestore;
SkScalar fTextRatio;
SkStrokeRec fStroke;
SkTDArray<const GrPath*> fPaths;
SkTDArray<SkMatrix> fTransforms;
SkPath fTmpPath;
SkMatrix fGlyphTransform;
bool fNeedsDeviceSpaceGlyphs;
};
#endif

View File

@ -167,11 +167,6 @@ void SkGpuDevice::initFromRenderTarget(GrContext* context,
fContext = context; fContext = context;
fContext->ref(); fContext->ref();
bool useDFFonts = !!(flags & kDFFonts_Flag);
fMainTextContext = SkNEW_ARGS(GrDistanceFieldTextContext, (fContext, fLeakyProperties,
useDFFonts));
fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties));
fRenderTarget = NULL; fRenderTarget = NULL;
fNeedClear = flags & kNeedClear_Flag; fNeedClear = flags & kNeedClear_Flag;
@ -192,6 +187,10 @@ void SkGpuDevice::initFromRenderTarget(GrContext* context,
(surface->info(), surface, SkToBool(flags & kCached_Flag))); (surface->info(), surface, SkToBool(flags & kCached_Flag)));
this->setPixelRef(pr)->unref(); this->setPixelRef(pr)->unref();
bool useDFFonts = !!(flags & kDFFonts_Flag);
fMainTextContext = fContext->createTextContext(fRenderTarget, fLeakyProperties, useDFFonts);
fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties));
} }
SkGpuDevice* SkGpuDevice::Create(GrContext* context, const SkImageInfo& origInfo, SkGpuDevice* SkGpuDevice::Create(GrContext* context, const SkImageInfo& origInfo,

View File

@ -1706,7 +1706,8 @@ DWORD SkScalerContext_GDI::getGDIGlyphPath(const SkGlyph& glyph, UINT flags,
LogFontTypeface::EnsureAccessible(this->getTypeface()); LogFontTypeface::EnsureAccessible(this->getTypeface());
total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22); total_size = GetGlyphOutlineW(fDDC, glyph.fID, flags, &gm, 0, NULL, &fMat22);
if (GDI_ERROR == total_size) { if (GDI_ERROR == total_size) {
SkASSERT(false); // GetGlyphOutlineW is known to fail for some characters, such as spaces.
// In these cases, just return that the glyph does not have a shape.
return 0; return 0;
} }
} }