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 "include/core/SkCanvas.h" 9 #include "include/core/SkColorFilter.h" 10 #include "include/core/SkFont.h" 11 #include "include/core/SkImage.h" 12 #include "include/core/SkPath.h" 13 #include "include/core/SkSurface.h" 14 #include "tools/viewer/Slide.h" 15 16 namespace skiagm { 17 18 class ShapeRenderer : public SkRefCntBase { 19 public: 20 inline static constexpr SkScalar kTileWidth = 20.f; 21 inline static constexpr SkScalar kTileHeight = 20.f; 22 23 // Draw the shape, limited to kTileWidth x kTileHeight. It must apply the local subpixel (tx, 24 // ty) translation and rotation by angle. Prior to these transform adjustments, the SkCanvas 25 // will only have pixel aligned translations (these are separated to make super-sampling 26 // renderers easier). 27 virtual void draw(SkCanvas* canvas, SkPaint* paint, 28 SkScalar tx, SkScalar ty, SkScalar angle) = 0; 29 30 virtual SkString name() = 0; 31 32 virtual sk_sp<ShapeRenderer> toHairline() = 0; 33 applyLocalTransform(SkCanvas * canvas,SkScalar tx,SkScalar ty,SkScalar angle)34 void applyLocalTransform(SkCanvas* canvas, SkScalar tx, SkScalar ty, SkScalar angle) { 35 canvas->translate(tx, ty); 36 canvas->rotate(angle, kTileWidth / 2.f, kTileHeight / 2.f); 37 } 38 }; 39 40 class RectRenderer : public ShapeRenderer { 41 public: Make()42 static sk_sp<ShapeRenderer> Make() { 43 return sk_sp<ShapeRenderer>(new RectRenderer()); 44 } 45 name()46 SkString name() override { return SkString("rect"); } 47 toHairline()48 sk_sp<ShapeRenderer> toHairline() override { 49 // Not really available but can't return nullptr 50 return Make(); 51 } 52 draw(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)53 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override { 54 SkScalar width = paint->getStrokeWidth(); 55 paint->setStyle(SkPaint::kFill_Style); 56 57 this->applyLocalTransform(canvas, tx, ty, angle); 58 canvas->drawRect(SkRect::MakeLTRB(kTileWidth / 2.f - width / 2.f, 2.f, 59 kTileWidth / 2.f + width / 2.f, kTileHeight - 2.f), 60 *paint); 61 } 62 63 private: RectRenderer()64 RectRenderer() {} 65 66 using INHERITED = ShapeRenderer; 67 }; 68 69 class PathRenderer : public ShapeRenderer { 70 public: MakeLine(bool hairline=false)71 static sk_sp<ShapeRenderer> MakeLine(bool hairline = false) { 72 return MakeCurve(0.f, hairline); 73 } 74 MakeLines(SkScalar depth,bool hairline=false)75 static sk_sp<ShapeRenderer> MakeLines(SkScalar depth, bool hairline = false) { 76 return MakeCurve(-depth, hairline); 77 } 78 MakeCurve(SkScalar depth,bool hairline=false)79 static sk_sp<ShapeRenderer> MakeCurve(SkScalar depth, bool hairline = false) { 80 return sk_sp<ShapeRenderer>(new PathRenderer(depth, hairline)); 81 } 82 name()83 SkString name() override { 84 SkString name; 85 if (fHairline) { 86 name.append("hairline"); 87 if (fDepth > 0.f) { 88 name.appendf("-curve-%.2f", fDepth); 89 } 90 } else if (fDepth > 0.f) { 91 name.appendf("curve-%.2f", fDepth); 92 } else if (fDepth < 0.f) { 93 name.appendf("line-%.2f", -fDepth); 94 } else { 95 name.append("line"); 96 } 97 98 return name; 99 } 100 toHairline()101 sk_sp<ShapeRenderer> toHairline() override { 102 return sk_sp<ShapeRenderer>(new PathRenderer(fDepth, true)); 103 } 104 draw(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)105 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override { 106 SkPath path; 107 path.moveTo(kTileWidth / 2.f, 2.f); 108 109 if (fDepth > 0.f) { 110 path.quadTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f, 111 kTileWidth / 2.f, kTileHeight - 2.f); 112 } else { 113 if (fDepth < 0.f) { 114 path.lineTo(kTileWidth / 2.f + fDepth, kTileHeight / 2.f); 115 } 116 path.lineTo(kTileWidth / 2.f, kTileHeight - 2.f); 117 } 118 119 if (fHairline) { 120 // Fake thinner hairlines by making it transparent, conflating coverage and alpha 121 SkColor4f color = paint->getColor4f(); 122 SkScalar width = paint->getStrokeWidth(); 123 if (width > 1.f) { 124 // Can't emulate width larger than a pixel 125 return; 126 } 127 paint->setColor4f({color.fR, color.fG, color.fB, width}, nullptr); 128 paint->setStrokeWidth(0.f); 129 } 130 131 // Adding round caps forces Ganesh to use the path renderer for lines instead of converting 132 // them to rectangles (which are already explicitly tested). However, when not curved, the 133 // GrStyledShape will still find a way to turn it into a rrect draw so it doesn't hit the 134 // path renderer in that condition. 135 paint->setStrokeCap(SkPaint::kRound_Cap); 136 paint->setStrokeJoin(SkPaint::kMiter_Join); 137 paint->setStyle(SkPaint::kStroke_Style); 138 139 this->applyLocalTransform(canvas, tx, ty, angle); 140 canvas->drawPath(path, *paint); 141 } 142 143 private: 144 SkScalar fDepth; // 0.f to make a line, otherwise outset of curve from end points 145 bool fHairline; 146 PathRenderer(SkScalar depth,bool hairline)147 PathRenderer(SkScalar depth, bool hairline) 148 : fDepth(depth) 149 , fHairline(hairline) {} 150 151 using INHERITED = ShapeRenderer; 152 }; 153 154 class OffscreenShapeRenderer : public ShapeRenderer { 155 public: 156 ~OffscreenShapeRenderer() override = default; 157 Make(sk_sp<ShapeRenderer> renderer,int supersample,bool forceRaster=false)158 static sk_sp<OffscreenShapeRenderer> Make(sk_sp<ShapeRenderer> renderer, int supersample, 159 bool forceRaster = false) { 160 SkASSERT(supersample > 0); 161 return sk_sp<OffscreenShapeRenderer>(new OffscreenShapeRenderer(std::move(renderer), 162 supersample, forceRaster)); 163 } 164 name()165 SkString name() override { 166 SkString name = fRenderer->name(); 167 if (fSupersampleFactor != 1) { 168 name.prependf("%dx-", fSupersampleFactor * fSupersampleFactor); 169 } 170 return name; 171 } 172 toHairline()173 sk_sp<ShapeRenderer> toHairline() override { 174 return Make(fRenderer->toHairline(), fSupersampleFactor, fForceRasterBackend); 175 } 176 draw(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)177 void draw(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) override { 178 // Subpixel translation+angle are applied in the offscreen buffer 179 this->prepareBuffer(canvas, paint, tx, ty, angle); 180 this->redraw(canvas); 181 } 182 183 // Exposed so that it's easy to fill the offscreen buffer, then draw zooms/filters of it before 184 // drawing the original scale back into the canvas. prepareBuffer(SkCanvas * canvas,SkPaint * paint,SkScalar tx,SkScalar ty,SkScalar angle)185 void prepareBuffer(SkCanvas* canvas, SkPaint* paint, SkScalar tx, SkScalar ty, SkScalar angle) { 186 auto info = SkImageInfo::Make(fSupersampleFactor * kTileWidth, 187 fSupersampleFactor * kTileHeight, 188 kRGBA_8888_SkColorType, kPremul_SkAlphaType); 189 auto surface = fForceRasterBackend ? SkSurface::MakeRaster(info) 190 : canvas->makeSurface(info); 191 192 surface->getCanvas()->save(); 193 // Make fully transparent so it is easy to determine pixels that are touched by partial cov. 194 surface->getCanvas()->clear(SK_ColorTRANSPARENT); 195 // Set up scaling to fit supersampling amount 196 surface->getCanvas()->scale(fSupersampleFactor, fSupersampleFactor); 197 fRenderer->draw(surface->getCanvas(), paint, tx, ty, angle); 198 surface->getCanvas()->restore(); 199 200 // Save image so it can be drawn zoomed in or to visualize touched pixels; only valid until 201 // the next call to draw() 202 fLastRendered = surface->makeImageSnapshot(); 203 } 204 redraw(SkCanvas * canvas,SkScalar scale=1.f,bool debugMode=false)205 void redraw(SkCanvas* canvas, SkScalar scale = 1.f, bool debugMode = false) { 206 SkASSERT(fLastRendered); 207 // Use medium quality filter to get mipmaps when drawing smaller, or use nearest filtering 208 // when upscaling 209 SkPaint blit; 210 if (debugMode) { 211 // Makes anything that's > 1/255 alpha fully opaque and sets color to medium green. 212 static constexpr float kFilter[] = { 213 0.f, 0.f, 0.f, 0.f, 16.f/255, 214 0.f, 0.f, 0.f, 0.f, 200.f/255, 215 0.f, 0.f, 0.f, 0.f, 16.f/255, 216 0.f, 0.f, 0.f, 255.f, 0.f 217 }; 218 219 blit.setColorFilter(SkColorFilters::Matrix(kFilter)); 220 } 221 222 auto sampling = scale > 1 ? SkSamplingOptions(SkFilterMode::kNearest) 223 : SkSamplingOptions(SkFilterMode::kLinear, 224 SkMipmapMode::kLinear); 225 226 canvas->scale(scale, scale); 227 canvas->drawImageRect(fLastRendered.get(), 228 SkRect::MakeWH(kTileWidth, kTileHeight), 229 SkRect::MakeWH(kTileWidth, kTileHeight), 230 sampling, &blit, SkCanvas::kFast_SrcRectConstraint); 231 } 232 233 private: 234 bool fForceRasterBackend; 235 sk_sp<SkImage> fLastRendered; 236 sk_sp<ShapeRenderer> fRenderer; 237 int fSupersampleFactor; 238 OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer,int supersample,bool forceRaster)239 OffscreenShapeRenderer(sk_sp<ShapeRenderer> renderer, int supersample, bool forceRaster) 240 : fForceRasterBackend(forceRaster) 241 , fLastRendered(nullptr) 242 , fRenderer(std::move(renderer)) 243 , fSupersampleFactor(supersample) { } 244 245 using INHERITED = ShapeRenderer; 246 }; 247 248 class ThinAASlide : public Slide { 249 public: ThinAASlide()250 ThinAASlide() { fName = "Thin-AA"; } 251 load(SkScalar w,SkScalar h)252 void load(SkScalar w, SkScalar h) 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.size(); ++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 draw(SkCanvas * canvas)284 void draw(SkCanvas* canvas) override { 285 canvas->clear(0xFFFFFFFF); 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 animate(double nanos)319 bool animate(double nanos) override { 320 SkScalar t = 1e-9 * nanos; 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 onChar(SkUnichar key)376 bool onChar(SkUnichar key) override { 377 switch(key) { 378 case 't': 379 // Toggle translation animation. 380 fAnimTranslate = !fAnimTranslate; 381 if (!fAnimTranslate && fAnimRotate && fCurrentStage != AnimStage::kRotate) { 382 // Turned off an active translation so go to rotating 383 fCurrentStage = AnimStage::kRotate; 384 } else if (fAnimTranslate && !fAnimRotate && 385 fCurrentStage == AnimStage::kRotate) { 386 // Turned on translation, rotation had been paused too, so reset the stage 387 fCurrentStage = this->getTranslationStage(); 388 } 389 return true; 390 case 'r': 391 // Toggle rotation animation. 392 fAnimRotate = !fAnimRotate; 393 if (!fAnimRotate && fAnimTranslate && fCurrentStage == AnimStage::kRotate) { 394 // Turned off an active rotation so go back to translation 395 fCurrentStage = this->getTranslationStage(); 396 } else if (fAnimRotate && !fAnimTranslate && 397 fCurrentStage != AnimStage::kRotate) { 398 // Turned on rotation, translation had been paused too, so reset to rotate 399 fCurrentStage = AnimStage::kRotate; 400 } 401 return true; 402 case 'u': fAngle = 0.f; return true; 403 case 'y': fAngle = 90.f; return true; 404 case ' ': fAngle = SkScalarMod(fAngle + 15.f, 360.f); return true; 405 case '-': fStrokeWidth = std::max(0.1f, fStrokeWidth - 0.05f); return true; 406 case '=': fStrokeWidth = std::min(1.f, fStrokeWidth + 0.05f); return true; 407 } 408 return false; 409 } 410 411 private: 412 // Base renderers that get wrapped on the offscreen renderers so that they can be transformed 413 // for visualization, or supersampled. 414 SkTArray<sk_sp<ShapeRenderer>> fShapes; 415 416 SkTArray<sk_sp<OffscreenShapeRenderer>> fNative; 417 SkTArray<sk_sp<OffscreenShapeRenderer>> fRaster; 418 SkTArray<sk_sp<OffscreenShapeRenderer>> fHairline; 419 SkTArray<sk_sp<OffscreenShapeRenderer>> fSS4; 420 SkTArray<sk_sp<OffscreenShapeRenderer>> fSS16; 421 422 SkScalar fStrokeWidth; 423 424 // Animated properties to stress the AA algorithms 425 enum class AnimStage { 426 kMoveRight, kMoveDown, kMoveLeft, kMoveUp, kRotate 427 } fCurrentStage; 428 SkScalar fLastFrameTime; 429 bool fAnimRotate; 430 bool fAnimTranslate; 431 432 // Current frame's animation state 433 SkScalar fSubpixelX; 434 SkScalar fSubpixelY; 435 SkScalar fAngle; 436 getTranslationStage()437 AnimStage getTranslationStage() { 438 // For paused translations (i.e. fAnimTranslate toggled while translating), the current 439 // stage moves to kRotate, but when restarting the translation animation, we want to 440 // go back to where we were without losing any progress. 441 if (fSubpixelX > -1.f) { 442 if (fSubpixelX >= 1.f) { 443 // Can only be moving down on right edge, given our transition states 444 return AnimStage::kMoveDown; 445 } else if (fSubpixelY > 0.f) { 446 // Can only be moving right along top edge 447 return AnimStage::kMoveRight; 448 } else { 449 // Must be moving left along bottom edge 450 return AnimStage::kMoveLeft; 451 } 452 } else { 453 // Moving up along the left edge, or is at the very top so start moving left 454 return fSubpixelY > -1.f ? AnimStage::kMoveUp : AnimStage::kMoveLeft; 455 } 456 } 457 drawShapes(SkCanvas * canvas,const char * name,int gridX,SkTArray<sk_sp<OffscreenShapeRenderer>> shapes)458 void drawShapes(SkCanvas* canvas, const char* name, int gridX, 459 SkTArray<sk_sp<OffscreenShapeRenderer>> shapes) { 460 SkAutoCanvasRestore autoRestore(canvas, /* save */ true); 461 462 for (int i = 0; i < shapes.size(); ++i) { 463 this->drawShape(canvas, name, gridX, shapes[i].get(), i == 0); 464 // drawShape positions the canvas properly for the next iteration 465 } 466 } 467 drawShape(SkCanvas * canvas,const char * name,int gridX,OffscreenShapeRenderer * shape,bool drawNameLabels)468 void drawShape(SkCanvas* canvas, const char* name, int gridX, 469 OffscreenShapeRenderer* shape, bool drawNameLabels) { 470 static constexpr SkScalar kZoomGridWidth = 8 * ShapeRenderer::kTileWidth + 8.f; 471 static constexpr SkRect kTile = SkRect::MakeWH(ShapeRenderer::kTileWidth, 472 ShapeRenderer::kTileHeight); 473 static constexpr SkRect kZoomTile = SkRect::MakeWH(8 * ShapeRenderer::kTileWidth, 474 8 * ShapeRenderer::kTileHeight); 475 476 // Labeling per shape and detailed labeling that isn't per-stroke 477 canvas->save(); 478 SkPaint text; 479 SkFont font(nullptr, 12); 480 481 if (gridX == 0) { 482 SkScalar centering = shape->name().size() * 4.f; // ad-hoc 483 484 canvas->save(); 485 canvas->translate(-10.f, 4 * ShapeRenderer::kTileHeight + centering); 486 canvas->rotate(-90.f); 487 canvas->drawString(shape->name(), 0.f, 0.f, font, text); 488 canvas->restore(); 489 } 490 if (drawNameLabels) { 491 canvas->drawString(name, gridX * kZoomGridWidth, -10.f, font, text); 492 } 493 canvas->restore(); 494 495 // Paints for outlines and actual shapes 496 SkPaint outline; 497 outline.setStyle(SkPaint::kStroke_Style); 498 SkPaint clear; 499 clear.setColor(SK_ColorWHITE); 500 501 SkPaint paint; 502 paint.setAntiAlias(true); 503 paint.setStrokeWidth(fStrokeWidth); 504 505 // Generate a saved image of the correct stroke width, but don't put it into the canvas 506 // yet since we want to draw the "original" size on top of the zoomed in version 507 shape->prepareBuffer(canvas, &paint, fSubpixelX, fSubpixelY, fAngle); 508 509 // Draw it at 8X zoom 510 SkScalar x = gridX * kZoomGridWidth; 511 512 canvas->save(); 513 canvas->translate(x, 0.f); 514 canvas->drawRect(kZoomTile, outline); 515 shape->redraw(canvas, 8.0f); 516 canvas->restore(); 517 518 // Draw the original 519 canvas->save(); 520 canvas->translate(x + 4.f, 4.f); 521 canvas->drawRect(kTile, clear); 522 canvas->drawRect(kTile, outline); 523 shape->redraw(canvas, 1.f); 524 canvas->restore(); 525 526 // Now redraw it into the coverage location (just to the right of the original scale) 527 canvas->save(); 528 canvas->translate(x + ShapeRenderer::kTileWidth + 8.f, 4.f); 529 canvas->drawRect(kTile, clear); 530 canvas->drawRect(kTile, outline); 531 shape->redraw(canvas, 1.f, /* debug */ true); 532 canvas->restore(); 533 534 // Lastly, shift the canvas translation down by 8 * kTH + padding for the next set of shapes 535 canvas->translate(0.f, 8.f * ShapeRenderer::kTileHeight + 20.f); 536 } 537 }; 538 539 ////////////////////////////////////////////////////////////////////////////// 540 541 DEF_SLIDE( return new ThinAASlide; ) 542 543 } // namespace skiagm 544