blob: 92d50718bd69c452d63f6ee2b3da740ae7b9fa08 [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/GrRecordingContext.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/GrMemoryPool.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrStyle.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/SkGr.h"
#include "src/gpu/effects/GrDistanceFieldGeoProc.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 <cstddef>
#include <memory>
#include <new>
namespace {
struct AtlasPt {
uint16_t u;
uint16_t v;
};
// Normal text mask, SDFT, or color.
struct Mask2DVertex {
SkPoint devicePos;
GrColor color;
AtlasPt atlasPos;
};
struct ARGB2DVertex {
ARGB2DVertex(SkPoint d, GrColor, AtlasPt a) : devicePos{d}, atlasPos{a} {}
SkPoint devicePos;
AtlasPt atlasPos;
};
// Perspective SDFT or SDFT forced to 3D or perspective color.
struct Mask3DVertex {
SkPoint3 devicePos;
GrColor color;
AtlasPt atlasPos;
};
struct ARGB3DVertex {
ARGB3DVertex(SkPoint3 d, GrColor, AtlasPt a) : devicePos{d}, atlasPos{a} {}
SkPoint3 devicePos;
AtlasPt atlasPos;
};
GrAtlasTextOp::MaskType op_mask_type(GrMaskFormat grMaskFormat) {
switch (grMaskFormat) {
case kA8_GrMaskFormat: return GrAtlasTextOp::MaskType::kGrayscaleCoverage;
case kA565_GrMaskFormat: return GrAtlasTextOp::MaskType::kLCDCoverage;
case kARGB_GrMaskFormat: return GrAtlasTextOp::MaskType::kColorBitmap;
}
SkUNREACHABLE;
}
SkPMColor4f calculate_colors(GrSurfaceDrawContext* sdc,
const SkPaint& paint,
const SkMatrixProvider& matrix,
GrMaskFormat grMaskFormat,
GrPaint* grPaint) {
GrRecordingContext* rContext = sdc->recordingContext();
const GrColorInfo& colorInfo = sdc->colorInfo();
if (grMaskFormat == kARGB_GrMaskFormat) {
SkPaintToGrPaintWithPrimitiveColor(rContext, colorInfo, paint, matrix, grPaint);
return SK_PMColor4fWHITE;
} else {
SkPaintToGrPaint(rContext, colorInfo, paint, matrix, grPaint);
return grPaint->getColor4f();
}
}
template<typename Quad, typename VertexData>
void fill_transformed_vertices_2D(SkZip<Quad, const GrGlyph*, const VertexData> quadData,
SkScalar dstPadding,
SkScalar strikeToSource,
GrColor color,
const SkMatrix& matrix) {
SkPoint inset = {dstPadding, dstPadding};
for (auto[quad, glyph, vertexData] : quadData) {
auto[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->fAtlasLocator.getUVs();
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
}
}
template<typename Quad, typename VertexData>
void fill_transformed_vertices_3D(SkZip<Quad, const GrGlyph*, const VertexData> quadData,
SkScalar dstPadding,
SkScalar strikeToSource,
GrColor color,
const SkMatrix& positionMatrix) {
SkPoint inset = {dstPadding, dstPadding};
auto mapXYZ = [&](SkScalar x, SkScalar y) {
SkPoint pt{x, y};
SkPoint3 result;
positionMatrix.mapHomogeneousPoints(&result, &pt, 1);
return result;
};
for (auto[quad, glyph, vertexData] : quadData) {
auto[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->fAtlasLocator.getUVs();
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
}
}
// Check for integer translate with the same 2x2 matrix.
std::tuple<bool, SkVector> check_integer_translate(
const SkMatrix& initialMatrix, const SkMatrix& drawMatrix) {
if (initialMatrix.getScaleX() != drawMatrix.getScaleX() ||
initialMatrix.getScaleY() != drawMatrix.getScaleY() ||
initialMatrix.getSkewX() != drawMatrix.getSkewX() ||
initialMatrix.getSkewY() != drawMatrix.getSkewY()) {
return {false, {0, 0}};
}
// 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.
SkVector translation = drawMatrix.mapXY(0, 0) - initialMatrix.mapXY(0, 0);
return {SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()), translation};
}
// -- PathSubRun -----------------------------------------------------------------------------------
class PathSubRun final : public GrSubRun {
struct PathGlyph;
public:
PathSubRun(bool isAntiAliased,
const SkStrikeSpec& strikeSpec,
const GrTextBlob& blob,
SkSpan<PathGlyph> paths,
std::unique_ptr<PathGlyph[], GrSubRunAllocator::ArrayDestroyer> pathData);
void draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const override;
bool canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const override;
GrAtlasSubRun* testingOnly_atlasSubRun() override;
static GrSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
bool isAntiAliased,
const SkStrikeSpec& strikeSpec,
const GrTextBlob& blob,
GrSubRunAllocator* alloc);
private:
struct PathGlyph {
PathGlyph(const SkPath& path, SkPoint origin);
SkPath fPath;
SkPoint fOrigin;
};
const bool fIsAntiAliased;
const SkStrikeSpec fStrikeSpec;
const SkSpan<const PathGlyph> fPaths;
const std::unique_ptr<PathGlyph[], GrSubRunAllocator::ArrayDestroyer> fPathData;
};
PathSubRun::PathSubRun(bool isAntiAliased,
const SkStrikeSpec& strikeSpec,
const GrTextBlob& blob,
SkSpan<PathGlyph> paths,
std::unique_ptr<PathGlyph[], GrSubRunAllocator::ArrayDestroyer> pathData)
: fIsAntiAliased{isAntiAliased}
, fStrikeSpec{strikeSpec}
, fPaths{paths}
, fPathData{std::move(pathData)} {}
void PathSubRun::draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const {
SkASSERT(!fPaths.empty());
SkPoint drawOrigin = glyphRunList.origin();
SkPaint runPaint{paint};
runPaint.setAntiAlias(fIsAntiAliased);
// 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 : 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(viewMatrix, pathMatrix);
GrStyledShape shape(path, paint);
GrBlurUtils::drawShapeWithMaskFilter(sdc->recordingContext(), sdc, 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 : 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);
GrBlurUtils::drawShapeWithMaskFilter(sdc->recordingContext(), sdc, clip, runPaint,
viewMatrix, shape);
}
}
}
bool PathSubRun::canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const {
return true;
}
GrSubRunOwner PathSubRun::Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
bool isAntiAliased,
const SkStrikeSpec& strikeSpec,
const GrTextBlob& blob,
GrSubRunAllocator* alloc) {
auto pathData = alloc->makeUniqueArray<PathGlyph>(
drawables.size(),
[&](int i){
auto [variant, pos] = drawables[i];
return PathGlyph{*variant.path(), pos};
});
SkSpan<PathGlyph> paths{pathData.get(), drawables.size()};
return alloc->makeUnique<PathSubRun>(
isAntiAliased, strikeSpec, blob, paths, std::move(pathData));
}
GrAtlasSubRun* PathSubRun::testingOnly_atlasSubRun() {
return nullptr;
};
// -- PathSubRun::PathGlyph ------------------------------------------------------------------------
PathSubRun::PathGlyph::PathGlyph(const SkPath& path, SkPoint origin)
: fPath(path)
, fOrigin(origin) {}
// -- GlyphVector ----------------------------------------------------------------------------------
class GlyphVector {
public:
union Variant {
// Initially, filled with packed id, but changed to GrGlyph* in the onPrepare stage.
SkPackedGlyphID packedGlyphID;
GrGlyph* grGlyph;
// Add ctors to help SkArenaAlloc create arrays.
Variant() : grGlyph{nullptr} {}
Variant(SkPackedGlyphID id) : packedGlyphID{id} {}
};
GlyphVector(const SkStrikeSpec& spec, SkSpan<Variant> glyphs);
static GlyphVector Make(
const SkStrikeSpec& spec, SkSpan<SkGlyphVariant> glyphs, GrSubRunAllocator* alloc);
SkSpan<const GrGlyph*> glyphs() const;
SkScalar strikeToSourceRatio() const { return fStrikeSpec.strikeToSourceRatio(); }
void packedGlyphIDToGrGlyph(GrStrikeCache* cache);
std::tuple<bool, int> regenerateAtlas(
int begin, int end,
GrMaskFormat maskFormat,
int srcPadding,
GrMeshDrawOp::Target *target,
bool bilerpPadding = false);
static size_t GlyphVectorSize(size_t count) {
return sizeof(Variant) * count;
}
private:
const SkStrikeSpec fStrikeSpec;
SkSpan<Variant> fGlyphs;
sk_sp<GrTextStrike> fStrike{nullptr};
uint64_t fAtlasGeneration{GrDrawOpAtlas::kInvalidAtlasGeneration};
GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
};
GlyphVector::GlyphVector(const SkStrikeSpec& spec, SkSpan<Variant> glyphs)
: fStrikeSpec{spec}
, fGlyphs{glyphs} { }
GlyphVector GlyphVector::Make(
const SkStrikeSpec &spec, SkSpan<SkGlyphVariant> glyphs, GrSubRunAllocator* alloc) {
Variant* variants = alloc->makePODArray<Variant>(glyphs.size());
for (auto [i, gv] : SkMakeEnumerate(glyphs)) {
variants[i] = gv.glyph()->getPackedID();
}
return GlyphVector{spec, SkSpan(variants, glyphs.size())};
}
SkSpan<const GrGlyph*> GlyphVector::glyphs() const {
return SkSpan(reinterpret_cast<const GrGlyph**>(fGlyphs.data()), fGlyphs.size());
}
void GlyphVector::packedGlyphIDToGrGlyph(GrStrikeCache* cache) {
if (fStrike == nullptr) {
fStrike = fStrikeSpec.findOrCreateGrStrike(cache);
for (auto& variant : fGlyphs) {
variant.grGlyph = fStrike->getGlyph(variant.packedGlyphID);
}
}
}
std::tuple<bool, int> GlyphVector::regenerateAtlas(int begin, int end,
GrMaskFormat maskFormat,
int srcPadding,
GrMeshDrawOp::Target* target,
bool bilerpPadding) {
GrAtlasManager* atlasManager = target->atlasManager();
GrDeferredUploadTarget* uploadTarget = target->deferredUploadTarget();
uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat);
this->packedGlyphIDToGrGlyph(target->strikeCache());
if (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..
fBulkUseToken.reset();
SkBulkGlyphMetricsAndImages metricsAndImages{fStrikeSpec};
// Update the atlas information in the GrStrike.
auto tokenTracker = uploadTarget->tokenTracker();
auto glyphs = fGlyphs.subspan(begin, end - begin);
int glyphsPlacedInAtlas = 0;
bool success = true;
for (const Variant& variant : glyphs) {
GrGlyph* grGlyph = variant.grGlyph;
SkASSERT(grGlyph != nullptr);
if (!atlasManager->hasGlyph(maskFormat, grGlyph)) {
const SkGlyph& skGlyph = *metricsAndImages.glyph(grGlyph->fPackedID);
auto code = atlasManager->addGlyphToAtlas(
skGlyph, grGlyph, srcPadding, target->resourceProvider(),
uploadTarget, bilerpPadding);
if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) {
success = code != GrDrawOpAtlas::ErrorCode::kError;
break;
}
}
atlasManager->addGlyphToBulkAndSetUseToken(
&fBulkUseToken, maskFormat, grGlyph,
tokenTracker->nextDrawToken());
glyphsPlacedInAtlas++;
}
// Update atlas generation if there are no more glyphs to put in the atlas.
if (success && begin + glyphsPlacedInAtlas == fGlyphs.count()) {
// Need to get the freshest value of the atlas' generation because
// updateTextureCoordinates may have changed it.
fAtlasGeneration = atlasManager->atlasGeneration(maskFormat);
}
return {success, glyphsPlacedInAtlas};
} else {
// The atlas hasn't changed, so our texture coordinates are still valid.
if (end == fGlyphs.count()) {
// The atlas hasn't changed and the texture coordinates are all still valid. Update
// all the plots used to the new use token.
atlasManager->setUseTokenBulk(fBulkUseToken,
uploadTarget->tokenTracker()->nextDrawToken(),
maskFormat);
}
return {true, end - begin};
}
}
// -- DirectMaskSubRun -----------------------------------------------------------------------------
class DirectMaskSubRun final : public GrSubRun, public GrAtlasSubRun {
public:
using DevicePosition = skvx::Vec<2, int16_t>;
DirectMaskSubRun(GrMaskFormat format,
GrTextBlob* blob,
const SkGlyphRect& deviceBounds,
SkSpan<const DevicePosition> devicePositions,
GlyphVector glyphs,
bool glyphsOutOfBounds);
static GrSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
GrSubRunAllocator* alloc);
void draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const override;
bool canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const override;
GrAtlasSubRun* testingOnly_atlasSubRun() override;
size_t vertexStride(const SkMatrix& drawMatrix) const override;
int glyphCount() const override;
std::tuple<const GrClip*, GrOp::Owner>
makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner) const override;
void testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) override;
std::tuple<bool, int>
regenerateAtlas(int begin, int end, GrMeshDrawOp::Target* target) const override;
void fillVertexData(void* vertexDst, int offset, int count, GrColor color,
const SkMatrix& positionMatrix, SkIRect clip) const override;
private:
// The rectangle that surrounds all the glyph bounding boxes in device space.
SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const;
const GrMaskFormat fMaskFormat;
GrTextBlob* const fBlob;
// The union of all the glyph bounds in device space.
const SkGlyphRect fGlyphDeviceBounds;
const SkSpan<const DevicePosition> fLeftTopDevicePos;
const bool fSomeGlyphsExcluded;
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
};
DirectMaskSubRun::DirectMaskSubRun(GrMaskFormat format,
GrTextBlob* blob,
const SkGlyphRect& deviceBounds,
SkSpan<const DevicePosition> devicePositions,
GlyphVector glyphs,
bool glyphsOutOfBounds)
: fMaskFormat{format}
, fBlob{blob}
, fGlyphDeviceBounds{deviceBounds}
, fLeftTopDevicePos{devicePositions}
, fSomeGlyphsExcluded{glyphsOutOfBounds}
, fGlyphs{glyphs} {}
GrSubRunOwner DirectMaskSubRun::Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
GrSubRunAllocator* alloc) {
DevicePosition* glyphLeftTop = alloc->makePODArray<DevicePosition>(drawables.size());
GlyphVector::Variant* glyphIDs = alloc->makePODArray<GlyphVector::Variant>(drawables.size());
// Because this is the direct case, the maximum width or height is the size that fits in the
// atlas. This boundary is checked below to ensure that the call to SkGlyphRect below will
// not overflow.
constexpr SkScalar kMaxPos =
std::numeric_limits<int16_t>::max() - SkStrikeCommon::kSkSideTooBigForAtlas;
SkGlyphRect runBounds = skglyph::empty_rect();
size_t goodPosCount = 0;
for (auto [variant, pos] : drawables) {
auto [x, y] = pos;
// Ensure that the .offset() call below does not overflow. And, at this point none of the
// rectangles are empty because they were culled before the run was created. Basically,
// cull all the glyphs that can't appear on the screen.
if (-kMaxPos < x && x < kMaxPos && -kMaxPos < y && y < kMaxPos) {
const SkGlyph* const skGlyph = variant;
const SkGlyphRect deviceBounds =
skGlyph->glyphRect().offset(SkScalarRoundToInt(x), SkScalarRoundToInt(y));
runBounds = skglyph::rect_union(runBounds, deviceBounds);
glyphLeftTop[goodPosCount] = deviceBounds.topLeft();
glyphIDs[goodPosCount].packedGlyphID = skGlyph->getPackedID();
goodPosCount += 1;
}
}
// Wow! no glyphs are in bounds and had non-empty bounds.
if (goodPosCount == 0) {
return nullptr;
}
// If some of the glyphs were excluded by the bounds, then this subrun can't be generally be
// used for other draws. Mark the subrun as not general.
bool glyphsExcluded = goodPosCount != drawables.size();
SkSpan<const DevicePosition> leftTop{glyphLeftTop, goodPosCount};
return alloc->makeUnique<DirectMaskSubRun>(
format, blob, runBounds, leftTop,
GlyphVector{strikeSpec, {glyphIDs, goodPosCount}}, glyphsExcluded);
}
void DirectMaskSubRun::draw(const GrClip* clip, const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const{
auto[drawingClip, op] = this->makeAtlasTextOp(
clip, viewMatrix, glyphRunList, paint, sdc, nullptr);
if (op != nullptr) {
sdc->addDrawOp(drawingClip, std::move(op));
}
}
bool
DirectMaskSubRun::canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const {
auto [reuse, translation] = check_integer_translate(fBlob->initialMatrix(), drawMatrix);
// If glyphs were excluded because of position bounds, then this subrun can only be reused if
// there is no change in position.
if (fSomeGlyphsExcluded) {
return translation.x() == 0 && translation.y() == 0;
}
return reuse;
}
size_t DirectMaskSubRun::vertexStride(const SkMatrix&) const {
if (fMaskFormat != kARGB_GrMaskFormat) {
return sizeof(Mask2DVertex);
} else {
return sizeof(ARGB2DVertex);
}
}
int DirectMaskSubRun::glyphCount() const {
return fGlyphs.glyphs().count();
}
namespace {
enum ClipMethod {
kClippedOut,
kUnclipped,
kGPUClipped,
kGeometryClipped
};
std::tuple<ClipMethod, SkIRect>
calculate_clip(const GrClip* clip, SkRect deviceBounds, SkRect glyphBounds) {
if (clip == nullptr && !deviceBounds.intersects(glyphBounds)) {
return {kClippedOut, SkIRect::MakeEmpty()};
} else if (clip != nullptr) {
switch (auto result = clip->preApply(glyphBounds, GrAA::kNo); result.fEffect) {
case GrClip::Effect::kClippedOut:
return {kClippedOut, SkIRect::MakeEmpty()};
case GrClip::Effect::kUnclipped:
return {kUnclipped, SkIRect::MakeEmpty()};
case GrClip::Effect::kClipped: {
if (result.fIsRRect && result.fRRect.isRect()) {
SkRect r = result.fRRect.rect();
if (result.fAA == GrAA::kNo || GrClip::IsPixelAligned(r)) {
SkIRect clipRect = SkIRect::MakeEmpty();
// Clip geometrically during onPrepare using clipRect.
r.round(&clipRect);
if (clipRect.contains(glyphBounds)) {
// If fully within the clip, signal no clipping using the empty rect.
return {kUnclipped, SkIRect::MakeEmpty()};
}
// Use the clipRect to clip the geometry.
return {kGeometryClipped, clipRect};
}
// Partial pixel clipped at this point. Have the GPU handle it.
}
}
break;
}
}
return {kGPUClipped, SkIRect::MakeEmpty()};
}
} // namespace
std::tuple<const GrClip*, GrOp::Owner>
DirectMaskSubRun::makeAtlasTextOp(const GrClip* clip, const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner) const {
SkASSERT(this->glyphCount() != 0);
const SkMatrix& drawMatrix = viewMatrix.localToDevice();
const SkPoint drawOrigin = glyphRunList.origin();
// We can clip geometrically using clipRect and ignore clip when an axis-aligned rectangular
// non-AA clip is used. If clipRect is empty, and clip is nullptr, then there is no clipping
// needed.
const SkRect subRunBounds = this->deviceRect(drawMatrix, drawOrigin);
const SkRect deviceBounds = SkRect::MakeWH(sdc->width(), sdc->height());
auto [clipMethod, clipRect] = calculate_clip(clip, deviceBounds, subRunBounds);
switch (clipMethod) {
case kClippedOut:
// Returning nullptr as op means skip this op.
return {nullptr, nullptr};
case kUnclipped:
case kGeometryClipped:
// GPU clip is not needed.
clip = nullptr;
break;
case kGPUClipped:
// Use the the GPU clip; clipRect is ignored.
break;
}
if (!clipRect.isEmpty()) { SkASSERT(clip == nullptr); }
GrPaint grPaint;
const SkPMColor4f drawingColor =
calculate_colors(sdc, paint, viewMatrix, fMaskFormat, &grPaint);
GrAtlasTextOp::Geometry* geometry = GrAtlasTextOp::Geometry::MakeForBlob(
*this,
drawMatrix,
drawOrigin,
clipRect,
sk_ref_sp<GrTextBlob>(fBlob),
drawingColor,
sdc->arenaAlloc());
GrRecordingContext* const rContext = sdc->recordingContext();
GrOp::Owner op = GrOp::Make<GrAtlasTextOp>(rContext,
op_mask_type(fMaskFormat),
false,
this->glyphCount(),
subRunBounds,
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void DirectMaskSubRun::testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) {
fGlyphs.packedGlyphIDToGrGlyph(cache);
}
std::tuple<bool, int>
DirectMaskSubRun::regenerateAtlas(int begin, int end, GrMeshDrawOp::Target* target) const {
return fGlyphs.regenerateAtlas(begin, end, fMaskFormat, 0, target);
}
// The 99% case. No clip. Non-color only.
void direct_2D(SkZip<Mask2DVertex[4],
const GrGlyph*,
const DirectMaskSubRun::DevicePosition> quadData,
GrColor color,
SkIPoint integralOriginOffset) {
for (auto[quad, glyph, leftTop] : quadData) {
auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
SkScalar dl = leftTop[0] + integralOriginOffset.x(),
dt = leftTop[1] + integralOriginOffset.y(),
dr = dl + (ar - al),
db = dt + (ab - at);
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
}
}
template <typename Rect>
auto ltbr(const Rect& r) {
return std::make_tuple(r.left(), r.top(), r.right(), r.bottom());
}
// Handle any combination of BW or color and clip or no clip.
template<typename Quad, typename VertexData>
void generalized_direct_2D(SkZip<Quad, const GrGlyph*, const VertexData> quadData,
GrColor color,
SkIPoint integralOriginOffset,
SkIRect* clip = nullptr) {
for (auto[quad, glyph, leftTop] : quadData) {
auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
uint16_t w = ar - al,
h = ab - at;
SkScalar l = (SkScalar)leftTop[0] + integralOriginOffset.x(),
t = (SkScalar)leftTop[1] + integralOriginOffset.y();
if (clip == nullptr) {
auto[dl, dt, dr, db] = SkRect::MakeLTRB(l, t, l + w, t + h);
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 {
SkIRect devIRect = SkIRect::MakeLTRB(l, t, l + w, t + h);
SkScalar dl, dt, dr, db;
if (!clip->containsNoEmptyCheck(devIRect)) {
if (SkIRect clipped; clipped.intersect(devIRect, *clip)) {
al += clipped.left() - devIRect.left();
at += clipped.top() - devIRect.top();
ar += clipped.right() - devIRect.right();
ab += clipped.bottom() - devIRect.bottom();
std::tie(dl, dt, dr, db) = ltbr(clipped);
} 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(al, at, ar, ab) = std::make_tuple(0, 0, 0, 0);
}
} else {
std::tie(dl, dt, dr, db) = ltbr(devIRect);
}
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
}
}
}
void DirectMaskSubRun::fillVertexData(void* vertexDst, int offset, int count, GrColor color,
const SkMatrix& positionMatrix, SkIRect clip) const {
auto quadData = [&](auto dst) {
return SkMakeZip(dst,
fGlyphs.glyphs().subspan(offset, count),
fLeftTopDevicePos.subspan(offset, count));
};
SkPoint originOffset = positionMatrix.mapOrigin() - fBlob->initialMatrix().mapOrigin();
SkIPoint integralOriginOffset =
{SkScalarRoundToInt(originOffset.x()), SkScalarRoundToInt(originOffset.y())};
if (clip.isEmpty()) {
if (fMaskFormat != kARGB_GrMaskFormat) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset);
} else {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
generalized_direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset);
}
} else {
if (fMaskFormat != kARGB_GrMaskFormat) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
generalized_direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset, &clip);
} else {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
generalized_direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset, &clip);
}
}
}
SkRect DirectMaskSubRun::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const {
SkIRect outBounds = fGlyphDeviceBounds.iRect();
// Calculate the offset from the initial device origin to the current device origin.
SkVector offset = drawMatrix.mapPoint(drawOrigin) - fBlob->initialMatrix().mapOrigin();
// The offset should be integer, but make sure.
SkIVector iOffset = {SkScalarRoundToInt(offset.x()), SkScalarRoundToInt(offset.y())};
return SkRect::Make(outBounds.makeOffset(iOffset));
}
GrAtlasSubRun* DirectMaskSubRun::testingOnly_atlasSubRun() {
return this;
}
// -- TransformedMaskSubRun ------------------------------------------------------------------------
class TransformedMaskSubRun final : public GrSubRun, public GrAtlasSubRun {
public:
struct VertexData {
const SkPoint pos;
// The rectangle of the glyphs in strike space. But, for kDirectMask this also implies a
// device space rect.
GrIRect16 rect;
};
TransformedMaskSubRun(GrMaskFormat format,
GrTextBlob* blob,
const SkRect& bounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs);
static GrSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
GrSubRunAllocator* alloc);
void draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const override;
bool canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const override;
GrAtlasSubRun* testingOnly_atlasSubRun() override;
std::tuple<const GrClip*, GrOp::Owner>
makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner) const override;
void testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) override;
std::tuple<bool, int> regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target* target) const override;
void fillVertexData(
void* vertexDst, int offset, int count,
GrColor color, const SkMatrix& positionMatrix, SkIRect clip) const override;
size_t vertexStride(const SkMatrix& drawMatrix) const override;
int glyphCount() const override;
private:
// The rectangle that surrounds all the glyph bounding boxes in device space.
SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const;
const GrMaskFormat fMaskFormat;
GrTextBlob* fBlob;
// The bounds in source space. The bounds are the joined rectangles of all the glyphs.
const SkRect fVertexBounds;
const SkSpan<const VertexData> fVertexData;
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
};
TransformedMaskSubRun::TransformedMaskSubRun(GrMaskFormat format,
GrTextBlob* blob,
const SkRect& bounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs)
: fMaskFormat{format}
, fBlob{blob}
, fVertexBounds{bounds}
, fVertexData{vertexData}
, fGlyphs{glyphs} { }
GrSubRunOwner TransformedMaskSubRun::Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrTextBlob* blob,
GrSubRunAllocator* alloc) {
SkRect bounds = SkRectPriv::MakeLargestInverted();
SkScalar strikeToSource = strikeSpec.strikeToSourceRatio();
SkSpan<VertexData> vertexData = alloc->makePODArray<VertexData>(
drawables,
[&](auto e) {
auto [variant, pos] = e;
SkGlyph* skGlyph = variant;
int16_t l = skGlyph->left(),
t = skGlyph->top(),
r = l + skGlyph->width(),
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 VertexData{pos, {l, t, r, b}};
});
return alloc->makeUnique<TransformedMaskSubRun>(
format, blob, bounds, vertexData,
GlyphVector::Make(strikeSpec, drawables.get<0>(), alloc));
}
void TransformedMaskSubRun::draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const {
auto[drawingClip, op] = this->makeAtlasTextOp(
clip, viewMatrix, glyphRunList, paint, sdc, nullptr);
if (op != nullptr) {
sdc->addDrawOp(drawingClip, std::move(op));
}
}
// If we are not scaling the cache entry to be larger, than a cache with smaller glyphs may be
// better.
bool TransformedMaskSubRun::canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const {
if (fBlob->initialMatrix().getMaxScale() < 1) {
return false;
}
return true;
}
std::tuple<const GrClip*, GrOp::Owner>
TransformedMaskSubRun::makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner) const {
SkASSERT(this->glyphCount() != 0);
SkPoint drawOrigin = glyphRunList.origin();
const SkMatrix& drawMatrix = viewMatrix.localToDevice();
GrPaint grPaint;
SkPMColor4f drawingColor = calculate_colors(sdc, paint, viewMatrix, fMaskFormat, &grPaint);
GrAtlasTextOp::Geometry* geometry = GrAtlasTextOp::Geometry::MakeForBlob(
*this,
drawMatrix,
drawOrigin,
SkIRect::MakeEmpty(),
sk_ref_sp<GrTextBlob>(fBlob),
drawingColor,
sdc->arenaAlloc());
GrRecordingContext* const rContext = sdc->recordingContext();
GrOp::Owner op = GrOp::Make<GrAtlasTextOp>(
rContext,
op_mask_type(fMaskFormat),
true,
this->glyphCount(),
this->deviceRect(drawMatrix, drawOrigin),
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void TransformedMaskSubRun::testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) {
fGlyphs.packedGlyphIDToGrGlyph(cache);
}
std::tuple<bool, int> TransformedMaskSubRun::regenerateAtlas(int begin, int end,
GrMeshDrawOp::Target* target) const {
return fGlyphs.regenerateAtlas(begin, end, fMaskFormat, 1, target, true);
}
void TransformedMaskSubRun::fillVertexData(
void* vertexDst, int offset, int count, GrColor color, const SkMatrix& positionMatrix,
SkIRect clip) const {
constexpr SkScalar kDstPadding = 0.f;
auto quadData = [&](auto dst) {
return SkMakeZip(dst,
fGlyphs.glyphs().subspan(offset, count),
fVertexData.subspan(offset, count));
};
if (!positionMatrix.hasPerspective()) {
if (fMaskFormat == GrMaskFormat::kARGB_GrMaskFormat) {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_2D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
} else {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_2D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
}
} else {
if (fMaskFormat == GrMaskFormat::kARGB_GrMaskFormat) {
using Quad = ARGB3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_3D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
} else {
using Quad = Mask3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_3D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
}
}
}
size_t TransformedMaskSubRun::vertexStride(const SkMatrix& drawMatrix) const {
switch (fMaskFormat) {
case kA8_GrMaskFormat:
return drawMatrix.hasPerspective() ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex);
case kARGB_GrMaskFormat:
return drawMatrix.hasPerspective() ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
default:
SkASSERT(!drawMatrix.hasPerspective());
return sizeof(Mask2DVertex);
}
SkUNREACHABLE;
}
int TransformedMaskSubRun::glyphCount() const {
return fVertexData.count();
}
SkRect TransformedMaskSubRun::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const {
SkRect outBounds = fVertexBounds;
outBounds.offset(drawOrigin);
return drawMatrix.mapRect(outBounds);
}
GrAtlasSubRun* TransformedMaskSubRun::testingOnly_atlasSubRun() {
return this;
}
// -- SDFTSubRun -----------------------------------------------------------------------------------
class SDFTSubRun final : public GrSubRun, public GrAtlasSubRun {
public:
struct VertexData {
const SkPoint pos;
// The rectangle of the glyphs in strike space.
GrIRect16 rect;
};
SDFTSubRun(GrMaskFormat format,
GrTextBlob* blob,
SkRect vertexBounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs,
bool useLCDText,
bool antiAliased);
static GrSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec,
GrTextBlob* blob,
GrSubRunAllocator* alloc);
void draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const override;
bool canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const override;
GrAtlasSubRun* testingOnly_atlasSubRun() override;
std::tuple<const GrClip*, GrOp::Owner>
makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner) const override;
void testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) override;
std::tuple<bool, int> regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target* target) const override;
void fillVertexData(
void* vertexDst, int offset, int count,
GrColor color, const SkMatrix& positionMatrix, SkIRect clip) const override;
size_t vertexStride(const SkMatrix& drawMatrix) const override;
int glyphCount() const override;
private:
// The rectangle that surrounds all the glyph bounding boxes in device space.
SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const;
const GrMaskFormat fMaskFormat;
GrTextBlob* fBlob;
// The bounds in source space. The bounds are the joined rectangles of all the glyphs.
const SkRect fVertexBounds;
const SkSpan<const VertexData> fVertexData;
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
const bool fUseLCDText;
const bool fAntiAliased;
};
SDFTSubRun::SDFTSubRun(GrMaskFormat format,
GrTextBlob* textBlob,
SkRect vertexBounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs,
bool useLCDText,
bool antiAliased)
: fMaskFormat{format}
, fBlob{textBlob}
, fVertexBounds{vertexBounds}
, fVertexData{vertexData}
, fGlyphs{glyphs}
, fUseLCDText{useLCDText}
, fAntiAliased{antiAliased} { }
bool has_some_antialiasing(const SkFont& font ) {
SkFont::Edging edging = font.getEdging();
return edging == SkFont::Edging::kAntiAlias
|| edging == SkFont::Edging::kSubpixelAntiAlias;
}
GrSubRunOwner SDFTSubRun::Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec,
GrTextBlob* blob,
GrSubRunAllocator* alloc) {
SkRect bounds = SkRectPriv::MakeLargestInverted();
auto mapper = [&, strikeToSource=strikeSpec.strikeToSourceRatio()](const auto& d) {
auto& [variant, pos] = d;
SkGlyph* skGlyph = variant;
int16_t l = skGlyph->left(),
t = skGlyph->top(),
r = l + skGlyph->width(),
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 VertexData{pos, {l, t, r, b}};
};
SkSpan<VertexData> vertexData = alloc->makePODArray<VertexData>(drawables, mapper);
return alloc->makeUnique<SDFTSubRun>(
kA8_GrMaskFormat,
blob,
bounds,
vertexData,
GlyphVector::Make(strikeSpec, drawables.get<0>(), alloc),
runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
has_some_antialiasing(runFont));
}
static std::tuple<GrAtlasTextOp::MaskType, uint32_t, bool> calculate_sdf_parameters(
const GrSurfaceDrawContext& sdc,
const SkMatrix& drawMatrix,
bool useLCDText,
bool isAntiAliased) {
const GrColorInfo& colorInfo = sdc.colorInfo();
const SkSurfaceProps& props = sdc.surfaceProps();
bool isBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
bool isLCD = useLCDText && SkPixelGeometryIsH(props.pixelGeometry());
using MT = GrAtlasTextOp::MaskType;
MT maskType = !isAntiAliased ? MT::kAliasedDistanceField
: isLCD ? (isBGR ? MT::kLCDBGRDistanceField
: MT::kLCDDistanceField)
: MT::kGrayscaleDistanceField;
bool useGammaCorrectDistanceTable = colorInfo.isLinearlyBlended();
uint32_t DFGPFlags = drawMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
DFGPFlags |= drawMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
DFGPFlags |= useGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
DFGPFlags |= MT::kAliasedDistanceField == maskType ? kAliased_DistanceFieldEffectFlag : 0;
if (isLCD) {
DFGPFlags |= kUseLCD_DistanceFieldEffectFlag;
DFGPFlags |= MT::kLCDBGRDistanceField == maskType ? kBGR_DistanceFieldEffectFlag : 0;
}
return {maskType, DFGPFlags, useGammaCorrectDistanceTable};
}
std::tuple<const GrClip*, GrOp::Owner >
SDFTSubRun::makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner) const {
SkASSERT(this->glyphCount() != 0);
SkASSERT(!viewMatrix.localToDevice().hasPerspective());
SkPoint drawOrigin = glyphRunList.origin();
const SkMatrix& drawMatrix = viewMatrix.localToDevice();
GrPaint grPaint;
SkPMColor4f drawingColor = calculate_colors(sdc, paint, viewMatrix, fMaskFormat, &grPaint);
auto [maskType, DFGPFlags, useGammaCorrectDistanceTable] =
calculate_sdf_parameters(*sdc, drawMatrix, fUseLCDText, fAntiAliased);
GrAtlasTextOp::Geometry* geometry = GrAtlasTextOp::Geometry::MakeForBlob(
*this,
drawMatrix,
drawOrigin,
SkIRect::MakeEmpty(),
sk_ref_sp<GrTextBlob>(fBlob),
drawingColor,
sdc->arenaAlloc());
GrRecordingContext* const rContext = sdc->recordingContext();
GrOp::Owner op = GrOp::Make<GrAtlasTextOp>(
rContext,
maskType,
true,
this->glyphCount(),
this->deviceRect(drawMatrix, drawOrigin),
SkPaintPriv::ComputeLuminanceColor(paint),
useGammaCorrectDistanceTable,
DFGPFlags,
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void SDFTSubRun::draw(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc) const {
auto[drawingClip, op] = this->makeAtlasTextOp(
clip, viewMatrix, glyphRunList, paint, sdc, nullptr);
if (op != nullptr) {
sdc->addDrawOp(drawingClip, std::move(op));
}
}
bool SDFTSubRun::canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const {
const SkMatrix& initialMatrix = fBlob->initialMatrix();
// 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 = initialMatrix.getMaxScale();
SkScalar scaleAdjust = newMaxScale / oldMaxScale;
auto [maxMinScale, minMaxScale] = fBlob->scaleBounds();
if (scaleAdjust < maxMinScale || scaleAdjust > minMaxScale) {
return false;
}
return true;
}
void SDFTSubRun::testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) {
fGlyphs.packedGlyphIDToGrGlyph(cache);
}
std::tuple<bool, int> SDFTSubRun::regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target *target) const {
return fGlyphs.regenerateAtlas(begin, end, fMaskFormat, SK_DistanceFieldInset, target);
}
size_t SDFTSubRun::vertexStride(const SkMatrix& drawMatrix) const {
return sizeof(Mask2DVertex);
}
void SDFTSubRun::fillVertexData(
void *vertexDst, int offset, int count,
GrColor color, const SkMatrix& positionMatrix, SkIRect clip) const {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_2D(
SkMakeZip((Quad*)vertexDst,
fGlyphs.glyphs().subspan(offset, count),
fVertexData.subspan(offset, count)),
SK_DistanceFieldInset,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
}
int SDFTSubRun::glyphCount() const {
return fVertexData.count();
}
SkRect SDFTSubRun::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const {
SkRect outBounds = fVertexBounds;
outBounds.offset(drawOrigin);
return drawMatrix.mapRect(outBounds);
}
GrAtlasSubRun* SDFTSubRun::testingOnly_atlasSubRun() {
return this;
}
} // namespace
// -- GrTextBlob::Key ------------------------------------------------------------------------------
static SkColor compute_canonical_color(const SkPaint& paint, bool lcd) {
SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
if (lcd) {
// This is the correct computation for canonicalColor, but there are tons of cases where LCD
// can be modified. For now we just regenerate if any run in a textblob has LCD.
// TODO figure out where all of these modifications are and see if we can incorporate that
// logic at a higher level *OR* use sRGB
//canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
// TODO we want to figure out a way to be able to use the canonical color on LCD text,
// see the note above. We pick a placeholder value for LCD text to ensure we always match
// the same key
return SK_ColorTRANSPARENT;
} else {
// A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
// gamma corrected masks anyways, nor color
U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
SkColorGetG(canonicalColor),
SkColorGetB(canonicalColor));
// reduce to our finite number of bits
canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
}
return canonicalColor;
}
auto GrTextBlob::Key::Make(const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
const SkSurfaceProps& surfaceProps,
const GrColorInfo& colorInfo,
const SkMatrix& drawMatrix,
const GrSDFTControl& control) -> std::tuple<bool, Key> {
SkMaskFilterBase::BlurRec blurRec;
// It might be worth caching these things, but its not clear at this time
// TODO for animated mask filters, this will fill up our cache. We need a safeguard here
const SkMaskFilter* maskFilter = paint.getMaskFilter();
bool canCache = glyphRunList.canCache() &&
!(paint.getPathEffect() ||
(maskFilter && !as_MFB(maskFilter)->asABlur(&blurRec)));
// If we're doing linear blending, then we can disable the gamma hacks.
// Otherwise, leave them on. In either case, we still want the contrast boost:
// TODO: Can we be even smarter about mask gamma based on the dest transfer function?
SkScalerContextFlags scalerContextFlags = colorInfo.isLinearlyBlended()
? SkScalerContextFlags::kBoostContrast
: SkScalerContextFlags::kFakeGammaAndBoostContrast;
GrTextBlob::Key key;
if (canCache) {
bool hasLCD = glyphRunList.anyRunsLCD();
// We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
SkPixelGeometry pixelGeometry =
hasLCD ? surfaceProps.pixelGeometry() : kUnknown_SkPixelGeometry;
GrColor canonicalColor = compute_canonical_color(paint, hasLCD);
key.fPixelGeometry = pixelGeometry;
key.fUniqueID = glyphRunList.uniqueID();
key.fStyle = paint.getStyle();
if (key.fStyle != SkPaint::kFill_Style) {
key.fFrameWidth = paint.getStrokeWidth();
key.fMiterLimit = paint.getStrokeMiter();
key.fJoin = paint.getStrokeJoin();
}
key.fHasBlur = maskFilter != nullptr;
if (key.fHasBlur) {
key.fBlurRec = blurRec;
}
key.fCanonicalColor = canonicalColor;
key.fScalerContextFlags = scalerContextFlags;
// Calculate the set of drawing types.
key.fSetOfDrawingTypes = 0;
for (auto& run : glyphRunList) {
key.fSetOfDrawingTypes |= control.drawingType(run.font(), paint, drawMatrix);
}
if (key.fSetOfDrawingTypes & GrSDFTControl::kDirect) {
// Store the fractional offset of the position. We know that the matrix can't be
// perspective at this point.
SkPoint mappedOrigin = drawMatrix.mapOrigin();
key.fDrawMatrix = drawMatrix;
key.fDrawMatrix.setTranslateX(
mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x()));
key.fDrawMatrix.setTranslateY(
mappedOrigin.y() - SkScalarFloorToScalar(mappedOrigin.y()));
} else {
// For path and SDFT, the matrix doesn't matter.
key.fDrawMatrix = SkMatrix::I();
}
}
return {canCache, key};
}
bool GrTextBlob::Key::operator==(const GrTextBlob::Key& that) const {
if (fUniqueID != that.fUniqueID) { return false; }
if (fCanonicalColor != that.fCanonicalColor) { return false; }
if (fStyle != that.fStyle) { return false; }
if (fStyle != SkPaint::kFill_Style) {
if (fFrameWidth != that.fFrameWidth ||
fMiterLimit != that.fMiterLimit ||
fJoin != that.fJoin) {
return false;
}
}
if (fPixelGeometry != that.fPixelGeometry) { return false; }
if (fHasBlur != that.fHasBlur) { return false; }
if (fHasBlur) {
if (fBlurRec.fStyle != that.fBlurRec.fStyle || fBlurRec.fSigma != that.fBlurRec.fSigma) {
return false;
}
}
if (fScalerContextFlags != that.fScalerContextFlags) { return false; }
// Just punt on perspective.
if (fDrawMatrix.hasPerspective()) {
return false;
}
if (fSetOfDrawingTypes != that.fSetOfDrawingTypes) {
return false;
}
if (fSetOfDrawingTypes & GrSDFTControl::kDirect) {
auto [compatible, _] = check_integer_translate(fDrawMatrix, that.fDrawMatrix);
return compatible;
}
return true;
}
// -- 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 SkPaint& paint,
const SkMatrix& drawMatrix,
const GrSDFTControl& control,
SkGlyphRunListPainter* painter) {
// The difference in alignment from the per-glyph data to the SubRun;
constexpr size_t alignDiff =
alignof(DirectMaskSubRun) - alignof(DirectMaskSubRun::DevicePosition);
constexpr size_t vertexDataToSubRunPadding = alignDiff > 0 ? alignDiff : 0;
size_t totalGlyphCount = glyphRunList.totalGlyphCount();
// The neededForSubRun is optimized for DirectMaskSubRun which is by far the most common case.
size_t bytesNeededForSubRun = GrBagOfBytes::PlatformMinimumSizeWithOverhead(
totalGlyphCount * sizeof(DirectMaskSubRun::DevicePosition)
+ GlyphVector::GlyphVectorSize(totalGlyphCount)
+ glyphRunList.runCount() * (sizeof(DirectMaskSubRun) + vertexDataToSubRunPadding),
alignof(GrTextBlob));
size_t allocationSize = sizeof(GrTextBlob) + bytesNeededForSubRun;
void* allocation = ::operator new (allocationSize);
SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint);
sk_sp<GrTextBlob> blob{new (allocation)
GrTextBlob(bytesNeededForSubRun, drawMatrix, initialLuminance)};
for (auto& glyphRun : glyphRunList) {
painter->processGlyphRun(glyphRun,
drawMatrix,
paint,
control,
blob.get(),
"GrTextBlob");
}
return blob;
}
void GrTextBlob::addKey(const Key& key) {
fKey = key;
}
bool GrTextBlob::hasPerspective() const { return fInitialMatrix.hasPerspective(); }
bool GrTextBlob::canReuse(const SkPaint& paint, const SkMatrix& drawMatrix) const {
// A singular matrix will create a GrTextBlob with no SubRuns, but unknown glyphs can
// also cause empty runs. If there are no subRuns, then regenerate.
if ((fSubRunList.isEmpty() || fSomeGlyphsExcluded) && fInitialMatrix != drawMatrix) {
return false;
}
// 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;
}
for (const GrSubRun& subRun : fSubRunList) {
if (!subRun.canReuse(paint, drawMatrix)) {
return false;
}
}
return true;
}
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) {
if (drawables.empty()) { return; }
auto addSameFormat = [&](const SkZip<SkGlyphVariant, SkPoint>& drawable, GrMaskFormat format) {
GrSubRunOwner subRun = addSingle(drawable, strikeSpec, format, this, &fAlloc);
if (subRun != nullptr) {
fSubRunList.append(std::move(subRun));
} else {
fSomeGlyphsExcluded = true;
}
};
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);
addSameFormat(sameFormat, format);
format = nextFormat;
startIndex = i;
}
}
auto sameFormat = drawables.last(drawables.size() - startIndex);
addSameFormat(sameFormat, format);
}
GrTextBlob::GrTextBlob(int allocSize,
const SkMatrix& drawMatrix,
SkColor initialLuminance)
: fAlloc{SkTAddOffset<char>(this, sizeof(GrTextBlob)), allocSize, allocSize/2}
, fSize{allocSize}
, fInitialMatrix{drawMatrix}
, fInitialLuminance{initialLuminance} { }
void GrTextBlob::processDeviceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) {
this->addMultiMaskFormat(DirectMaskSubRun::Make, drawables, strikeSpec);
}
void GrTextBlob::processSourcePaths(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec) {
fSubRunList.append(PathSubRun::Make(drawables,
has_some_antialiasing(runFont),
strikeSpec,
*this,
&fAlloc));
}
void GrTextBlob::processSourceSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
const SkFont& runFont,
SkScalar minScale,
SkScalar maxScale) {
fMaxMinScale = std::max(minScale, fMaxMinScale);
fMinMaxScale = std::min(maxScale, fMinMaxScale);
fSubRunList.append(SDFTSubRun::Make(drawables, runFont, strikeSpec, this, &fAlloc));
}
void GrTextBlob::processSourceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec) {
this->addMultiMaskFormat(TransformedMaskSubRun::Make, drawables, strikeSpec);
}
// -- GrBagOfBytes ---------------------------------------------------------------------------------
GrBagOfBytes::GrBagOfBytes(char* bytes, size_t size, size_t firstHeapAllocation)
: fFibProgression(size, firstHeapAllocation) {
SkASSERT_RELEASE(size < kMaxByteSize);
SkASSERT_RELEASE(firstHeapAllocation < kMaxByteSize);
std::size_t space = size;
void* ptr = bytes;
if (bytes && std::align(kMaxAlignment, sizeof(Block), ptr, space)) {
this->setupBytesAndCapacity(bytes, size);
new (fEndByte) Block(nullptr, nullptr);
}
}
GrBagOfBytes::GrBagOfBytes(size_t firstHeapAllocation)
: GrBagOfBytes(nullptr, 0, firstHeapAllocation) {}
GrBagOfBytes::~GrBagOfBytes() {
Block* cursor = reinterpret_cast<Block*>(fEndByte);
while (cursor != nullptr) {
char* toDelete = cursor->fBlockStart;
cursor = cursor->fPrevious;
delete [] toDelete;
}
}
GrBagOfBytes::Block::Block(char* previous, char* startOfBlock)
: fBlockStart{startOfBlock}
, fPrevious{reinterpret_cast<Block*>(previous)} {}
void* GrBagOfBytes::alignedBytes(int size, int alignment) {
SkASSERT_RELEASE(0 < size && size < kMaxByteSize);
SkASSERT_RELEASE(0 < alignment && alignment <= kMaxAlignment);
SkASSERT_RELEASE(SkIsPow2(alignment));
return this->allocateBytes(size, alignment);
}
void GrBagOfBytes::setupBytesAndCapacity(char* bytes, int size) {
// endByte must be aligned to the maximum alignment to allow tracking alignment using capacity;
// capacity and endByte are both aligned to max alignment.
intptr_t endByte = reinterpret_cast<intptr_t>(bytes + size - sizeof(Block)) & -kMaxAlignment;
fEndByte = reinterpret_cast<char*>(endByte);
fCapacity = fEndByte - bytes;
}
void GrBagOfBytes::needMoreBytes(int requestedSize, int alignment) {
int nextBlockSize = fFibProgression.nextBlockSize();
const int size = PlatformMinimumSizeWithOverhead(
std::max(requestedSize, nextBlockSize), alignof(max_align_t));
char* const bytes = new char[size];
// fEndByte is changed by setupBytesAndCapacity. Remember it to link back to.
char* const previousBlock = fEndByte;
this->setupBytesAndCapacity(bytes, size);
// Make a block to delete these bytes, and points to the previous block.
new (fEndByte) Block{previousBlock, bytes};
// Make fCapacity the alignment for the requested object.
fCapacity = fCapacity & -alignment;
SkASSERT(fCapacity >= requestedSize);
}
// -- GrTextBlobAllocator --------------------------------------------------------------------------
GrSubRunAllocator::GrSubRunAllocator(char* bytes, int size, int firstHeapAllocation)
: fAlloc{bytes, SkTo<size_t>(size), SkTo<size_t>(firstHeapAllocation)} {}
GrSubRunAllocator::GrSubRunAllocator(int firstHeapAllocation)
: GrSubRunAllocator(nullptr, 0, firstHeapAllocation) {}
void* GrSubRunAllocator::alignedBytes(int unsafeSize, int unsafeAlignment) {
return fAlloc.alignedBytes(unsafeSize, unsafeAlignment);
}
// ----------------------------- Begin no cache implementation -------------------------------------
namespace {
// -- DirectMaskSubRunNoCache ----------------------------------------------------------------------
class DirectMaskSubRunNoCache final : public GrAtlasSubRun {
public:
using DevicePosition = skvx::Vec<2, int16_t>;
DirectMaskSubRunNoCache(GrMaskFormat format,
const SkRect& bounds,
SkSpan<const DevicePosition> devicePositions,
GlyphVector glyphs);
static GrAtlasSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrSubRunAllocator* alloc);
size_t vertexStride(const SkMatrix& drawMatrix) const override;
int glyphCount() const override;
std::tuple<const GrClip*, GrOp::Owner>
makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner subRunOwner) const override;
void testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) override;
std::tuple<bool, int>
regenerateAtlas(int begin, int end, GrMeshDrawOp::Target* target) const override;
void fillVertexData(void* vertexDst, int offset, int count, GrColor color,
const SkMatrix& positionMatrix, SkIRect clip) const override;
private:
const GrMaskFormat fMaskFormat;
// The vertex bounds in device space. The bounds are the joined rectangles of all the glyphs.
const SkRect fGlyphDeviceBounds;
const SkSpan<const DevicePosition> fLeftTopDevicePos;
// Space for geometry
alignas(alignof(GrAtlasTextOp::Geometry)) char fGeom[sizeof(GrAtlasTextOp::Geometry)];
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
};
DirectMaskSubRunNoCache::DirectMaskSubRunNoCache(GrMaskFormat format,
const SkRect& deviceBounds,
SkSpan<const DevicePosition> devicePositions,
GlyphVector glyphs)
: fMaskFormat{format}
, fGlyphDeviceBounds{deviceBounds}
, fLeftTopDevicePos{devicePositions}
, fGlyphs{glyphs} {}
GrAtlasSubRunOwner DirectMaskSubRunNoCache::Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrSubRunAllocator* alloc) {
DevicePosition* glyphLeftTop = alloc->makePODArray<DevicePosition>(drawables.size());
GlyphVector::Variant* glyphIDs = static_cast<GlyphVector::Variant*>(
alloc->alignedBytes(drawables.size() * sizeof(GlyphVector::Variant),
alignof(GlyphVector::Variant)));
// Because this is the direct case, the maximum width or height is the size that fits in the
// atlas. This boundary is checked below to ensure that the call to SkGlyphRect below will
// not overflow.
constexpr SkScalar kMaxPos =
std::numeric_limits<int16_t>::max() - SkStrikeCommon::kSkSideTooBigForAtlas;
SkGlyphRect runBounds = skglyph::empty_rect();
size_t goodPosCount = 0;
for (auto [variant, pos] : drawables) {
auto [x, y] = pos;
// Ensure that the .offset() call below does not overflow. And, at this point none of the
// rectangles are empty because they were culled before the run was created. Basically,
// cull all the glyphs that can't appear on the screen.
if (-kMaxPos < x && x < kMaxPos && -kMaxPos < y && y < kMaxPos) {
const SkGlyph* const skGlyph = variant;
const SkGlyphRect deviceBounds =
skGlyph->glyphRect().offset(SkScalarRoundToInt(x), SkScalarRoundToInt(y));
runBounds = skglyph::rect_union(runBounds, deviceBounds);
glyphLeftTop[goodPosCount] = deviceBounds.topLeft();
glyphIDs[goodPosCount].packedGlyphID = skGlyph->getPackedID();
goodPosCount += 1;
}
}
// Wow! no glyphs are in bounds and had non-empty bounds.
if (goodPosCount == 0) {
return nullptr;
}
SkSpan<const DevicePosition> leftTop{glyphLeftTop, goodPosCount};
return alloc->makeUnique<DirectMaskSubRunNoCache>(
format, runBounds.rect(), leftTop,
GlyphVector{strikeSpec, {glyphIDs, goodPosCount}});
}
size_t DirectMaskSubRunNoCache::vertexStride(const SkMatrix&) const {
if (fMaskFormat != kARGB_GrMaskFormat) {
return sizeof(Mask2DVertex);
} else {
return sizeof(ARGB2DVertex);
}
}
int DirectMaskSubRunNoCache::glyphCount() const {
return fGlyphs.glyphs().count();
}
std::tuple<const GrClip*, GrOp::Owner>
DirectMaskSubRunNoCache::makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner subRunOwner) const {
SkASSERT(this->glyphCount() != 0);
const SkMatrix& drawMatrix = viewMatrix.localToDevice();
const SkPoint drawOrigin = glyphRunList.origin();
// We can clip geometrically using clipRect and ignore clip when an axis-aligned rectangular
// non-AA clip is used. If clipRect is empty, and clip is nullptr, then there is no clipping
// needed.
const SkRect deviceBounds = SkRect::MakeWH(sdc->width(), sdc->height());
auto [clipMethod, clipRect] = calculate_clip(clip, deviceBounds, fGlyphDeviceBounds);
switch (clipMethod) {
case kClippedOut:
// Returning nullptr as op means skip this op.
return {nullptr, nullptr};
case kUnclipped:
case kGeometryClipped:
// GPU clip is not needed.
clip = nullptr;
break;
case kGPUClipped:
// Use the the GPU clip; clipRect is ignored.
break;
}
if (!clipRect.isEmpty()) { SkASSERT(clip == nullptr); }
GrPaint grPaint;
const SkPMColor4f drawingColor =
calculate_colors(sdc, paint, viewMatrix, fMaskFormat, &grPaint);
GrRecordingContext* const rContext = sdc->recordingContext();
GrAtlasTextOp::Geometry* geometry = new ((void*)fGeom) GrAtlasTextOp::Geometry{
*this,
drawMatrix,
drawOrigin,
clipRect,
nullptr,
std::move(subRunOwner),
drawingColor
};
GrOp::Owner op = GrOp::Make<GrAtlasTextOp>(rContext,
op_mask_type(fMaskFormat),
false,
this->glyphCount(),
fGlyphDeviceBounds,
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void DirectMaskSubRunNoCache::testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) {
fGlyphs.packedGlyphIDToGrGlyph(cache);
}
std::tuple<bool, int>
DirectMaskSubRunNoCache::regenerateAtlas(int begin, int end, GrMeshDrawOp::Target* target) const {
return fGlyphs.regenerateAtlas(begin, end, fMaskFormat, 0, target);
}
// The 99% case. No clip. Non-color only.
void direct_2D2(SkZip<Mask2DVertex[4],
const GrGlyph*,
const DirectMaskSubRunNoCache::DevicePosition> quadData,
GrColor color) {
for (auto[quad, glyph, leftTop] : quadData) {
auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
SkScalar dl = leftTop[0],
dt = leftTop[1],
dr = dl + (ar - al),
db = dt + (ab - at);
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
}
}
void DirectMaskSubRunNoCache::fillVertexData(void* vertexDst, int offset, int count, GrColor color,
const SkMatrix& positionMatrix, SkIRect clip) const {
auto quadData = [&](auto dst) {
return SkMakeZip(dst,
fGlyphs.glyphs().subspan(offset, count),
fLeftTopDevicePos.subspan(offset, count));
};
if (clip.isEmpty()) {
if (fMaskFormat != kARGB_GrMaskFormat) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
direct_2D2(quadData((Quad*)vertexDst), color);
} else {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
generalized_direct_2D(quadData((Quad*)vertexDst), color, {0,0});
}
} else {
if (fMaskFormat != kARGB_GrMaskFormat) {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
generalized_direct_2D(quadData((Quad*)vertexDst), color, {0,0}, &clip);
} else {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
generalized_direct_2D(quadData((Quad*)vertexDst), color, {0,0}, &clip);
}
}
}
// -- TransformedMaskSubRunNoCache -----------------------------------------------------------------
class TransformedMaskSubRunNoCache final : public GrAtlasSubRun {
public:
struct VertexData {
const SkPoint pos;
// The rectangle of the glyphs in strike space. But, for kDirectMask this also implies a
// device space rect.
GrIRect16 rect;
};
TransformedMaskSubRunNoCache(GrMaskFormat format,
const SkRect& bounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs);
static GrAtlasSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrSubRunAllocator* alloc);
std::tuple<const GrClip*, GrOp::Owner>
makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner subRunOwner) const override;
void testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) override;
std::tuple<bool, int> regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target* target) const override;
void fillVertexData(
void* vertexDst, int offset, int count,
GrColor color, const SkMatrix& positionMatrix, SkIRect clip) const override;
size_t vertexStride(const SkMatrix& drawMatrix) const override;
int glyphCount() const override;
private:
// The rectangle that surrounds all the glyph bounding boxes in device space.
SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const;
const GrMaskFormat fMaskFormat;
// The bounds in source space. The bounds are the joined rectangles of all the glyphs.
const SkRect fVertexBounds;
const SkSpan<const VertexData> fVertexData;
// Space for geometry
alignas(alignof(GrAtlasTextOp::Geometry)) char fGeom[sizeof(GrAtlasTextOp::Geometry)];
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
};
TransformedMaskSubRunNoCache::TransformedMaskSubRunNoCache(GrMaskFormat format,
const SkRect& bounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs)
: fMaskFormat{format}
, fVertexBounds{bounds}
, fVertexData{vertexData}
, fGlyphs{glyphs} { }
GrAtlasSubRunOwner TransformedMaskSubRunNoCache::Make(
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
GrMaskFormat format,
GrSubRunAllocator* alloc) {
SkRect bounds = SkRectPriv::MakeLargestInverted();
auto initializer = [&, strikeToSource=strikeSpec.strikeToSourceRatio()](auto drawable) {
auto [variant, pos] = drawable;
SkGlyph* skGlyph = variant;
int16_t l = skGlyph->left(),
t = skGlyph->top(),
r = l + skGlyph->width(),
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 VertexData{pos, {l, t, r, b}};
};
SkSpan<VertexData> vertexData = alloc->makePODArray<VertexData>(drawables, initializer);
return alloc->makeUnique<TransformedMaskSubRunNoCache>(
format, bounds, vertexData,
GlyphVector::Make(strikeSpec, drawables.get<0>(), alloc));
}
std::tuple<const GrClip*, GrOp::Owner>
TransformedMaskSubRunNoCache::makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner subRunOwner) const {
SkASSERT(this->glyphCount() != 0);
SkPoint drawOrigin = glyphRunList.origin();
const SkMatrix& drawMatrix = viewMatrix.localToDevice();
GrPaint grPaint;
SkPMColor4f drawingColor = calculate_colors(sdc, paint, viewMatrix, fMaskFormat, &grPaint);
// We can clip geometrically using clipRect and ignore clip if we're not using SDFs or
// transformed glyphs, and we have an axis-aligned rectangular non-AA clip.
GrAtlasTextOp::Geometry* geometry = new ((void*)fGeom) GrAtlasTextOp::Geometry{
*this,
drawMatrix,
drawOrigin,
SkIRect::MakeEmpty(),
nullptr,
std::move(subRunOwner),
drawingColor
};
GrRecordingContext* rContext = sdc->recordingContext();
GrOp::Owner op = GrOp::Make<GrAtlasTextOp>(
rContext,
op_mask_type(fMaskFormat),
true,
this->glyphCount(),
this->deviceRect(drawMatrix, drawOrigin),
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void TransformedMaskSubRunNoCache::testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) {
fGlyphs.packedGlyphIDToGrGlyph(cache);
}
std::tuple<bool, int> TransformedMaskSubRunNoCache::regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target* target) const {
return fGlyphs.regenerateAtlas(begin, end, fMaskFormat, 1, target, true);
}
void TransformedMaskSubRunNoCache::fillVertexData(
void* vertexDst, int offset, int count, GrColor color,
const SkMatrix& positionMatrix, SkIRect clip) const {
constexpr SkScalar kDstPadding = 0.f;
auto quadData = [&](auto dst) {
return SkMakeZip(dst,
fGlyphs.glyphs().subspan(offset, count),
fVertexData.subspan(offset, count));
};
if (!positionMatrix.hasPerspective()) {
if (fMaskFormat == GrMaskFormat::kARGB_GrMaskFormat) {
using Quad = ARGB2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_2D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
} else {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_2D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
}
} else {
if (fMaskFormat == GrMaskFormat::kARGB_GrMaskFormat) {
using Quad = ARGB3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_3D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
} else {
using Quad = Mask3DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_3D(
quadData((Quad*) vertexDst),
kDstPadding,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
}
}
}
size_t TransformedMaskSubRunNoCache::vertexStride(const SkMatrix& drawMatrix) const {
switch (fMaskFormat) {
case kA8_GrMaskFormat:
return drawMatrix.hasPerspective() ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex);
case kARGB_GrMaskFormat:
return drawMatrix.hasPerspective() ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
default:
SkASSERT(!drawMatrix.hasPerspective());
return sizeof(Mask2DVertex);
}
SkUNREACHABLE;
}
int TransformedMaskSubRunNoCache::glyphCount() const {
return fVertexData.count();
}
SkRect TransformedMaskSubRunNoCache::deviceRect(
const SkMatrix& drawMatrix, SkPoint drawOrigin) const {
SkRect outBounds = fVertexBounds;
outBounds.offset(drawOrigin);
return drawMatrix.mapRect(outBounds);
}
// -- SDFTSubRunNoCache ----------------------------------------------------------------------------
class SDFTSubRunNoCache final : public GrAtlasSubRun {
public:
struct VertexData {
const SkPoint pos;
// The rectangle of the glyphs in strike space.
GrIRect16 rect;
};
SDFTSubRunNoCache(GrMaskFormat format,
SkRect vertexBounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs,
bool useLCDText,
bool antiAliased);
static GrAtlasSubRunOwner Make(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec,
GrSubRunAllocator* alloc);
std::tuple<const GrClip*, GrOp::Owner>
makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner subRunOwner) const override;
void testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) override;
std::tuple<bool, int> regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target* target) const override;
void fillVertexData(
void* vertexDst, int offset, int count,
GrColor color, const SkMatrix& positionMatrix, SkIRect clip) const override;
size_t vertexStride(const SkMatrix& drawMatrix) const override;
int glyphCount() const override;
private:
// The rectangle that surrounds all the glyph bounding boxes in device space.
SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const;
const GrMaskFormat fMaskFormat;
// The bounds in source space. The bounds are the joined rectangles of all the glyphs.
const SkRect fVertexBounds;
const SkSpan<const VertexData> fVertexData;
// Space for geometry
alignas(alignof(GrAtlasTextOp::Geometry)) char fGeom[sizeof(GrAtlasTextOp::Geometry)];
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
const bool fUseLCDText;
const bool fAntiAliased;
};
SDFTSubRunNoCache::SDFTSubRunNoCache(GrMaskFormat format,
SkRect vertexBounds,
SkSpan<const VertexData> vertexData,
GlyphVector glyphs,
bool useLCDText,
bool antiAliased)
: fMaskFormat{format}
, fVertexBounds{vertexBounds}
, fVertexData{vertexData}
, fGlyphs{glyphs}
, fUseLCDText{useLCDText}
, fAntiAliased{antiAliased} { }
GrAtlasSubRunOwner SDFTSubRunNoCache::Make(
const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec,
GrSubRunAllocator* alloc) {
SkRect bounds = SkRectPriv::MakeLargestInverted();
auto initializer = [&, strikeToSource=strikeSpec.strikeToSourceRatio()](auto drawable) {
auto [variant, pos] = drawable;
SkGlyph* skGlyph = variant;
int16_t l = skGlyph->left(),
t = skGlyph->top(),
r = l + skGlyph->width(),
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 VertexData{pos, {l, t, r, b}};
};
SkSpan<VertexData> vertexData = alloc->makePODArray<VertexData>(drawables, initializer);
return alloc->makeUnique<SDFTSubRunNoCache>(
kA8_GrMaskFormat,
bounds,
vertexData,
GlyphVector::Make(strikeSpec, drawables.get<0>(), alloc),
runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
has_some_antialiasing(runFont));
}
std::tuple<const GrClip*, GrOp::Owner>
SDFTSubRunNoCache::makeAtlasTextOp(const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint,
GrSurfaceDrawContext* sdc,
GrAtlasSubRunOwner subRunOwner) const {
SkASSERT(this->glyphCount() != 0);
SkPoint drawOrigin = glyphRunList.origin();
const SkMatrix& drawMatrix = viewMatrix.localToDevice();
GrPaint grPaint;
SkPMColor4f drawingColor = calculate_colors(sdc, paint, viewMatrix, fMaskFormat, &grPaint);
auto [maskType, DFGPFlags, useGammaCorrectDistanceTable] =
calculate_sdf_parameters(*sdc, drawMatrix, fUseLCDText, fAntiAliased);
GrAtlasTextOp::Geometry* geometry = new ((void*)fGeom) GrAtlasTextOp::Geometry {
*this,
drawMatrix,
drawOrigin,
SkIRect::MakeEmpty(),
nullptr,
std::move(subRunOwner),
drawingColor
};
GrRecordingContext* rContext = sdc->recordingContext();
GrOp::Owner op = GrOp::Make<GrAtlasTextOp>(
rContext,
maskType,
true,
this->glyphCount(),
this->deviceRect(drawMatrix, drawOrigin),
SkPaintPriv::ComputeLuminanceColor(paint),
useGammaCorrectDistanceTable,
DFGPFlags,
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void SDFTSubRunNoCache::testingOnly_packedGlyphIDToGrGlyph(GrStrikeCache *cache) {
fGlyphs.packedGlyphIDToGrGlyph(cache);
}
std::tuple<bool, int> SDFTSubRunNoCache::regenerateAtlas(
int begin, int end, GrMeshDrawOp::Target *target) const {
return fGlyphs.regenerateAtlas(begin, end, fMaskFormat, SK_DistanceFieldInset, target);
}
size_t SDFTSubRunNoCache::vertexStride(const SkMatrix& drawMatrix) const {
return sizeof(Mask2DVertex);
}
void SDFTSubRunNoCache::fillVertexData(
void *vertexDst, int offset, int count,
GrColor color, const SkMatrix& positionMatrix, SkIRect clip) const {
using Quad = Mask2DVertex[4];
SkASSERT(sizeof(Quad) == this->vertexStride(positionMatrix) * kVerticesPerGlyph);
fill_transformed_vertices_2D(
SkMakeZip((Quad*)vertexDst,
fGlyphs.glyphs().subspan(offset, count),
fVertexData.subspan(offset, count)),
SK_DistanceFieldInset,
fGlyphs.strikeToSourceRatio(),
color,
positionMatrix);
}
int SDFTSubRunNoCache::glyphCount() const {
return fVertexData.count();
}
SkRect SDFTSubRunNoCache::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const {
SkRect outBounds = fVertexBounds;
outBounds.offset(drawOrigin);
return drawMatrix.mapRect(outBounds);
}
} // namespace
GrSubRunNoCachePainter::GrSubRunNoCachePainter(GrSurfaceDrawContext* sdc,
GrSubRunAllocator* alloc,
const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkGlyphRunList& glyphRunList,
const SkPaint& paint)
: fSDC{sdc}
, fAlloc{alloc}
, fClip{clip}
, fViewMatrix{viewMatrix}
, fGlyphRunList{glyphRunList}
, fPaint {paint} {}
void GrSubRunNoCachePainter::processDeviceMasks(
const SkZip<SkGlyphVariant, SkPoint>& drawables, const SkStrikeSpec& strikeSpec) {
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);
this->draw(DirectMaskSubRunNoCache::Make(sameFormat, strikeSpec, format, fAlloc));
format = nextFormat;
startIndex = i;
}
}
auto sameFormat = drawables.last(drawables.size() - startIndex);
this->draw(DirectMaskSubRunNoCache::Make(sameFormat, strikeSpec, format, fAlloc));
}
void GrSubRunNoCachePainter::processSourceMasks(
const SkZip<SkGlyphVariant, SkPoint>& drawables, const SkStrikeSpec& strikeSpec) {
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);
this->draw(
TransformedMaskSubRunNoCache::Make(
sameFormat, strikeSpec, format, fAlloc));
format = nextFormat;
startIndex = i;
}
}
auto sameFormat = drawables.last(drawables.size() - startIndex);
this->draw(
TransformedMaskSubRunNoCache::Make(sameFormat, strikeSpec, format, fAlloc));
}
void GrSubRunNoCachePainter::processSourcePaths(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkFont& runFont,
const SkStrikeSpec& strikeSpec) {
SkASSERT(!drawables.empty());
SkPoint drawOrigin = fGlyphRunList.origin();
const SkPaint& drawPaint = fPaint;
SkPaint runPaint{drawPaint};
runPaint.setAntiAlias(has_some_antialiasing(runFont));
// 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 = strikeSpec.strikeToSourceRatio();
SkMatrix strikeToSource = SkMatrix::Scale(scale, scale);
strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y());
if (!needsExactCTM) {
for (auto [variant, pos] : drawables) {
const SkPath& path = *variant.path();
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(pos.x(), pos.y());
SkPreConcatMatrixProvider strikeToDevice(fViewMatrix, pathMatrix);
GrStyledShape shape(path, drawPaint);
GrBlurUtils::drawShapeWithMaskFilter(
fSDC->recordingContext(), fSDC, fClip, runPaint,
strikeToDevice, shape);
}
} else {
// Transform the path to device space because the deviceMatrix must be unchanged to
// draw effect, filter or shader paths.
for (auto [variant, pos] : drawables) {
const SkPath& path = *variant.path();
// Transform the glyph to source space.
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(pos.x(), pos.y());
SkPath sourceSpacePath;
path.transform(pathMatrix, &sourceSpacePath);
sourceSpacePath.setIsVolatile(true);
GrStyledShape shape(sourceSpacePath, drawPaint);
GrBlurUtils::drawShapeWithMaskFilter(
fSDC->recordingContext(), fSDC, fClip, runPaint, fViewMatrix, shape);
}
}
}
void GrSubRunNoCachePainter::processSourceSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables,
const SkStrikeSpec& strikeSpec,
const SkFont& runFont,
SkScalar minScale, SkScalar maxScale) {
if (drawables.empty()) {
return;
}
this->draw(SDFTSubRunNoCache::Make(drawables, runFont, strikeSpec, fAlloc));
}
void GrSubRunNoCachePainter::draw(GrAtlasSubRunOwner subRun) {
GrAtlasSubRun* subRunPtr = subRun.get();
auto [drawingClip, op] = subRunPtr->makeAtlasTextOp(
fClip, fViewMatrix, fGlyphRunList, fPaint, fSDC, std::move(subRun));
if (op != nullptr) {
fSDC->addDrawOp(drawingClip, std::move(op));
}
}