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(¤tPath);
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