| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkColorFilter.h" |
| #include "include/gpu/GrContext.h" |
| #include "include/private/SkTemplates.h" |
| #include "src/core/SkMaskFilterBase.h" |
| #include "src/core/SkMatrixPriv.h" |
| #include "src/core/SkMatrixProvider.h" |
| #include "src/core/SkPaintPriv.h" |
| #include "src/core/SkStrikeSpec.h" |
| #include "src/gpu/GrBlurUtils.h" |
| #include "src/gpu/GrClip.h" |
| #include "src/gpu/GrStyle.h" |
| #include "src/gpu/geometry/GrStyledShape.h" |
| #include "src/gpu/ops/GrAtlasTextOp.h" |
| #include "src/gpu/text/GrAtlasManager.h" |
| #include "src/gpu/text/GrStrikeCache.h" |
| #include "src/gpu/text/GrTextBlob.h" |
| #include "src/gpu/text/GrTextTarget.h" |
| |
| #include <cstddef> |
| #include <new> |
| |
| |
| // -- GrTextBlob::Key ------------------------------------------------------------------------------ |
| GrTextBlob::Key::Key() { sk_bzero(this, sizeof(Key)); } |
| |
| bool GrTextBlob::Key::operator==(const GrTextBlob::Key& other) const { |
| return 0 == memcmp(this, &other, sizeof(Key)); |
| } |
| |
| // -- GrTextBlob::PathGlyph ------------------------------------------------------------------------ |
| GrTextBlob::PathGlyph::PathGlyph(const SkPath& path, SkPoint origin) |
| : fPath(path) |
| , fOrigin(origin) {} |
| |
| // -- GrTextBlob::SubRun --------------------------------------------------------------------------- |
| GrTextBlob::SubRun::SubRun(SubRunType type, GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec, |
| GrMaskFormat format, SkRect vertexBounds, |
| const SkSpan<VertexData>& vertexData) |
| : fBlob{textBlob} |
| , fType{type} |
| , fMaskFormat{format} |
| , fStrikeSpec{strikeSpec} |
| , fVertexBounds{vertexBounds} |
| , fVertexData{vertexData} { |
| SkASSERT(fType != kTransformedPath); |
| } |
| |
| GrTextBlob::SubRun::SubRun(GrTextBlob* textBlob, const SkStrikeSpec& strikeSpec) |
| : fBlob{textBlob} |
| , fType{kTransformedPath} |
| , fMaskFormat{kA8_GrMaskFormat} |
| , fStrikeSpec{strikeSpec} |
| , fVertexBounds{SkRect::MakeEmpty()} |
| , fVertexData{SkSpan<VertexData>{}} { } |
| |
| void GrTextBlob::SubRun::resetBulkUseToken() { fBulkUseToken.reset(); } |
| |
| GrDrawOpAtlas::BulkUseTokenUpdater* GrTextBlob::SubRun::bulkUseToken() { return &fBulkUseToken; } |
| GrMaskFormat GrTextBlob::SubRun::maskFormat() const { return fMaskFormat; } |
| |
| size_t GrTextBlob::SubRun::vertexStride() const { |
| switch (this->maskFormat()) { |
| case kA8_GrMaskFormat: |
| return this->hasW() ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex); |
| case kARGB_GrMaskFormat: |
| return this->hasW() ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex); |
| default: |
| SkASSERT(!this->hasW()); |
| return sizeof(Mask2DVertex); |
| } |
| SkUNREACHABLE; |
| } |
| |
| size_t GrTextBlob::SubRun::quadOffset(size_t index) const { |
| return index * kVerticesPerGlyph * this->vertexStride(); |
| } |
| |
| template <typename Rect> |
| static auto ltbr(const Rect& r) { |
| return std::make_tuple(r.left(), r.top(), r.right(), r.bottom()); |
| } |
| |
| void GrTextBlob::SubRun::fillVertexData( |
| void *vertexDst, int offset, int count, |
| GrColor color, const SkMatrix& drawMatrix, SkPoint drawOrigin, SkIRect clip) const { |
| |
| SkMatrix matrix = drawMatrix; |
| matrix.preTranslate(drawOrigin.x(), drawOrigin.y()); |
| |
| auto transformed2D = [&](auto dst, SkScalar dstPadding, SkScalar srcPadding) { |
| SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio(); |
| SkPoint inset = {dstPadding, dstPadding}; |
| for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) { |
| auto[glyph, pos, rect] = vertexData; |
| auto [l, t, r, b] = rect; |
| SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos, |
| sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos; |
| SkPoint lt = matrix.mapXY(sLT.x(), sLT.y()), |
| lb = matrix.mapXY(sLT.x(), sRB.y()), |
| rt = matrix.mapXY(sRB.x(), sLT.y()), |
| rb = matrix.mapXY(sRB.x(), sRB.y()); |
| auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(srcPadding); |
| quad[0] = {lt, color, {al, at}}; // L,T |
| quad[1] = {lb, color, {al, ab}}; // L,B |
| quad[2] = {rt, color, {ar, at}}; // R,T |
| quad[3] = {rb, color, {ar, ab}}; // R,B |
| } |
| }; |
| |
| auto transformed3D = [&](auto dst, SkScalar dstPadding, SkScalar srcPadding) { |
| SkScalar strikeToSource = fStrikeSpec.strikeToSourceRatio(); |
| SkPoint inset = {dstPadding, dstPadding}; |
| auto mapXYZ = [&](SkScalar x, SkScalar y) { |
| SkPoint pt{x, y}; |
| SkPoint3 result; |
| matrix.mapHomogeneousPoints(&result, &pt, 1); |
| return result; |
| }; |
| for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) { |
| auto[glyph, pos, rect] = vertexData; |
| auto [l, t, r, b] = rect; |
| SkPoint sLT = (SkPoint::Make(l, t) + inset) * strikeToSource + pos, |
| sRB = (SkPoint::Make(r, b) - inset) * strikeToSource + pos; |
| SkPoint3 lt = mapXYZ(sLT.x(), sLT.y()), |
| lb = mapXYZ(sLT.x(), sRB.y()), |
| rt = mapXYZ(sRB.x(), sLT.y()), |
| rb = mapXYZ(sRB.x(), sRB.y()); |
| auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(srcPadding); |
| quad[0] = {lt, color, {al, at}}; // L,T |
| quad[1] = {lb, color, {al, ab}}; // L,B |
| quad[2] = {rt, color, {ar, at}}; // R,T |
| quad[3] = {rb, color, {ar, ab}}; // R,B |
| } |
| }; |
| |
| auto direct2D = [&](auto dst, SkIRect* clip) { |
| // Rectangles in device space |
| SkPoint originInDeviceSpace = matrix.mapXY(0, 0); |
| for (auto[quad, vertexData] : SkMakeZip(dst, fVertexData.subspan(offset, count))) { |
| auto[glyph, pos, rect] = vertexData; |
| auto[l, t, r, b] = rect; |
| auto[fx, fy] = pos + originInDeviceSpace; |
| auto[al, at, ar, ab] = glyph.grGlyph->fAtlasLocator.getUVs(0); |
| if (clip == nullptr) { |
| SkScalar dx = SkScalarRoundToScalar(fx), |
| dy = SkScalarRoundToScalar(fy); |
| auto[dl, dt, dr, db] = SkRect::MakeLTRB(l + dx, t + dy, r + dx, b + dy); |
| quad[0] = {{dl, dt}, color, {al, at}}; // L,T |
| quad[1] = {{dl, db}, color, {al, ab}}; // L,B |
| quad[2] = {{dr, dt}, color, {ar, at}}; // R,T |
| quad[3] = {{dr, db}, color, {ar, ab}}; // R,B |
| } else { |
| int dx = SkScalarRoundToInt(fx), |
| dy = SkScalarRoundToInt(fy); |
| SkIRect devIRect = SkIRect::MakeLTRB(l + dx, t + dy, r + dx, b + dy); |
| SkScalar dl, dt, dr, db; |
| uint16_t tl, tt, tr, tb; |
| if (!clip->containsNoEmptyCheck(devIRect)) { |
| if (SkIRect clipped; clipped.intersect(devIRect, *clip)) { |
| int lD = clipped.left() - devIRect.left(); |
| int tD = clipped.top() - devIRect.top(); |
| int rD = clipped.right() - devIRect.right(); |
| int bD = clipped.bottom() - devIRect.bottom(); |
| int indexLT, indexRB; |
| std::tie(dl, dt, dr, db) = ltbr(clipped); |
| std::tie(tl, tt, indexLT) = |
| GrDrawOpAtlas::UnpackIndexFromTexCoords(al, at); |
| std::tie(tr, tb, indexRB) = |
| GrDrawOpAtlas::UnpackIndexFromTexCoords(ar, ab); |
| std::tie(tl, tt) = |
| GrDrawOpAtlas::PackIndexInTexCoords(tl + lD, tt + tD, indexLT); |
| std::tie(tr, tb) = |
| GrDrawOpAtlas::PackIndexInTexCoords(tr + rD, tb + bD, indexRB); |
| } else { |
| // TODO: omit generating any vertex data for fully clipped glyphs ? |
| std::tie(dl, dt, dr, db) = std::make_tuple(0, 0, 0, 0); |
| std::tie(tl, tt, tr, tb) = std::make_tuple(0, 0, 0, 0); |
| } |
| |
| } else { |
| std::tie(dl, dt, dr, db) = ltbr(devIRect); |
| std::tie(tl, tt, tr, tb) = std::tie(al, at, ar, ab); |
| } |
| quad[0] = {{dl, dt}, color, {tl, tt}}; // L,T |
| quad[1] = {{dl, db}, color, {tl, tb}}; // L,B |
| quad[2] = {{dr, dt}, color, {tr, tt}}; // R,T |
| quad[3] = {{dr, db}, color, {tr, tb}}; // R,B |
| } |
| } |
| }; |
| |
| switch (fType) { |
| case kDirectMask: { |
| if (clip.isEmpty()) { |
| if (this->maskFormat() != kARGB_GrMaskFormat) { |
| using Quad = Mask2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| direct2D((Quad*) vertexDst, nullptr); |
| } else { |
| using Quad = ARGB2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| direct2D((Quad*) vertexDst, nullptr); |
| } |
| } else { |
| if (this->maskFormat() != kARGB_GrMaskFormat) { |
| using Quad = Mask2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| direct2D((Quad*) vertexDst, &clip); |
| } else { |
| using Quad = ARGB2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| direct2D((Quad*) vertexDst, &clip); |
| } |
| } |
| break; |
| } |
| case kTransformedMask: { |
| if (!this->hasW()) { |
| if (this->maskFormat() == GrMaskFormat::kARGB_GrMaskFormat) { |
| using Quad = ARGB2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| transformed2D((Quad*) vertexDst, 0, 1); |
| } else { |
| using Quad = Mask2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| transformed2D((Quad*) vertexDst, 0, 1); |
| } |
| } else { |
| if (this->maskFormat() == GrMaskFormat::kARGB_GrMaskFormat) { |
| using Quad = ARGB3DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| transformed3D((Quad*) vertexDst, 0, 1); |
| } else { |
| using Quad = Mask3DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| transformed3D((Quad*) vertexDst, 0, 1); |
| } |
| } |
| break; |
| } |
| case kTransformedSDFT: { |
| if (!this->hasW()) { |
| using Quad = Mask2DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| transformed2D((Quad*) vertexDst, SK_DistanceFieldInset, SK_DistanceFieldInset); |
| } else { |
| using Quad = Mask3DVertex[4]; |
| SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); |
| transformed3D((Quad*) vertexDst, SK_DistanceFieldInset, SK_DistanceFieldInset); |
| } |
| break; |
| } |
| case kTransformedPath: |
| SK_ABORT("Paths don't generate vertex data."); |
| } |
| } |
| |
| int GrTextBlob::SubRun::glyphCount() const { |
| return fVertexData.count(); |
| } |
| |
| bool GrTextBlob::SubRun::drawAsDistanceFields() const { return fType == kTransformedSDFT; } |
| |
| bool GrTextBlob::SubRun::drawAsPaths() const { return fType == kTransformedPath; } |
| |
| bool GrTextBlob::SubRun::needsTransform() const { |
| return fType == kTransformedPath || |
| fType == kTransformedMask || |
| fType == kTransformedSDFT; |
| } |
| |
| bool GrTextBlob::SubRun::needsPadding() const { |
| return fType == kTransformedPath || fType == kTransformedMask; |
| } |
| |
| int GrTextBlob::SubRun::atlasPadding() const { |
| return SkTo<int>(this->needsPadding()); |
| } |
| |
| auto GrTextBlob::SubRun::vertexData() const -> SkSpan<const VertexData> { |
| return fVertexData; |
| } |
| |
| bool GrTextBlob::SubRun::hasW() const { |
| if (fType == kTransformedSDFT || fType == kTransformedMask || fType == kTransformedPath) { |
| return fBlob->hasPerspective(); |
| } |
| |
| // The viewMatrix is implicitly SkMatrix::I when drawing kDirectMask, because it is not |
| // used. |
| return false; |
| } |
| |
| void GrTextBlob::SubRun::prepareGrGlyphs(GrStrikeCache* strikeCache) { |
| if (fStrike) { |
| return; |
| } |
| |
| fStrike = fStrikeSpec.findOrCreateGrStrike(strikeCache); |
| |
| for (auto& tmp : fVertexData) { |
| tmp.glyph.grGlyph = fStrike->getGlyph(tmp.glyph.packedGlyphID); |
| } |
| } |
| |
| SkRect GrTextBlob::SubRun::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const { |
| SkRect outBounds = fVertexBounds; |
| if (this->needsTransform()) { |
| // if the glyph needs transformation offset the by the new origin, and map to device space. |
| outBounds.offset(drawOrigin); |
| outBounds = drawMatrix.mapRect(outBounds); |
| } else { |
| SkPoint offset = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y()); |
| // The vertex bounds are already {0, 0} based, so just add the new origin offset. |
| outBounds.offset(offset); |
| |
| // Due to floating point numerical inaccuracies, we have to round out here |
| outBounds.roundOut(); |
| } |
| return outBounds; |
| } |
| |
| GrGlyph* GrTextBlob::SubRun::grGlyph(int i) const { |
| return fVertexData[i].glyph.grGlyph; |
| } |
| |
| void GrTextBlob::SubRun::setUseLCDText(bool useLCDText) { fUseLCDText = useLCDText; } |
| bool GrTextBlob::SubRun::hasUseLCDText() const { return fUseLCDText; } |
| void GrTextBlob::SubRun::setAntiAliased(bool antiAliased) { fAntiAliased = antiAliased; } |
| bool GrTextBlob::SubRun::isAntiAliased() const { return fAntiAliased; } |
| const SkStrikeSpec& GrTextBlob::SubRun::strikeSpec() const { return fStrikeSpec; } |
| |
| auto GrTextBlob::SubRun::MakePaths( |
| const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkFont& runFont, |
| const SkStrikeSpec& strikeSpec, |
| GrTextBlob* blob, |
| SkArenaAlloc* alloc) -> SubRun* { |
| SubRun* subRun = alloc->make<SubRun>(blob, strikeSpec); |
| subRun->setAntiAliased(runFont.hasSomeAntiAliasing()); |
| for (auto [variant, pos] : drawables) { |
| subRun->fPaths.emplace_back(*variant.path(), pos); |
| } |
| return subRun; |
| }; |
| |
| auto GrTextBlob::SubRun::MakeSDFT( |
| const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkFont& runFont, |
| const SkStrikeSpec& strikeSpec, |
| GrTextBlob* blob, |
| SkArenaAlloc* alloc) -> SubRun* { |
| SubRun* subRun = SubRun::InitForAtlas( |
| kTransformedSDFT, drawables, strikeSpec, kA8_GrMaskFormat, blob, alloc); |
| subRun->setUseLCDText(runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias); |
| subRun->setAntiAliased(runFont.hasSomeAntiAliasing()); |
| return subRun; |
| } |
| |
| auto GrTextBlob::SubRun::MakeDirectMask( |
| const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec, |
| GrMaskFormat format, |
| GrTextBlob* blob, |
| SkArenaAlloc* alloc) -> SubRun* { |
| return SubRun::InitForAtlas(kDirectMask, drawables, strikeSpec, format, blob, alloc); |
| } |
| |
| auto GrTextBlob::SubRun::MakeTransformedMask( |
| const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec, |
| GrMaskFormat format, |
| GrTextBlob* blob, |
| SkArenaAlloc* alloc) -> SubRun* { |
| return SubRun::InitForAtlas(kTransformedMask, drawables, strikeSpec, format, blob, alloc); |
| } |
| |
| void GrTextBlob::SubRun::insertSubRunOpsIntoTarget(GrTextTarget* target, |
| const SkSurfaceProps& props, |
| const SkPaint& paint, |
| const GrClip* clip, |
| const SkMatrixProvider& deviceMatrix, |
| SkPoint drawOrigin) { |
| if (this->drawAsPaths()) { |
| SkPaint runPaint{paint}; |
| runPaint.setAntiAlias(this->isAntiAliased()); |
| // If there are shaders, blurs or styles, the path must be scaled into source |
| // space independently of the CTM. This allows the CTM to be correct for the |
| // different effects. |
| GrStyle style(runPaint); |
| |
| bool needsExactCTM = runPaint.getShader() |
| || style.applies() |
| || runPaint.getMaskFilter(); |
| |
| // Calculate the matrix that maps the path glyphs from their size in the strike to |
| // the graphics source space. |
| SkScalar scale = this->fStrikeSpec.strikeToSourceRatio(); |
| SkMatrix strikeToSource = SkMatrix::Scale(scale, scale); |
| strikeToSource.postTranslate(drawOrigin.x(), drawOrigin.y()); |
| if (!needsExactCTM) { |
| for (const auto& pathPos : this->fPaths) { |
| const SkPath& path = pathPos.fPath; |
| const SkPoint pos = pathPos.fOrigin; // Transform the glyph to source space. |
| SkMatrix pathMatrix = strikeToSource; |
| pathMatrix.postTranslate(pos.x(), pos.y()); |
| SkPreConcatMatrixProvider strikeToDevice(deviceMatrix, pathMatrix); |
| |
| GrStyledShape shape(path, paint); |
| target->drawShape(clip, runPaint, strikeToDevice, shape); |
| } |
| } else { |
| // Transform the path to device because the deviceMatrix must be unchanged to |
| // draw effect, filter or shader paths. |
| for (const auto& pathPos : this->fPaths) { |
| const SkPath& path = pathPos.fPath; |
| const SkPoint pos = pathPos.fOrigin; |
| // Transform the glyph to source space. |
| SkMatrix pathMatrix = strikeToSource; |
| pathMatrix.postTranslate(pos.x(), pos.y()); |
| |
| SkPath deviceOutline; |
| path.transform(pathMatrix, &deviceOutline); |
| deviceOutline.setIsVolatile(true); |
| GrStyledShape shape(deviceOutline, paint); |
| target->drawShape(clip, runPaint, deviceMatrix, shape); |
| } |
| } |
| } else { |
| int glyphCount = this->glyphCount(); |
| if (0 == glyphCount) { |
| return; |
| } |
| |
| bool skipClip = false; |
| SkIRect clipRect = SkIRect::MakeEmpty(); |
| SkRect rtBounds = SkRect::MakeWH(target->width(), target->height()); |
| // We can clip geometrically if we're not using SDFs or transformed glyphs, |
| // and we have an axis-aligned rectangular non-AA clip |
| if (!this->drawAsDistanceFields() && !this->needsTransform()) { |
| // We only need to do clipping work if the subrun isn't contained by the clip |
| skipClip = true; |
| SkRect subRunBounds = this->deviceRect(deviceMatrix.localToDevice(), drawOrigin); |
| if (!clip && !rtBounds.intersects(subRunBounds)) { |
| // If the subrun is completely outside, don't add an op for it |
| return; |
| } else if (clip) { |
| GrClip::PreClipResult result = clip->preApply(subRunBounds); |
| if (result.fEffect == GrClip::Effect::kClipped) { |
| if (result.fIsRRect && result.fRRect.isRect() && result.fAA == GrAA::kNo) { |
| // Embed non-AA axis-aligned clip into the draw |
| result.fRRect.getBounds().round(&clipRect); |
| } else { |
| // Can't actually skip the regular clipping |
| skipClip = false; |
| } |
| } else if (result.fEffect == GrClip::Effect::kClippedOut) { |
| return; |
| } |
| } |
| } |
| |
| auto op = this->makeOp(deviceMatrix, drawOrigin, clipRect, paint, props, target); |
| if (op != nullptr) { |
| target->addDrawOp(skipClip ? nullptr : clip, std::move(op)); |
| } |
| } |
| } |
| |
| std::unique_ptr<GrAtlasTextOp> GrTextBlob::SubRun::makeOp( |
| const SkMatrixProvider& matrixProvider, |
| SkPoint drawOrigin, |
| const SkIRect& clipRect, |
| const SkPaint& paint, |
| const SkSurfaceProps& props, |
| GrTextTarget* target) { |
| |
| if (this->drawAsDistanceFields()) { |
| // TODO: Can we be even smarter based on the dest transfer function? |
| return GrAtlasTextOp::MakeDistanceField(target->renderTargetContext(), |
| paint, |
| this, |
| matrixProvider, |
| drawOrigin, |
| clipRect); |
| } else { |
| return GrAtlasTextOp::MakeBitmap(target->renderTargetContext(), |
| paint, |
| this, |
| matrixProvider, |
| drawOrigin, |
| clipRect); |
| } |
| } |
| |
| auto GrTextBlob::SubRun::InitForAtlas(SubRunType type, |
| const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec, |
| GrMaskFormat format, |
| GrTextBlob* blob, |
| SkArenaAlloc* alloc) -> SubRun* { |
| size_t vertexCount = drawables.size(); |
| using Data = VertexData; |
| SkRect bounds = SkRectPriv::MakeLargestInverted(); |
| auto initializer = [&, strikeToSource=strikeSpec.strikeToSourceRatio()](size_t i) { |
| auto [variant, pos] = drawables[i]; |
| SkGlyph* skGlyph = variant; |
| int16_t l = skGlyph->left(); |
| int16_t t = skGlyph->top(); |
| int16_t r = l + skGlyph->width(); |
| int16_t b = t + skGlyph->height(); |
| SkPoint lt = SkPoint::Make(l, t) * strikeToSource + pos, |
| rb = SkPoint::Make(r, b) * strikeToSource + pos; |
| |
| bounds.joinPossiblyEmptyRect(SkRect::MakeLTRB(lt.x(), lt.y(), rb.x(), rb.y())); |
| return Data{{skGlyph->getPackedID()}, pos, {l, t, r, b}}; |
| }; |
| |
| SkSpan<Data> vertexData{ |
| alloc->makeInitializedArray<Data>(vertexCount, initializer), vertexCount}; |
| |
| SubRun* subRun = alloc->make<SubRun>(type, blob, strikeSpec, format, bounds, vertexData); |
| |
| return subRun; |
| } |
| |
| |
| // -- GrTextBlob ----------------------------------------------------------------------------------- |
| void GrTextBlob::operator delete(void* p) { ::operator delete(p); } |
| void* GrTextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); } |
| void* GrTextBlob::operator new(size_t, void* p) { return p; } |
| |
| GrTextBlob::~GrTextBlob() = default; |
| |
| sk_sp<GrTextBlob> GrTextBlob::Make(const SkGlyphRunList& glyphRunList, const SkMatrix& drawMatrix) { |
| // The difference in alignment from the storage of VertexData to SubRun; |
| constexpr size_t alignDiff = alignof(SubRun) - alignof(SubRun::VertexData); |
| constexpr size_t vertexDataToSubRunPadding = alignDiff > 0 ? alignDiff : 0; |
| size_t arenaSize = sizeof(SubRun::VertexData) * glyphRunList.totalGlyphCount() |
| + glyphRunList.runCount() * (sizeof(SubRun) + vertexDataToSubRunPadding); |
| |
| size_t allocationSize = sizeof(GrTextBlob) + arenaSize; |
| |
| void* allocation = ::operator new (allocationSize); |
| |
| SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(glyphRunList.paint()); |
| sk_sp<GrTextBlob> blob{new (allocation) GrTextBlob{ |
| arenaSize, drawMatrix, glyphRunList.origin(), initialLuminance}}; |
| |
| return blob; |
| } |
| |
| void GrTextBlob::setupKey(const GrTextBlob::Key& key, const SkMaskFilterBase::BlurRec& blurRec, |
| const SkPaint& paint) { |
| fKey = key; |
| if (key.fHasBlur) { |
| fBlurRec = blurRec; |
| } |
| if (key.fStyle != SkPaint::kFill_Style) { |
| fStrokeInfo.fFrameWidth = paint.getStrokeWidth(); |
| fStrokeInfo.fMiterLimit = paint.getStrokeMiter(); |
| fStrokeInfo.fJoin = paint.getStrokeJoin(); |
| } |
| } |
| const GrTextBlob::Key& GrTextBlob::GetKey(const GrTextBlob& blob) { return blob.fKey; } |
| uint32_t GrTextBlob::Hash(const GrTextBlob::Key& key) { return SkOpts::hash(&key, sizeof(Key)); } |
| |
| bool GrTextBlob::hasDistanceField() const { |
| return SkToBool(fTextType & kHasDistanceField_TextType); |
| } |
| bool GrTextBlob::hasBitmap() const { return SkToBool(fTextType & kHasBitmap_TextType); } |
| bool GrTextBlob::hasPerspective() const { return fInitialMatrix.hasPerspective(); } |
| |
| void GrTextBlob::setHasDistanceField() { fTextType |= kHasDistanceField_TextType; } |
| void GrTextBlob::setHasBitmap() { fTextType |= kHasBitmap_TextType; } |
| void GrTextBlob::setMinAndMaxScale(SkScalar scaledMin, SkScalar scaledMax) { |
| // we init fMaxMinScale and fMinMaxScale in the constructor |
| fMaxMinScale = std::max(scaledMin, fMaxMinScale); |
| fMinMaxScale = std::min(scaledMax, fMinMaxScale); |
| } |
| |
| bool GrTextBlob::canReuse(const SkPaint& paint, |
| const SkMaskFilterBase::BlurRec& blurRec, |
| const SkMatrix& drawMatrix, |
| SkPoint drawOrigin) { |
| // If we have LCD text then our canonical color will be set to transparent, in this case we have |
| // to regenerate the blob on any color change |
| // We use the grPaint to get any color filter effects |
| if (fKey.fCanonicalColor == SK_ColorTRANSPARENT && |
| fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) { |
| return false; |
| } |
| |
| if (fInitialMatrix.hasPerspective() != drawMatrix.hasPerspective()) { |
| return false; |
| } |
| |
| /** This could be relaxed for blobs with only distance field glyphs. */ |
| if (fInitialMatrix.hasPerspective() && !SkMatrixPriv::CheapEqual(fInitialMatrix, drawMatrix)) { |
| return false; |
| } |
| |
| // We only cache one masked version |
| if (fKey.fHasBlur && |
| (fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle)) { |
| return false; |
| } |
| |
| // Similarly, we only cache one version for each style |
| if (fKey.fStyle != SkPaint::kFill_Style && |
| (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() || |
| fStrokeInfo.fMiterLimit != paint.getStrokeMiter() || |
| fStrokeInfo.fJoin != paint.getStrokeJoin())) { |
| return false; |
| } |
| |
| // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls |
| // for mixed blobs if this becomes an issue. |
| if (this->hasBitmap() && this->hasDistanceField()) { |
| // Identical view matrices and we can reuse in all cases |
| return SkMatrixPriv::CheapEqual(fInitialMatrix, drawMatrix) && drawOrigin == fInitialOrigin; |
| } |
| |
| if (this->hasBitmap()) { |
| if (fInitialMatrix.getScaleX() != drawMatrix.getScaleX() || |
| fInitialMatrix.getScaleY() != drawMatrix.getScaleY() || |
| fInitialMatrix.getSkewX() != drawMatrix.getSkewX() || |
| fInitialMatrix.getSkewY() != drawMatrix.getSkewY()) { |
| return false; |
| } |
| |
| // TODO(herb): this is not needed for full pixel glyph choice, but is needed to adjust |
| // the quads properly. Devise a system that regenerates the quads from original data |
| // using the transform to allow this to be used in general. |
| |
| // We can update the positions in the text blob without regenerating the whole |
| // blob, but only for integer translations. |
| // Calculate the translation in source space to a translation in device space by mapping |
| // (0, 0) through both the initial matrix and the draw matrix; take the difference. |
| SkMatrix initialMatrix{fInitialMatrix}; |
| initialMatrix.preTranslate(fInitialOrigin.x(), fInitialOrigin.y()); |
| SkPoint initialDeviceOrigin{0, 0}; |
| initialMatrix.mapPoints(&initialDeviceOrigin, 1); |
| SkMatrix completeDrawMatrix{drawMatrix}; |
| completeDrawMatrix.preTranslate(drawOrigin.x(), drawOrigin.y()); |
| SkPoint drawDeviceOrigin{0, 0}; |
| completeDrawMatrix.mapPoints(&drawDeviceOrigin, 1); |
| SkPoint translation = drawDeviceOrigin - initialDeviceOrigin; |
| |
| if (!SkScalarIsInt(translation.x()) || !SkScalarIsInt(translation.y())) { |
| return false; |
| } |
| } else if (this->hasDistanceField()) { |
| // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different |
| // distance field being generated, so we have to regenerate in those cases |
| SkScalar newMaxScale = drawMatrix.getMaxScale(); |
| SkScalar oldMaxScale = fInitialMatrix.getMaxScale(); |
| SkScalar scaleAdjust = newMaxScale / oldMaxScale; |
| if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) { |
| return false; |
| } |
| } |
| |
| // If the blob is all paths, there is no reason to regenerate. |
| return true; |
| } |
| |
| const GrTextBlob::Key& GrTextBlob::key() const { return fKey; } |
| size_t GrTextBlob::size() const { return fSize; } |
| |
| template<typename AddSingleMaskFormat> |
| void GrTextBlob::addMultiMaskFormat( |
| AddSingleMaskFormat addSingle, |
| const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec) { |
| this->setHasBitmap(); |
| if (drawables.empty()) { return; } |
| |
| auto glyphSpan = drawables.get<0>(); |
| SkGlyph* glyph = glyphSpan[0]; |
| GrMaskFormat format = GrGlyph::FormatFromSkGlyph(glyph->maskFormat()); |
| size_t startIndex = 0; |
| for (size_t i = 1; i < drawables.size(); i++) { |
| glyph = glyphSpan[i]; |
| GrMaskFormat nextFormat = GrGlyph::FormatFromSkGlyph(glyph->maskFormat()); |
| if (format != nextFormat) { |
| auto sameFormat = drawables.subspan(startIndex, i - startIndex); |
| SubRun* subRun = addSingle(sameFormat, strikeSpec, format, this, &fAlloc); |
| this->insertSubRun(subRun); |
| format = nextFormat; |
| startIndex = i; |
| } |
| } |
| auto sameFormat = drawables.last(drawables.size() - startIndex); |
| SubRun* subRun = addSingle(sameFormat, strikeSpec, format, this, &fAlloc); |
| this->insertSubRun(subRun); |
| } |
| |
| GrTextBlob::GrTextBlob(size_t allocSize, |
| const SkMatrix& drawMatrix, |
| SkPoint origin, |
| SkColor initialLuminance) |
| : fSize{allocSize} |
| , fInitialMatrix{drawMatrix} |
| , fInitialOrigin{origin} |
| , fInitialLuminance{initialLuminance} |
| , fAlloc{SkTAddOffset<char>(this, sizeof(GrTextBlob)), allocSize, allocSize/2} { } |
| |
| void GrTextBlob::insertSubRun(SubRun* subRun) { |
| fSubRunList.addToTail(subRun); |
| } |
| |
| void GrTextBlob::processDeviceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec) { |
| |
| this->addMultiMaskFormat(SubRun::MakeDirectMask, drawables, strikeSpec); |
| } |
| |
| void GrTextBlob::processSourcePaths(const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkFont& runFont, |
| const SkStrikeSpec& strikeSpec) { |
| this->setHasBitmap(); |
| SubRun* subRun = SubRun::MakePaths(drawables, runFont, strikeSpec, this, &fAlloc); |
| this->insertSubRun(subRun); |
| } |
| |
| void GrTextBlob::processSourceSDFT(const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec, |
| const SkFont& runFont, |
| SkScalar minScale, |
| SkScalar maxScale) { |
| this->setHasDistanceField(); |
| this->setMinAndMaxScale(minScale, maxScale); |
| SubRun* subRun = SubRun::MakeSDFT(drawables, runFont, strikeSpec, this, &fAlloc); |
| this->insertSubRun(subRun); |
| } |
| |
| void GrTextBlob::processSourceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables, |
| const SkStrikeSpec& strikeSpec) { |
| this->addMultiMaskFormat(SubRun::MakeTransformedMask, drawables, strikeSpec); |
| } |
| |
| auto GrTextBlob::firstSubRun() const -> SubRun* { return fSubRunList.head(); } |
| |
| // -- GrTextBlob::VertexRegenerator ---------------------------------------------------------------- |
| GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourceProvider, |
| GrTextBlob::SubRun* subRun, |
| GrDeferredUploadTarget* uploadTarget, |
| GrAtlasManager* fullAtlasManager) |
| : fResourceProvider(resourceProvider) |
| , fUploadTarget(uploadTarget) |
| , fFullAtlasManager(fullAtlasManager) |
| , fSubRun(subRun) { } |
| |
| std::tuple<bool, int> GrTextBlob::VertexRegenerator::updateTextureCoordinates( |
| const int begin, const int end) { |
| |
| SkASSERT(fSubRun->isPrepared()); |
| |
| SkBulkGlyphMetricsAndImages metricsAndImages{fSubRun->strikeSpec()}; |
| |
| // Update the atlas information in the GrStrike. |
| auto tokenTracker = fUploadTarget->tokenTracker(); |
| auto vertexData = fSubRun->vertexData().subspan(begin, end - begin); |
| int glyphsPlacedInAtlas = 0; |
| for (auto [glyph, pos, rect] : vertexData) { |
| GrGlyph* grGlyph = glyph.grGlyph; |
| SkASSERT(grGlyph != nullptr); |
| |
| if (!fFullAtlasManager->hasGlyph(fSubRun->maskFormat(), grGlyph)) { |
| const SkGlyph& skGlyph = *metricsAndImages.glyph(grGlyph->fPackedID); |
| auto code = fFullAtlasManager->addGlyphToAtlas( |
| skGlyph, fSubRun->atlasPadding(), grGlyph, fResourceProvider, fUploadTarget); |
| if (code != GrDrawOpAtlas::ErrorCode::kSucceeded) { |
| return {code != GrDrawOpAtlas::ErrorCode::kError, glyphsPlacedInAtlas}; |
| } |
| } |
| fFullAtlasManager->addGlyphToBulkAndSetUseToken( |
| fSubRun->bulkUseToken(), fSubRun->maskFormat(), grGlyph, |
| tokenTracker->nextDrawToken()); |
| glyphsPlacedInAtlas++; |
| } |
| |
| return {true, glyphsPlacedInAtlas}; |
| } |
| |
| std::tuple<bool, int> GrTextBlob::VertexRegenerator::regenerate(int begin, int end) { |
| uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat()); |
| |
| if (fSubRun->fAtlasGeneration != currentAtlasGen) { |
| // Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration |
| // is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls.. |
| fSubRun->resetBulkUseToken(); |
| auto [success, glyphsPlacedInAtlas] = this->updateTextureCoordinates(begin, end); |
| |
| // Update atlas generation if there are no more glyphs to put in the atlas. |
| if (success && begin + glyphsPlacedInAtlas == fSubRun->glyphCount()) { |
| // Need to get the freshest value of the atlas' generation because |
| // updateTextureCoordinates may have changed it. |
| fSubRun->fAtlasGeneration = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat()); |
| } |
| |
| return {success, glyphsPlacedInAtlas}; |
| } else { |
| // The atlas hasn't changed, so our texture coordinates are still valid. |
| if (end == fSubRun->glyphCount()) { |
| // The atlas hasn't changed and the texture coordinates are all still valid. Update |
| // all the plots used to the new use token. |
| fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(), |
| fUploadTarget->tokenTracker()->nextDrawToken(), |
| fSubRun->maskFormat()); |
| } |
| return {true, end - begin}; |
| } |
| } |