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