| /* |
| * 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 |