1 /* 2 * Copyright 2019 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 "Sample.h" 9 10 #include "SkAnimTimer.h" 11 #include "SkCanvas.h" 12 #include "SkColorFilter.h" 13 #include "SkFont.h" 14 #include "SkImage.h" 15 #include "SkPath.h" 16 #include "SkSurface.h" 17 18 namespace skiagm { 19 20 class ShapeRenderer : public SkRefCntBase { 21 public: 22 static constexpr SkScalar kTileWidth = 20.f; 23 static constexpr SkScalar kTileHeight = 20.f; 24 ~ShapeRenderer()25 virtual ~ShapeRenderer() {} 26 27 // Draw the shape, limited to kTileWidth x kTileHeight. It must apply the local subpixel (tx, 28 // ty) translation and rotation by angle. Prior to these transform adjustments, the SkCanvas 29 // will only have pixel aligned translations (these are separated to make super-sampling 30 // renderers easier). 31 virtual void draw(SkCanvas* canvas, SkPaint* paint, 32 SkScalar tx, SkScalar ty, SkScalar angle) = 0; 33 34 virtual SkString name() = 0; 35 36 virtual sk_sp<ShapeRenderer> toHairline() = 0; 37 applyLocalTransform(SkCanvas * canvas,SkScalar tx,SkScalar ty,SkScalar angle)38 void applyLocalTransform(SkCanvas* canvas, SkScalar tx, SkScalar ty, SkScalar angle) { 39 canvas->translate(tx, ty); 40 canvas->rotate(angle, kTileWidth / 2.f, kTileHeight / 2.f); 41 } 42 }; 43 44 class RectRenderer : public ShapeRenderer { 45 public: Make()46 static sk_sp<ShapeRenderer> Make() { 47 return sk_sp<ShapeRenderer>(new RectRenderer()); 48 } 49 name()50 SkString name() override { return SkString("rect"); } 51 toHairline()52 sk_sp<ShapeRenderer> toHairline() override { 53 // Not really available but can't return nullptr 54 return Make(); 55 } 56 draw(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)57 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override { 58 SkScalar width = paint->getStrokeWidth(); 59 paint->setStyle(SkPaint::kFill_Style); 60 61 this->applyLocalTransform(canvas, tx, ty, angle); 62 canvas->drawRect(SkRect::MakeLTRB(kTileWidth / 2.f - width / 2.f, 2.f, 63 kTileWidth / 2.f + width / 2.f, kTileHeight - 2.f), 64 *paint); 65 } 66 67 private: RectRenderer()68 RectRenderer() {} 69 70 typedef ShapeRenderer INHERITED; 71 }; 72 73 class PathRenderer : public ShapeRenderer { 74 public: MakeLine(bool hairline=false)75 static sk_sp<ShapeRenderer> MakeLine(bool hairline = false) { 76 return MakeCurve(0.f, hairline); 77 } 78 MakeLines(SkScalar depth,bool hairline=false)79 static sk_sp<ShapeRenderer> MakeLines(SkScalar depth, bool hairline = false) { 80 return MakeCurve(-depth, hairline); 81 } 82 MakeCurve(SkScalar depth,bool hairline=false)83 static sk_sp<ShapeRenderer> MakeCurve(SkScalar depth, bool hairline = false) { 84 return sk_sp<ShapeRenderer>(new PathRenderer(depth, hairline)); 85 } 86 name()87 SkString name() override { 88 SkString name; 89 if (fHairline) { 90 name.append("hairline"); 91 if (fDepth > 0.f) { 92 name.appendf("-curve-%.2f", fDepth); 93 } 94 } else if (fDepth > 0.f) { 95 name.appendf("curve-%.2f", fDepth); 96 } else if (fDepth < 0.f) { 97 name.appendf("line-%.2f", -fDepth); 98 } else { 99 name.append("line"); 100 } 101 102 return name; 103 } 104 toHairline()105 sk_sp<ShapeRenderer> toHairline() override { 106 return sk_sp<ShapeRenderer>(new PathRenderer(fDepth, true)); 107 } 108 draw(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)109 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override { 110 SkPath path; 111 path.moveTo(kTileWidth / 2.f, 2.f); 112 113 if (fDepth > 0.f) { 114 path.quadTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f, 115 kTileWidth / 2.f, kTileHeight - 2.f); 116 } else { 117 if (fDepth < 0.f) { 118 path.lineTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f); 119 } 120 path.lineTo(kTileWidth / 2.f, kTileHeight - 2.f); 121 } 122 123 if (fHairline) { 124 // Fake thinner hairlines by making it transparent, conflating coverage and alpha 125 SkColor4f color = paint->getColor4f(); 126 SkScalar width = paint->getStrokeWidth(); 127 if (width > 1.f) { 128 // Can't emulate width larger than a pixel 129 return; 130 } 131 paint->setColor4f({color.fR, color.fG, color.fB, width}, nullptr); 132 paint->setStrokeWidth(0.f); 133 } 134 135 // Adding round caps forces Ganesh to use the path renderer for lines instead of converting 136 // them to rectangles (which are already explicitly tested). However, when not curved, the 137 // GrShape will still find a way to turn it into a rrect draw so it doesn't hit the 138 // path renderer in that condition. 139 paint->setStrokeCap(SkPaint::kRound_Cap); 140 paint->setStrokeJoin(SkPaint::kMiter_Join); 141 paint->setStyle(SkPaint::kStroke_Style); 142 143 this->applyLocalTransform(canvas, tx, ty, angle); 144 canvas->drawPath(path, *paint); 145 } 146 147 private: 148 SkScalar fDepth; // 0.f to make a line, otherwise outset of curve from end points 149 bool fHairline; 150 PathRenderer(SkScalar depth,bool hairline)151 PathRenderer(SkScalar depth, bool hairline) 152 : fDepth(depth) 153 , fHairline(hairline) {} 154 155 typedef ShapeRenderer INHERITED; 156 }; 157 158 class OffscreenShapeRenderer : public ShapeRenderer { 159 public: 160 ~OffscreenShapeRenderer() override = default; 161 Make(sk_sp<ShapeRenderer> renderer,int supersample,bool forceRaster=false)162 static sk_sp<OffscreenShapeRenderer> Make(sk_sp<ShapeRenderer> renderer, int supersample, 163 bool forceRaster = false) { 164 SkASSERT(supersample > 0); 165 return sk_sp<OffscreenShapeRenderer>(new OffscreenShapeRenderer(std::move(renderer), 166 supersample, forceRaster)); 167 } 168 name()169 SkString name() override { 170 SkString name = fRenderer->name(); 171 if (fSupersampleFactor != 1) { 172 name.prependf("%dx-", fSupersampleFactor * fSupersampleFactor); 173 } 174 return name; 175 } 176 toHairline()177 sk_sp<ShapeRenderer> toHairline() override { 178 return Make(fRenderer->toHairline(), fSupersampleFactor, fForceRasterBackend); 179 } 180 draw(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)181 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override { 182 // Subpixel translation+angle are applied in the offscreen buffer 183 this->prepareBuffer(canvas, paint, tx, ty, angle); 184 this->redraw(canvas); 185 } 186 187 // Exposed so that it's easy to fill the offscreen buffer, then draw zooms/filters of it before 188 // drawing the original scale back into the canvas. prepareBuffer(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)189 void prepareBuffer(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) { 190 auto info = SkImageInfo::Make(fSupersampleFactor * kTileWidth, 191 fSupersampleFactor * kTileHeight, 192 kRGBA_8888_SkColorType, kPremul_SkAlphaType); 193 auto surface = fForceRasterBackend ? SkSurface::MakeRaster(info) 194 : canvas->makeSurface(info); 195 196 surface->getCanvas()->save(); 197 // Make fully transparent so it is easy to determine pixels that are touched by partial cov. 198 surface->getCanvas()->clear(SK_ColorTRANSPARENT); 199 // Set up scaling to fit supersampling amount 200 surface->getCanvas()->scale(fSupersampleFactor, fSupersampleFactor); 201 fRenderer->draw(surface->getCanvas(), paint, tx, ty, angle); 202 surface->getCanvas()->restore(); 203 204 // Save image so it can be drawn zoomed in or to visualize touched pixels; only valid until 205 // the next call to draw() 206 fLastRendered = surface->makeImageSnapshot(); 207 } 208 redraw(SkCanvas * canvas,SkScalar scale=1.f,bool debugMode=false)209 void redraw(SkCanvas* canvas, SkScalar scale = 1.f, bool debugMode = false) { 210 SkASSERT(fLastRendered); 211 // Use medium quality filter to get mipmaps when drawing smaller, or use nearest filtering 212 // when upscaling 213 SkPaint blit; 214 blit.setFilterQuality(scale > 1.f ? kNone_SkFilterQuality : kMedium_SkFilterQuality); 215 if (debugMode) { 216 // Makes anything that's > 1/255 alpha fully opaque and sets color to medium green. 217 static constexpr SkScalar kFilter[] = { 218 0.f, 0.f, 0.f, 0.f, 16.f, 219 0.f, 0.f, 0.f, 0.f, 200.f, 220 0.f, 0.f, 0.f, 0.f, 16.f, 221 0.f, 0.f, 0.f, 255.f, 0.f 222 }; 223 224 blit.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(kFilter)); 225 } 226 227 canvas->scale(scale, scale); 228 canvas->drawImageRect(fLastRendered, SkRect::MakeWH(kTileWidth, kTileHeight), &blit); 229 } 230 231 private: 232 bool fForceRasterBackend; 233 sk_sp<SkImage> fLastRendered; 234 sk_sp<ShapeRenderer> fRenderer; 235 int fSupersampleFactor; 236 OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer,int supersample,bool forceRaster)237 OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer, int supersample, bool forceRaster) 238 : fForceRasterBackend(forceRaster) 239 , fLastRendered(nullptr) 240 , fRenderer(std::move(renderer)) 241 , fSupersampleFactor(supersample) { } 242 243 typedef ShapeRenderer INHERITED; 244 }; 245 246 class ThinAASample : public Sample { 247 public: ThinAASample()248 ThinAASample() { 249 this->setBGColor(0xFFFFFFFF); 250 } 251 252 protected: onOnceBeforeDraw()253 void onOnceBeforeDraw() override { 254 // Setup all base renderers 255 fShapes.push_back(RectRenderer::Make()); 256 fShapes.push_back(PathRenderer::MakeLine()); 257 fShapes.push_back(PathRenderer::MakeLines(4.f)); // 2 segments 258 fShapes.push_back(PathRenderer::MakeCurve(2.f)); // Shallow curve 259 fShapes.push_back(PathRenderer::MakeCurve(8.f)); // Deep curve 260 261 for (int i = 0; i < fShapes.count(); ++i) { 262 fNative.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1)); 263 fRaster.push_back(OffscreenShapeRenderer::Make(fShapes[i], 1, /* raster */ true)); 264 fSS4.push_back(OffscreenShapeRenderer::Make(fShapes[i], 4)); // 4x4 -> 16 samples 265 fSS16.push_back(OffscreenShapeRenderer::Make(fShapes[i], 8)); // 8x8 -> 64 samples 266 267 fHairline.push_back(OffscreenShapeRenderer::Make(fRaster[i]->toHairline(), 1)); 268 } 269 270 // Start it at something subpixel 271 fStrokeWidth = 0.5f; 272 273 fSubpixelX = 0.f; 274 fSubpixelY = 0.f; 275 fAngle = 0.f; 276 277 fCurrentStage = AnimStage::kMoveLeft; 278 fLastFrameTime = -1.f; 279 280 // Don't animate in the beginning 281 fAnimTranslate = false; 282 fAnimRotate = false; 283 } 284 onDrawContent(SkCanvas * canvas)285 void onDrawContent(SkCanvas* canvas) override { 286 // Move away from screen edge and add instructions 287 SkPaint text; 288 SkFont font(nullptr, 12); 289 canvas->translate(60.f, 20.f); 290 canvas->drawString("Each row features a rendering command under different AA strategies. " 291 "Native refers to the current backend of the viewer, e.g. OpenGL.", 292 0, 0, font, text); 293 294 canvas->drawString(SkStringPrintf("Stroke width: %.2f ('-' to decrease, '=' to increase)", 295 fStrokeWidth), 0, 24, font, text); 296 canvas->drawString(SkStringPrintf("Rotation: %.3f ('r' to animate, 'y' sets to 90, 'u' sets" 297 " to 0, 'space' adds 15)", fAngle), 0, 36, font, text); 298 canvas->drawString(SkStringPrintf("Translation: %.3f, %.3f ('t' to animate)", 299 fSubpixelX, fSubpixelY), 0, 48, font, text); 300 301 canvas->translate(0.f, 100.f); 302 303 // Draw with surface matching current viewer surface type 304 this->drawShapes(canvas, "Native", 0, fNative); 305 306 // Draw with forced raster backend so it's easy to compare side-by-side 307 this->drawShapes(canvas, "Raster", 1, fRaster); 308 309 // Draw paths as hairlines + alpha hack 310 this->drawShapes(canvas, "Hairline", 2, fHairline); 311 312 // Draw at 4x supersampling in bottom left 313 this->drawShapes(canvas, "SSx16", 3, fSS4); 314 315 // And lastly 16x supersampling in bottom right 316 this->drawShapes(canvas, "SSx64", 4, fSS16); 317 } 318 onAnimate(const SkAnimTimer & timer)319 bool onAnimate(const SkAnimTimer& timer) override { 320 SkScalar t = timer.secs(); 321 SkScalar dt = fLastFrameTime < 0.f ? 0.f : t - fLastFrameTime; 322 fLastFrameTime = t; 323 324 if (!fAnimRotate && !fAnimTranslate) { 325 // Keep returning true so that the last frame time is tracked 326 fLastFrameTime = -1.f; 327 return false; 328 } 329 330 switch(fCurrentStage) { 331 case AnimStage::kMoveLeft: 332 fSubpixelX += 2.f * dt; 333 if (fSubpixelX >= 1.f) { 334 fSubpixelX = 1.f; 335 fCurrentStage = AnimStage::kMoveDown; 336 } 337 break; 338 case AnimStage::kMoveDown: 339 fSubpixelY += 2.f * dt; 340 if (fSubpixelY >= 1.f) { 341 fSubpixelY = 1.f; 342 fCurrentStage = AnimStage::kMoveRight; 343 } 344 break; 345 case AnimStage::kMoveRight: 346 fSubpixelX -= 2.f * dt; 347 if (fSubpixelX <= -1.f) { 348 fSubpixelX = -1.f; 349 fCurrentStage = AnimStage::kMoveUp; 350 } 351 break; 352 case AnimStage::kMoveUp: 353 fSubpixelY -= 2.f * dt; 354 if (fSubpixelY <= -1.f) { 355 fSubpixelY = -1.f; 356 fCurrentStage = fAnimRotate ? AnimStage::kRotate : AnimStage::kMoveLeft; 357 } 358 break; 359 case AnimStage::kRotate: { 360 SkScalar newAngle = fAngle + dt * 15.f; 361 bool completed = SkScalarMod(newAngle, 15.f) < SkScalarMod(fAngle, 15.f); 362 fAngle = SkScalarMod(newAngle, 360.f); 363 if (completed) { 364 // Make sure we're on a 15 degree boundary 365 fAngle = 15.f * SkScalarRoundToScalar(fAngle / 15.f); 366 if (fAnimTranslate) { 367 fCurrentStage = this->getTranslationStage(); 368 } 369 } 370 } break; 371 } 372 373 return true; 374 } 375 onQuery(Sample::Event * evt)376 bool onQuery(Sample::Event* evt) override { 377 if (Sample::TitleQ(*evt)) { 378 Sample::TitleR(evt, "Thin-AA"); 379 return true; 380 } 381 382 SkUnichar key; 383 if (Sample::CharQ(*evt, &key)) { 384 switch(key) { 385 case 't': 386 // Toggle translation animation. 387 fAnimTranslate = !fAnimTranslate; 388 if (!fAnimTranslate && fAnimRotate && fCurrentStage != AnimStage::kRotate) { 389 // Turned off an active translation so go to rotating 390 fCurrentStage = AnimStage::kRotate; 391 } else if (fAnimTranslate && !fAnimRotate && 392 fCurrentStage == AnimStage::kRotate) { 393 // Turned on translation, rotation had been paused too, so reset the stage 394 fCurrentStage = this->getTranslationStage(); 395 } 396 return true; 397 case 'r': 398 // Toggle rotation animation. 399 fAnimRotate = !fAnimRotate; 400 if (!fAnimRotate && fAnimTranslate && fCurrentStage == AnimStage::kRotate) { 401 // Turned off an active rotation so go back to translation 402 fCurrentStage = this->getTranslationStage(); 403 } else if (fAnimRotate && !fAnimTranslate && 404 fCurrentStage != AnimStage::kRotate) { 405 // Turned on rotation, translation had been paused too, so reset to rotate 406 fCurrentStage = AnimStage::kRotate; 407 } 408 return true; 409 case 'u': fAngle = 0.f; return true; 410 case 'y': fAngle = 90.f; return true; 411 case ' ': fAngle = SkScalarMod(fAngle + 15.f, 360.f); return true; 412 case '-': fStrokeWidth = SkMaxScalar(0.1f, fStrokeWidth - 0.05f); return true; 413 case '=': fStrokeWidth = SkMinScalar(1.f, fStrokeWidth + 0.05f); return true; 414 } 415 } 416 return this->INHERITED::onQuery(evt); 417 } 418 419 private: 420 // Base renderers that get wrapped on the offscreen renderers so that they can be transformed 421 // for visualization, or supersampled. 422 SkTArray<sk_sp<ShapeRenderer>> fShapes; 423 424 SkTArray<sk_sp<OffscreenShapeRenderer>> fNative; 425 SkTArray<sk_sp<OffscreenShapeRenderer>> fRaster; 426 SkTArray<sk_sp<OffscreenShapeRenderer>> fHairline; 427 SkTArray<sk_sp<OffscreenShapeRenderer>> fSS4; 428 SkTArray<sk_sp<OffscreenShapeRenderer>> fSS16; 429 430 SkScalar fStrokeWidth; 431 432 // Animated properties to stress the AA algorithms 433 enum class AnimStage { 434 kMoveRight, kMoveDown, kMoveLeft, kMoveUp, kRotate 435 } fCurrentStage; 436 SkScalar fLastFrameTime; 437 bool fAnimRotate; 438 bool fAnimTranslate; 439 440 // Current frame's animation state 441 SkScalar fSubpixelX; 442 SkScalar fSubpixelY; 443 SkScalar fAngle; 444 getTranslationStage()445 AnimStage getTranslationStage() { 446 // For paused translations (i.e. fAnimTranslate toggled while translating), the current 447 // stage moves to kRotate, but when restarting the translation animation, we want to 448 // go back to where we were without losing any progress. 449 if (fSubpixelX > -1.f) { 450 if (fSubpixelX >= 1.f) { 451 // Can only be moving down on right edge, given our transition states 452 return AnimStage::kMoveDown; 453 } else if (fSubpixelY > 0.f) { 454 // Can only be moving right along top edge 455 return AnimStage::kMoveRight; 456 } else { 457 // Must be moving left along bottom edge 458 return AnimStage::kMoveLeft; 459 } 460 } else { 461 // Moving up along the left edge, or is at the very top so start moving left 462 return fSubpixelY > -1.f ? AnimStage::kMoveUp : AnimStage::kMoveLeft; 463 } 464 } 465 drawShapes(SkCanvas * canvas,const char * name,int gridX,SkTArray<sk_sp<OffscreenShapeRenderer>> shapes)466 void drawShapes(SkCanvas* canvas, const char* name, int gridX, 467 SkTArray<sk_sp<OffscreenShapeRenderer>> shapes) { 468 SkAutoCanvasRestore autoRestore(canvas, /* save */ true); 469 470 for (int i = 0; i < shapes.count(); ++i) { 471 this->drawShape(canvas, name, gridX, shapes[i].get(), i == 0); 472 // drawShape positions the canvas properly for the next iteration 473 } 474 } 475 drawShape(SkCanvas * canvas,const char * name,int gridX,OffscreenShapeRenderer * shape,bool drawNameLabels)476 void drawShape(SkCanvas* canvas, const char* name, int gridX, 477 OffscreenShapeRenderer* shape, bool drawNameLabels) { 478 static constexpr SkScalar kZoomGridWidth = 8 * ShapeRenderer::kTileWidth + 8.f; 479 static constexpr SkRect kTile = SkRect::MakeWH(ShapeRenderer::kTileWidth, 480 ShapeRenderer::kTileHeight); 481 static constexpr SkRect kZoomTile = SkRect::MakeWH(8 * ShapeRenderer::kTileWidth, 482 8 * ShapeRenderer::kTileHeight); 483 484 // Labeling per shape and detailed labeling that isn't per-stroke 485 canvas->save(); 486 SkPaint text; 487 SkFont font(nullptr, 12); 488 489 if (gridX == 0) { 490 SkString name = shape->name(); 491 SkScalar centering = name.size() * 4.f; // ad-hoc 492 493 canvas->save(); 494 canvas->translate(-10.f, 4 * ShapeRenderer::kTileHeight + centering); 495 canvas->rotate(-90.f); 496 canvas->drawString(shape->name(), 0.f, 0.f, font, text); 497 canvas->restore(); 498 } 499 if (drawNameLabels) { 500 canvas->drawString(name, gridX * kZoomGridWidth, -10.f, font, text); 501 } 502 canvas->restore(); 503 504 // Paints for outlines and actual shapes 505 SkPaint outline; 506 outline.setStyle(SkPaint::kStroke_Style); 507 SkPaint clear; 508 clear.setColor(SK_ColorWHITE); 509 510 SkPaint paint; 511 paint.setAntiAlias(true); 512 paint.setStrokeWidth(fStrokeWidth); 513 514 // Generate a saved image of the correct stroke width, but don't put it into the canvas 515 // yet since we want to draw the "original" size on top of the zoomed in version 516 shape->prepareBuffer(canvas, &paint, fSubpixelX, fSubpixelY, fAngle); 517 518 // Draw it at 8X zoom 519 SkScalar x = gridX * kZoomGridWidth; 520 521 canvas->save(); 522 canvas->translate(x, 0.f); 523 canvas->drawRect(kZoomTile, outline); 524 shape->redraw(canvas, 8.0f); 525 canvas->restore(); 526 527 // Draw the original 528 canvas->save(); 529 canvas->translate(x + 4.f, 4.f); 530 canvas->drawRect(kTile, clear); 531 canvas->drawRect(kTile, outline); 532 shape->redraw(canvas, 1.f); 533 canvas->restore(); 534 535 // Now redraw it into the coverage location (just to the right of the original scale) 536 canvas->save(); 537 canvas->translate(x + ShapeRenderer::kTileWidth + 8.f, 4.f); 538 canvas->drawRect(kTile, clear); 539 canvas->drawRect(kTile, outline); 540 shape->redraw(canvas, 1.f, /* debug */ true); 541 canvas->restore(); 542 543 // Lastly, shift the canvas translation down by 8 * kTH + padding for the next set of shapes 544 canvas->translate(0.f, 8.f * ShapeRenderer::kTileHeight + 20.f); 545 } 546 547 typedef Sample INHERITED; 548 }; 549 550 ////////////////////////////////////////////////////////////////////////////// 551 552 DEF_SAMPLE( return new ThinAASample; ) 553 554 } 555