xref: /aosp_15_r20/external/skia/tools/viewer/MotionMarkSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2013 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 #include <vector>
8 
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkPath.h"
11 #include "include/effects/SkGradientShader.h"
12 #include "src/base/SkRandom.h"
13 #include "tools/Resources.h"
14 #include "tools/gpu/YUVUtils.h"
15 #include "tools/viewer/Slide.h"
16 
17 // Implementation in C++ of some WebKit MotionMark tests
18 // Tests implemented so far:
19 // * Canvas Lines
20 // * Canvas Arcs
21 // * Paths
22 // Based on https://github.com/WebKit/MotionMark/blob/main/MotionMark/
23 
24 class MMObject {
25 public:
26     virtual ~MMObject() = default;
27 
28     virtual void draw(SkCanvas* canvas) = 0;
29 
30     virtual void animate(double /*nanos*/) = 0;
31 };
32 
33 class Stage {
34 public:
Stage(SkSize size,int startingObjectCount,int objectIncrement)35     Stage(SkSize size, int startingObjectCount, int objectIncrement)
36             : fSize(size)
37             , fStartingObjectCount(startingObjectCount)
38             , fObjectIncrement(objectIncrement) {}
39     virtual ~Stage() = default;
40 
41     // The default impls of draw() and animate() simply iterate over fObjects and call the
42     // MMObject function.
draw(SkCanvas * canvas)43     virtual void draw(SkCanvas* canvas) {
44         for (size_t i = 0; i < fObjects.size(); ++i) {
45             fObjects[i]->draw(canvas);
46         }
47     }
48 
animate(double nanos)49     virtual bool animate(double nanos) {
50         for (size_t i = 0; i < fObjects.size(); ++i) {
51             fObjects[i]->animate(nanos);
52         }
53         return true;
54     }
55 
56     // The default impl handles +/- to add or remove N objects from the scene
onChar(SkUnichar uni)57     virtual bool onChar(SkUnichar uni) {
58         bool handled = false;
59         switch (uni) {
60             case '+':
61             case '=':
62                 for (int i = 0; i < fObjectIncrement; ++i) {
63                     fObjects.push_back(this->createObject());
64                 }
65                 handled = true;
66                 break;
67             case '-':
68             case '_':
69                 if (fObjects.size() > (size_t) fObjectIncrement) {
70                     fObjects.resize(fObjects.size() - (size_t) fObjectIncrement);
71                 }
72                 handled = true;
73                 break;
74             default:
75                 break;
76         }
77 
78         return handled;
79     }
80 
81 protected:
82     virtual std::unique_ptr<MMObject> createObject() = 0;
83 
initializeObjects()84     void initializeObjects() {
85         for (int i = 0; i < fStartingObjectCount; ++i) {
86             fObjects.push_back(this->createObject());
87         }
88     }
89 
90     [[maybe_unused]] SkSize fSize;
91 
92     int fStartingObjectCount;
93     int fObjectIncrement;
94 
95     std::vector<std::unique_ptr<MMObject>> fObjects;
96     SkRandom fRandom;
97 };
98 
99 class MotionMarkSlide : public Slide {
100 public:
101     MotionMarkSlide() = default;
102 
onChar(SkUnichar uni)103     bool onChar(SkUnichar uni) override {
104         return fStage->onChar(uni);
105     }
106 
draw(SkCanvas * canvas)107     void draw(SkCanvas* canvas) override {
108         fStage->draw(canvas);
109     }
110 
animate(double nanos)111     bool animate(double nanos) override {
112         return fStage->animate(nanos);
113     }
114 
115 protected:
116     std::unique_ptr<Stage> fStage;
117 };
118 
119 
120 namespace {
121 
time_counter_value(double nanos,float factor)122 float time_counter_value(double nanos, float factor) {
123     constexpr double kMillisPerNano = 0.0000001;
124     return static_cast<float>(nanos*kMillisPerNano)/factor;
125 }
126 
time_fractional_value(double nanos,float cycleLengthMs)127 float time_fractional_value(double nanos, float cycleLengthMs) {
128     return SkScalarFraction(time_counter_value(nanos, cycleLengthMs));
129 }
130 
131 // The following functions match the input processing that Chrome's canvas2d layer performs before
132 // calling into Skia.
133 
134 // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc;drc=572074cb06425797e7e110511db405134cf67e2f;l=299
canonicalize_angle(float * startAngle,float * endAngle)135 void canonicalize_angle(float* startAngle, float* endAngle) {
136     float newStartAngle = SkScalarMod(*startAngle, 360.f);
137     float delta = newStartAngle - *startAngle;
138     *startAngle = newStartAngle;
139     *endAngle = *endAngle + delta;
140 }
141 
142 // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc;drc=572074cb06425797e7e110511db405134cf67e2f;l=245
adjust_end_angle(float startAngle,float endAngle,bool ccw)143 float adjust_end_angle(float startAngle, float endAngle, bool ccw) {
144     float newEndAngle = endAngle;
145     if (!ccw && endAngle - startAngle >= 360.f) {
146         newEndAngle = startAngle + 360.f;
147     } else if (ccw && startAngle - endAngle >= 360.f) {
148         newEndAngle = startAngle - 360.f;
149     } else if (!ccw && startAngle > endAngle) {
150         newEndAngle = startAngle + (360.f - SkScalarMod(startAngle - endAngle, 360.f));
151     } else if (ccw && startAngle < endAngle) {
152         newEndAngle = startAngle - (360.f - SkScalarMod(endAngle - startAngle, 360.f));
153     }
154 
155     return newEndAngle;
156 }
157 
158 }  // namespace
159 
160 ///////////////////////////////////////////////////////////////////////////////////////////////////
161 // Canvas Lines
162 ///////////////////////////////////////////////////////////////////////////////////////////////////
163 struct LineSegmentParams {
164     float fCircleRadius;
165     SkPoint fCircleCenters[4];
166     float fLineLengthMaximum;
167     float fLineMinimum;
168 };
169 
170 class CanvasLineSegment : public MMObject {
171 public:
CanvasLineSegment(SkRandom * random,const LineSegmentParams & params)172     CanvasLineSegment(SkRandom* random, const LineSegmentParams& params) {
173         int circle = random->nextRangeU(0, 3);
174 
175         static constexpr SkColor kColors[] = {
176             0xffe01040, 0xff10c030, 0xff744cba, 0xffe05010
177         };
178         fColor = kColors[circle];
179         fLineWidth = std::pow(random->nextF(), 12) * 20 + 3;
180         fOmega = random->nextF() * 3 + 0.2f;
181         float theta = random->nextRangeF(0, 2*SK_ScalarPI);
182         fCosTheta = std::cos(theta);
183         fSinTheta = std::sin(theta);
184         fStart = params.fCircleCenters[circle] + SkPoint::Make(params.fCircleRadius * fCosTheta,
185                                                                params.fCircleRadius * fSinTheta);
186         fLength = params.fLineMinimum;
187         fLength += std::pow(random->nextF(), 8) * params.fLineLengthMaximum;
188         fSegmentDirection = random->nextF() > 0.5 ? -1 : 1;
189     }
190 
191     ~CanvasLineSegment() override = default;
192 
draw(SkCanvas * canvas)193     void draw(SkCanvas* canvas) override {
194         SkPaint paint;
195         paint.setAntiAlias(true);
196         paint.setColor(fColor);
197         paint.setStrokeWidth(fLineWidth);
198         paint.setStyle(SkPaint::kStroke_Style);
199 
200         SkPoint end = {
201             fStart.fX + fSegmentDirection * fLength * fCosTheta,
202             fStart.fY + fSegmentDirection * fLength * fSinTheta
203         };
204         canvas->drawLine(fStart, end, paint);
205     }
206 
animate(double nanos)207     void animate(double nanos) override {
208         fLength += std::sin(time_counter_value(nanos, 100) * fOmega);
209     }
210 
211 private:
212     SkColor fColor;
213     float fLineWidth;
214     float fOmega;
215     float fCosTheta;
216     float fSinTheta;
217     SkPoint fStart;
218     float fLength;
219     float fSegmentDirection;
220 };
221 
222 class CanvasLineSegmentStage : public Stage {
223 public:
CanvasLineSegmentStage(SkSize size)224     CanvasLineSegmentStage(SkSize size)
225             : Stage(size, /*startingObjectCount=*/5000, /*objectIncrement*/1000) {
226         fParams.fLineMinimum = 20;
227         fParams.fLineLengthMaximum = 40;
228         fParams.fCircleRadius = fSize.fWidth/8 - .4 * (fParams.fLineMinimum +
229                                                        fParams.fLineLengthMaximum);
230         fParams.fCircleCenters[0] = SkPoint::Make(5.5 / 32 * fSize.fWidth, 2.1 / 3*fSize.fHeight);
231         fParams.fCircleCenters[1] = SkPoint::Make(12.5 / 32 * fSize.fWidth, .9 / 3*fSize.fHeight);
232         fParams.fCircleCenters[2] = SkPoint::Make(19.5 / 32 * fSize.fWidth, 2.1 / 3*fSize.fHeight);
233         fParams.fCircleCenters[3] = SkPoint::Make(26.5 / 32 * fSize.fWidth, .9 / 3*fSize.fHeight);
234         fHalfSize = SkSize::Make(fSize.fWidth * 0.5f, fSize.fHeight * 0.5f);
235         fTwoFifthsSizeX = fSize.fWidth * .4;
236 
237         this->initializeObjects();
238     }
239 
240     ~CanvasLineSegmentStage() override = default;
241 
draw(SkCanvas * canvas)242     void draw(SkCanvas* canvas) override {
243         canvas->clear(SK_ColorWHITE);
244 
245         float dx = fTwoFifthsSizeX * std::cos(fCurrentAngle);
246         float dy = fTwoFifthsSizeX * std::sin(fCurrentAngle);
247 
248         float colorStopStep = SkScalarInterp(-.1f, .1f, fCurrentGradientStep);
249         int brightnessStep = SkScalarRoundToInt(SkScalarInterp(32, 64, fCurrentGradientStep));
250 
251         SkColor color1Step = SkColorSetARGB(brightnessStep,
252                                             brightnessStep,
253                                             (brightnessStep << 1),
254                                             102);
255         SkColor color2Step = SkColorSetARGB((brightnessStep << 1),
256                                             (brightnessStep << 1),
257                                             brightnessStep,
258                                             102);
259         SkPoint pts[2] = {
260             {fHalfSize.fWidth + dx, fHalfSize.fHeight + dy},
261             {fHalfSize.fWidth - dx, fHalfSize.fHeight - dy}
262         };
263         SkColor colors[] = {
264             color1Step,
265             color1Step,
266             color2Step,
267             color2Step
268         };
269         float pos[] = {
270             0,
271             0.2f + colorStopStep,
272             0.8f - colorStopStep,
273             1
274         };
275         sk_sp<SkShader> gradientShader = SkGradientShader::MakeLinear(pts, colors, pos, 4,
276                                                                       SkTileMode::kClamp, 0);
277 
278         SkPaint paint;
279         paint.setAntiAlias(true);
280         paint.setStrokeWidth(15);
281         for (int i = 0; i < 4; i++) {
282             const SkColor strokeColors[] = {
283                 0xffe01040, 0xff10c030, 0xff744cba, 0xffe05010
284             };
285             const SkColor fillColors[] = {
286                 0xff70051d, 0xff016112, 0xff2F0C6E, 0xff702701
287             };
288             paint.setColor(strokeColors[i]);
289             paint.setStyle(SkPaint::kStroke_Style);
290             SkRect arcRect = SkRect::MakeXYWH(fParams.fCircleCenters[i].fX - fParams.fCircleRadius,
291                                               fParams.fCircleCenters[i].fY- fParams.fCircleRadius,
292                                               2*fParams.fCircleRadius,
293                                               2*fParams.fCircleRadius);
294             canvas->drawArc(arcRect, 0, 360, false, paint);
295             paint.setColor(fillColors[i]);
296             paint.setStyle(SkPaint::kFill_Style);
297             canvas->drawArc(arcRect, 0, 360, false, paint);
298             paint.setShader(gradientShader);
299             canvas->drawArc(arcRect, 0, 360, false, paint);
300             paint.setShader(nullptr);
301         }
302 
303         this->Stage::draw(canvas);
304     }
305 
animate(double nanos)306     bool animate(double nanos) override {
307         fCurrentAngle = time_fractional_value(nanos, 3000) * SK_ScalarPI * 2;
308         fCurrentGradientStep = 0.5f + 0.5f * std::sin(
309                                        time_fractional_value(nanos, 5000) * SK_ScalarPI * 2);
310 
311         this->Stage::animate(nanos);
312         return true;
313     }
314 
createObject()315     std::unique_ptr<MMObject> createObject() override {
316         return std::make_unique<CanvasLineSegment>(&fRandom,fParams);
317     }
318 private:
319     LineSegmentParams fParams;
320     SkSize fHalfSize;
321     float fTwoFifthsSizeX;
322     float fCurrentAngle = 0;
323     float fCurrentGradientStep = 0.5f;
324 };
325 
326 ///////////////////////////////////////////////////////////////////////////////////////////////////
327 // Canvas Arcs
328 ///////////////////////////////////////////////////////////////////////////////////////////////////
329 
330 class CanvasArc : public MMObject {
331 public:
CanvasArc(SkRandom * random,SkSize size)332     CanvasArc(SkRandom* random, SkSize size) {
333         constexpr float kMaxX = 6;
334         constexpr float kMaxY = 3;
335 
336         const SkColor baseColors[3] = {
337             0xff101010, 0xff808080, 0xffc0c0c0
338         };
339         const SkColor bonusColors[3] = {
340             0xffe01040, 0xff10c030, 0xffe05010
341         };
342         float distanceX = size.fWidth / kMaxX;
343         float distanceY = size.fHeight / (kMaxY + 1);
344         int randY = random->nextRangeU(0, kMaxY);
345         int randX = random->nextRangeU(0, kMaxX - 1 * (randY % 2));
346 
347         fPoint = SkPoint::Make(distanceX * (randX + (randY % 2) / 2), distanceY * (randY + 0.5f));
348 
349         fRadius = 20 + std::pow(random->nextF(), 5) * (std::min(distanceX, distanceY) / 1.8f);
350         fStartAngle = random->nextRangeF(0, 2*SK_ScalarPI);
351         fEndAngle = random->nextRangeF(0, 2*SK_ScalarPI);
352         fOmega = (random->nextF() - 0.5f) * 0.3f;
353         fCounterclockwise = random->nextBool();
354         // The MotionMark code appends a random element from an array and appends it to the color
355         // array, then randomly picks from that. We'll just pick that random element and use it
356         // if the index is out of bounds for the base color array.
357         SkColor bonusColor = bonusColors[(randX + sk_float_ceil2int(randY * 0.5f)) % 3];
358         int colorIndex = random->nextRangeU(0, 3);
359         fColor = colorIndex == 3 ? bonusColor : baseColors[colorIndex];
360         fLineWidth = 1 + std::pow(random->nextF(), 5) * 30;
361         fDoStroke = random->nextRangeU(0, 3) != 0;
362     }
363 
364     ~CanvasArc() override = default;
365 
draw(SkCanvas * canvas)366     void draw(SkCanvas* canvas) override {
367         SkPaint paint;
368         paint.setAntiAlias(true);
369         paint.setColor(fColor);
370         SkRect arcRect = SkRect::MakeXYWH(fPoint.fX - fRadius, fPoint.fY - fRadius,
371                                           2*fRadius, 2*fRadius);
372 
373         float startAngleDeg = fStartAngle * 180.f / SK_ScalarPI;
374         float endAngleDeg = fEndAngle * 180.f / SK_ScalarPI;
375         canonicalize_angle(&startAngleDeg, &endAngleDeg);
376         endAngleDeg = adjust_end_angle(startAngleDeg, endAngleDeg, fCounterclockwise);
377 
378         float sweepAngle = startAngleDeg - endAngleDeg;
379 
380         if (fDoStroke) {
381             paint.setStrokeWidth(fLineWidth);
382             paint.setStyle(SkPaint::kStroke_Style);
383             canvas->drawArc(arcRect, startAngleDeg, sweepAngle, false, paint);
384         } else {
385             paint.setStyle(SkPaint::kFill_Style);
386             // The MotionMark code creates a path for fills via lineTo(point), arc(), lineTo(point).
387             // For now we'll just use drawArc for both but might need to revisit.
388             canvas->drawArc(arcRect, startAngleDeg, sweepAngle, true, paint);
389         }
390     }
391 
animate(double)392     void animate(double /*nanos*/) override {
393         fStartAngle += fOmega;
394         fEndAngle += fOmega / 2;
395     }
396 
397 private:
398     SkPoint fPoint;
399     float fRadius;
400     float fStartAngle; // in radians
401     float fEndAngle;   // in radians
402     SkColor fColor;
403     float fOmega;      // in radians
404     bool fDoStroke;
405     bool fCounterclockwise;
406     float fLineWidth;
407 };
408 
409 class CanvasArcStage : public Stage {
410 public:
CanvasArcStage(SkSize size)411     CanvasArcStage(SkSize size)
412             : Stage(size, /*startingObjectCount=*/1000, /*objectIncrement=*/200) {
413         this->initializeObjects();
414     }
415 
416     ~CanvasArcStage() override = default;
417 
draw(SkCanvas * canvas)418     void draw(SkCanvas* canvas) override {
419         canvas->clear(SK_ColorWHITE);
420         this->Stage::draw(canvas);
421     }
422 
createObject()423     std::unique_ptr<MMObject> createObject() override {
424         return std::make_unique<CanvasArc>(&fRandom, fSize);
425     }
426 };
427 
428 ///////////////////////////////////////////////////////////////////////////////////////////////////
429 // Paths
430 ///////////////////////////////////////////////////////////////////////////////////////////////////
431 
432 class CanvasLinePoint : public MMObject {
433 protected:
setEndPoint(SkRandom * random,SkSize size,SkPoint * prevCoord)434     void setEndPoint(SkRandom* random, SkSize size, SkPoint* prevCoord) {
435         const SkSize kGridSize = { 80, 40 };
436         const SkPoint kGridCenter = { 40, 20 };
437         const SkPoint kOffsets[4] = {
438             {-4, 0},
439             {2, 0},
440             {1, -2},
441             {1, 2}
442         };
443 
444         SkPoint coordinate = prevCoord ? *prevCoord : kGridCenter;
445         if (prevCoord) {
446             SkPoint offset = kOffsets[random->nextRangeU(0, 3)];
447             coordinate += offset;
448             if (coordinate.fX < 0 || coordinate.fX > kGridSize.width())
449                 coordinate.fX -= offset.fX * 2;
450             if (coordinate.fY < 0 || coordinate.fY > kGridSize.height())
451                 coordinate.fY -= offset.fY * 2;
452         }
453 
454         fPoint = SkPoint::Make((coordinate.fX + 0.5f) * size.width() / (kGridSize.width() + 1),
455                                (coordinate.fY + 0.5f) * size.height() / (kGridSize.height() + 1));
456         fCoordinate = coordinate;
457     }
458 
459 public:
CanvasLinePoint(SkRandom * random,SkSize size,SkPoint * prev)460     CanvasLinePoint(SkRandom* random, SkSize size, SkPoint* prev) {
461         const SkColor kColors[7] = {
462             0xff101010, 0xff808080, 0xffc0c0c0, 0xff101010, 0xff808080, 0xffc0c0c0, 0xffe01040
463         };
464         fColor = kColors[random->nextRangeU(0, 6)];
465 
466         fWidth = std::pow(random->nextF(), 5) * 20 + 1;
467         fIsSplit = random->nextBool();
468 
469         this->setEndPoint(random, size, prev);
470     }
471 
472     ~CanvasLinePoint() override = default;
473 
append(SkPath * path)474     virtual void append(SkPath* path) {
475         path->lineTo(fPoint);
476     }
477 
478     // unused, all the work is done by append
draw(SkCanvas *)479     void draw(SkCanvas*) override {}
animate(double)480     void animate(double) override {}
481 
getColor()482     SkColor getColor() { return fColor; }
getWidth()483     float getWidth() { return fWidth; }
getPoint()484     SkPoint getPoint() { return fPoint; }
getCoord()485     SkPoint getCoord() { return fCoordinate; }
isSplit()486     bool isSplit() { return fIsSplit; }
toggleIsSplit()487     void toggleIsSplit() { fIsSplit = !fIsSplit; }
488 
489 private:
490     SkPoint fPoint;
491     SkPoint fCoordinate;
492     SkColor fColor;
493     float fWidth;
494     bool fIsSplit;
495 };
496 
497 class CanvasQuadraticSegment : public CanvasLinePoint {
498 public:
CanvasQuadraticSegment(SkRandom * random,SkSize size,SkPoint * prev)499     CanvasQuadraticSegment(SkRandom* random, SkSize size, SkPoint* prev)
500             : CanvasLinePoint(random, size, prev) {
501         // Note: The construction of these points is odd but mirrors the Javascript code.
502 
503         // The chosen point from the base constructor is instead the control point.
504         fPoint2 = this->getPoint();
505 
506         // Get another random point for the actual end point of the segment.
507         this->setEndPoint(random, size, prev);
508     }
509 
append(SkPath * path)510     void append(SkPath* path) override {
511         path->quadTo(fPoint2, this->getPoint());
512     }
513 
514 private:
515     SkPoint fPoint2;
516 };
517 
518 class CanvasBezierSegment : public CanvasLinePoint {
519 public:
CanvasBezierSegment(SkRandom * random,SkSize size,SkPoint * prev)520     CanvasBezierSegment(SkRandom* random, SkSize size, SkPoint* prev)
521             : CanvasLinePoint(random, size, prev) {
522         // Note: The construction of these points is odd but mirrors the Javascript code.
523 
524         // The chosen point from the base constructor is instead the control point.
525         fPoint2 = this->getPoint();
526 
527         // Get the second control point.
528         this->setEndPoint(random, size, prev);
529         fPoint3 = this->getPoint();
530 
531         // Get third random point for the actual end point of the segment.
532         this->setEndPoint(random, size, prev);
533     }
534 
append(SkPath * path)535     void append(SkPath* path) override {
536         path->cubicTo(fPoint2, fPoint3, this->getPoint());
537     }
538 
539 private:
540     SkPoint fPoint2;
541     SkPoint fPoint3;
542 };
543 
544 
make_line_path(SkRandom * random,SkSize size,SkPoint * prev)545 std::unique_ptr<CanvasLinePoint> make_line_path(SkRandom* random, SkSize size, SkPoint* prev) {
546     int choice = random->nextRangeU(0, 3);
547     switch (choice) {
548         case 0:
549             return std::make_unique<CanvasQuadraticSegment>(random, size, prev);
550             break;
551         case 1:
552             return std::make_unique<CanvasBezierSegment>(random, size, prev);
553             break;
554         case 2:
555         case 3:
556         default:
557             return std::make_unique<CanvasLinePoint>(random, size, prev);
558             break;
559     }
560 }
561 
562 class CanvasLinePathStage : public Stage {
563 public:
CanvasLinePathStage(SkSize size)564     CanvasLinePathStage(SkSize size)
565             : Stage(size, /*startingObjectCount=*/5000, /*objectIncrement=*/1000) {
566         this->initializeObjects();
567     }
568 
569     ~CanvasLinePathStage() override = default;
570 
draw(SkCanvas * canvas)571     void draw(SkCanvas* canvas) override {
572         canvas->clear(SK_ColorWHITE);
573 
574         SkPath currentPath;
575         SkPaint paint;
576         paint.setAntiAlias(true);
577         paint.setStyle(SkPaint::kStroke_Style);
578         for (size_t i = 0; i < fObjects.size(); ++i) {
579             CanvasLinePoint* object = reinterpret_cast<CanvasLinePoint*>(fObjects[i].get());
580             if (i == 0) {
581                 paint.setStrokeWidth(object->getWidth());
582                 paint.setColor(object->getColor());
583                 currentPath.moveTo(object->getPoint());
584             } else {
585                 object->append(&currentPath);
586 
587                 if (object->isSplit()) {
588                     canvas->drawPath(currentPath, paint);
589 
590                     paint.setStrokeWidth(object->getWidth());
591                     paint.setColor(object->getColor());
592                     currentPath.reset();
593                     currentPath.moveTo(object->getPoint());
594                 }
595 
596                 if (fRandom.nextF() > 0.995) {
597                     object->toggleIsSplit();
598                 }
599             }
600         }
601         canvas->drawPath(currentPath, paint);
602     }
603 
animate(double)604     bool animate(double /*nanos*/) override {
605         // Nothing to do, but return true so we redraw.
606         return true;
607     }
608 
createObject()609     std::unique_ptr<MMObject> createObject() override {
610         if (fObjects.empty()) {
611             return make_line_path(&fRandom, fSize, nullptr);
612         } else {
613             CanvasLinePoint* prevObject = reinterpret_cast<CanvasLinePoint*>(fObjects.back().get());
614             SkPoint coord = prevObject->getCoord();
615             return make_line_path(&fRandom, fSize, &coord);
616         }
617     }
618 };
619 
620 ///////////////////////////////////////////////////////////////////////////////////////////////////
621 // Bouncing Particles
622 ///////////////////////////////////////////////////////////////////////////////////////////////////
623 
random_position(SkRandom * random,SkSize maxPosition)624 SkPoint random_position(SkRandom* random, SkSize maxPosition) {
625     return {(float)random->nextRangeU(0, maxPosition.width()),
626             (float)random->nextRangeU(0, maxPosition.height())};
627 }
628 
random_angle(SkRandom * random)629 float random_angle(SkRandom* random) {
630     return random->nextRangeF(0, 2*SK_FloatPI);
631 }
632 
random_velocity(SkRandom * random,float maxVelocity)633 float random_velocity(SkRandom* random, float maxVelocity) {
634     return random->nextRangeF(maxVelocity/8, maxVelocity);
635 }
636 
637 class Rotater {
638 public:
Rotater(float rotateInterval)639     Rotater(float rotateInterval)
640             : fTimeDelta(0)
641             , fRotateInterval(rotateInterval) {}
642 
next(float timeDelta)643     void next(float timeDelta) {
644         fTimeDelta = SkScalarMod(fTimeDelta + timeDelta, fRotateInterval);
645     }
646 
degrees()647     float degrees() {
648         return (360 * fTimeDelta) / fRotateInterval;
649     }
650 
651 private:
652     float fTimeDelta;
653     float fRotateInterval;
654 };
655 
random_rotater(SkRandom * random)656 Rotater random_rotater(SkRandom* random) {
657     return Rotater(random->nextRangeF(10, 100));
658 }
659 
point_on_circle(float angle,float radius)660 SkPoint point_on_circle(float angle, float radius) {
661     return {radius * SkScalarCos(angle), radius * SkScalarSin(angle)};
662 }
663 
664 class BouncingParticle : public MMObject {
665 public:
BouncingParticle(SkRandom * random,SkSize stageSize,SkSize particleSize,float maxVelocity)666     BouncingParticle(SkRandom* random, SkSize stageSize, SkSize particleSize, float maxVelocity)
667             : fStageSize(stageSize)
668             , fSize(particleSize)
669             , fPosition(random_position(random,
670                                         {stageSize.fWidth - particleSize.fWidth,
671                                          stageSize.fHeight - particleSize.fHeight}))
672             , fAngle(random_angle(random))
673             , fVelocity(random_velocity(random, maxVelocity))
674             , fRotater(random_rotater(random)) {
675     }
676 
animate(double deltaNanos)677     void animate(double deltaNanos) override {
678         // TODO: consolidate and pass in millis to the Stages
679         constexpr double kMillisPerNano = 0.0000001;
680         fPosition += point_on_circle(fAngle, fVelocity * (deltaNanos * kMillisPerNano));
681         fRotater.next(deltaNanos * kMillisPerNano);
682 
683         // If particle is going to move off right side
684         if (fPosition.fX + fSize.width() > fStageSize.width()) {
685             // If direction is East-South, go West-South.
686             if (fAngle >= 0 && fAngle < SK_FloatPI / 2)
687                 fAngle = SK_FloatPI - fAngle;
688             // If angle is East-North, go West-North.
689             else if (fAngle > SK_FloatPI / 2 * 3)
690                 fAngle = fAngle - (fAngle - SK_FloatPI / 2 * 3) * 2;
691             // Make sure the particle does not go outside the stage boundaries.
692             fPosition.fX = fStageSize.width() - fSize.width();
693         }
694 
695         // If particle is going to move off left side
696         if (fPosition.fX < 0) {
697             // If angle is West-South, go East-South.
698             if (fAngle > SK_FloatPI / 2 && fAngle < SK_FloatPI)
699                 fAngle = SK_FloatPI - fAngle;
700             // If angle is West-North, go East-North.
701             else if (fAngle > SK_FloatPI && fAngle < SK_FloatPI / 2 * 3)
702                 fAngle = fAngle + (SK_FloatPI / 2 * 3 - fAngle) * 2;
703             // Make sure the particle does not go outside the stage boundaries.
704             fPosition.fX = 0;
705         }
706 
707         // If particle is going to move off bottom side
708         if (fPosition.fY + fSize.height() > fStageSize.height()) {
709             // If direction is South, go North.
710             if (fAngle > 0 && fAngle < SK_FloatPI)
711                 fAngle = SK_FloatPI * 2 - fAngle;
712             // Make sure the particle does not go outside the stage boundaries.
713             fPosition.fY = fStageSize.height() - fSize.height();
714         }
715 
716         // If particle is going to move off top side
717         if (fPosition.fY < 0) {
718             // If direction is North, go South.
719             if (fAngle > SK_FloatPI && fAngle < SK_FloatPI * 2)
720                 fAngle = fAngle - (fAngle - SK_FloatPI) * 2;
721             // Make sure the particle does not go outside the stage boundaries.
722             fPosition.fY = 0;
723         }
724     }
725 
726 protected:
727     SkSize fStageSize;
728     SkSize fSize;
729     SkPoint fPosition;
730     float fAngle;
731     float fVelocity;
732     Rotater fRotater;
733 };
734 
735 class BouncingParticlesStage : public Stage {
736 public:
BouncingParticlesStage(SkSize size)737     BouncingParticlesStage(SkSize size)
738             : Stage(size, /*startingObjectCount=*/3000, /*objectIncrement=*/500)
739             , fParticleSize({150, 150})
740             , fMaxVelocity(10){
741     }
742 
animate(double nanos)743     bool animate(double nanos) override {
744         // The particles take delta time
745         if (fLastTime < 0) {
746             fLastTime = nanos;
747         }
748         for (size_t i = 0; i < fObjects.size(); ++i) {
749             fObjects[i]->animate(nanos - fLastTime);
750         }
751         fLastTime = nanos;
752         return true;
753     }
754 
755 protected:
756     SkSize fParticleSize;
757     float fMaxVelocity;
758     double fLastTime = -1;
759 };
760 
761 
762 class BouncingTaggedImage : public BouncingParticle {
763 public:
BouncingTaggedImage(SkRandom * random,SkSize stageSize,SkSize particleSize,float maxVelocity)764     BouncingTaggedImage(SkRandom* random, SkSize stageSize, SkSize particleSize, float maxVelocity)
765             : BouncingParticle(random, stageSize, particleSize, maxVelocity) {
766         this->move();
767         fRect = SkRect::MakeSize(fSize);
768         fRect.offset(-fSize.width()/2, -fSize.height()/2);
769     }
770 
move()771     void move() {
772         fTransform = SkMatrix::RotateDeg(std::floor(fRotater.degrees()));
773         fTransform.setTranslateX(fPosition.fX);
774         fTransform.setTranslateY(fPosition.fY);
775     }
776 
animate(double deltaNanos)777     void animate(double deltaNanos) override {
778         BouncingParticle::animate(deltaNanos);
779         this->move();
780     }
781 
draw(SkCanvas * canvas)782     void draw(SkCanvas* canvas) override {
783         // handled by the Stage
784     }
785 
transform()786     const SkMatrix& transform() { return fTransform; }
rect()787     SkRect rect() { return fRect; }
788 
789 private:
790     SkMatrix fTransform;
791     SkRect fRect;
792 };
793 
794 
795 class BouncingTaggedImagesStage : public BouncingParticlesStage {
796 public:
BouncingTaggedImagesStage(SkSize size)797     BouncingTaggedImagesStage(SkSize size) : BouncingParticlesStage(size) {
798 
799         this->initializeObjects();
800     }
801 
802     ~BouncingTaggedImagesStage() override = default;
803 
initImages(SkCanvas * canvas)804     void initImages(SkCanvas* canvas) {
805         const char* kImageSrcs[kImageCount] = {
806             "images/brickwork-texture.jpg",
807             "images/dog.jpg",
808             "images/color_wheel.jpg",
809             "images/mandrill_512_q075.jpg",
810             "images/flutter_logo.jpg",
811         };
812 
813         auto rContext = canvas->recordingContext();
814 #if defined(SK_GRAPHITE)
815         skgpu::graphite::Recorder* recorder = nullptr;
816         recorder = canvas->recorder();
817 #endif
818         for (int i = 0; i < kImageCount; ++i) {
819             auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(GetResourceAsData(kImageSrcs[i]),
820                                                            skgpu::Mipmapped::kYes);
821             SkASSERT(lazyYUV);
822 #if defined(SK_GRAPHITE)
823             if (recorder) {
824                 fImages[i] = lazyYUV->refImage(recorder,
825                                                sk_gpu_test::LazyYUVImage::Type::kFromPixmaps);
826             } else
827 #endif
828             {
829                 fImages[i] = lazyYUV->refImage(rContext,
830                                                sk_gpu_test::LazyYUVImage::Type::kFromPixmaps);
831             }
832         }
833     }
834 
draw(SkCanvas * canvas)835     void draw(SkCanvas* canvas) override {
836         if (fNeedToInitImages) {
837             this->initImages(canvas);
838             fNeedToInitImages = false;
839         }
840 
841         canvas->clear(SK_ColorWHITE);
842 
843         SkPaint paint;
844         SkSamplingOptions sampling(SkFilterMode::kLinear,
845                                    SkMipmapMode::kNearest);
846         for (size_t i = 0; i < fObjects.size(); ++i) {
847             BouncingTaggedImage* object = reinterpret_cast<BouncingTaggedImage*>(fObjects[i].get());
848 
849             canvas->save();
850             canvas->concat(object->transform());
851             canvas->drawImageRect(fImages[i % kImageCount], object->rect(), sampling, nullptr);
852 
853             canvas->restore();
854         }
855     }
856 
createObject()857     std::unique_ptr<MMObject> createObject() override {
858         return std::make_unique<BouncingTaggedImage>(&fRandom, fSize, fParticleSize, fMaxVelocity);
859     }
860 
reset()861     void reset() {
862         fNeedToInitImages = true;
863     }
864 
865 private:
866     static constexpr int kImageCount = 5;
867 
868     bool fNeedToInitImages = true;
869     sk_sp<SkImage> fImages[kImageCount];
870 };
871 
872 ///////////////////////////////////////////////////////////////////////////////////////////////////
873 
874 class CanvasLinesSlide : public MotionMarkSlide {
875 public:
CanvasLinesSlide()876     CanvasLinesSlide() {fName = "MotionMarkCanvasLines"; }
877 
load(SkScalar w,SkScalar h)878     void load(SkScalar w, SkScalar h) override {
879         fStage = std::make_unique<CanvasLineSegmentStage>(SkSize::Make(w, h));
880     }
881 };
882 
883 class CanvasArcsSlide : public MotionMarkSlide {
884 public:
CanvasArcsSlide()885     CanvasArcsSlide() {fName = "MotionMarkCanvasArcs"; }
886 
load(SkScalar w,SkScalar h)887     void load(SkScalar w, SkScalar h) override {
888         fStage = std::make_unique<CanvasArcStage>(SkSize::Make(w, h));
889     }
890 };
891 
892 class PathsSlide : public MotionMarkSlide {
893 public:
PathsSlide()894     PathsSlide() {fName = "MotionMarkPaths"; }
895 
load(SkScalar w,SkScalar h)896     void load(SkScalar w, SkScalar h) override {
897         fStage = std::make_unique<CanvasLinePathStage>(SkSize::Make(w, h));
898     }
899 };
900 
901 class BouncingTaggedImagesSlide : public MotionMarkSlide {
902 public:
BouncingTaggedImagesSlide()903     BouncingTaggedImagesSlide() {fName = "MotionMarkBouncingTaggedImages"; }
904 
load(SkScalar w,SkScalar h)905     void load(SkScalar w, SkScalar h) override {
906         fStage = std::make_unique<BouncingTaggedImagesStage>(SkSize::Make(w, h));
907     }
908 
gpuTeardown()909     void gpuTeardown() override {
910         if (fStage) {
911             reinterpret_cast<BouncingTaggedImagesStage*>(fStage.get())->reset();
912         }
913     }
914 };
915 
916 DEF_SLIDE( return new CanvasLinesSlide(); )
917 DEF_SLIDE( return new CanvasArcsSlide(); )
918 DEF_SLIDE( return new PathsSlide(); )
919 DEF_SLIDE( return new BouncingTaggedImagesSlide(); )
920