// -- 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,
};
// -- 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);
// 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; constbool fIsAntiAliased;
// 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());
}
PathOpSubmitter::~PathOpSubmitter() { // If we have converted glyph IDs to paths, then clean up the SkPaths. if (fPathsAreCreated) { for (auto& idOrPath : fIDsOrPaths) {
idOrPath.fPath.~SkPath();
}
}
}
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;
}
});
// 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());
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();
}
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());
}
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());
std::tuple<ClipMethod, SkIRect>
calculate_clip(const GrClip* clip, SkRect deviceBounds, SkRect glyphBounds) { if (clip == nullptr && !deviceBounds.intersects(glyphBounds)) { return {kClippedOut, SkIRect::MakeEmpty()};
} elseif (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)} {}
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); }
}
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;
}
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must // be single threaded. mutable GlyphVector fGlyphs;
}; // class TransformedMaskSubRun
// The regenerateAtlas method mutates fGlyphs. It should be called from onPrepare which must // be single threaded. mutable GlyphVector fGlyphs;
}; // class SDFTSubRun
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
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;
}
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);
}
// 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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.