1 /* 2 * Copyright 2015 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "GrTextBlob.h" 9 #include "GrBlurUtils.h" 10 #include "GrClip.h" 11 #include "GrContext.h" 12 #include "GrShape.h" 13 #include "GrStyle.h" 14 #include "GrTextTarget.h" 15 #include "SkColorFilter.h" 16 #include "SkMaskFilterBase.h" 17 #include "SkPaintPriv.h" 18 #include "ops/GrAtlasTextOp.h" 19 20 #include <new> 21 22 template <size_t N> static size_t sk_align(size_t s) { 23 return ((s + (N-1)) / N) * N; 24 } 25 26 sk_sp<GrTextBlob> GrTextBlob::Make(int glyphCount, int runCount, GrColor color) { 27 // We allocate size for the GrTextBlob itself, plus size for the vertices array, 28 // and size for the glyphIds array. 29 size_t verticesCount = glyphCount * kVerticesPerGlyph * kMaxVASize; 30 31 size_t blobStart = 0; 32 size_t vertex = sk_align<alignof(char)> (blobStart + sizeof(GrTextBlob) * 1); 33 size_t glyphs = sk_align<alignof(GrGlyph*)> (vertex + sizeof(char) * verticesCount); 34 size_t runs = sk_align<alignof(GrTextBlob::Run)>(glyphs + sizeof(GrGlyph*) * glyphCount); 35 size_t size = (runs + sizeof(GrTextBlob::Run) * runCount); 36 37 void* allocation = ::operator new (size); 38 39 if (CACHE_SANITY_CHECK) { 40 sk_bzero(allocation, size); 41 } 42 43 sk_sp<GrTextBlob> blob{new (allocation) GrTextBlob{}}; 44 blob->fSize = size; 45 46 // setup offsets for vertices / glyphs 47 blob->fVertices = SkTAddOffset<char>(blob.get(), vertex); 48 blob->fGlyphs = SkTAddOffset<GrGlyph*>(blob.get(), glyphs); 49 blob->fRuns = SkTAddOffset<GrTextBlob::Run>(blob.get(), runs); 50 51 // Initialize runs 52 for (int i = 0; i < runCount; i++) { 53 new (&blob->fRuns[i]) GrTextBlob::Run{blob.get(), color}; 54 } 55 blob->fRunCountLimit = runCount; 56 return blob; 57 } 58 59 void GrTextBlob::Run::setupFont(const SkPaint& skPaint, 60 const SkFont& skFont, 61 const SkDescriptor& cacheDescriptor) { 62 fTypeface = skFont.refTypefaceOrDefault(); 63 SkScalerContextEffects effects{skPaint}; 64 fPathEffect = sk_ref_sp(effects.fPathEffect); 65 fMaskFilter = sk_ref_sp(effects.fMaskFilter); 66 // if we have an override descriptor for the run, then we should use that 67 SkAutoDescriptor* desc = 68 fARGBFallbackDescriptor.get() ? fARGBFallbackDescriptor.get() : &fDescriptor; 69 // Set up the descriptor for possible cache lookups during regen. 70 desc->reset(cacheDescriptor); 71 } 72 73 void GrTextBlob::Run::appendPathGlyph(const SkPath& path, SkPoint position, 74 SkScalar scale, bool preTransformed) { 75 fPathGlyphs.push_back(PathGlyph(path, position.x(), position.y(), scale, preTransformed)); 76 } 77 78 bool GrTextBlob::mustRegenerate(const SkPaint& paint, bool anyRunHasSubpixelPosition, 79 const SkMaskFilterBase::BlurRec& blurRec, 80 const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { 81 // If we have LCD text then our canonical color will be set to transparent, in this case we have 82 // to regenerate the blob on any color change 83 // We use the grPaint to get any color filter effects 84 if (fKey.fCanonicalColor == SK_ColorTRANSPARENT && 85 fLuminanceColor != SkPaintPriv::ComputeLuminanceColor(paint)) { 86 return true; 87 } 88 89 if (fInitialViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) { 90 return true; 91 } 92 93 /** This could be relaxed for blobs with only distance field glyphs. */ 94 if (fInitialViewMatrix.hasPerspective() && !fInitialViewMatrix.cheapEqualTo(viewMatrix)) { 95 return true; 96 } 97 98 // We only cache one masked version 99 if (fKey.fHasBlur && 100 (fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle)) { 101 return true; 102 } 103 104 // Similarly, we only cache one version for each style 105 if (fKey.fStyle != SkPaint::kFill_Style && 106 (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() || 107 fStrokeInfo.fMiterLimit != paint.getStrokeMiter() || 108 fStrokeInfo.fJoin != paint.getStrokeJoin())) { 109 return true; 110 } 111 112 // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls 113 // for mixed blobs if this becomes an issue. 114 if (this->hasBitmap() && this->hasDistanceField()) { 115 // Identical viewmatrices and we can reuse in all cases 116 if (fInitialViewMatrix.cheapEqualTo(viewMatrix) && x == fInitialX && y == fInitialY) { 117 return false; 118 } 119 return true; 120 } 121 122 if (this->hasBitmap()) { 123 if (fInitialViewMatrix.getScaleX() != viewMatrix.getScaleX() || 124 fInitialViewMatrix.getScaleY() != viewMatrix.getScaleY() || 125 fInitialViewMatrix.getSkewX() != viewMatrix.getSkewX() || 126 fInitialViewMatrix.getSkewY() != viewMatrix.getSkewY()) { 127 return true; 128 } 129 130 // If the text blob only has full pixel glyphs, then fractional part of the position does 131 // not affect the SkGlyphs used. 132 if (anyRunHasSubpixelPosition) { 133 // We can update the positions in the text blob without regenerating the whole 134 // blob, but only for integer translations. 135 // This cool bit of math will determine the necessary translation to apply to the 136 // already generated vertex coordinates to move them to the correct position. 137 SkScalar transX = viewMatrix.getTranslateX() + 138 viewMatrix.getScaleX() * (x - fInitialX) + 139 viewMatrix.getSkewX() * (y - fInitialY) - 140 fInitialViewMatrix.getTranslateX(); 141 SkScalar transY = viewMatrix.getTranslateY() + 142 viewMatrix.getSkewY() * (x - fInitialX) + 143 viewMatrix.getScaleY() * (y - fInitialY) - 144 fInitialViewMatrix.getTranslateY(); 145 if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) { 146 return true; 147 } 148 } 149 } else if (this->hasDistanceField()) { 150 // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different 151 // distance field being generated, so we have to regenerate in those cases 152 SkScalar newMaxScale = viewMatrix.getMaxScale(); 153 SkScalar oldMaxScale = fInitialViewMatrix.getMaxScale(); 154 SkScalar scaleAdjust = newMaxScale / oldMaxScale; 155 if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) { 156 return true; 157 } 158 } 159 160 // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case 161 // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate 162 // the blob anyways at flush time, so no need to regenerate explicitly 163 return false; 164 } 165 166 inline std::unique_ptr<GrAtlasTextOp> GrTextBlob::makeOp( 167 const SubRun& info, int glyphCount, uint16_t run, uint16_t subRun, 168 const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect, 169 const SkPaint& paint, const SkPMColor4f& filteredColor, const SkSurfaceProps& props, 170 const GrDistanceFieldAdjustTable* distanceAdjustTable, GrTextTarget* target) { 171 GrMaskFormat format = info.maskFormat(); 172 173 GrPaint grPaint; 174 target->makeGrPaint(info.maskFormat(), paint, viewMatrix, &grPaint); 175 std::unique_ptr<GrAtlasTextOp> op; 176 if (info.drawAsDistanceFields()) { 177 // TODO: Can we be even smarter based on the dest transfer function? 178 op = GrAtlasTextOp::MakeDistanceField( 179 target->getContext(), std::move(grPaint), glyphCount, distanceAdjustTable, 180 target->colorSpaceInfo().isLinearlyBlended(), 181 SkPaintPriv::ComputeLuminanceColor(paint), 182 props, info.isAntiAliased(), info.hasUseLCDText()); 183 } else { 184 op = GrAtlasTextOp::MakeBitmap(target->getContext(), std::move(grPaint), format, glyphCount, 185 info.needsTransform()); 186 } 187 GrAtlasTextOp::Geometry& geometry = op->geometry(); 188 geometry.fViewMatrix = viewMatrix; 189 geometry.fClipRect = clipRect; 190 geometry.fBlob = SkRef(this); 191 geometry.fRun = run; 192 geometry.fSubRun = subRun; 193 geometry.fColor = info.maskFormat() == kARGB_GrMaskFormat ? SK_PMColor4fWHITE : filteredColor; 194 geometry.fX = x; 195 geometry.fY = y; 196 op->init(); 197 return op; 198 } 199 200 static void calculate_translation(bool applyVM, 201 const SkMatrix& newViewMatrix, SkScalar newX, SkScalar newY, 202 const SkMatrix& currentViewMatrix, SkScalar currentX, 203 SkScalar currentY, SkScalar* transX, SkScalar* transY) { 204 if (applyVM) { 205 *transX = newViewMatrix.getTranslateX() + 206 newViewMatrix.getScaleX() * (newX - currentX) + 207 newViewMatrix.getSkewX() * (newY - currentY) - 208 currentViewMatrix.getTranslateX(); 209 210 *transY = newViewMatrix.getTranslateY() + 211 newViewMatrix.getSkewY() * (newX - currentX) + 212 newViewMatrix.getScaleY() * (newY - currentY) - 213 currentViewMatrix.getTranslateY(); 214 } else { 215 *transX = newX - currentX; 216 *transY = newY - currentY; 217 } 218 } 219 220 void GrTextBlob::flush(GrTextTarget* target, const SkSurfaceProps& props, 221 const GrDistanceFieldAdjustTable* distanceAdjustTable, 222 const SkPaint& paint, const SkPMColor4f& filteredColor, const GrClip& clip, 223 const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { 224 225 // GrTextBlob::makeOp only takes uint16_t values for run and subRun indices. 226 // Encountering something larger than this is highly unlikely, so we'll just not draw it. 227 int lastRun = SkTMin(fRunCountLimit, (1 << 16)) - 1; 228 // For each run in the GrTextBlob we're going to churn through all the glyphs. 229 // Each run is broken into a path part and a Mask / DFT / ARGB part. 230 for (int runIndex = 0; runIndex <= lastRun; runIndex++) { 231 232 Run& run = fRuns[runIndex]; 233 234 // first flush any path glyphs 235 if (run.fPathGlyphs.count()) { 236 SkPaint runPaint{paint}; 237 runPaint.setAntiAlias(run.fAntiAlias); 238 239 for (int i = 0; i < run.fPathGlyphs.count(); i++) { 240 GrTextBlob::Run::PathGlyph& pathGlyph = run.fPathGlyphs[i]; 241 242 SkMatrix ctm; 243 const SkPath* path = &pathGlyph.fPath; 244 245 // TmpPath must be in the same scope as GrShape shape below. 246 SkTLazy<SkPath> tmpPath; 247 248 // The glyph positions and glyph outlines are either in device space or in source 249 // space based on fPreTransformed. 250 if (!pathGlyph.fPreTransformed) { 251 // Positions and outlines are in source space. 252 253 ctm = viewMatrix; 254 255 SkMatrix pathMatrix = SkMatrix::MakeScale(pathGlyph.fScale, pathGlyph.fScale); 256 257 // The origin for the blob may have changed, so figure out the delta. 258 SkVector originShift = SkPoint{x, y} - SkPoint{fInitialX, fInitialY}; 259 260 // Shift the original glyph location in source space to the position of the new 261 // blob. 262 pathMatrix.postTranslate(originShift.x() + pathGlyph.fX, 263 originShift.y() + pathGlyph.fY); 264 265 // If there are shaders, blurs or styles, the path must be scaled into source 266 // space independently of the CTM. This allows the CTM to be correct for the 267 // different effects. 268 GrStyle style(runPaint); 269 bool scalePath = runPaint.getShader() 270 || style.applies() 271 || runPaint.getMaskFilter(); 272 if (!scalePath) { 273 // Scale can be applied to CTM -- no effects. 274 275 ctm.preConcat(pathMatrix); 276 } else { 277 // Scale the outline into source space. 278 279 // Transform the path form the normalized outline to source space. This 280 // way the CTM will remain the same so it can be used by the effects. 281 SkPath* sourceOutline = tmpPath.init(); 282 path->transform(pathMatrix, sourceOutline); 283 sourceOutline->setIsVolatile(true); 284 path = sourceOutline; 285 } 286 287 288 } else { 289 // Positions and outlines are in device space. 290 291 SkPoint originalOrigin = {fInitialX, fInitialY}; 292 fInitialViewMatrix.mapPoints(&originalOrigin, 1); 293 294 SkPoint newOrigin = {x, y}; 295 viewMatrix.mapPoints(&newOrigin, 1); 296 297 // The origin shift in device space. 298 SkPoint originShift = newOrigin - originalOrigin; 299 300 // Shift the original glyph location in device space to the position of the 301 // new blob. 302 ctm = SkMatrix::MakeTrans(originShift.x() + pathGlyph.fX, 303 originShift.y() + pathGlyph.fY); 304 } 305 306 // TODO: we are losing the mutability of the path here 307 GrShape shape(*path, paint); 308 309 target->drawShape(clip, runPaint, ctm, shape); 310 } 311 } 312 313 // then flush each subrun, if any 314 if (!run.fInitialized) { 315 continue; 316 } 317 318 int lastSubRun = SkTMin(run.fSubRunInfo.count(), 1 << 16) - 1; 319 for (int subRun = 0; subRun <= lastSubRun; subRun++) { 320 const SubRun& info = run.fSubRunInfo[subRun]; 321 int glyphCount = info.glyphCount(); 322 if (0 == glyphCount) { 323 continue; 324 } 325 326 bool skipClip = false; 327 bool submitOp = true; 328 SkIRect clipRect = SkIRect::MakeEmpty(); 329 SkRect rtBounds = SkRect::MakeWH(target->width(), target->height()); 330 SkRRect clipRRect; 331 GrAA aa; 332 // We can clip geometrically if we're not using SDFs or transformed glyphs, 333 // and we have an axis-aligned rectangular non-AA clip 334 if (!info.drawAsDistanceFields() && !info.needsTransform() && 335 clip.isRRect(rtBounds, &clipRRect, &aa) && 336 clipRRect.isRect() && GrAA::kNo == aa) { 337 skipClip = true; 338 // We only need to do clipping work if the subrun isn't contained by the clip 339 SkRect subRunBounds; 340 this->computeSubRunBounds(&subRunBounds, runIndex, subRun, viewMatrix, x, y, 341 false); 342 if (!clipRRect.getBounds().contains(subRunBounds)) { 343 // If the subrun is completely outside, don't add an op for it 344 if (!clipRRect.getBounds().intersects(subRunBounds)) { 345 submitOp = false; 346 } 347 else { 348 clipRRect.getBounds().round(&clipRect); 349 } 350 } 351 } 352 353 if (submitOp) { 354 auto op = this->makeOp(info, glyphCount, runIndex, subRun, viewMatrix, x, y, 355 clipRect, paint, filteredColor, props, distanceAdjustTable, 356 target); 357 if (op) { 358 if (skipClip) { 359 target->addDrawOp(GrNoClip(), std::move(op)); 360 } 361 else { 362 target->addDrawOp(clip, std::move(op)); 363 } 364 } 365 } 366 } 367 368 } 369 } 370 371 std::unique_ptr<GrDrawOp> GrTextBlob::test_makeOp( 372 int glyphCount, uint16_t run, uint16_t subRun, const SkMatrix& viewMatrix, 373 SkScalar x, SkScalar y, const SkPaint& paint, const SkPMColor4f& filteredColor, 374 const SkSurfaceProps& props, const GrDistanceFieldAdjustTable* distanceAdjustTable, 375 GrTextTarget* target) { 376 const GrTextBlob::SubRun& info = fRuns[run].fSubRunInfo[subRun]; 377 SkIRect emptyRect = SkIRect::MakeEmpty(); 378 return this->makeOp(info, glyphCount, run, subRun, viewMatrix, x, y, emptyRect, 379 paint, filteredColor, props, distanceAdjustTable, target); 380 } 381 382 void GrTextBlob::AssertEqual(const GrTextBlob& l, const GrTextBlob& r) { 383 SkASSERT_RELEASE(l.fSize == r.fSize); 384 385 SkASSERT_RELEASE(l.fBlurRec.fSigma == r.fBlurRec.fSigma); 386 SkASSERT_RELEASE(l.fBlurRec.fStyle == r.fBlurRec.fStyle); 387 388 SkASSERT_RELEASE(l.fStrokeInfo.fFrameWidth == r.fStrokeInfo.fFrameWidth); 389 SkASSERT_RELEASE(l.fStrokeInfo.fMiterLimit == r.fStrokeInfo.fMiterLimit); 390 SkASSERT_RELEASE(l.fStrokeInfo.fJoin == r.fStrokeInfo.fJoin); 391 392 SkASSERT_RELEASE(l.fKey == r.fKey); 393 //SkASSERT_RELEASE(l.fPaintColor == r.fPaintColor); // Colors might not actually be identical 394 SkASSERT_RELEASE(l.fMaxMinScale == r.fMaxMinScale); 395 SkASSERT_RELEASE(l.fMinMaxScale == r.fMinMaxScale); 396 SkASSERT_RELEASE(l.fTextType == r.fTextType); 397 398 SkASSERT_RELEASE(l.fRunCountLimit == r.fRunCountLimit); 399 for (int i = 0; i < l.fRunCountLimit; i++) { 400 const Run& lRun = l.fRuns[i]; 401 const Run& rRun = r.fRuns[i]; 402 403 if (lRun.fTypeface.get()) { 404 SkASSERT_RELEASE(rRun.fTypeface.get()); 405 SkASSERT_RELEASE(SkTypeface::Equal(lRun.fTypeface.get(), rRun.fTypeface.get())); 406 } else { 407 SkASSERT_RELEASE(!rRun.fTypeface.get()); 408 } 409 410 411 SkASSERT_RELEASE(lRun.fDescriptor.getDesc()); 412 SkASSERT_RELEASE(rRun.fDescriptor.getDesc()); 413 SkASSERT_RELEASE(*lRun.fDescriptor.getDesc() == *rRun.fDescriptor.getDesc()); 414 415 if (lRun.fARGBFallbackDescriptor.get()) { 416 SkASSERT_RELEASE(lRun.fARGBFallbackDescriptor->getDesc()); 417 SkASSERT_RELEASE(rRun.fARGBFallbackDescriptor.get() && rRun.fARGBFallbackDescriptor->getDesc()); 418 SkASSERT_RELEASE(*lRun.fARGBFallbackDescriptor->getDesc() == 419 *rRun.fARGBFallbackDescriptor->getDesc()); 420 } else { 421 SkASSERT_RELEASE(!rRun.fARGBFallbackDescriptor.get()); 422 } 423 424 // color can be changed 425 //SkASSERT(lRun.fColor == rRun.fColor); 426 SkASSERT_RELEASE(lRun.fInitialized == rRun.fInitialized); 427 428 SkASSERT_RELEASE(lRun.fSubRunInfo.count() == rRun.fSubRunInfo.count()); 429 for(int j = 0; j < lRun.fSubRunInfo.count(); j++) { 430 const SubRun& lSubRun = lRun.fSubRunInfo[j]; 431 const SubRun& rSubRun = rRun.fSubRunInfo[j]; 432 433 // TODO we can do this check, but we have to apply the VM to the old vertex bounds 434 //SkASSERT_RELEASE(lSubRun.vertexBounds() == rSubRun.vertexBounds()); 435 436 if (lSubRun.strike()) { 437 SkASSERT_RELEASE(rSubRun.strike()); 438 SkASSERT_RELEASE(GrTextStrike::GetKey(*lSubRun.strike()) == 439 GrTextStrike::GetKey(*rSubRun.strike())); 440 441 } else { 442 SkASSERT_RELEASE(!rSubRun.strike()); 443 } 444 445 SkASSERT_RELEASE(lSubRun.vertexStartIndex() == rSubRun.vertexStartIndex()); 446 SkASSERT_RELEASE(lSubRun.vertexEndIndex() == rSubRun.vertexEndIndex()); 447 SkASSERT_RELEASE(lSubRun.glyphStartIndex() == rSubRun.glyphStartIndex()); 448 SkASSERT_RELEASE(lSubRun.glyphEndIndex() == rSubRun.glyphEndIndex()); 449 SkASSERT_RELEASE(lSubRun.maskFormat() == rSubRun.maskFormat()); 450 SkASSERT_RELEASE(lSubRun.drawAsDistanceFields() == rSubRun.drawAsDistanceFields()); 451 SkASSERT_RELEASE(lSubRun.hasUseLCDText() == rSubRun.hasUseLCDText()); 452 } 453 454 SkASSERT_RELEASE(lRun.fPathGlyphs.count() == rRun.fPathGlyphs.count()); 455 for (int i = 0; i < lRun.fPathGlyphs.count(); i++) { 456 const Run::PathGlyph& lPathGlyph = lRun.fPathGlyphs[i]; 457 const Run::PathGlyph& rPathGlyph = rRun.fPathGlyphs[i]; 458 459 SkASSERT_RELEASE(lPathGlyph.fPath == rPathGlyph.fPath); 460 // We can't assert that these have the same translations 461 } 462 } 463 } 464 465 void GrTextBlob::SubRun::computeTranslation(const SkMatrix& viewMatrix, 466 SkScalar x, SkScalar y, SkScalar* transX, 467 SkScalar* transY) { 468 // Don't use the matrix to translate on distance field for fallback subruns. 469 calculate_translation(!this->drawAsDistanceFields() && !this->isFallback(), viewMatrix, 470 x, y, fCurrentViewMatrix, fX, fY, transX, transY); 471 fCurrentViewMatrix = viewMatrix; 472 fX = x; 473 fY = y; 474 } 475