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