/* * 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/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/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 "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/SkPathEffectBase.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/SubRunAllocator.h" #include "src/text/gpu/SubRunControl.h" #include "src/text/gpu/VertexFiller.h" #include #include #include #include #include #include #if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS) #include "include/core/SkRRect.h" #include "include/private/SkColorData.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/core/SkPaintPriv.h" #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" class GrRecordingContext; 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 get_packedIDs(SkZip accepted) { return accepted.get<0>(); } SkSpan get_glyphIDs(SkZip accepted) { return accepted.get<0>(); } template SkSpan get_positions(SkZip 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&>(that.fIDsOrPaths), SkSpan{})} , 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 positions, SkSpan idsOrPaths, SkStrikePromise&& strikePromise); ~PathOpSubmitter(); static PathOpSubmitter Make(SkZip accepted, bool isAntiAliased, SkScalar strikeToSourceScale, SkStrikePromise&& strikePromise, SubRunAllocator* alloc); int unflattenSize() const; void flatten(SkWriteBuffer& buffer) const; static std::optional 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 fIDsOrPaths; const SkSpan 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::MakeFromBuffer(SkReadBuffer& buffer, SubRunAllocator* alloc, const SkStrikeClient* client) { std::optional 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 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(glyphCount)) { return std::nullopt; } auto idsOrPaths = SkSpan(alloc->makeUniqueArray(glyphCount).release(), glyphCount); for (auto& idOrPath : idsOrPaths) { idOrPath.fGlyphID = SkTo(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 positions, SkSpan 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 accepted, bool isAntiAliased, SkScalar strikeToSourceScale, SkStrikePromise&& strikePromise, SubRunAllocator* alloc) { auto mapToIDOrPath = [](SkGlyphID glyphID) { return IDOrPath{glyphID}; }; IDOrPath* const rawIDsOrPaths = alloc->makeUniqueArray(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 accepted, bool isAntiAliased, SkScalar strikeToSourceScale, SkStrikePromise&& strikePromise, SubRunAllocator* alloc) { return alloc->makeUnique( PathOpSubmitter::Make( accepted, isAntiAliased, strikeToSourceScale, std::move(strikePromise), alloc)); } void draw(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint, sk_sp, 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(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 positions, SkSpan idsOrDrawables, SkStrikePromise&& strikePromise); static DrawableOpSubmitter Make(SkZip 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(get_glyphIDs(accepted), mapToIDOrDrawable), std::move(strikePromise)}; } int unflattenSize() const; void flatten(SkWriteBuffer& buffer) const; static std::optional MakeFromBuffer(SkReadBuffer& buffer, SubRunAllocator* alloc, const SkStrikeClient* client); void submitDraws(SkCanvas* canvas, SkPoint drawOrigin, const SkPaint& paint) const; private: const SkScalar fStrikeToSourceScale; const SkSpan fPositions; const SkSpan 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::MakeFromBuffer( SkReadBuffer& buffer, SubRunAllocator* alloc, const SkStrikeClient* client) { std::optional 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 positions = MakePointsFromBuffer(buffer, alloc); if (positions.empty()) { return std::nullopt; } const int glyphCount = SkCount(positions); if (!buffer.validateCanReadN(glyphCount)) { return std::nullopt; } auto idsOrDrawables = alloc->makePODArray(glyphCount); for (int i = 0; i < SkToInt(glyphCount); ++i) { // Remember, we stored an int for glyph id. idsOrDrawables[i].fGlyphID = SkTo(buffer.readInt()); } if (!buffer.isValid()) { return std::nullopt; } return DrawableOpSubmitter{strikeToSourceScale, positions, SkSpan(idsOrDrawables, glyphCount), std::move(strikePromise.value())}; } DrawableOpSubmitter::DrawableOpSubmitter( SkScalar strikeToSourceScale, SkSpan positions, SkSpan 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 drawables, SkScalar strikeToSourceScale, SkStrikePromise&& strikePromise, SubRunAllocator* alloc) { return alloc->makeUnique( 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, 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(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 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 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(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( std::move(*vertexFiller), std::move(*glyphVector)); } void draw(SkCanvas*, SkPoint drawOrigin, const SkPaint& paint, sk_sp subRunStorage, const AtlasDrawDelegate& drawAtlas) const override { drawAtlas(this, drawOrigin, paint, std::move(subRunStorage), {/* isSDF = */false, fVertexFiller.isLCD(), fVertexFiller.grMaskType()}); } int unflattenSize() const override { return sizeof(DirectMaskSubRun) + fGlyphs.unflattenSize() + fVertexFiller.unflattenSize(); } int glyphCount() const override { return SkCount(fGlyphs.glyphs()); } SkSpan 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 makeAtlasTextOp( const GrClip* clip, const SkMatrix& viewMatrix, SkPoint drawOrigin, const SkPaint& paint, sk_sp&& 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(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 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 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( 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( 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 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 subRunStorage, const AtlasDrawDelegate& drawAtlas) const override { drawAtlas(this, drawOrigin, paint, std::move(subRunStorage), {/* isSDF = */false, fVertexFiller.isLCD(), fVertexFiller.grMaskType()}); } #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 makeAtlasTextOp( const GrClip* clip, const SkMatrix& viewMatrix, SkPoint drawOrigin, const SkPaint& paint, sk_sp&& 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(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 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 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 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( 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(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 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 subRunStorage, const AtlasDrawDelegate& drawAtlas) const override { drawAtlas(this, drawOrigin, paint, std::move(subRunStorage), {/* isSDF = */true, /* isLCD = */fUseLCDText, skgpu::MaskFormat::kA8}); } #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 makeAtlasTextOp( const GrClip* clip, const SkMatrix& viewMatrix, SkPoint drawOrigin, const SkPaint& paint, sk_sp&& 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(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 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 void add_multi_mask_format( AddSingleMaskFormat addSingleMaskFormat, SkZip 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(); 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(positionMatrix); int subRunCount = buffer.readInt(); 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 glyphs) { StrikeMutationMonitor m{strike}; SkScalar maxDimension = 0; for (SkGlyphID glyphID : glyphs) { SkGlyphDigest digest = strike->digestFor(kMask, SkPackedGlyphID{glyphID}); maxDimension = std::max(static_cast(digest.maxDimension()), maxDimension); } return maxDimension; } #if !defined(SK_DISABLE_SDF_TEXT) std::tuple, SkZip, SkRect> prepare_for_SDFT_drawing(StrikeForGPU* strike, const SkMatrix& creationMatrix, SkZip source, SkZip acceptedBuffer, SkZip 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, SkRect> prepare_for_direct_mask_drawing(StrikeForGPU* strike, const SkMatrix& positionMatrix, SkZip source, SkZip acceptedBuffer, SkZip 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, SkRect> prepare_for_mask_drawing(StrikeForGPU* strike, const SkMatrix& creationMatrix, SkZip source, SkZip acceptedBuffer, SkZip 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> prepare_for_path_drawing(StrikeForGPU* strike, SkZip source, SkZip acceptedBuffer, SkZip 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> prepare_for_drawable_drawing(StrikeForGPU* strike, SkZip source, SkZip acceptedBuffer, SkZip 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 make_sdft_strike_spec(const SkFont& font, const SkPaint& paint, const SkSurfaceProps& surfaceProps, const SkMatrix& deviceMatrix, const SkPoint& textLocation, const sktext::gpu::SubRunControl& 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) { SkPathEffectBase::DashInfo dashInfo; if (as_PEB(pathEffect)->asADash(&dashInfo) == SkPathEffectBase::DashType::kDash) { if (dashInfo.fCount > 0) { // Allocate the intervals. std::vector scaledIntervals(dashInfo.fCount); dashInfo.fIntervals = scaledIntervals.data(); // Call again to get the interval data. (void)as_PEB(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.fSubRunControl != nullptr); SubRunContainerOwner container = alloc->makeUnique(positionMatrix); // If there is no SubRunControl description ignore all SubRuns. if (strikeDeviceInfo.fSubRunControl == nullptr) { return container; } const SkSurfaceProps deviceProps = strikeDeviceInfo.fSurfaceProps; const SkScalerContextFlags scalerContextFlags = strikeDeviceInfo.fScalerContextFlags; const SubRunControl* subRunControl = strikeDeviceInfo.fSubRunControl; #if !defined(SK_DISABLE_SDF_TEXT) const SkScalar maxMaskSize = subRunControl->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 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 (subRunControl->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, *subRunControl); if (!SkScalarNearlyZero(strikeToSourceScale)) { sk_sp 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 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 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 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 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()) { const bool isAntiAliased = subRunControl->forcePathAA() || has_some_antialiasing(runFont); container->fSubRuns.append( PathSubRun::Make(accepted, isAntiAliased, 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 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 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 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 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 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(glyphCount))) { return {}; } SkPoint* positionsData = alloc->makePODArray(glyphCount); if (!buffer.readPointArray(positionsData, glyphCount)) { return {}; } return {positionsData, glyphCount}; } } // namespace sktext::gpu