blob: 6cc472e7abe29823f96b9c99ab04c848ed18feb6 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/text/gpu/SubRunContainer.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkDrawable.h"
#include "include/core/SkFont.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathEffect.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkStrokeRec.h"
#include "include/core/SkSurfaceProps.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkDashPathEffect.h"
#include "include/private/SkColorData.h"
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkOnce.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTLogic.h"
#include "include/private/base/SkTo.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/base/SkZip.h"
#include "src/core/SkDevice.h"
#include "src/core/SkDistanceFieldGen.h"
#include "src/core/SkEnumerate.h"
#include "src/core/SkFontPriv.h"
#include "src/core/SkGlyph.h"
#include "src/core/SkMask.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkScalerContext.h"
#include "src/core/SkStrike.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkStrikeSpec.h"
#include "src/core/SkWriteBuffer.h"
#include "src/gpu/AtlasTypes.h"
#include "src/text/GlyphRun.h"
#include "src/text/StrikeForGPU.h"
#include "src/text/gpu/Glyph.h"
#include "src/text/gpu/GlyphVector.h"
#include "src/text/gpu/SDFMaskFilter.h"
#include "src/text/gpu/SDFTControl.h"
#include "src/text/gpu/SubRunAllocator.h"
#include "src/text/gpu/VertexFiller.h"
#include <algorithm>
#include <climits>
#include <cstdint>
#include <initializer_list>
#include <new>
#include <optional>
#include <vector>
class GrRecordingContext;
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
#include "src/gpu/ganesh/GrClip.h"
#include "src/gpu/ganesh/GrColorInfo.h"
#include "src/gpu/ganesh/GrFragmentProcessor.h"
#include "src/gpu/ganesh/GrPaint.h"
#include "src/gpu/ganesh/SkGr.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#include "src/gpu/ganesh/effects/GrDistanceFieldGeoProc.h"
#include "src/gpu/ganesh/ops/AtlasTextOp.h"
using AtlasTextOp = skgpu::ganesh::AtlasTextOp;
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
using namespace skia_private;
using namespace skglyph;
// -- GPU Text -------------------------------------------------------------------------------------
// Naming conventions
// * drawMatrix - the CTM from the canvas.
// * drawOrigin - the x, y location of the drawTextBlob call.
// * positionMatrix - this is the combination of the drawMatrix and the drawOrigin:
// positionMatrix = drawMatrix * TranslationMatrix(drawOrigin.x, drawOrigin.y);
//
// Note:
// In order to transform Slugs, you need to set the fSupportBilerpFromGlyphAtlas on
// GrContextOptions.
namespace sktext::gpu {
// -- SubRunStreamTag ------------------------------------------------------------------------------
enum SubRun::SubRunStreamTag : int {
kBad = 0, // Make this 0 to line up with errors from readInt.
kDirectMaskStreamTag,
#if !defined(SK_DISABLE_SDF_TEXT)
kSDFTStreamTag,
#endif
kTransformMaskStreamTag,
kPathStreamTag,
kDrawableStreamTag,
kSubRunStreamTagCount,
};
} // namespace sktext::gpu
using MaskFormat = skgpu::MaskFormat;
using namespace sktext;
using namespace sktext::gpu;
namespace {
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
SkPMColor4f calculate_colors(skgpu::ganesh::SurfaceDrawContext* sdc,
const SkPaint& paint,
const SkMatrix& matrix,
MaskFormat maskFormat,
GrPaint* grPaint) {
GrRecordingContext* rContext = sdc->recordingContext();
const GrColorInfo& colorInfo = sdc->colorInfo();
const SkSurfaceProps& props = sdc->surfaceProps();
if (maskFormat == MaskFormat::kARGB) {
SkPaintToGrPaintReplaceShader(rContext, colorInfo, paint, matrix, nullptr, props, grPaint);
float a = grPaint->getColor4f().fA;
return {a, a, a, a};
}
SkPaintToGrPaint(rContext, colorInfo, paint, matrix, props, grPaint);
return grPaint->getColor4f();
}
SkMatrix position_matrix(const SkMatrix& drawMatrix, SkPoint drawOrigin) {
SkMatrix position_matrix = drawMatrix;
return position_matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
}
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
SkSpan<const SkPackedGlyphID> get_packedIDs(SkZip<const SkPackedGlyphID, const SkPoint> accepted) {
return accepted.get<0>();
}
SkSpan<const SkGlyphID> get_glyphIDs(SkZip<const SkGlyphID, const SkPoint> accepted) {
return accepted.get<0>();
}
template <typename U>
SkSpan<const SkPoint> get_positions(SkZip<U, const SkPoint> accepted) {
return accepted.template get<1>();
}
// -- PathOpSubmitter ------------------------------------------------------------------------------
// PathOpSubmitter holds glyph ids until ready to draw. During drawing, the glyph ids are
// converted to SkPaths. PathOpSubmitter can only be serialized when it is holding glyph ids;
// it can only be serialized before submitDraws has been called.
class PathOpSubmitter {
public:
PathOpSubmitter() = delete;
PathOpSubmitter(const PathOpSubmitter&) = delete;
const PathOpSubmitter& operator=(const PathOpSubmitter&) = delete;
PathOpSubmitter(PathOpSubmitter&& that)
// Transfer ownership of fIDsOrPaths from that to this.
: fIDsOrPaths{std::exchange(
const_cast<SkSpan<IDOrPath>&>(that.fIDsOrPaths), SkSpan<IDOrPath>{})}
, fPositions{that.fPositions}
, fStrikeToSourceScale{that.fStrikeToSourceScale}
, fIsAntiAliased{that.fIsAntiAliased}
, fStrikePromise{std::move(that.fStrikePromise)} {}
PathOpSubmitter& operator=(PathOpSubmitter&& that) {
this->~PathOpSubmitter();
new (this) PathOpSubmitter{std::move(that)};
return *this;
}
PathOpSubmitter(bool isAntiAliased,
SkScalar strikeToSourceScale,
SkSpan<SkPoint> positions,
SkSpan<IDOrPath> idsOrPaths,
SkStrikePromise&& strikePromise);
~PathOpSubmitter();
static PathOpSubmitter Make(SkZip<const SkGlyphID, const SkPoint> accepted,
bool isAntiAliased,
SkScalar strikeToSourceScale,
SkStrikePromise&& strikePromise,
SubRunAllocator* alloc);
int unflattenSize() const;
void flatten(SkWriteBuffer& buffer) const;
static std::optional<PathOpSubmitter> MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client);
// submitDraws is not thread safe. It only occurs the single thread drawing portion of the GPU
// rendering.
void submitDraws(SkCanvas*,
SkPoint drawOrigin,
const SkPaint& paint) const;
private:
// When PathOpSubmitter is created only the glyphIDs are needed, during the submitDraws call,
// the glyphIDs are converted to SkPaths.
const SkSpan<IDOrPath> fIDsOrPaths;
const SkSpan<const SkPoint> fPositions;
const SkScalar fStrikeToSourceScale;
const bool fIsAntiAliased;
mutable SkStrikePromise fStrikePromise;
mutable SkOnce fConvertIDsToPaths;
mutable bool fPathsAreCreated{false};
};
int PathOpSubmitter::unflattenSize() const {
return fPositions.size_bytes() + fIDsOrPaths.size_bytes();
}
void PathOpSubmitter::flatten(SkWriteBuffer& buffer) const {
fStrikePromise.flatten(buffer);
buffer.writeInt(fIsAntiAliased);
buffer.writeScalar(fStrikeToSourceScale);
buffer.writePointArray(fPositions.data(), SkCount(fPositions));
for (IDOrPath& idOrPath : fIDsOrPaths) {
buffer.writeInt(idOrPath.fGlyphID);
}
}
std::optional<PathOpSubmitter> PathOpSubmitter::MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
std::optional<SkStrikePromise> strikePromise =
SkStrikePromise::MakeFromBuffer(buffer, client, SkStrikeCache::GlobalStrikeCache());
if (!buffer.validate(strikePromise.has_value())) {
return std::nullopt;
}
bool isAntiAlias = buffer.readInt();
SkScalar strikeToSourceScale = buffer.readScalar();
if (!buffer.validate(0 < strikeToSourceScale)) { return std::nullopt; }
SkSpan<SkPoint> positions = MakePointsFromBuffer(buffer, alloc);
if (positions.empty()) { return std::nullopt; }
const int glyphCount = SkCount(positions);
// Remember, we stored an int for glyph id.
if (!buffer.validateCanReadN<int>(glyphCount)) { return std::nullopt; }
auto idsOrPaths = SkSpan(alloc->makeUniqueArray<IDOrPath>(glyphCount).release(), glyphCount);
for (auto& idOrPath : idsOrPaths) {
idOrPath.fGlyphID = SkTo<SkGlyphID>(buffer.readInt());
}
if (!buffer.isValid()) { return std::nullopt; }
return PathOpSubmitter{isAntiAlias,
strikeToSourceScale,
positions,
idsOrPaths,
std::move(strikePromise.value())};
}
PathOpSubmitter::PathOpSubmitter(
bool isAntiAliased,
SkScalar strikeToSourceScale,
SkSpan<SkPoint> positions,
SkSpan<IDOrPath> idsOrPaths,
SkStrikePromise&& strikePromise)
: fIDsOrPaths{idsOrPaths}
, fPositions{positions}
, fStrikeToSourceScale{strikeToSourceScale}
, fIsAntiAliased{isAntiAliased}
, fStrikePromise{std::move(strikePromise)} {
SkASSERT(!fPositions.empty());
}
PathOpSubmitter::~PathOpSubmitter() {
// If we have converted glyph IDs to paths, then clean up the SkPaths.
if (fPathsAreCreated) {
for (auto& idOrPath : fIDsOrPaths) {
idOrPath.fPath.~SkPath();
}
}
}
PathOpSubmitter PathOpSubmitter::Make(SkZip<const SkGlyphID, const SkPoint> accepted,
bool isAntiAliased,
SkScalar strikeToSourceScale,
SkStrikePromise&& strikePromise,
SubRunAllocator* alloc) {
auto mapToIDOrPath = [](SkGlyphID glyphID) { return IDOrPath{glyphID}; };
IDOrPath* const rawIDsOrPaths =
alloc->makeUniqueArray<IDOrPath>(get_glyphIDs(accepted), mapToIDOrPath).release();
return PathOpSubmitter{isAntiAliased,
strikeToSourceScale,
alloc->makePODSpan(get_positions(accepted)),
SkSpan(rawIDsOrPaths, accepted.size()),
std::move(strikePromise)};
}
void
PathOpSubmitter::submitDraws(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint) const {
// Convert the glyph IDs to paths if it hasn't been done yet. This is thread safe.
fConvertIDsToPaths([&]() {
if (SkStrike* strike = fStrikePromise.strike()) {
strike->glyphIDsToPaths(fIDsOrPaths);
// Drop ref to strike so that it can be purged from the cache if needed.
fStrikePromise.resetStrike();
fPathsAreCreated = true;
}
});
SkPaint runPaint{paint};
runPaint.setAntiAlias(fIsAntiAliased);
SkMaskFilterBase* maskFilter = as_MFB(runPaint.getMaskFilter());
// Calculate the matrix that maps the path glyphs from their size in the strike to
// the graphics source space.
SkMatrix strikeToSource = SkMatrix::Scale(fStrikeToSourceScale, fStrikeToSourceScale);
strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y());
// If there are shaders, non-blur mask filters 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.
SkStrokeRec style(runPaint);
bool needsExactCTM = runPaint.getShader()
|| runPaint.getPathEffect()
|| (!style.isFillStyle() && !style.isHairlineStyle())
|| (maskFilter != nullptr && !maskFilter->asABlur(nullptr));
if (!needsExactCTM) {
SkMaskFilterBase::BlurRec blurRec;
// If there is a blur mask filter, then sigma needs to be adjusted to account for the
// scaling of fStrikeToSourceScale.
if (maskFilter != nullptr && maskFilter->asABlur(&blurRec)) {
runPaint.setMaskFilter(
SkMaskFilter::MakeBlur(blurRec.fStyle, blurRec.fSigma / fStrikeToSourceScale));
}
for (auto [idOrPath, pos] : SkMakeZip(fIDsOrPaths, fPositions)) {
// Transform the glyph to source space.
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(pos.x(), pos.y());
SkAutoCanvasRestore acr(canvas, true);
canvas->concat(pathMatrix);
canvas->drawPath(idOrPath.fPath, runPaint);
}
} else {
// Transform the path to device because the deviceMatrix must be unchanged to
// draw effect, filter or shader paths.
for (auto [idOrPath, pos] : SkMakeZip(fIDsOrPaths, fPositions)) {
// Transform the glyph to source space.
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(pos.x(), pos.y());
SkPath deviceOutline;
idOrPath.fPath.transform(pathMatrix, &deviceOutline);
deviceOutline.setIsVolatile(true);
canvas->drawPath(deviceOutline, runPaint);
}
}
}
// -- PathSubRun -----------------------------------------------------------------------------------
class PathSubRun final : public SubRun {
public:
PathSubRun(PathOpSubmitter&& pathDrawing) : fPathDrawing(std::move(pathDrawing)) {}
static SubRunOwner Make(SkZip<const SkGlyphID, const SkPoint> accepted,
bool isAntiAliased,
SkScalar strikeToSourceScale,
SkStrikePromise&& strikePromise,
SubRunAllocator* alloc) {
return alloc->makeUnique<PathSubRun>(
PathOpSubmitter::Make(
accepted, isAntiAliased, strikeToSourceScale, std::move(strikePromise), alloc));
}
void draw(SkCanvas* canvas,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt>,
const AtlasDrawDelegate&) const override {
fPathDrawing.submitDraws(canvas, drawOrigin, paint);
}
int unflattenSize() const override;
bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
return true;
}
const AtlasSubRun* testingOnly_atlasSubRun() const override { return nullptr; }
static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client);
protected:
SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kPathStreamTag; }
void doFlatten(SkWriteBuffer& buffer) const override;
private:
PathOpSubmitter fPathDrawing;
};
int PathSubRun::unflattenSize() const {
return sizeof(PathSubRun) + fPathDrawing.unflattenSize();
}
void PathSubRun::doFlatten(SkWriteBuffer& buffer) const {
fPathDrawing.flatten(buffer);
}
SubRunOwner PathSubRun::MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
auto pathOpSubmitter = PathOpSubmitter::MakeFromBuffer(buffer, alloc, client);
if (!buffer.validate(pathOpSubmitter.has_value())) { return nullptr; }
return alloc->makeUnique<PathSubRun>(std::move(*pathOpSubmitter));
}
// -- DrawableOpSubmitter --------------------------------------------------------------------------
// Shared code for submitting GPU ops for drawing glyphs as drawables.
class DrawableOpSubmitter {
public:
DrawableOpSubmitter() = delete;
DrawableOpSubmitter(const DrawableOpSubmitter&) = delete;
const DrawableOpSubmitter& operator=(const DrawableOpSubmitter&) = delete;
DrawableOpSubmitter(DrawableOpSubmitter&& that)
: fStrikeToSourceScale{that.fStrikeToSourceScale}
, fPositions{that.fPositions}
, fIDsOrDrawables{that.fIDsOrDrawables}
, fStrikePromise{std::move(that.fStrikePromise)} {}
DrawableOpSubmitter& operator=(DrawableOpSubmitter&& that) {
this->~DrawableOpSubmitter();
new (this) DrawableOpSubmitter{std::move(that)};
return *this;
}
DrawableOpSubmitter(SkScalar strikeToSourceScale,
SkSpan<SkPoint> positions,
SkSpan<IDOrDrawable> idsOrDrawables,
SkStrikePromise&& strikePromise);
static DrawableOpSubmitter Make(SkZip<const SkGlyphID, const SkPoint> accepted,
SkScalar strikeToSourceScale,
SkStrikePromise&& strikePromise,
SubRunAllocator* alloc) {
auto mapToIDOrDrawable = [](const SkGlyphID glyphID) { return IDOrDrawable{glyphID}; };
return DrawableOpSubmitter{
strikeToSourceScale,
alloc->makePODSpan(get_positions(accepted)),
alloc->makePODArray<IDOrDrawable>(get_glyphIDs(accepted), mapToIDOrDrawable),
std::move(strikePromise)};
}
int unflattenSize() const;
void flatten(SkWriteBuffer& buffer) const;
static std::optional<DrawableOpSubmitter> MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client);
void submitDraws(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint) const;
private:
const SkScalar fStrikeToSourceScale;
const SkSpan<SkPoint> fPositions;
const SkSpan<IDOrDrawable> fIDsOrDrawables;
// When the promise is converted to a strike it acts as the ref on the strike to keep the
// SkDrawable data alive.
mutable SkStrikePromise fStrikePromise;
mutable SkOnce fConvertIDsToDrawables;
};
int DrawableOpSubmitter::unflattenSize() const {
return fPositions.size_bytes() + fIDsOrDrawables.size_bytes();
}
void DrawableOpSubmitter::flatten(SkWriteBuffer& buffer) const {
fStrikePromise.flatten(buffer);
buffer.writeScalar(fStrikeToSourceScale);
buffer.writePointArray(fPositions.data(), SkCount(fPositions));
for (IDOrDrawable idOrDrawable : fIDsOrDrawables) {
buffer.writeInt(idOrDrawable.fGlyphID);
}
}
std::optional<DrawableOpSubmitter> DrawableOpSubmitter::MakeFromBuffer(
SkReadBuffer& buffer, SubRunAllocator* alloc, const SkStrikeClient* client) {
std::optional<SkStrikePromise> strikePromise =
SkStrikePromise::MakeFromBuffer(buffer, client, SkStrikeCache::GlobalStrikeCache());
if (!buffer.validate(strikePromise.has_value())) {
return std::nullopt;
}
SkScalar strikeToSourceScale = buffer.readScalar();
if (!buffer.validate(0 < strikeToSourceScale)) { return std::nullopt; }
SkSpan<SkPoint> positions = MakePointsFromBuffer(buffer, alloc);
if (positions.empty()) { return std::nullopt; }
const int glyphCount = SkCount(positions);
if (!buffer.validateCanReadN<int>(glyphCount)) { return std::nullopt; }
auto idsOrDrawables = alloc->makePODArray<IDOrDrawable>(glyphCount);
for (int i = 0; i < SkToInt(glyphCount); ++i) {
// Remember, we stored an int for glyph id.
idsOrDrawables[i].fGlyphID = SkTo<SkGlyphID>(buffer.readInt());
}
SkASSERT(buffer.isValid());
return DrawableOpSubmitter{strikeToSourceScale,
positions,
SkSpan(idsOrDrawables, glyphCount),
std::move(strikePromise.value())};
}
DrawableOpSubmitter::DrawableOpSubmitter(
SkScalar strikeToSourceScale,
SkSpan<SkPoint> positions,
SkSpan<IDOrDrawable> idsOrDrawables,
SkStrikePromise&& strikePromise)
: fStrikeToSourceScale{strikeToSourceScale}
, fPositions{positions}
, fIDsOrDrawables{idsOrDrawables}
, fStrikePromise(std::move(strikePromise)) {
SkASSERT(!fPositions.empty());
}
void
DrawableOpSubmitter::submitDraws(SkCanvas* canvas, SkPoint drawOrigin,const SkPaint& paint) const {
// Convert glyph IDs to Drawables if it hasn't been done yet.
fConvertIDsToDrawables([&]() {
fStrikePromise.strike()->glyphIDsToDrawables(fIDsOrDrawables);
// Do not call resetStrike() because the strike must remain owned to ensure the Drawable
// data is not freed.
});
// Calculate the matrix that maps the path glyphs from their size in the strike to
// the graphics source space.
SkMatrix strikeToSource = SkMatrix::Scale(fStrikeToSourceScale, fStrikeToSourceScale);
strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y());
// Transform the path to device because the deviceMatrix must be unchanged to
// draw effect, filter or shader paths.
for (auto [i, position] : SkMakeEnumerate(fPositions)) {
SkDrawable* drawable = fIDsOrDrawables[i].fDrawable;
if (drawable == nullptr) {
// This better be pinned to keep the drawable data alive.
fStrikePromise.strike()->verifyPinnedStrike();
SkDEBUGFAIL("Drawable should not be nullptr.");
continue;
}
// Transform the glyph to source space.
SkMatrix pathMatrix = strikeToSource;
pathMatrix.postTranslate(position.x(), position.y());
SkAutoCanvasRestore acr(canvas, false);
SkRect drawableBounds = drawable->getBounds();
pathMatrix.mapRect(&drawableBounds);
canvas->saveLayer(&drawableBounds, &paint);
drawable->draw(canvas, &pathMatrix);
}
}
// -- DrawableSubRun -------------------------------------------------------------------------------
class DrawableSubRun : public SubRun {
public:
DrawableSubRun(DrawableOpSubmitter&& drawingDrawing)
: fDrawingDrawing(std::move(drawingDrawing)) {}
static SubRunOwner Make(SkZip<const SkGlyphID, const SkPoint> drawables,
SkScalar strikeToSourceScale,
SkStrikePromise&& strikePromise,
SubRunAllocator* alloc) {
return alloc->makeUnique<DrawableSubRun>(
DrawableOpSubmitter::Make(drawables,
strikeToSourceScale,
std::move(strikePromise),
alloc));
}
static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client);
void draw(SkCanvas* canvas,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt>,
const AtlasDrawDelegate&) const override {
fDrawingDrawing.submitDraws(canvas, drawOrigin, paint);
}
int unflattenSize() const override;
bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override;
const AtlasSubRun* testingOnly_atlasSubRun() const override;
protected:
SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kDrawableStreamTag; }
void doFlatten(SkWriteBuffer& buffer) const override;
private:
DrawableOpSubmitter fDrawingDrawing;
};
int DrawableSubRun::unflattenSize() const {
return sizeof(DrawableSubRun) + fDrawingDrawing.unflattenSize();
}
void DrawableSubRun::doFlatten(SkWriteBuffer& buffer) const {
fDrawingDrawing.flatten(buffer);
}
SubRunOwner DrawableSubRun::MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
auto drawableOpSubmitter = DrawableOpSubmitter::MakeFromBuffer(buffer, alloc, client);
if (!buffer.validate(drawableOpSubmitter.has_value())) { return nullptr; }
return alloc->makeUnique<DrawableSubRun>(std::move(*drawableOpSubmitter));
}
bool DrawableSubRun::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
return true;
}
const AtlasSubRun* DrawableSubRun::testingOnly_atlasSubRun() const {
return nullptr;
}
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
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()};
}
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
// -- DirectMaskSubRun -----------------------------------------------------------------------------
class DirectMaskSubRun final : public SubRun, public AtlasSubRun {
public:
DirectMaskSubRun(VertexFiller&& vertexFiller,
GlyphVector&& glyphs)
: fVertexFiller{std::move(vertexFiller)}
, fGlyphs{std::move(glyphs)} {}
static SubRunOwner Make(SkRect creationBounds,
SkZip<const SkPackedGlyphID, const SkPoint> accepted,
const SkMatrix& creationMatrix,
SkStrikePromise&& strikePromise,
MaskFormat maskType,
SubRunAllocator* alloc) {
auto vertexFiller = VertexFiller::Make(maskType,
creationMatrix,
creationBounds,
get_positions(accepted),
alloc,
kIsDirect);
auto glyphVector =
GlyphVector::Make(std::move(strikePromise), get_packedIDs(accepted), alloc);
return alloc->makeUnique<DirectMaskSubRun>(std::move(vertexFiller), std::move(glyphVector));
}
static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
return nullptr;
}
SkASSERT(buffer.isValid());
return alloc->makeUnique<DirectMaskSubRun>(
std::move(*vertexFiller), std::move(*glyphVector));
}
void draw(SkCanvas*,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt> subRunStorage,
const AtlasDrawDelegate& drawAtlas) const override {
drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
{/* isSDF = */false, fVertexFiller.isLCD()});
}
int unflattenSize() const override {
return sizeof(DirectMaskSubRun) +
fGlyphs.unflattenSize() +
fVertexFiller.unflattenSize();
}
int glyphCount() const override {
return SkCount(fGlyphs.glyphs());
}
SkSpan<const Glyph*> glyphs() const override {
return fGlyphs.glyphs();
}
MaskFormat maskFormat() const override { return fVertexFiller.grMaskType(); }
int glyphSrcPadding() const override { return 0; }
unsigned short instanceFlags() const override {
return (unsigned short)fVertexFiller.grMaskType();
}
void testingOnly_packedGlyphIDToGlyph(StrikeCache* cache) const override {
fGlyphs.packedGlyphIDToGlyph(cache);
}
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
size_t vertexStride(const SkMatrix& drawMatrix) const override {
return fVertexFiller.vertexStride(drawMatrix);
}
std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
const GrClip* clip,
const SkMatrix& viewMatrix,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt>&& subRunStorage,
skgpu::ganesh::SurfaceDrawContext* sdc) const override {
SkASSERT(this->glyphCount() != 0);
const SkMatrix& positionMatrix = position_matrix(viewMatrix, drawOrigin);
auto [integerTranslate, subRunDeviceBounds] =
fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
if (subRunDeviceBounds.isEmpty()) {
return {nullptr, nullptr};
}
// Rect for optimized bounds clipping when doing an integer translate.
SkIRect geometricClipRect = SkIRect::MakeEmpty();
if (integerTranslate) {
// 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, subRunDeviceBounds);
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 th GPU clip; clipRect is ignored.
break;
}
geometricClipRect = clipRect;
if (!geometricClipRect.isEmpty()) { SkASSERT(clip == nullptr); }
}
GrPaint grPaint;
const SkPMColor4f drawingColor = calculate_colors(sdc,
paint,
viewMatrix,
fVertexFiller.grMaskType(),
&grPaint);
auto geometry = AtlasTextOp::Geometry::Make(*this,
viewMatrix,
drawOrigin,
geometricClipRect,
std::move(subRunStorage),
drawingColor,
sdc->arenaAlloc());
GrRecordingContext* const rContext = sdc->recordingContext();
GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
fVertexFiller.opMaskType(),
!integerTranslate,
this->glyphCount(),
subRunDeviceBounds,
geometry,
sdc->colorInfo(),
std::move(grPaint));
return {clip, std::move(op)};
}
void fillVertexData(void* vertexDst, int offset, int count,
GrColor color,
const SkMatrix& drawMatrix, SkPoint drawOrigin,
SkIRect clip) const override {
const SkMatrix positionMatrix = position_matrix(drawMatrix, drawOrigin);
fVertexFiller.fillVertexData(offset, count,
fGlyphs.glyphs(),
color,
positionMatrix,
clip,
vertexDst);
}
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
std::tuple<bool, int> regenerateAtlas(int begin, int end,
RegenerateAtlasDelegate regenerateAtlas) const override {
return regenerateAtlas(
&fGlyphs, begin, end, fVertexFiller.grMaskType(), this->glyphSrcPadding());
}
const VertexFiller& vertexFiller() const override { return fVertexFiller; }
bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
auto [reuse, _] = fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
return reuse;
}
const AtlasSubRun* testingOnly_atlasSubRun() const override {
return this;
}
protected:
SubRunStreamTag subRunStreamTag() const override {
return SubRunStreamTag::kDirectMaskStreamTag;
}
void doFlatten(SkWriteBuffer& buffer) const override {
fVertexFiller.flatten(buffer);
fGlyphs.flatten(buffer);
}
private:
const VertexFiller fVertexFiller;
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
};
// -- TransformedMaskSubRun ------------------------------------------------------------------------
class TransformedMaskSubRun final : public SubRun, public AtlasSubRun {
public:
TransformedMaskSubRun(bool isBigEnough,
VertexFiller&& vertexFiller,
GlyphVector&& glyphs)
: fIsBigEnough{isBigEnough}
, fVertexFiller{std::move(vertexFiller)}
, fGlyphs{std::move(glyphs)} {}
static SubRunOwner Make(SkZip<const SkPackedGlyphID, const SkPoint> accepted,
const SkMatrix& initialPositionMatrix,
SkStrikePromise&& strikePromise,
SkMatrix creationMatrix,
SkRect creationBounds,
MaskFormat maskType,
SubRunAllocator* alloc) {
auto vertexFiller = VertexFiller::Make(maskType,
creationMatrix,
creationBounds,
get_positions(accepted),
alloc,
kIsTransformed);
auto glyphVector = GlyphVector::Make(
std::move(strikePromise), get_packedIDs(accepted), alloc);
return alloc->makeUnique<TransformedMaskSubRun>(
initialPositionMatrix.getMaxScale() >= 1,
std::move(vertexFiller),
std::move(glyphVector));
}
static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
return nullptr;
}
const bool isBigEnough = buffer.readBool();
return alloc->makeUnique<TransformedMaskSubRun>(
isBigEnough, std::move(*vertexFiller), std::move(*glyphVector));
}
int unflattenSize() const override {
return sizeof(TransformedMaskSubRun) +
fGlyphs.unflattenSize() +
fVertexFiller.unflattenSize();
}
bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
// If we are not scaling the cache entry to be larger, than a cache with smaller glyphs may
// be better.
return fIsBigEnough;
}
const AtlasSubRun* testingOnly_atlasSubRun() const override { return this; }
void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const override {
fGlyphs.packedGlyphIDToGlyph(cache);
}
int glyphCount() const override { return SkCount(fGlyphs.glyphs()); }
SkSpan<const Glyph*> glyphs() const override {
return fGlyphs.glyphs();
}
unsigned short instanceFlags() const override {
return (unsigned short)fVertexFiller.grMaskType();
}
MaskFormat maskFormat() const override { return fVertexFiller.grMaskType(); }
int glyphSrcPadding() const override { return 1; }
void draw(SkCanvas*,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt> subRunStorage,
const AtlasDrawDelegate& drawAtlas) const override {
drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
{/* isSDF = */false, fVertexFiller.isLCD()});
}
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
size_t vertexStride(const SkMatrix& drawMatrix) const override {
return fVertexFiller.vertexStride(drawMatrix);
}
std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
const GrClip* clip,
const SkMatrix& viewMatrix,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt>&& subRunStorage,
skgpu::ganesh::SurfaceDrawContext* sdc) const override {
SkASSERT(this->glyphCount() != 0);
GrPaint grPaint;
SkPMColor4f drawingColor = calculate_colors(sdc,
paint,
viewMatrix,
fVertexFiller.grMaskType(),
&grPaint);
auto geometry = AtlasTextOp::Geometry::Make(*this,
viewMatrix,
drawOrigin,
SkIRect::MakeEmpty(),
std::move(subRunStorage),
drawingColor,
sdc->arenaAlloc());
GrRecordingContext* const rContext = sdc->recordingContext();
SkMatrix positionMatrix = position_matrix(viewMatrix, drawOrigin);
auto [_, deviceRect] = fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
fVertexFiller.opMaskType(),
true,
this->glyphCount(),
deviceRect,
geometry,
sdc->colorInfo(),
std::move(grPaint));
return {clip, std::move(op)};
}
void fillVertexData(
void* vertexDst, int offset, int count,
GrColor color,
const SkMatrix& drawMatrix, SkPoint drawOrigin,
SkIRect clip) const override {
const SkMatrix positionMatrix = position_matrix(drawMatrix, drawOrigin);
fVertexFiller.fillVertexData(offset, count,
fGlyphs.glyphs(),
color,
positionMatrix,
clip,
vertexDst);
}
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
std::tuple<bool, int> regenerateAtlas(int begin, int end,
RegenerateAtlasDelegate regenerateAtlas) const override {
return regenerateAtlas(
&fGlyphs, begin, end, fVertexFiller.grMaskType(), this->glyphSrcPadding());
}
const VertexFiller& vertexFiller() const override { return fVertexFiller; }
protected:
SubRunStreamTag subRunStreamTag() const override {
return SubRunStreamTag::kTransformMaskStreamTag;
}
void doFlatten(SkWriteBuffer& buffer) const override {
fVertexFiller.flatten(buffer);
fGlyphs.flatten(buffer);
buffer.writeBool(fIsBigEnough);
}
private:
const bool fIsBigEnough;
const VertexFiller fVertexFiller;
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
}; // class TransformedMaskSubRun
// -- SDFTSubRun -----------------------------------------------------------------------------------
bool has_some_antialiasing(const SkFont& font ) {
SkFont::Edging edging = font.getEdging();
return edging == SkFont::Edging::kAntiAlias
|| edging == SkFont::Edging::kSubpixelAntiAlias;
}
#if !defined(SK_DISABLE_SDF_TEXT)
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
static std::tuple<AtlasTextOp::MaskType, uint32_t, bool> calculate_sdf_parameters(
const skgpu::ganesh::SurfaceDrawContext& sdc,
const SkMatrix& drawMatrix,
bool useLCDText,
bool isAntiAliased) {
const GrColorInfo& colorInfo = sdc.colorInfo();
const SkSurfaceProps& props = sdc.surfaceProps();
using MT = AtlasTextOp::MaskType;
bool isLCD = useLCDText && props.pixelGeometry() != kUnknown_SkPixelGeometry;
MT maskType = !isAntiAliased ? MT::kAliasedDistanceField
: isLCD ? 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;
DFGPFlags |= drawMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0;
if (isLCD) {
bool isBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
bool isVertical = SkPixelGeometryIsV(props.pixelGeometry());
DFGPFlags |= kUseLCD_DistanceFieldEffectFlag;
DFGPFlags |= isBGR ? kBGR_DistanceFieldEffectFlag : 0;
DFGPFlags |= isVertical ? kPortrait_DistanceFieldEffectFlag : 0;
}
return {maskType, DFGPFlags, useGammaCorrectDistanceTable};
}
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
class SDFTSubRun final : public SubRun, public AtlasSubRun {
public:
SDFTSubRun(bool useLCDText,
bool antiAliased,
const SDFTMatrixRange& matrixRange,
VertexFiller&& vertexFiller,
GlyphVector&& glyphs)
: fUseLCDText{useLCDText}
, fAntiAliased{antiAliased}
, fMatrixRange{matrixRange}
, fVertexFiller{std::move(vertexFiller)}
, fGlyphs{std::move(glyphs)} { }
static SubRunOwner Make(SkZip<const SkPackedGlyphID, const SkPoint> accepted,
const SkFont& runFont,
SkStrikePromise&& strikePromise,
const SkMatrix& creationMatrix,
SkRect creationBounds,
const SDFTMatrixRange& matrixRange,
SubRunAllocator* alloc) {
auto vertexFiller = VertexFiller::Make(MaskFormat::kA8,
creationMatrix,
creationBounds,
get_positions(accepted),
alloc,
kIsTransformed);
auto glyphVector = GlyphVector::Make(
std::move(strikePromise), get_packedIDs(accepted), alloc);
return alloc->makeUnique<SDFTSubRun>(
runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
has_some_antialiasing(runFont),
matrixRange,
std::move(vertexFiller),
std::move(glyphVector));
}
static SubRunOwner MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
int useLCD = buffer.readInt();
int isAntiAliased = buffer.readInt();
SDFTMatrixRange matrixRange = SDFTMatrixRange::MakeFromBuffer(buffer);
auto vertexFiller = VertexFiller::MakeFromBuffer(buffer, alloc);
if (!buffer.validate(vertexFiller.has_value())) { return nullptr; }
if (!buffer.validate(vertexFiller.value().grMaskType() == MaskFormat::kA8)) {
return nullptr;
}
auto glyphVector = GlyphVector::MakeFromBuffer(buffer, client, alloc);
if (!buffer.validate(glyphVector.has_value())) { return nullptr; }
if (!buffer.validate(SkCount(glyphVector->glyphs()) == vertexFiller->count())) {
return nullptr;
}
return alloc->makeUnique<SDFTSubRun>(useLCD,
isAntiAliased,
matrixRange,
std::move(*vertexFiller),
std::move(*glyphVector));
}
int unflattenSize() const override {
return sizeof(SDFTSubRun) + fGlyphs.unflattenSize() + fVertexFiller.unflattenSize();
}
bool canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const override {
return fMatrixRange.matrixInRange(positionMatrix);
}
const AtlasSubRun* testingOnly_atlasSubRun() const override { return this; }
void testingOnly_packedGlyphIDToGlyph(StrikeCache *cache) const override {
fGlyphs.packedGlyphIDToGlyph(cache);
}
int glyphCount() const override { return fVertexFiller.count(); }
MaskFormat maskFormat() const override {
SkASSERT(fVertexFiller.grMaskType() == MaskFormat::kA8);
return MaskFormat::kA8;
}
int glyphSrcPadding() const override { return SK_DistanceFieldInset; }
SkSpan<const Glyph*> glyphs() const override {
return fGlyphs.glyphs();
}
unsigned short instanceFlags() const override {
return (unsigned short)MaskFormat::kA8;
}
void draw(SkCanvas*,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt> subRunStorage,
const AtlasDrawDelegate& drawAtlas) const override {
drawAtlas(this, drawOrigin, paint, std::move(subRunStorage),
{/* isSDF = */true, /* isLCD = */fUseLCDText});
}
#if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
size_t vertexStride(const SkMatrix& drawMatrix) const override {
return fVertexFiller.vertexStride(drawMatrix);
}
std::tuple<const GrClip*, GrOp::Owner> makeAtlasTextOp(
const GrClip* clip,
const SkMatrix& viewMatrix,
SkPoint drawOrigin,
const SkPaint& paint,
sk_sp<SkRefCnt>&& subRunStorage,
skgpu::ganesh::SurfaceDrawContext* sdc) const override {
SkASSERT(this->glyphCount() != 0);
GrPaint grPaint;
SkPMColor4f drawingColor = calculate_colors(sdc,
paint,
viewMatrix,
MaskFormat::kA8,
&grPaint);
auto [maskType, DFGPFlags, useGammaCorrectDistanceTable] =
calculate_sdf_parameters(*sdc, viewMatrix, fUseLCDText, fAntiAliased);
auto geometry = AtlasTextOp::Geometry::Make(*this,
viewMatrix,
drawOrigin,
SkIRect::MakeEmpty(),
std::move(subRunStorage),
drawingColor,
sdc->arenaAlloc());
GrRecordingContext* const rContext = sdc->recordingContext();
SkMatrix positionMatrix = position_matrix(viewMatrix, drawOrigin);
auto [_, deviceRect] = fVertexFiller.deviceRectAndCheckTransform(positionMatrix);
GrOp::Owner op = GrOp::Make<AtlasTextOp>(rContext,
maskType,
true,
this->glyphCount(),
deviceRect,
SkPaintPriv::ComputeLuminanceColor(paint),
useGammaCorrectDistanceTable,
DFGPFlags,
geometry,
std::move(grPaint));
return {clip, std::move(op)};
}
void fillVertexData(
void *vertexDst, int offset, int count,
GrColor color,
const SkMatrix& drawMatrix, SkPoint drawOrigin,
SkIRect clip) const override {
const SkMatrix positionMatrix = position_matrix(drawMatrix, drawOrigin);
fVertexFiller.fillVertexData(offset, count,
fGlyphs.glyphs(),
color,
positionMatrix,
clip,
vertexDst);
}
#endif // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
std::tuple<bool, int> regenerateAtlas(int begin, int end,
RegenerateAtlasDelegate regenerateAtlas) const override {
return regenerateAtlas(&fGlyphs, begin, end, MaskFormat::kA8, this->glyphSrcPadding());
}
const VertexFiller& vertexFiller() const override { return fVertexFiller; }
protected:
SubRunStreamTag subRunStreamTag() const override { return SubRunStreamTag::kSDFTStreamTag; }
void doFlatten(SkWriteBuffer& buffer) const override {
buffer.writeInt(fUseLCDText);
buffer.writeInt(fAntiAliased);
fMatrixRange.flatten(buffer);
fVertexFiller.flatten(buffer);
fGlyphs.flatten(buffer);
}
private:
const bool fUseLCDText;
const bool fAntiAliased;
const SDFTMatrixRange fMatrixRange;
const VertexFiller fVertexFiller;
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must
// be single threaded.
mutable GlyphVector fGlyphs;
}; // class SDFTSubRun
#endif // !defined(SK_DISABLE_SDF_TEXT)
// -- SubRun ---------------------------------------------------------------------------------------
template<typename AddSingleMaskFormat>
void add_multi_mask_format(
AddSingleMaskFormat addSingleMaskFormat,
SkZip<const SkPackedGlyphID, const SkPoint, const SkMask::Format> accepted) {
if (accepted.empty()) { return; }
auto maskSpan = accepted.get<2>();
MaskFormat format = Glyph::FormatFromSkGlyph(maskSpan[0]);
size_t startIndex = 0;
for (size_t i = 1; i < accepted.size(); i++) {
MaskFormat nextFormat = Glyph::FormatFromSkGlyph(maskSpan[i]);
if (format != nextFormat) {
auto interval = accepted.subspan(startIndex, i - startIndex);
// Only pass the packed glyph ids and positions.
auto glyphsWithSameFormat = SkMakeZip(interval.get<0>(), interval.get<1>());
// Take a ref on the strike. This should rarely happen.
addSingleMaskFormat(glyphsWithSameFormat, format);
format = nextFormat;
startIndex = i;
}
}
auto interval = accepted.last(accepted.size() - startIndex);
auto glyphsWithSameFormat = SkMakeZip(interval.get<0>(), interval.get<1>());
addSingleMaskFormat(glyphsWithSameFormat, format);
}
} // namespace
namespace sktext::gpu {
SubRun::~SubRun() = default;
void SubRun::flatten(SkWriteBuffer& buffer) const {
buffer.writeInt(this->subRunStreamTag());
this->doFlatten(buffer);
}
SubRunOwner SubRun::MakeFromBuffer(SkReadBuffer& buffer,
SubRunAllocator* alloc,
const SkStrikeClient* client) {
using Maker = SubRunOwner (*)(SkReadBuffer&,
SubRunAllocator*,
const SkStrikeClient*);
static Maker makers[kSubRunStreamTagCount] = {
nullptr, // 0 index is bad.
DirectMaskSubRun::MakeFromBuffer,
#if !defined(SK_DISABLE_SDF_TEXT)
SDFTSubRun::MakeFromBuffer,
#endif
TransformedMaskSubRun::MakeFromBuffer,
PathSubRun::MakeFromBuffer,
DrawableSubRun::MakeFromBuffer,
};
int subRunTypeInt = buffer.readInt();
SkASSERT(kBad < subRunTypeInt && subRunTypeInt < kSubRunStreamTagCount);
if (!buffer.validate(kBad < subRunTypeInt && subRunTypeInt < kSubRunStreamTagCount)) {
return nullptr;
}
auto maker = makers[subRunTypeInt];
if (!buffer.validate(maker != nullptr)) { return nullptr; }
return maker(buffer, alloc, client);
}
// -- SubRunContainer ------------------------------------------------------------------------------
SubRunContainer::SubRunContainer(const SkMatrix& initialPositionMatrix)
: fInitialPositionMatrix{initialPositionMatrix} {}
void SubRunContainer::flattenAllocSizeHint(SkWriteBuffer& buffer) const {
int unflattenSizeHint = 0;
for (auto& subrun : fSubRuns) {
unflattenSizeHint += subrun.unflattenSize();
}
buffer.writeInt(unflattenSizeHint);
}
int SubRunContainer::AllocSizeHintFromBuffer(SkReadBuffer& buffer) {
int subRunsSizeHint = buffer.readInt();
// Since the hint doesn't affect correctness, if it looks fishy just pick a reasonable
// value.
if (subRunsSizeHint < 0 || (1 << 16) < subRunsSizeHint) {
subRunsSizeHint = 128;
}
return subRunsSizeHint;
}
void SubRunContainer::flattenRuns(SkWriteBuffer& buffer) const {
buffer.writeMatrix(fInitialPositionMatrix);
int subRunCount = 0;
for ([[maybe_unused]] auto& subRun : fSubRuns) {
subRunCount += 1;
}
buffer.writeInt(subRunCount);
for (auto& subRun : fSubRuns) {
subRun.flatten(buffer);
}
}
SubRunContainerOwner SubRunContainer::MakeFromBufferInAlloc(SkReadBuffer& buffer,
const SkStrikeClient* client,
SubRunAllocator* alloc) {
SkMatrix positionMatrix;
buffer.readMatrix(&positionMatrix);
if (!buffer.isValid()) { return nullptr; }
SubRunContainerOwner container = alloc->makeUnique<SubRunContainer>(positionMatrix);
int subRunCount = buffer.readInt();
SkASSERT(subRunCount > 0);
if (!buffer.validate(subRunCount > 0)) { return nullptr; }
for (int i = 0; i < subRunCount; ++i) {
auto subRunOwner = SubRun::MakeFromBuffer(buffer, alloc, client);
if (!buffer.validate(subRunOwner != nullptr)) { return nullptr; }
if (subRunOwner != nullptr) {
container->fSubRuns.append(std::move(subRunOwner));
}
}
return container;
}
size_t SubRunContainer::EstimateAllocSize(const GlyphRunList& glyphRunList) {
// The difference in alignment from the per-glyph data to the SubRun;
constexpr size_t alignDiff = alignof(DirectMaskSubRun) - alignof(SkPoint);
constexpr size_t vertexDataToSubRunPadding = alignDiff > 0 ? alignDiff : 0;
size_t totalGlyphCount = glyphRunList.totalGlyphCount();
// This is optimized for DirectMaskSubRun which is by far the most common case.
return totalGlyphCount * sizeof(SkPoint)
+ GlyphVector::GlyphVectorSize(totalGlyphCount)
+ glyphRunList.runCount() * (sizeof(DirectMaskSubRun) + vertexDataToSubRunPadding)
+ sizeof(SubRunContainer);
}
SkScalar find_maximum_glyph_dimension(StrikeForGPU* strike, SkSpan<const SkGlyphID> glyphs) {
StrikeMutationMonitor m{strike};
SkScalar maxDimension = 0;
for (SkGlyphID glyphID : glyphs) {
SkGlyphDigest digest = strike->digestFor(kMask, SkPackedGlyphID{glyphID});
maxDimension = std::max(static_cast<SkScalar>(digest.maxDimension()), maxDimension);
}
return maxDimension;
}
#if !defined(SK_DISABLE_SDF_TEXT)
std::tuple<SkZip<const SkPackedGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>, SkRect>
prepare_for_SDFT_drawing(StrikeForGPU* strike,
const SkMatrix& creationMatrix,
SkZip<const SkGlyphID, const SkPoint> source,
SkZip<SkPackedGlyphID, SkPoint> acceptedBuffer,
SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
int acceptedSize = 0,
rejectedSize = 0;
SkGlyphRect boundingRect = skglyph::empty_rect();
StrikeMutationMonitor m{strike};
for (const auto [glyphID, pos] : source) {
if (!SkIsFinite(pos.x(), pos.y())) {
continue;
}
const SkPackedGlyphID packedID{glyphID};
switch (const SkGlyphDigest digest = strike->digestFor(skglyph::kSDFT, packedID);
digest.actionFor(skglyph::kSDFT)) {
case GlyphAction::kAccept: {
SkPoint mappedPos = creationMatrix.mapPoint(pos);
const SkGlyphRect glyphBounds =
digest.bounds()
// The SDFT glyphs have 2-pixel wide padding that should
// not be used in calculating the source rectangle.
.inset(SK_DistanceFieldInset, SK_DistanceFieldInset)
.offset(mappedPos);
boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
acceptedBuffer[acceptedSize++] = std::make_tuple(packedID, glyphBounds.leftTop());
break;
}
case GlyphAction::kReject:
rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
break;
default:
break;
}
}
return {acceptedBuffer.first(acceptedSize),
rejectedBuffer.first(rejectedSize),
boundingRect.rect()};
}
#endif
std::tuple<SkZip<const SkPackedGlyphID, const SkPoint, const SkMask::Format>,
SkZip<SkGlyphID, SkPoint>,
SkRect>
prepare_for_direct_mask_drawing(StrikeForGPU* strike,
const SkMatrix& positionMatrix,
SkZip<const SkGlyphID, const SkPoint> source,
SkZip<SkPackedGlyphID, SkPoint, SkMask::Format> acceptedBuffer,
SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
const SkIPoint mask = strike->roundingSpec().ignorePositionFieldMask;
const SkPoint halfSampleFreq = strike->roundingSpec().halfAxisSampleFreq;
// Build up the mapping from source space to device space. Add the rounding constant
// halfSampleFreq, so we just need to floor to get the device result.
SkMatrix positionMatrixWithRounding = positionMatrix;
positionMatrixWithRounding.postTranslate(halfSampleFreq.x(), halfSampleFreq.y());
int acceptedSize = 0,
rejectedSize = 0;
SkGlyphRect boundingRect = skglyph::empty_rect();
StrikeMutationMonitor m{strike};
for (auto [glyphID, pos] : source) {
if (!SkIsFinite(pos.x(), pos.y())) {
continue;
}
const SkPoint mappedPos = positionMatrixWithRounding.mapPoint(pos);
const SkPackedGlyphID packedID{glyphID, mappedPos, mask};
switch (const SkGlyphDigest digest = strike->digestFor(skglyph::kDirectMask, packedID);
digest.actionFor(skglyph::kDirectMask)) {
case GlyphAction::kAccept: {
const SkPoint roundedPos{SkScalarFloorToScalar(mappedPos.x()),
SkScalarFloorToScalar(mappedPos.y())};
const SkGlyphRect glyphBounds = digest.bounds().offset(roundedPos);
boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
acceptedBuffer[acceptedSize++] =
std::make_tuple(packedID, glyphBounds.leftTop(), digest.maskFormat());
break;
}
case GlyphAction::kReject:
rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
break;
default:
break;
}
}
return {acceptedBuffer.first(acceptedSize),
rejectedBuffer.first(rejectedSize),
boundingRect.rect()};
}
std::tuple<SkZip<const SkPackedGlyphID, const SkPoint, const SkMask::Format>,
SkZip<SkGlyphID, SkPoint>,
SkRect>
prepare_for_mask_drawing(StrikeForGPU* strike,
const SkMatrix& creationMatrix,
SkZip<const SkGlyphID, const SkPoint> source,
SkZip<SkPackedGlyphID, SkPoint, SkMask::Format> acceptedBuffer,
SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
int acceptedSize = 0,
rejectedSize = 0;
SkGlyphRect boundingRect = skglyph::empty_rect();
StrikeMutationMonitor m{strike};
for (auto [glyphID, pos] : source) {
if (!SkIsFinite(pos.x(), pos.y())) {
continue;
}
const SkPackedGlyphID packedID{glyphID};
switch (const SkGlyphDigest digest = strike->digestFor(kMask, packedID);
digest.actionFor(kMask)) {
case GlyphAction::kAccept: {
const SkPoint mappedPos = creationMatrix.mapPoint(pos);
const SkGlyphRect glyphBounds = digest.bounds().offset(mappedPos);
boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
acceptedBuffer[acceptedSize++] =
std::make_tuple(packedID, glyphBounds.leftTop(), digest.maskFormat());
break;
}
case GlyphAction::kReject:
rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
break;
default:
break;
}
}
return {acceptedBuffer.first(acceptedSize),
rejectedBuffer.first(rejectedSize),
boundingRect.rect()};
}
std::tuple<SkZip<const SkGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>>
prepare_for_path_drawing(StrikeForGPU* strike,
SkZip<const SkGlyphID, const SkPoint> source,
SkZip<SkGlyphID, SkPoint> acceptedBuffer,
SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
int acceptedSize = 0;
int rejectedSize = 0;
StrikeMutationMonitor m{strike};
for (const auto [glyphID, pos] : source) {
if (!SkIsFinite(pos.x(), pos.y())) {
continue;
}
switch (strike->digestFor(skglyph::kPath, SkPackedGlyphID{glyphID})
.actionFor(skglyph::kPath)) {
case GlyphAction::kAccept:
acceptedBuffer[acceptedSize++] = std::make_tuple(glyphID, pos);
break;
case GlyphAction::kReject:
rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
break;
default:
break;
}
}
return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
}
std::tuple<SkZip<const SkGlyphID, const SkPoint>, SkZip<SkGlyphID, SkPoint>>
prepare_for_drawable_drawing(StrikeForGPU* strike,
SkZip<const SkGlyphID, const SkPoint> source,
SkZip<SkGlyphID, SkPoint> acceptedBuffer,
SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
int acceptedSize = 0;
int rejectedSize = 0;
StrikeMutationMonitor m{strike};
for (const auto [glyphID, pos] : source) {
if (!SkIsFinite(pos.x(), pos.y())) {
continue;
}
switch (strike->digestFor(skglyph::kDrawable, SkPackedGlyphID{glyphID})
.actionFor(skglyph::kDrawable)) {
case GlyphAction::kAccept:
acceptedBuffer[acceptedSize++] = std::make_tuple(glyphID, pos);
break;
case GlyphAction::kReject:
rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
break;
default:
break;
}
}
return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
}
#if !defined(SK_DISABLE_SDF_TEXT)
static std::tuple<SkStrikeSpec, SkScalar, sktext::gpu::SDFTMatrixRange>
make_sdft_strike_spec(const SkFont& font, const SkPaint& paint,
const SkSurfaceProps& surfaceProps, const SkMatrix& deviceMatrix,
const SkPoint& textLocation, const sktext::gpu::SDFTControl& control) {
// Add filter to the paint which creates the SDFT data for A8 masks.
SkPaint dfPaint{paint};
dfPaint.setMaskFilter(sktext::gpu::SDFMaskFilter::Make());
auto [dfFont, strikeToSourceScale, matrixRange] = control.getSDFFont(font, deviceMatrix,
textLocation);
// Adjust the stroke width by the scale factor for drawing the SDFT.
dfPaint.setStrokeWidth(paint.getStrokeWidth() / strikeToSourceScale);
// Check for dashing and adjust the intervals.
if (SkPathEffect* pathEffect = paint.getPathEffect(); pathEffect != nullptr) {
SkPathEffect::DashInfo dashInfo;
if (pathEffect->asADash(&dashInfo) == SkPathEffect::kDash_DashType) {
if (dashInfo.fCount > 0) {
// Allocate the intervals.
std::vector<SkScalar> scaledIntervals(dashInfo.fCount);
dashInfo.fIntervals = scaledIntervals.data();
// Call again to get the interval data.
(void)pathEffect->asADash(&dashInfo);
for (SkScalar& interval : scaledIntervals) {
interval /= strikeToSourceScale;
}
auto scaledDashes = SkDashPathEffect::Make(scaledIntervals.data(),
scaledIntervals.size(),
dashInfo.fPhase / strikeToSourceScale);
dfPaint.setPathEffect(scaledDashes);
}
}
}
// Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
// passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
SkScalerContextFlags flags = SkScalerContextFlags::kNone;
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(dfFont, dfPaint, surfaceProps, flags,
SkMatrix::I());
return std::make_tuple(std::move(strikeSpec), strikeToSourceScale, matrixRange);
}
#endif
SubRunContainerOwner SubRunContainer::MakeInAlloc(
const GlyphRunList& glyphRunList,
const SkMatrix& positionMatrix,
const SkPaint& runPaint,
SkStrikeDeviceInfo strikeDeviceInfo,
StrikeForGPUCacheInterface* strikeCache,
SubRunAllocator* alloc,
SubRunCreationBehavior creationBehavior,
const char* tag) {
SkASSERT(alloc != nullptr);
SkASSERT(strikeDeviceInfo.fSDFTControl != nullptr);
SubRunContainerOwner container = alloc->makeUnique<SubRunContainer>(positionMatrix);
// If there is no SDFT description ignore all SubRuns.
if (strikeDeviceInfo.fSDFTControl == nullptr) {
return container;
}
const SkSurfaceProps deviceProps = strikeDeviceInfo.fSurfaceProps;
const SkScalerContextFlags scalerContextFlags = strikeDeviceInfo.fScalerContextFlags;
#if !defined(SK_DISABLE_SDF_TEXT)
const SDFTControl SDFTControl = *strikeDeviceInfo.fSDFTControl;
const SkScalar maxMaskSize = SDFTControl.maxSize();
#else
const SkScalar maxMaskSize = 256;
#endif
// TODO: hoist the buffer structure to the GlyphRunBuilder. The buffer structure here is
// still begin tuned, and this is expected to be slower until tuned.
const int maxGlyphRunSize = glyphRunList.maxGlyphRunSize();
// Accepted buffers.
STArray<64, SkPackedGlyphID> acceptedPackedGlyphIDs;
STArray<64, SkGlyphID> acceptedGlyphIDs;
STArray<64, SkPoint> acceptedPositions;
STArray<64, SkMask::Format> acceptedFormats;
acceptedPackedGlyphIDs.resize(maxGlyphRunSize);
acceptedGlyphIDs.resize(maxGlyphRunSize);
acceptedPositions.resize(maxGlyphRunSize);
acceptedFormats.resize(maxGlyphRunSize);
// Rejected buffers.
STArray<64, SkGlyphID> rejectedGlyphIDs;
STArray<64, SkPoint> rejectedPositions;
rejectedGlyphIDs.resize(maxGlyphRunSize);
rejectedPositions.resize(maxGlyphRunSize);
const auto rejectedBuffer = SkMakeZip(rejectedGlyphIDs, rejectedPositions);
const SkPoint glyphRunListLocation = glyphRunList.sourceBounds().center();
// Handle all the runs in the glyphRunList
for (auto& glyphRun : glyphRunList) {
SkZip<const SkGlyphID, const SkPoint> source = glyphRun.source();
const SkFont& runFont = glyphRun.font();
const SkScalar approximateDeviceTextSize =
// Since the positionMatrix has the origin prepended, use the plain
// sourceBounds from above.
SkFontPriv::ApproximateTransformedTextSize(runFont, positionMatrix,
glyphRunListLocation);
// Atlas mask cases - SDFT and direct mask
// Only consider using direct or SDFT drawing if not drawing hairlines and not too big.
if ((runPaint.getStyle() != SkPaint::kStroke_Style || runPaint.getStrokeWidth() != 0) &&
approximateDeviceTextSize < maxMaskSize) {
#if !defined(SK_DISABLE_SDF_TEXT)
// SDFT case
if (SDFTControl.isSDFT(approximateDeviceTextSize, runPaint, positionMatrix)) {
// Process SDFT - This should be the .009% case.
const auto& [strikeSpec, strikeToSourceScale, matrixRange] =
make_sdft_strike_spec(
runFont, runPaint, deviceProps, positionMatrix,
glyphRunListLocation, SDFTControl);
if (!SkScalarNearlyZero(strikeToSourceScale)) {
sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
// The creationMatrix needs to scale the strike data when inverted and
// multiplied by the positionMatrix. The final CTM should be:
// [positionMatrix][scale by strikeToSourceScale],
// which should equal the following because of the transform during the vertex
// calculation,
// [positionMatrix][creationMatrix]^-1.
// So, the creation matrix needs to be
// [scale by 1/strikeToSourceScale].
SkMatrix creationMatrix =
SkMatrix::Scale(1.f/strikeToSourceScale, 1.f/strikeToSourceScale);
auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions);
auto [accepted, rejected, creationBounds] = prepare_for_SDFT_drawing(
strike.get(), creationMatrix, source, acceptedBuffer, rejectedBuffer);
source = rejected;
if (creationBehavior == kAddSubRuns && !accepted.empty()) {
container->fSubRuns.append(SDFTSubRun::Make(
accepted,
runFont,
strike->strikePromise(),
creationMatrix,
creationBounds,
matrixRange,
alloc));
}
}
}
#endif // !defined(SK_DISABLE_SDF_TEXT)
// Direct Mask case
// Handle all the directly mapped mask subruns.
if (!source.empty() && !positionMatrix.hasPerspective()) {
// Process masks including ARGB - this should be the 99.99% case.
// This will handle medium size emoji that are sharing the run with SDFT drawn text.
// If things are too big they will be passed along to the drawing of last resort
// below.
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
runFont, runPaint, deviceProps, scalerContextFlags, positionMatrix);
sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs,
acceptedPositions,
acceptedFormats);
auto [accepted, rejected, creationBounds] = prepare_for_direct_mask_drawing(
strike.get(), positionMatrix, source, acceptedBuffer, rejectedBuffer);
source = rejected;
if (creationBehavior == kAddSubRuns && !accepted.empty()) {
auto addGlyphsWithSameFormat =
[&, bounds = creationBounds](
SkZip<const SkPackedGlyphID, const SkPoint> subrun,
MaskFormat format) {
container->fSubRuns.append(
DirectMaskSubRun::Make(bounds,
subrun,
container->initialPosition(),
strike->strikePromise(),
format,
alloc));
};
add_multi_mask_format(addGlyphsWithSameFormat, accepted);
}
}
}
// Drawable case
// Handle all the drawable glyphs - usually large or perspective color glyphs.
if (!source.empty()) {
auto [strikeSpec, strikeToSourceScale] =
SkStrikeSpec::MakePath(runFont, runPaint, deviceProps, scalerContextFlags);
if (!SkScalarNearlyZero(strikeToSourceScale)) {
sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
auto acceptedBuffer = SkMakeZip(acceptedGlyphIDs, acceptedPositions);
auto [accepted, rejected] =
prepare_for_drawable_drawing(strike.get(), source, acceptedBuffer, rejectedBuffer);
source = rejected;
if (creationBehavior == kAddSubRuns && !accepted.empty()) {
container->fSubRuns.append(
DrawableSubRun::Make(
accepted,
strikeToSourceScale,
strike->strikePromise(),
alloc));
}
}
}
// Path case
// Handle path subruns. Mainly, large or large perspective glyphs with no color.
if (!source.empty()) {
auto [strikeSpec, strikeToSourceScale] =
SkStrikeSpec::MakePath(runFont, runPaint, deviceProps, scalerContextFlags);
if (!SkScalarNearlyZero(strikeToSourceScale)) {
sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
auto acceptedBuffer = SkMakeZip(acceptedGlyphIDs, acceptedPositions);
auto [accepted, rejected] =
prepare_for_path_drawing(strike.get(), source, acceptedBuffer, rejectedBuffer);
source = rejected;
if (creationBehavior == kAddSubRuns && !accepted.empty()) {
container->fSubRuns.append(
PathSubRun::Make(accepted,
has_some_antialiasing(runFont),
strikeToSourceScale,
strike->strikePromise(),
alloc));
}
}
}
// Drawing of last resort case
// Draw all the rest of the rejected glyphs from above. This scales out of the atlas to
// the screen, so quality will suffer. This mainly handles large color or perspective
// color not handled by Drawables.
if (!source.empty() && !SkScalarNearlyZero(approximateDeviceTextSize)) {
// Creation matrix will be changed below to meet the following criteria:
// * No perspective - the font scaler and the strikes can't handle perspective masks.
// * Fits atlas - creationMatrix will be conditioned so that the maximum glyph
// dimension for this run will be < kMaxBilerpAtlasDimension.
SkMatrix creationMatrix = positionMatrix;
// Condition creationMatrix for perspective.
if (creationMatrix.hasPerspective()) {
// Find a scale factor that reduces pixelation caused by keystoning.
SkPoint center = glyphRunList.sourceBounds().center();
SkScalar maxAreaScale = SkMatrixPriv::DifferentialAreaScale(creationMatrix, center);
SkScalar perspectiveFactor = 1;
if (SkIsFinite(maxAreaScale) && !SkScalarNearlyZero(maxAreaScale)) {
perspectiveFactor = SkScalarSqrt(maxAreaScale);
}
// Masks can not be created in perspective. Create a non-perspective font with a
// scale that will support the perspective keystoning.
creationMatrix = SkMatrix::Scale(perspectiveFactor, perspectiveFactor);
}
// Reduce to make a one pixel border for the bilerp padding.
static const constexpr SkScalar kMaxBilerpAtlasDimension =
SkGlyphDigest::kSkSideTooBigForAtlas - 2;
// Get the raw glyph IDs to simulate device drawing to figure the maximum device
// dimension.
const SkSpan<const SkGlyphID> glyphs = get_glyphIDs(source);
// maxGlyphDimension always returns an integer even though the return type is SkScalar.
auto maxGlyphDimension = [&](const SkMatrix& m) {
const SkStrikeSpec strikeSpec = SkStrikeSpec::MakeTransformMask(
runFont, runPaint, deviceProps, scalerContextFlags, m);
const sk_sp<StrikeForGPU> gaugingStrike =
strikeSpec.findOrCreateScopedStrike(strikeCache);
const SkScalar maxDimension =
find_maximum_glyph_dimension(gaugingStrike.get(), glyphs);
// TODO: There is a problem where a small character (say .) and a large
// character (say M) are in the same run. If the run is scaled to be very
// large, then the M may return 0 because its dimensions are > 65535, but
// the small character produces regular result because its largest dimension
// is < 65535. This will create an improper scale factor causing the M to be
// too large to fit in the atlas. Tracked by skia:13714.
return maxDimension;
};
// Condition the creationMatrix so that glyphs fit in the atlas.
for (SkScalar maxDimension = maxGlyphDimension(creationMatrix);
kMaxBilerpAtlasDimension < maxDimension;
maxDimension = maxGlyphDimension(creationMatrix))
{
// The SkScalerContext has a limit of 65536 maximum dimension.
// reductionFactor will always be < 1 because
// maxDimension > kMaxBilerpAtlasDimension, and because maxDimension will always
// be an integer the reduction factor will always be at most 254 / 255.
SkScalar reductionFactor = kMaxBilerpAtlasDimension / maxDimension;
creationMatrix.postScale(reductionFactor, reductionFactor);
}
// Draw using the creationMatrix.
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeTransformMask(
runFont, runPaint, deviceProps, scalerContextFlags, creationMatrix);
sk_sp<StrikeForGPU> strike = strikeSpec.findOrCreateScopedStrike(strikeCache);
auto acceptedBuffer =
SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions, acceptedFormats);
auto [accepted, rejected, creationBounds] =
prepare_for_mask_drawing(
strike.get(), creationMatrix, source, acceptedBuffer, rejectedBuffer);
source = rejected;
if (creationBehavior == kAddSubRuns && !accepted.empty()) {
auto addGlyphsWithSameFormat =
[&, bounds = creationBounds](
SkZip<const SkPackedGlyphID, const SkPoint> subrun,
MaskFormat format) {
container->fSubRuns.append(
TransformedMaskSubRun::Make(subrun,
container->initialPosition(),
strike->strikePromise(),
creationMatrix,
bounds,
format,
alloc));
};
add_multi_mask_format(addGlyphsWithSameFormat, accepted);
}
}
}
return container;
}
void SubRunContainer::draw(SkCanvas* canvas,
SkPoint drawOrigin,
const SkPaint& paint,
const SkRefCnt* subRunStorage,
const AtlasDrawDelegate& atlasDelegate) const {
for (auto& subRun : fSubRuns) {
subRun.draw(canvas, drawOrigin, paint, sk_ref_sp(subRunStorage), atlasDelegate);
}
}
bool SubRunContainer::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
for (const SubRun& subRun : fSubRuns) {
if (!subRun.canReuse(paint, positionMatrix)) {
return false;
}
}
return true;
}
// Returns the empty span if there is a problem reading the positions.
SkSpan<SkPoint> MakePointsFromBuffer(SkReadBuffer& buffer, SubRunAllocator* alloc) {
uint32_t glyphCount = buffer.getArrayCount();
// Zero indicates a problem with serialization.
if (!buffer.validate(glyphCount != 0)) { return {}; }
// Check that the count will not overflow the arena.
if (!buffer.validate(glyphCount <= INT_MAX &&
BagOfBytes::WillCountFit<SkPoint>(glyphCount))) { return {}; }
SkPoint* positionsData = alloc->makePODArray<SkPoint>(glyphCount);
if (!buffer.readPointArray(positionsData, glyphCount)) { return {}; }
return {positionsData, glyphCount};
}
} // namespace sktext::gpu