blob: c6b6739228610f99b8e457c2fd3d67addaef6ea3 [file] [log] [blame]
/*
* 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 GrTextBlob_DEFINED
#define GrTextBlob_DEFINED
#include "include/core/SkPathEffect.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkSurfaceProps.h"
#include "src/core/SkDescriptor.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkOpts.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkStrikeSpec.h"
#include "src/core/SkTInternalLList.h"
#include "src/gpu/GrColor.h"
#include "src/gpu/GrDrawOpAtlas.h"
#include "src/gpu/text/GrStrikeCache.h"
#include "src/gpu/text/GrTextContext.h"
#include "src/gpu/text/GrTextTarget.h"
class GrAtlasManager;
class GrAtlasTextOp;
struct GrDistanceFieldAdjustTable;
struct GrGlyph;
class SkTextBlob;
class SkTextBlobRunIterator;
// With this flag enabled, the GrTextContext will, as a sanity check, regenerate every blob
// that comes in to verify the integrity of its cache
// This is of dubious value, and maybe should be removed. I checked it on 11/21/2019, and many
// tests failed.
#define CACHE_SANITY_CHECK 0
/*
* A GrTextBlob 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 GrTextBlob 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 GrTextBlob is to ensure that
* the GrAtlas will not evict anything the Blob needs.
*
* *WARNING* If you add new fields to this struct, then you may need to to update AssertEqual
*/
class GrTextBlob : public SkNVRefCnt<GrTextBlob>, public SkGlyphRunPainterInterface {
public:
class SubRun;
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrTextBlob);
using SubRunBufferSpec = std::tuple<uint32_t, uint32_t, size_t, size_t>;
class VertexRegenerator;
void generateFromGlyphRunList(const GrShaderCaps& shaderCaps,
const GrTextContext::Options& options,
const SkPaint& paint,
const SkMatrix& viewMatrix,
const SkSurfaceProps& props,
const SkGlyphRunList& glyphRunList,
SkGlyphRunListPainter* glyphPainter);
// Make an empty GrTextBlob, with all the invariants set to make the right decisions when
// adding SubRuns.
static sk_sp<GrTextBlob> Make(
int glyphCount,
bool forceWForDistanceFields,
GrColor color,
GrStrikeCache* strikeCache);
struct Key {
Key() {
sk_bzero(this, sizeof(Key));
}
uint32_t fUniqueID;
// Color may affect the gamma of the mask we generate, but in a fairly limited way.
// Each color is assigned to on of a fixed number of buckets based on its
// luminance. For each luminance bucket there is a "canonical color" that
// represents the bucket. This functionality is currently only supported for A8
SkColor fCanonicalColor;
SkPaint::Style fStyle;
SkPixelGeometry fPixelGeometry;
bool fHasBlur;
uint32_t fScalerContextFlags;
bool operator==(const Key& other) const {
return 0 == memcmp(this, &other, sizeof(Key));
}
};
void setupKey(const GrTextBlob::Key& key,
const SkMaskFilterBase::BlurRec& blurRec,
const SkPaint& paint) {
fKey = key;
if (key.fHasBlur) {
fBlurRec = blurRec;
}
if (key.fStyle != SkPaint::kFill_Style) {
fStrokeInfo.fFrameWidth = paint.getStrokeWidth();
fStrokeInfo.fMiterLimit = paint.getStrokeMiter();
fStrokeInfo.fJoin = paint.getStrokeJoin();
}
}
static const Key& GetKey(const GrTextBlob& blob) {
return blob.fKey;
}
static uint32_t Hash(const Key& key) {
return SkOpts::hash(&key, sizeof(Key));
}
void operator delete(void* p) {
::operator delete(p);
}
void* operator new(size_t) {
SK_ABORT("All blobs are created by placement new.");
}
void* operator new(size_t, void* p) { return p; }
bool hasDistanceField() const { return SkToBool(fTextType & kHasDistanceField_TextType); }
bool hasBitmap() const { return SkToBool(fTextType & kHasBitmap_TextType); }
void setHasDistanceField() { fTextType |= kHasDistanceField_TextType; }
void setHasBitmap() { fTextType |= kHasBitmap_TextType; }
void setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax) {
// we init fMaxMinScale and fMinMaxScale in the constructor
fMaxMinScale = SkMaxScalar(scaledMin, fMaxMinScale);
fMinMaxScale = SkMinScalar(scaledMax, fMinMaxScale);
}
bool hasPerspective() const {
return fInitialViewMatrix.hasPerspective();
}
static size_t GetVertexStride(GrMaskFormat maskFormat, bool hasWCoord) {
switch (maskFormat) {
case kA8_GrMaskFormat:
return hasWCoord ? kGrayTextDFPerspectiveVASize : kGrayTextVASize;
case kARGB_GrMaskFormat:
return hasWCoord ? kColorTextPerspectiveVASize : kColorTextVASize;
default:
SkASSERT(!hasWCoord);
return kLCDTextVASize;
}
}
bool mustRegenerate(const SkPaint&, bool, const SkMaskFilterBase::BlurRec& blurRec,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
void flush(GrTextTarget*, const SkSurfaceProps& props,
const GrDistanceFieldAdjustTable* distanceAdjustTable,
const SkPaint& paint, const SkPMColor4f& filteredColor, const GrClip& clip,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
void computeSubRunBounds(SkRect* outBounds, const SubRun& subRun,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
bool needsGlyphTransform) {
// We don't yet position distance field text on the cpu, so we have to map the vertex bounds
// into device space.
// We handle vertex bounds differently for distance field text and bitmap text because
// the vertex bounds of bitmap text are in device space. If we are flushing multiple runs
// from one blob then we are going to pay the price here of mapping the rect for each run.
*outBounds = subRun.vertexBounds();
if (needsGlyphTransform) {
// Distance field text is positioned with the (X,Y) as part of the glyph position,
// and currently the view matrix is applied on the GPU
outBounds->offset(x - fInitialX, y - fInitialY);
viewMatrix.mapRect(outBounds);
} else {
// Bitmap text is fully positioned on the CPU, and offset by an (X,Y) translate in
// device space.
SkMatrix boundsMatrix = fInitialViewMatrixInverse;
boundsMatrix.postTranslate(-fInitialX, -fInitialY);
boundsMatrix.postTranslate(x, y);
boundsMatrix.postConcat(viewMatrix);
boundsMatrix.mapRect(outBounds);
// Due to floating point numerical inaccuracies, we have to round out here
outBounds->roundOut(outBounds);
}
}
// position + local coord
static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
static const size_t kColorTextPerspectiveVASize = sizeof(SkPoint3) + sizeof(SkIPoint16);
static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16);
static const size_t kGrayTextDFPerspectiveVASize =
sizeof(SkPoint3) + sizeof(GrColor) + sizeof(SkIPoint16);
static const size_t kLCDTextVASize = kGrayTextVASize;
static const size_t kMaxVASize = kGrayTextDFPerspectiveVASize;
static const int kVerticesPerGlyph = 4;
static void AssertEqual(const GrTextBlob&, const GrTextBlob&);
// The color here is the GrPaint color, and it is used to determine whether we
// have to regenerate LCD text blobs.
// We use this color vs the SkPaint color because it has the colorfilter applied.
void initReusableBlob(SkColor luminanceColor, const SkMatrix& viewMatrix,
SkScalar x, SkScalar y) {
fLuminanceColor = luminanceColor;
this->setupViewMatrix(viewMatrix, x, y);
}
void initThrowawayBlob(const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
this->setupViewMatrix(viewMatrix, x, y);
}
const Key& key() const { return fKey; }
size_t size() const { return fSize; }
~GrTextBlob() override { }
////////////////////////////////////////////////////////////////////////////////////////////////
// Internal test methods
std::unique_ptr<GrDrawOp> test_makeOp(int glyphCount,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
const SkPaint& paint, const SkPMColor4f& filteredColor,
const SkSurfaceProps&, const GrDistanceFieldAdjustTable*,
GrTextTarget*);
private:
GrTextBlob(GrStrikeCache* strikeCache, GrColor color, bool forceWForDistanceFields)
: fColor{color}
, fStrikeCache{strikeCache}
, fForceWForDistanceFields{forceWForDistanceFields} { }
// This function will only be called when we are generating a blob from scratch. We record the
// initial view matrix and initial offsets(x,y), because we record vertex bounds relative to
// these numbers. When blobs are reused with new matrices, we need to return to model space so
// we can update the vertex bounds appropriately.
void setupViewMatrix(const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
fInitialViewMatrix = viewMatrix;
if (!viewMatrix.invert(&fInitialViewMatrixInverse)) {
fInitialViewMatrixInverse = SkMatrix::I();
}
fInitialX = x;
fInitialY = y;
}
public:
// Any glyphs that can't be rendered with the base or override descriptor
// are rendered as paths
struct PathGlyph {
PathGlyph(const SkPath& path, SkPoint origin)
: fPath(path)
, fOrigin(origin) {}
SkPath fPath;
SkPoint fOrigin;
};
enum SubRunType {
kDirectMask,
kTransformedMask,
kTransformedPath,
kTransformedSDFT
};
bool hasW(SubRunType type) const {
if (type == kTransformedSDFT) {
return this->hasPerspective() || fForceWForDistanceFields;
} else if (type == kTransformedMask || type == kTransformedPath) {
return this->hasPerspective();
}
// The viewMatrix is implicitly SkMatrix::I when drawing kDirectMask, because it is not
// used.
return false;
}
// Hold data to draw the different types of sub run. SubRuns are produced knowing all the
// glyphs that are included in them.
class SubRun {
public:
// SubRun for masks
SubRun(SubRunType type,
GrTextBlob* textBlob,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
const SubRunBufferSpec& bufferSpec,
sk_sp<GrTextStrike>&& grStrike);
// SubRun for paths
SubRun(GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec);
void appendGlyphs(const SkZip<SkGlyphVariant, SkPoint>& drawables);
// TODO when this object is more internal, drop the privacy
void resetBulkUseToken() { fBulkUseToken.reset(); }
GrDrawOpAtlas::BulkUseTokenUpdater* bulkUseToken() { return &fBulkUseToken; }
void setStrike(sk_sp<GrTextStrike> strike) { fStrike = std::move(strike); }
GrTextStrike* strike() const { return fStrike.get(); }
sk_sp<GrTextStrike> refStrike() const { return fStrike; }
void setAtlasGeneration(uint64_t atlasGeneration) { fAtlasGeneration = atlasGeneration;}
uint64_t atlasGeneration() const { return fAtlasGeneration; }
size_t vertexStartIndex() const { return fVertexStartIndex; }
uint32_t glyphCount() const { return fGlyphEndIndex - fGlyphStartIndex; }
uint32_t glyphStartIndex() const { return fGlyphStartIndex; }
void setColor(GrColor color) { fColor = color; }
GrColor color() const { return fColor; }
GrMaskFormat maskFormat() const { return fMaskFormat; }
const SkRect& vertexBounds() const { return fVertexBounds; }
void joinGlyphBounds(const SkRect& glyphBounds) {
fVertexBounds.joinNonEmptyArg(glyphBounds);
}
void init(const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
fCurrentViewMatrix = viewMatrix;
fX = x;
fY = y;
}
// This function assumes the translation will be applied before it is called again
void computeTranslation(const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
SkScalar* transX, SkScalar* transY);
bool drawAsDistanceFields() const { return fType == kTransformedSDFT; }
bool drawAsPaths() const { return fType == kTransformedPath; }
bool needsTransform() const {
return fType == kTransformedPath
|| fType == kTransformedMask
|| fType == kTransformedSDFT;
}
bool hasW() const {
return fBlob->hasW(fType);
}
// df properties
void setUseLCDText(bool useLCDText) { fFlags.useLCDText = useLCDText; }
bool hasUseLCDText() const { return fFlags.useLCDText; }
void setAntiAliased(bool antiAliased) { fFlags.antiAliased = antiAliased; }
bool isAntiAliased() const { return fFlags.antiAliased; }
const SkStrikeSpec& strikeSpec() const { return fStrikeSpec; }
const SubRunType fType;
GrTextBlob* const fBlob;
const GrMaskFormat fMaskFormat;
const uint32_t fGlyphStartIndex;
const uint32_t fGlyphEndIndex;
const size_t fVertexStartIndex;
const size_t fVertexEndIndex;
const SkStrikeSpec fStrikeSpec;
sk_sp<GrTextStrike> fStrike;
struct {
bool useLCDText:1;
bool antiAliased:1;
} fFlags{false, false};
GrColor fColor;
GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
SkRect fVertexBounds = SkRectPriv::MakeLargestInverted();
uint64_t fAtlasGeneration{GrDrawOpAtlas::kInvalidAtlasGeneration};
SkScalar fX;
SkScalar fY;
SkMatrix fCurrentViewMatrix;
std::vector<PathGlyph> fPaths;
}; // SubRun
SubRun* makeSubRun(SubRunType type,
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format);
void addSingleMaskFormat(
SubRunType type,
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format);
void addMultiMaskFormat(
SubRunType type,
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec);
void addSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
const SkFont& runFont,
SkScalar minScale,
SkScalar maxScale);
private:
std::unique_ptr<GrAtlasTextOp> makeOp(
SubRun& info, int glyphCount,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect,
const SkPaint& paint, const SkPMColor4f& filteredColor, const SkSurfaceProps&,
const GrDistanceFieldAdjustTable*, GrTextTarget*);
void processDeviceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) override;
void processSourcePaths(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec) override;
void processSourceSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
const SkFont& runFont,
SkScalar minScale,
SkScalar maxScale) override;
void processSourceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) override;
// Pool of bytes for vertex data.
char* fVertices;
// How much (in bytes) of the vertex data is used while accumulating SubRuns.
size_t fVerticesCursor{0};
// Pointers to every glyph that will be drawn.
GrGlyph** fGlyphs;
// Number of glyphs stored in fGlyphs while accumulating SubRuns.
uint32_t fGlyphsCursor{0};
// Assume one run per text blob.
SkSTArray<1, SubRun> fSubRuns;
// The color of the text to draw for solid colors.
const GrColor fColor;
// Lifetime: The GrStrikeCache is owned by and has the same lifetime as the GrRecordingContext.
// The GrRecordingContext also owns the GrTextBlob cache which owns this GrTextBlob.
GrStrikeCache* const fStrikeCache;
SkMaskFilterBase::BlurRec fBlurRec;
struct StrokeInfo {
SkScalar fFrameWidth;
SkScalar fMiterLimit;
SkPaint::Join fJoin;
};
StrokeInfo fStrokeInfo;
Key fKey;
SkMatrix fInitialViewMatrix;
SkMatrix fInitialViewMatrixInverse;
size_t fSize;
SkColor fLuminanceColor;
SkScalar fInitialX;
SkScalar fInitialY;
// We can reuse distance field text, but only if the new viewmatrix would not result in
// a mip change. Because there can be multiple runs in a blob, we track the overall
// maximum minimum scale, and minimum maximum scale, we can support before we need to regen
SkScalar fMaxMinScale{-SK_ScalarMax};
SkScalar fMinMaxScale{SK_ScalarMax};
// From the distance field options to force distance fields to have a W coordinate.
const bool fForceWForDistanceFields;
enum TextType {
kHasDistanceField_TextType = 0x1,
kHasBitmap_TextType = 0x2,
};
uint8_t fTextType{0};
};
/**
* Used to produce vertices for a subrun of a blob. The vertices are cached in the blob itself.
* This is invoked each time a sub run is drawn. It regenerates the vertex data as required either
* because of changes to the atlas or because of different draw parameters (e.g. color change). In
* rare cases the draw may have to interrupted and flushed in the middle of the sub run in order to
* free up atlas space. Thus, this generator is stateful and should be invoked in a loop until the
* entire sub run has been completed.
*/
class GrTextBlob::VertexRegenerator {
public:
/**
* Consecutive VertexRegenerators often use the same SkGlyphCache. If the same instance of
* SkAutoGlyphCache is reused then it can save the cost of multiple detach/attach operations of
* SkGlyphCache.
*/
VertexRegenerator(GrResourceProvider*, GrTextBlob*,
GrTextBlob::SubRun* subRun,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
GrDeferredUploadTarget*, GrStrikeCache*, GrAtlasManager*);
struct Result {
/**
* Was regenerate() able to draw all the glyphs from the sub run? If not flush all glyph
* draws and call regenerate() again.
*/
bool fFinished = true;
/**
* How many glyphs were regenerated. Will be equal to the sub run's glyph count if
* fType is kFinished.
*/
int fGlyphsRegenerated = 0;
/**
* Pointer where the caller finds the first regenerated vertex.
*/
const char* fFirstVertex;
};
bool regenerate(Result*);
private:
bool doRegen(Result*, bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs);
GrResourceProvider* fResourceProvider;
const SkMatrix& fViewMatrix;
GrTextBlob* fBlob;
GrDeferredUploadTarget* fUploadTarget;
GrStrikeCache* fGrStrikeCache;
GrAtlasManager* fFullAtlasManager;
SkTLazy<SkBulkGlyphMetricsAndImages> fMetricsAndImages;
SubRun* fSubRun;
GrColor fColor;
SkScalar fTransX;
SkScalar fTransY;
uint32_t fRegenFlags = 0;
int fCurrGlyph = 0;
bool fBrokenRun = false;
};
#endif // GrTextBlob_DEFINED