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 large chunks of code from GrBitmapTextContext (drawText) and GrDistanceFieldTextContext (drawPosText). 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. R=jvanverth@google.com, bsalomon@google.com Author: kkinnunen@nvidia.com Review URL: https://codereview.chromium.org/196133014 git-svn-id: http://skia.googlecode.com/svn/trunk@13962 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
parent
db35dab49e
commit
dcb8ef9e86
@ -110,6 +110,8 @@
|
||||
'<(skia_src_path)/gpu/GrStencil.h',
|
||||
'<(skia_src_path)/gpu/GrStencilAndCoverPathRenderer.cpp',
|
||||
'<(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.h',
|
||||
'<(skia_src_path)/gpu/GrTBSearch.h',
|
||||
|
@ -1144,6 +1144,7 @@ private:
|
||||
friend class SkPDFDevice;
|
||||
friend class GrBitmapTextContext;
|
||||
friend class GrDistanceFieldTextContext;
|
||||
friend class GrStencilAndCoverTextContext;
|
||||
friend class SkTextToPathIter;
|
||||
friend class SkCanonicalizePaint;
|
||||
|
||||
|
@ -35,6 +35,7 @@ class GrResourceEntry;
|
||||
class GrResourceCache;
|
||||
class GrStencilBuffer;
|
||||
class GrTestTarget;
|
||||
class GrTextContext;
|
||||
class GrTextureParams;
|
||||
class GrVertexBuffer;
|
||||
class GrVertexBufferAllocPool;
|
||||
@ -116,6 +117,15 @@ public:
|
||||
*/
|
||||
int getGpuTextureCacheResourceCount() const;
|
||||
|
||||
/**
|
||||
* 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&);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Textures
|
||||
|
||||
@ -954,6 +964,7 @@ private:
|
||||
// addExistingTextureToCache
|
||||
friend class GrTexture;
|
||||
friend class GrStencilAndCoverPathRenderer;
|
||||
friend class GrStencilAndCoverTextContext;
|
||||
|
||||
// Add an existing texture to the texture cache. This is intended solely
|
||||
// for use with textures released from an GrAutoScratchTexture.
|
||||
|
@ -14,24 +14,26 @@
|
||||
|
||||
#include "GrAARectRenderer.h"
|
||||
#include "GrBufferAllocPool.h"
|
||||
#include "GrGpu.h"
|
||||
#include "GrDistanceFieldTextContext.h"
|
||||
#include "GrDrawTargetCaps.h"
|
||||
#include "GrIndexBuffer.h"
|
||||
#include "GrGpu.h"
|
||||
#include "GrInOrderDrawBuffer.h"
|
||||
#include "GrIndexBuffer.h"
|
||||
#include "GrOvalRenderer.h"
|
||||
#include "GrPathRenderer.h"
|
||||
#include "GrPathUtils.h"
|
||||
#include "GrResourceCache.h"
|
||||
#include "GrSoftwarePathRenderer.h"
|
||||
#include "GrStencilAndCoverTextContext.h"
|
||||
#include "GrStencilBuffer.h"
|
||||
#include "GrTextStrike.h"
|
||||
#include "GrTracing.h"
|
||||
#include "SkGr.h"
|
||||
#include "SkRTConf.h"
|
||||
#include "SkRRect.h"
|
||||
#include "SkRTConf.h"
|
||||
#include "SkStrokeRec.h"
|
||||
#include "SkTLazy.h"
|
||||
#include "SkTLS.h"
|
||||
#include "SkTLazy.h"
|
||||
#include "SkTraceEvent.h"
|
||||
|
||||
// It can be useful to set this to false to test whether a bug is caused by using the
|
||||
@ -227,6 +229,17 @@ int GrContext::getGpuTextureCacheResourceCount() const {
|
||||
return fTextureCache->getCachedResourceCount();
|
||||
}
|
||||
|
||||
GrTextContext* GrContext::createTextContext(GrRenderTarget* renderTarget,
|
||||
const SkDeviceProperties&
|
||||
leakyProperties) {
|
||||
if (fGpu->caps()->pathRenderingSupport()) {
|
||||
if (renderTarget->getStencilBuffer() && renderTarget->isMultisampled()) {
|
||||
return SkNEW_ARGS(GrStencilAndCoverTextContext, (this, leakyProperties));
|
||||
}
|
||||
}
|
||||
return SkNEW_ARGS(GrDistanceFieldTextContext, (this, leakyProperties));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GrTexture* GrContext::findAndRefTexture(const GrTextureDesc& desc,
|
||||
|
289
src/gpu/GrStencilAndCoverTextContext.cpp
Normal file
289
src/gpu/GrStencilAndCoverTextContext.cpp
Normal file
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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 "SkGlyphCache.h"
|
||||
#include "SkGpuDevice.h"
|
||||
#include "SkPath.h"
|
||||
|
||||
static const int kBaseSCFontSize = 64;
|
||||
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);
|
||||
|
||||
// nothing to draw
|
||||
if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Measure first if needed.
|
||||
if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
|
||||
SkFixed stopX = 0;
|
||||
SkFixed stopY = 0;
|
||||
|
||||
const char* textPtr = text;
|
||||
while (textPtr < stop) {
|
||||
// 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 fx = SkScalarToFixed(x) + SK_FixedHalf;
|
||||
SkFixed fy = SkScalarToFixed(y) + SK_FixedHalf;
|
||||
SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
|
||||
|
||||
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->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;
|
||||
|
||||
SkTDArray<const GrPath*> paths;
|
||||
SkTDArray<SkMatrix> transforms;
|
||||
|
||||
if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
|
||||
while (text < stop) {
|
||||
// the last 2 parameters are ignored
|
||||
const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
|
||||
|
||||
if (glyph.fWidth) {
|
||||
SkScalar x = pos[0];
|
||||
SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
|
||||
this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
|
||||
glyph.getSubXFixed(),
|
||||
glyph.getSubYFixed()),
|
||||
SkPoint::Make(x, y),
|
||||
strike,
|
||||
scaler);
|
||||
}
|
||||
|
||||
pos += scalarsPerPosition;
|
||||
}
|
||||
} else {
|
||||
int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
|
||||
while (text < stop) {
|
||||
// the last 2 parameters are ignored
|
||||
const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
|
||||
|
||||
if (glyph.fWidth) {
|
||||
SkScalar x = pos[0];
|
||||
SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
|
||||
x -= SkFixedToScalar((glyph.fAdvanceX >> alignShift));
|
||||
y -= SkFixedToScalar((glyph.fAdvanceY >> alignShift));
|
||||
|
||||
this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(),
|
||||
glyph.getSubXFixed(),
|
||||
glyph.getSubYFixed()),
|
||||
SkPoint::Make(x, y),
|
||||
strike,
|
||||
scaler);
|
||||
}
|
||||
pos += scalarsPerPosition;
|
||||
}
|
||||
}
|
||||
|
||||
this->finish();
|
||||
}
|
||||
|
||||
bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
|
||||
return !paint.getRasterizer()
|
||||
&& !paint.getMaskFilter()
|
||||
&& (paint.getStyle() == SkPaint::kFill_Style
|
||||
|| paint.getStrokeWidth() > 0);
|
||||
|
||||
}
|
||||
|
||||
static bool has_thick_frame(const SkPaint& paint) {
|
||||
return paint.getStrokeWidth() > 0 &&
|
||||
paint.getStyle() != SkPaint::kFill_Style;
|
||||
}
|
||||
|
||||
void GrStencilAndCoverTextContext::init(const GrPaint& paint,
|
||||
const SkPaint& skPaint,
|
||||
size_t textByteLength) {
|
||||
GrTextContext::init(paint, skPaint);
|
||||
fTextRatio = fSkPaint.getTextSize() / kBaseSCFontSize;
|
||||
fSkPaint.setTextSize(SkIntToScalar(kBaseSCFontSize));
|
||||
fSkPaint.setLCDRenderText(false);
|
||||
fSkPaint.setAutohinted(false);
|
||||
fSkPaint.setSubpixelText(true);
|
||||
if (has_thick_frame(fSkPaint)) {
|
||||
// Compensate the glyphs being scaled up by fTextRatio, by scaling the
|
||||
// stroke down.
|
||||
fSkPaint.setStrokeWidth(fSkPaint.getStrokeWidth() / fTextRatio);
|
||||
}
|
||||
fStroke = SkStrokeRec(fSkPaint);
|
||||
|
||||
// Make glyph cache produce paths geometry for fill. We will stroke them
|
||||
// by passing fStroke to drawPath.
|
||||
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 (scaler->getGlyphPath(glyph->glyphID(), &fTmpPath)) {
|
||||
*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);
|
||||
GrTextContext::finish();
|
||||
}
|
||||
|
52
src/gpu/GrStencilAndCoverTextContext.h
Normal file
52
src/gpu/GrStencilAndCoverTextContext.h
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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;
|
||||
};
|
||||
|
||||
#endif
|
@ -188,9 +188,6 @@ void SkGpuDevice::initFromRenderTarget(GrContext* context,
|
||||
fContext = context;
|
||||
fContext->ref();
|
||||
|
||||
fMainTextContext = SkNEW_ARGS(GrDistanceFieldTextContext, (fContext, fLeakyProperties));
|
||||
fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties));
|
||||
|
||||
fRenderTarget = NULL;
|
||||
fNeedClear = flags & kNeedClear_Flag;
|
||||
|
||||
@ -212,6 +209,9 @@ void SkGpuDevice::initFromRenderTarget(GrContext* context,
|
||||
SkPixelRef* pr = SkNEW_ARGS(SkGrPixelRef, (info, surface, SkToBool(flags & kCached_Flag)));
|
||||
|
||||
this->setPixelRef(pr)->unref();
|
||||
|
||||
fMainTextContext = fContext->createTextContext(fRenderTarget, fLeakyProperties);
|
||||
fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties));
|
||||
}
|
||||
|
||||
SkGpuDevice* SkGpuDevice::Create(GrContext* context, const SkImageInfo& origInfo,
|
||||
@ -268,9 +268,6 @@ SkGpuDevice::SkGpuDevice(GrContext* context,
|
||||
fContext = context;
|
||||
fContext->ref();
|
||||
|
||||
fMainTextContext = SkNEW_ARGS(GrDistanceFieldTextContext, (fContext, fLeakyProperties));
|
||||
fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties));
|
||||
|
||||
fRenderTarget = NULL;
|
||||
fNeedClear = false;
|
||||
|
||||
@ -309,6 +306,9 @@ SkGpuDevice::SkGpuDevice(GrContext* context,
|
||||
width, height);
|
||||
SkASSERT(false);
|
||||
}
|
||||
|
||||
fMainTextContext = fContext->createTextContext(fRenderTarget, fLeakyProperties);
|
||||
fFallbackTextContext = SkNEW_ARGS(GrBitmapTextContext, (fContext, fLeakyProperties));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user