blob: 875d9390f046379f16d9c0b55b6e27179a6a5a87 [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.
*/
#include "include/core/SkColorFilter.h"
#include "include/gpu/GrContext.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkStrikeSpec.h"
#include "src/gpu/GrBlurUtils.h"
#include "src/gpu/GrClip.h"
#include "src/gpu/GrStyle.h"
#include "src/gpu/geometry/GrStyledShape.h"
#include "src/gpu/ops/GrAtlasTextOp.h"
#include "src/gpu/text/GrAtlasManager.h"
#include "src/gpu/text/GrStrikeCache.h"
#include "src/gpu/text/GrTextBlob.h"
#include "src/gpu/text/GrTextTarget.h"
#include <cstddef>
#include <new>
// -- GrTextBlob::Key ------------------------------------------------------------------------------
GrTextBlob::Key::Key() { sk_bzero(this, sizeof(Key)); }
bool GrTextBlob::Key::operator==(const GrTextBlob::Key& other) const {
return 0 == memcmp(this, &other, sizeof(Key));
}
// -- GrTextBlob::PathGlyph ------------------------------------------------------------------------
GrTextBlob::PathGlyph::PathGlyph(const SkPath& path, SkPoint origin)
: fPath(path)
, fOrigin(origin) {}
// -- GrTextBlob::SubRun ---------------------------------------------------------------------------
GrTextBlob::SubRun::SubRun(SubRunType type, GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec,
GrMaskFormat format, SkRect vertexBounds,
const SkSpan<VertexData>& vertexData)
: fBlob{textBlob}
, fType{type}
, fMaskFormat{format}
, fStrikeSpec{strikeSpec}
, fVertexBounds{vertexBounds}
, fVertexData{vertexData} {
SkASSERT(fType != kTransformedPath);
}
GrTextBlob::SubRun::SubRun(GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec)
: fBlob{textBlob}
, fType{kTransformedPath}
, fMaskFormat{kA8_GrMaskFormat}
, fStrikeSpec{strikeSpec}
, fVertexBounds{SkRect::MakeEmpty()}
, fVertexData{SkSpan<VertexData>{}} { }
void GrTextBlob::SubRun::resetBulkUseToken() { fBulkUseToken.reset(); }
GrDrawOpAtlas::BulkUseTokenUpdater* GrTextBlob::SubRun::bulkUseToken() { return &fBulkUseToken; }
GrMaskFormat GrTextBlob::SubRun::maskFormat() const { return fMaskFormat; }
size_t GrTextBlob::SubRun::vertexStride() const {
switch (this->maskFormat()) {
case kA8_GrMaskFormat:
return this->hasW() ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex);
case kARGB_GrMaskFormat:
return this->hasW() ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
default:
SkASSERT(!this->hasW());
return sizeof(Mask2DVertex);
}
SkUNREACHABLE;
}
size_t GrTextBlob::SubRun::quadOffset(size_t index) const {
return index * kVerticesPerGlyph * this->vertexStride();
}
template <typename Rect>
static auto ltbr(const Rect& r) {
return std::make_tuple(r.left(), r.top(), r.right(), r.bottom());
}
void GrTextBlob::SubRun::fillVertexData(
void *vertexDst, int offset, int count,
GrColor color, const SkMatrix& drawMatrix, SkPoint drawOrigin, SkIRect clip) const {
SkMatrix matrix = drawMatrix;
matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
auto transformed2D = [&](auto dst, SkScalar dstPadding, SkScalar srcPadding) {
SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
SkPoint inset = {dstPadding, dstPadding};
for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) {
auto[glyph, pos, rect] = vertexData;
auto [l, t, r, b] = rect;
SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos,
sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos;
SkPoint lt = matrix.mapXY(sLT.x(), sLT.y()),
lb = matrix.mapXY(sLT.x(), sRB.y()),
rt = matrix.mapXY(sRB.x(), sLT.y()),
rb = matrix.mapXY(sRB.x(), sRB.y());
auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(srcPadding);
quad[0] = {lt, color, {al, at}}; // L,T
quad[1] = {lb, color, {al, ab}}; // L,B
quad[2] = {rt, color, {ar, at}}; // R,T
quad[3] = {rb, color, {ar, ab}}; // R,B
}
};
auto transformed3D = [&](auto dst, SkScalar dstPadding, SkScalar srcPadding) {
SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio();
SkPoint inset = {dstPadding, dstPadding};
auto mapXYZ = [&](SkScalar x, SkScalar y) {
SkPoint pt{x, y};
SkPoint3 result;
matrix.mapHomogeneousPoints(&result, &pt, 1);
return result;
};
for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) {
auto[glyph, pos, rect] = vertexData;
auto [l, t, r, b] = rect;
SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos,
sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos;
SkPoint3 lt = mapXYZ(sLT.x(), sLT.y()),
lb = mapXYZ(sLT.x(), sRB.y()),
rt = mapXYZ(sRB.x(), sLT.y()),
rb = mapXYZ(sRB.x(), sRB.y());
auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(srcPadding);
quad[0] = {lt, color, {al, at}}; // L,T
quad[1] = {lb, color, {al, ab}}; // L,B
quad[2] = {rt, color, {ar, at}}; // R,T
quad[3] = {rb, color, {ar, ab}}; // R,B
}
};
auto direct2D = [&](auto dst, SkIRect* clip) {
// Rectangles in device space
SkPoint originInDeviceSpace = matrix.mapXY(0, 0);
for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) {
auto[glyph, pos, rect] = vertexData;
auto[l, t, r, b] = rect;
auto[fx, fy] = pos + originInDeviceSpace;
auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(0);
if (clip == nullptr) {
SkScalar dx = SkScalarRoundToScalar(fx),
dy = SkScalarRoundToScalar(fy);
auto[dl, dt, dr, db] = SkRect::MakeLTRB(l + dx, t + dy, r + dx, b + dy);
quad[0] = {{dl, dt}, color, {al, at}}; // L,T
quad[1] = {{dl, db}, color, {al, ab}}; // L,B
quad[2] = {{dr, dt}, color, {ar, at}}; // R,T
quad[3] = {{dr, db}, color, {ar, ab}}; // R,B
} else {
int dx = SkScalarRoundToInt(fx),
dy = SkScalarRoundToInt(fy);
SkIRect devIRect = SkIRect::MakeLTRB(l + dx, t + dy, r + dx, b + dy);
SkScalar dl, dt, dr, db;
uint16_t tl, tt, tr, tb;
if (!clip->containsNoEmptyCheck(devIRect)) {
if (SkIRect clipped; clipped.intersect(devIRect, *clip)) {
int lD = clipped.left() - devIRect.left();
int tD = clipped.top() - devIRect.top();
int rD = clipped.right() - devIRect.right();
int bD = clipped.bottom() - devIRect.bottom();
int indexLT, indexRB;
std::tie(dl, dt, dr, db) = ltbr(clipped);
std::tie(tl, tt, indexLT) =
GrDrawOpAtlas::UnpackIndexFromTexCoords(al, at);
std::tie(tr, tb, indexRB) =
GrDrawOpAtlas::UnpackIndexFromTexCoords(ar, ab);
std::tie(tl, tt) =
GrDrawOpAtlas::PackIndexInTexCoords(tl + lD, tt + tD, indexLT);
std::tie(tr, tb) =
GrDrawOpAtlas::PackIndexInTexCoords(tr + rD, tb + bD, indexRB);
} else {
// TODO: omit generating any vertex data for fully clipped glyphs ?
std::tie(dl, dt, dr, db) = std::make_tuple(0, 0, 0, 0);
std::tie(tl, tt, tr, tb) = std::make_tuple(0, 0, 0, 0);
}
} else {
std::tie(dl, dt, dr, db) = ltbr(devIRect);
std::tie(tl, tt, tr, tb) = std::tie(al, at, ar, ab);
}
quad[0] = {{dl, dt}, color, {tl, tt}}; // L,T
quad[1] = {{dl, db}, color, {tl, tb}}; // L,B
quad[2] = {{dr, dt}, color, {tr, tt}}; // R,T
quad[3] = {{dr, db}, color, {tr, tb}}; // R,B
}
}
};
switch (fType) {
case kDirectMask: {
if (clip.isEmpty()) {
if (this->maskFormat() != kARGB_GrMaskFormat) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
direct2D((Quad*) vertexDst, nullptr);
} else {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
direct2D((Quad*) vertexDst, nullptr);
}
} else {
if (this->maskFormat() != kARGB_GrMaskFormat) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
direct2D((Quad*) vertexDst, &clip);
} else {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
direct2D((Quad*) vertexDst, &clip);
}
}
break;
}
case kTransformedMask: {
if (!this->hasW()) {
if (this->maskFormat() == GrMaskFormat::kARGB_GrMaskFormat) {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
transformed2D((Quad*) vertexDst, 0, 1);
} else {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
transformed2D((Quad*) vertexDst, 0, 1);
}
} else {
if (this->maskFormat() == GrMaskFormat::kARGB_GrMaskFormat) {
using Quad = ARGB3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
transformed3D((Quad*) vertexDst, 0, 1);
} else {
using Quad = Mask3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
transformed3D((Quad*) vertexDst, 0, 1);
}
}
break;
}
case kTransformedSDFT: {
if (!this->hasW()) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
transformed2D((Quad*) vertexDst, SK_DistanceFieldInset, SK_DistanceFieldInset);
} else {
using Quad = Mask3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph);
transformed3D((Quad*) vertexDst, SK_DistanceFieldInset, SK_DistanceFieldInset);
}
break;
}
case kTransformedPath:
SK_ABORT("Paths don't generate vertex data.");
}
}
int GrTextBlob::SubRun::glyphCount() const {
return fVertexData.count();
}
bool GrTextBlob::SubRun::drawAsDistanceFields() const { return fType == kTransformedSDFT; }
bool GrTextBlob::SubRun::drawAsPaths() const { return fType == kTransformedPath; }
bool GrTextBlob::SubRun::needsTransform() const {
return fType == kTransformedPath ||
fType == kTransformedMask ||
fType == kTransformedSDFT;
}
bool GrTextBlob::SubRun::needsPadding() const {
return fType == kTransformedPath || fType == kTransformedMask;
}
int GrTextBlob::SubRun::atlasPadding() const {
return SkTo<int>(this->needsPadding());
}
auto GrTextBlob::SubRun::vertexData() const -> SkSpan<const VertexData> {
return fVertexData;
}
bool GrTextBlob::SubRun::hasW() const {
if (fType == kTransformedSDFT || fType == kTransformedMask || fType == kTransformedPath) {
return fBlob->hasPerspective();
}
// The viewMatrix is implicitly SkMatrix::I when drawing kDirectMask, because it is not
// used.
return false;
}
void GrTextBlob::SubRun::prepareGrGlyphs(GrStrikeCache* strikeCache) {
if (fStrike) {
return;
}
fStrike = fStrikeSpec.findOrCreateGrStrike(strikeCache);
for (auto& tmp : fVertexData) {
tmp.glyph.grGlyph = fStrike->getGlyph(tmp.glyph.packedGlyphID);
}
}
SkRect GrTextBlob::SubRun::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const {
SkRect outBounds = fVertexBounds;
if (this->needsTransform()) {
// if the glyph needs transformation offset the by the new origin, and map to device space.
outBounds.offset(drawOrigin);
outBounds = drawMatrix.mapRect(outBounds);
} else {
SkPoint offset = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y());
// The vertex bounds are already {0, 0} based, so just add the new origin offset.
outBounds.offset(offset);
// Due to floating point numerical inaccuracies, we have to round out here
outBounds.roundOut();
}
return outBounds;
}
GrGlyph* GrTextBlob::SubRun::grGlyph(int i) const {
return fVertexData[i].glyph.grGlyph;
}
void GrTextBlob::SubRun::setUseLCDText(bool useLCDText) { fUseLCDText = useLCDText; }
bool GrTextBlob::SubRun::hasUseLCDText() const { return fUseLCDText; }
void GrTextBlob::SubRun::setAntiAliased(bool antiAliased) { fAntiAliased = antiAliased; }
bool GrTextBlob::SubRun::isAntiAliased() const { return fAntiAliased; }
const SkStrikeSpec& GrTextBlob::SubRun::strikeSpec() const { return fStrikeSpec; }
auto GrTextBlob::SubRun::MakePaths(
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec,
GrTextBlob* blob,
SkArenaAlloc* alloc) -> SubRun* {
SubRun* subRun = alloc->make<SubRun>(blob, strikeSpec);
subRun->setAntiAliased(runFont.hasSomeAntiAliasing());
for (auto [variant, pos] : drawables) {
subRun->fPaths.emplace_back(*variant.path(), pos);
}
return subRun;
};
auto GrTextBlob::SubRun::MakeSDFT(
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec,
GrTextBlob* blob,
SkArenaAlloc* alloc) -> SubRun* {
SubRun* subRun = SubRun::InitForAtlas(
kTransformedSDFT, drawables, strikeSpec, kA8_GrMaskFormat, blob, alloc);
subRun->setUseLCDText(runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias);
subRun->setAntiAliased(runFont.hasSomeAntiAliasing());
return subRun;
}
auto GrTextBlob::SubRun::MakeDirectMask(
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
SkArenaAlloc* alloc) -> SubRun* {
return SubRun::InitForAtlas(kDirectMask, drawables, strikeSpec, format, blob, alloc);
}
auto GrTextBlob::SubRun::MakeTransformedMask(
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
SkArenaAlloc* alloc) -> SubRun* {
return SubRun::InitForAtlas(kTransformedMask, drawables, strikeSpec, format, blob, alloc);
}
void GrTextBlob::SubRun::insertSubRunOpsIntoTarget(GrTextTarget* target,
const SkSurfaceProps& props,
const SkPaint& paint,
const GrClip* clip,
const SkMatrixProvider& deviceMatrix,
SkPoint drawOrigin) {
if (this->drawAsPaths()) {
SkPaint runPaint{paint};
runPaint.setAntiAlias(this->isAntiAliased());
// If there are shaders, blurs or styles, the path must be scaled into source
// space independently of the CTM. This allows the CTM to be correct for the
// different effects.
GrStyle style(runPaint);
bool needsExactCTM = runPaint.getShader()
|| style.applies()
|| runPaint.getMaskFilter();
// Calculate the matrix that maps the path glyphs from their size in the strike to
// the graphics source space.
SkScalar scale = this->fStrikeSpec.strikeToSourceRatio();
SkMatrix strikeToSource = SkMatrix::Scale(scale, scale);
strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y());
if (!needsExactCTM) {
for (const auto& pathPos : this->fPaths) {
const SkPath& path = pathPos.fPath;
const SkPoint pos = pathPos.fOrigin; // Transform the glyph to source space.
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(pos.x(), pos.y());
SkPreConcatMatrixProvider strikeToDevice(deviceMatrix, pathMatrix);
GrStyledShape shape(path, paint);
target->drawShape(clip, runPaint, strikeToDevice, shape);
}
} else {
// Transform the path to device because the deviceMatrix must be unchanged to
// draw effect, filter or shader paths.
for (const auto& pathPos : this->fPaths) {
const SkPath& path = pathPos.fPath;
const SkPoint pos = pathPos.fOrigin;
// Transform the glyph to source space.
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(pos.x(), pos.y());
SkPath deviceOutline;
path.transform(pathMatrix, &deviceOutline);
deviceOutline.setIsVolatile(true);
GrStyledShape shape(deviceOutline, paint);
target->drawShape(clip, runPaint, deviceMatrix, shape);
}
}
} else {
int glyphCount = this->glyphCount();
if (0 == glyphCount) {
return;
}
bool skipClip = false;
SkIRect clipRect = SkIRect::MakeEmpty();
SkRect rtBounds = SkRect::MakeWH(target->width(), target->height());
SkRRect clipRRect = SkRRect::MakeRect(rtBounds);
GrAA aa;
// We can clip geometrically if we're not using SDFs or transformed glyphs,
// and we have an axis-aligned rectangular non-AA clip
if (!this->drawAsDistanceFields() &&
!this->needsTransform() &&
(!clip || (clip->isRRect(&clipRRect, &aa) &&
clipRRect.isRect() && GrAA::kNo == aa))) {
// We only need to do clipping work if the subrun isn't contained by the clip
SkRect subRunBounds = this->deviceRect(deviceMatrix.localToDevice(), drawOrigin);
if (!clipRRect.getBounds().contains(subRunBounds)) {
// If the subrun is completely outside, don't add an op for it
if (!clipRRect.getBounds().intersects(subRunBounds)) {
return;
} else {
clipRRect.getBounds().round(&clipRect);
}
}
skipClip = true;
}
auto op = this->makeOp(deviceMatrix, drawOrigin, clipRect, paint, props, target);
if (op != nullptr) {
target->addDrawOp(skipClip ? nullptr : clip, std::move(op));
}
}
}
SkPMColor4f generate_filtered_color(const SkPaint& paint, const GrColorInfo& colorInfo) {
SkColor4f c = paint.getColor4f();
if (auto* xform = colorInfo.colorSpaceXformFromSRGB()) {
c = xform->apply(c);
}
if (auto* cf = paint.getColorFilter()) {
c = cf->filterColor4f(c, colorInfo.colorSpace(), colorInfo.colorSpace());
}
return c.premul();
}
std::unique_ptr<GrAtlasTextOp> GrTextBlob::SubRun::makeOp(const SkMatrixProvider& matrixProvider,
SkPoint drawOrigin,
const SkIRect& clipRect,
const SkPaint& paint,
const SkSurfaceProps& props,
GrTextTarget* target) {
GrPaint grPaint;
target->makeGrPaint(this->maskFormat(), paint, matrixProvider, &grPaint);
const GrColorInfo& colorInfo = target->colorInfo();
// This is the color the op will use to draw.
SkPMColor4f drawingColor = generate_filtered_color(paint, colorInfo);
if (this->drawAsDistanceFields()) {
// TODO: Can we be even smarter based on the dest transfer function?
return GrAtlasTextOp::MakeDistanceField(target->getContext(),
std::move(grPaint),
this,
matrixProvider.localToDevice(),
drawOrigin,
clipRect,
drawingColor,
target->colorInfo().isLinearlyBlended(),
SkPaintPriv::ComputeLuminanceColor(paint),
props);
} else {
return GrAtlasTextOp::MakeBitmap(target->getContext(),
std::move(grPaint),
this,
matrixProvider.localToDevice(),
drawOrigin,
clipRect,
drawingColor);
}
}
auto GrTextBlob::SubRun::InitForAtlas(SubRunType type,
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
SkArenaAlloc* alloc) -> SubRun* {
size_t vertexCount = drawables.size();
using Data = VertexData;
SkRect bounds = SkRectPriv::MakeLargestInverted();
auto initializer = [&, strikeToSource=strikeSpec.strikeToSourceRatio()](size_t i) {
auto [variant, pos] = drawables[i];
SkGlyph* skGlyph = variant;
int16_t l = skGlyph->left();
int16_t t = skGlyph->top();
int16_t r = l + skGlyph->width();
int16_t b = t + skGlyph->height();
SkPoint lt = SkPoint::Make(l, t) * strikeToSource + pos,
rb = SkPoint::Make(r, b) * strikeToSource + pos;
bounds.joinPossiblyEmptyRect(SkRect::MakeLTRB(lt.x(), lt.y(), rb.x(), rb.y()));
return Data{{skGlyph->getPackedID()}, pos, {l, t, r, b}};
};
SkSpan<Data> vertexData{
alloc->makeInitializedArray<Data>(vertexCount, initializer), vertexCount};
SubRun* subRun = alloc->make<SubRun>(type, blob, strikeSpec, format, bounds, vertexData);
return subRun;
}
// -- GrTextBlob -----------------------------------------------------------------------------------
void GrTextBlob::operator delete(void* p) { ::operator delete(p); }
void* GrTextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); }
void* GrTextBlob::operator new(size_t, void* p) { return p; }
GrTextBlob::~GrTextBlob() = default;
sk_sp<GrTextBlob> GrTextBlob::Make(const SkGlyphRunList& glyphRunList, const SkMatrix& drawMatrix) {
// The difference in alignment from the storage of VertexData to SubRun;
constexpr size_t alignDiff = alignof(SubRun) - alignof(SubRun::VertexData);
constexpr size_t vertexDataToSubRunPadding = alignDiff > 0 ? alignDiff : 0;
size_t arenaSize = sizeof(SubRun::VertexData) * glyphRunList.totalGlyphCount()
+ glyphRunList.runCount() * (sizeof(SubRun) + vertexDataToSubRunPadding);
size_t allocationSize = sizeof(GrTextBlob) + arenaSize;
void* allocation = ::operator new (allocationSize);
SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(glyphRunList.paint());
sk_sp<GrTextBlob> blob{new (allocation) GrTextBlob{
arenaSize, drawMatrix, glyphRunList.origin(), initialLuminance}};
return blob;
}
void GrTextBlob::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();
}
}
const GrTextBlob::Key& GrTextBlob::GetKey(const GrTextBlob& blob) { return blob.fKey; }
uint32_t GrTextBlob::Hash(const GrTextBlob::Key& key) { return SkOpts::hash(&key, sizeof(Key)); }
bool GrTextBlob::hasDistanceField() const {
return SkToBool(fTextType & kHasDistanceField_TextType);
}
bool GrTextBlob::hasBitmap() const { return SkToBool(fTextType & kHasBitmap_TextType); }
bool GrTextBlob::hasPerspective() const { return fInitialMatrix.hasPerspective(); }
void GrTextBlob::setHasDistanceField() { fTextType |= kHasDistanceField_TextType; }
void GrTextBlob::setHasBitmap() { fTextType |= kHasBitmap_TextType; }
void GrTextBlob::setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax) {
// we init fMaxMinScale and fMinMaxScale in the constructor
fMaxMinScale = std::max(scaledMin, fMaxMinScale);
fMinMaxScale = std::min(scaledMax, fMinMaxScale);
}
bool GrTextBlob::canReuse(const SkPaint& paint,
const SkMaskFilterBase::BlurRec& blurRec,
const SkMatrix& drawMatrix,
SkPoint drawOrigin) {
// If we have LCD text then our canonical color will be set to transparent, in this case we have
// to regenerate the blob on any color change
// We use the grPaint to get any color filter effects
if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) {
return false;
}
if (fInitialMatrix.hasPerspective() != drawMatrix.hasPerspective()) {
return false;
}
/** This could be relaxed for blobs with only distance field glyphs. */
if (fInitialMatrix.hasPerspective() && !SkMatrixPriv::CheapEqual(fInitialMatrix, drawMatrix)) {
return false;
}
// We only cache one masked version
if (fKey.fHasBlur &&
(fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle)) {
return false;
}
// Similarly, we only cache one version for each style
if (fKey.fStyle != SkPaint::kFill_Style &&
(fStrokeInfo.fFrameWidth != paint.getStrokeWidth() ||
fStrokeInfo.fMiterLimit != paint.getStrokeMiter() ||
fStrokeInfo.fJoin != paint.getStrokeJoin())) {
return false;
}
// Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls
// for mixed blobs if this becomes an issue.
if (this->hasBitmap() && this->hasDistanceField()) {
// Identical view matrices and we can reuse in all cases
return SkMatrixPriv::CheapEqual(fInitialMatrix, drawMatrix) && drawOrigin == fInitialOrigin;
}
if (this->hasBitmap()) {
if (fInitialMatrix.getScaleX() != drawMatrix.getScaleX() ||
fInitialMatrix.getScaleY() != drawMatrix.getScaleY() ||
fInitialMatrix.getSkewX() != drawMatrix.getSkewX() ||
fInitialMatrix.getSkewY() != drawMatrix.getSkewY()) {
return false;
}
// TODO(herb): this is not needed for full pixel glyph choice, but is needed to adjust
// the quads properly. Devise a system that regenerates the quads from original data
// using the transform to allow this to be used in general.
// We can update the positions in the text blob without regenerating the whole
// blob, but only for integer translations.
// Calculate the translation in source space to a translation in device space by mapping
// (0, 0) through both the initial matrix and the draw matrix; take the difference.
SkMatrix initialMatrix{fInitialMatrix};
initialMatrix.preTranslate(fInitialOrigin.x(), fInitialOrigin.y());
SkPoint initialDeviceOrigin{0, 0};
initialMatrix.mapPoints(&initialDeviceOrigin, 1);
SkMatrix completeDrawMatrix{drawMatrix};
completeDrawMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
SkPoint drawDeviceOrigin{0, 0};
completeDrawMatrix.mapPoints(&drawDeviceOrigin, 1);
SkPoint translation = drawDeviceOrigin - initialDeviceOrigin;
if (!SkScalarIsInt(translation.x()) || !SkScalarIsInt(translation.y())) {
return false;
}
} else if (this->hasDistanceField()) {
// A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different
// distance field being generated, so we have to regenerate in those cases
SkScalar newMaxScale = drawMatrix.getMaxScale();
SkScalar oldMaxScale = fInitialMatrix.getMaxScale();
SkScalar scaleAdjust = newMaxScale / oldMaxScale;
if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) {
return false;
}
}
// If the blob is all paths, there is no reason to regenerate.
return true;
}
void GrTextBlob::insertOpsIntoTarget(GrTextTarget* target,
const SkSurfaceProps& props,
const SkPaint& paint,
const GrClip* clip,
const SkMatrixProvider& deviceMatrix,
SkPoint drawOrigin) {
for (SubRun* subRun = fFirstSubRun; subRun != nullptr; subRun = subRun->fNextSubRun) {
subRun->insertSubRunOpsIntoTarget(target, props, paint, clip, deviceMatrix, drawOrigin);
}
}
const GrTextBlob::Key& GrTextBlob::key() const { return fKey; }
size_t GrTextBlob::size() const { return fSize; }
template<typename AddSingleMaskFormat>
void GrTextBlob::addMultiMaskFormat(
AddSingleMaskFormat addSingle,
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) {
this->setHasBitmap();
if (drawables.empty()) { return; }
auto glyphSpan = drawables.get<0>();
SkGlyph* glyph = glyphSpan[0];
GrMaskFormat format = GrGlyph::FormatFromSkGlyph(glyph->maskFormat());
size_t startIndex = 0;
for (size_t i = 1; i < drawables.size(); i++) {
glyph = glyphSpan[i];
GrMaskFormat nextFormat = GrGlyph::FormatFromSkGlyph(glyph->maskFormat());
if (format != nextFormat) {
auto sameFormat = drawables.subspan(startIndex, i - startIndex);
SubRun* subRun = addSingle(sameFormat, strikeSpec, format, this, &fAlloc);
this->insertSubRun(subRun);
format = nextFormat;
startIndex = i;
}
}
auto sameFormat = drawables.last(drawables.size() - startIndex);
SubRun* subRun = addSingle(sameFormat, strikeSpec, format, this, &fAlloc);
this->insertSubRun(subRun);
}
GrTextBlob::GrTextBlob(size_t allocSize,
const SkMatrix& drawMatrix,
SkPoint origin,
SkColor initialLuminance)
: fSize{allocSize}
, fInitialMatrix{drawMatrix}
, fInitialOrigin{origin}
, fInitialLuminance{initialLuminance}
, fAlloc{SkTAddOffset<char>(this, sizeof(GrTextBlob)), allocSize, allocSize/2} { }
void GrTextBlob::insertSubRun(SubRun* subRun) {
if (fFirstSubRun == nullptr) {
fFirstSubRun = subRun;
fLastSubRun = subRun;
} else {
fLastSubRun->fNextSubRun = subRun;
fLastSubRun = subRun;
}
}
void GrTextBlob::processDeviceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) {
this->addMultiMaskFormat(SubRun::MakeDirectMask, drawables, strikeSpec);
}
void GrTextBlob::processSourcePaths(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec) {
this->setHasBitmap();
SubRun* subRun = SubRun::MakePaths(drawables, runFont, strikeSpec, this, &fAlloc);
this->insertSubRun(subRun);
}
void GrTextBlob::processSourceSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
const SkFont& runFont,
SkScalar minScale,
SkScalar maxScale) {
this->setHasDistanceField();
this->setMinAndMaxScale(minScale, maxScale);
SubRun* subRun = SubRun::MakeSDFT(drawables, runFont, strikeSpec, this, &fAlloc);
this->insertSubRun(subRun);
}
void GrTextBlob::processSourceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) {
this->addMultiMaskFormat(SubRun::MakeTransformedMask, drawables, strikeSpec);
}
auto GrTextBlob::firstSubRun() const -> SubRun* { return fFirstSubRun; }
// -- GrTextBlob::VertexRegenerator ----------------------------------------------------------------
GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourceProvider,
GrTextBlob::SubRun* subRun,
GrDeferredUploadTarget* uploadTarget,
GrAtlasManager* fullAtlasManager)
: fResourceProvider(resourceProvider)
, fUploadTarget(uploadTarget)
, fFullAtlasManager(fullAtlasManager)
, fSubRun(subRun) { }
std::tuple<bool, int> GrTextBlob::VertexRegenerator::updateTextureCoordinates(
const int begin, const int end) {
SkASSERT(fSubRun->isPrepared());
SkBulkGlyphMetricsAndImages metricsAndImages{fSubRun->strikeSpec()};
// Update the atlas information in the GrStrike.
auto tokenTracker = fUploadTarget->tokenTracker();
auto vertexData = fSubRun->vertexData().subspan(begin, end - begin);
int glyphsPlacedInAtlas = 0;
for (auto [glyph, pos, rect] : vertexData) {
GrGlyph* grGlyph = glyph.grGlyph;
SkASSERT(grGlyph != nullptr);
if (!fFullAtlasManager->hasGlyph(fSubRun->maskFormat(), grGlyph)) {
const SkGlyph& skGlyph = *metricsAndImages.glyph(grGlyph->fPackedID);
auto code = fFullAtlasManager->addGlyphToAtlas(
skGlyph, fSubRun->atlasPadding(), grGlyph, fResourceProvider, fUploadTarget);
if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
return {code != GrDrawOpAtlas::ErrorCode::kError, glyphsPlacedInAtlas};
}
}
fFullAtlasManager->addGlyphToBulkAndSetUseToken(
fSubRun->bulkUseToken(), fSubRun->maskFormat(), grGlyph,
tokenTracker->nextDrawToken());
glyphsPlacedInAtlas++;
}
return {true, glyphsPlacedInAtlas};
}
std::tuple<bool, int> GrTextBlob::VertexRegenerator::regenerate(int begin, int end) {
uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
if (fSubRun->fAtlasGeneration != currentAtlasGen) {
// Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
// is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls..
fSubRun->resetBulkUseToken();
auto [success, glyphsPlacedInAtlas] = this->updateTextureCoordinates(begin, end);
// Update atlas generation if there are no more glyphs to put in the atlas.
if (success && begin + glyphsPlacedInAtlas == fSubRun->glyphCount()) {
// Need to get the freshest value of the atlas' generation because
// updateTextureCoordinates may have changed it.
fSubRun->fAtlasGeneration = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
}
return {success, glyphsPlacedInAtlas};
} else {
// The atlas hasn't changed, so our texture coordinates are still valid.
if (end == fSubRun->glyphCount()) {
// The atlas hasn't changed and the texture coordinates are all still valid. Update
// all the plots used to the new use token.
fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
fUploadTarget->tokenTracker()->nextDrawToken(),
fSubRun->maskFormat());
}
return {true, end - begin};
}
}