/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include "include/core/SkCanvas.h" #include "include/core/SkPath.h" #include "include/effects/SkGradientShader.h" #include "src/base/SkRandom.h" #include "tools/Resources.h" #include "tools/gpu/YUVUtils.h" #include "tools/viewer/Slide.h" // Implementation in C++ of some WebKit MotionMark tests // Tests implemented so far: // * Canvas Lines // * Canvas Arcs // * Paths // Based on https://github.com/WebKit/MotionMark/blob/main/MotionMark/ class MMObject { public: virtual ~MMObject() = default; virtual void draw(SkCanvas* canvas) = 0; virtual void animate(double /*nanos*/) = 0; }; class Stage { public: Stage(SkSize size, int startingObjectCount, int objectIncrement) : fSize(size) , fStartingObjectCount(startingObjectCount) , fObjectIncrement(objectIncrement) {} virtual ~Stage() = default; // The default impls of draw() and animate() simply iterate over fObjects and call the // MMObject function. virtual void draw(SkCanvas* canvas) { for (size_t i = 0; i < fObjects.size(); ++i) { fObjects[i]->draw(canvas); } } virtual bool animate(double nanos) { for (size_t i = 0; i < fObjects.size(); ++i) { fObjects[i]->animate(nanos); } return true; } // The default impl handles +/- to add or remove N objects from the scene virtual bool onChar(SkUnichar uni) { bool handled = false; switch (uni) { case '+': case '=': for (int i = 0; i < fObjectIncrement; ++i) { fObjects.push_back(this->createObject()); } handled = true; break; case '-': case '_': if (fObjects.size() > (size_t) fObjectIncrement) { fObjects.resize(fObjects.size() - (size_t) fObjectIncrement); } handled = true; break; default: break; } return handled; } protected: virtual std::unique_ptr createObject() = 0; void initializeObjects() { for (int i = 0; i < fStartingObjectCount; ++i) { fObjects.push_back(this->createObject()); } } [[maybe_unused]] SkSize fSize; int fStartingObjectCount; int fObjectIncrement; std::vector> fObjects; SkRandom fRandom; }; class MotionMarkSlide : public Slide { public: MotionMarkSlide() = default; bool onChar(SkUnichar uni) override { return fStage->onChar(uni); } void draw(SkCanvas* canvas) override { fStage->draw(canvas); } bool animate(double nanos) override { return fStage->animate(nanos); } protected: std::unique_ptr fStage; }; namespace { float time_counter_value(double nanos, float factor) { constexpr double kMillisPerNano = 0.0000001; return static_cast(nanos*kMillisPerNano)/factor; } float time_fractional_value(double nanos, float cycleLengthMs) { return SkScalarFraction(time_counter_value(nanos, cycleLengthMs)); } // The following functions match the input processing that Chrome's canvas2d layer performs before // calling into Skia. // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc;drc=572074cb06425797e7e110511db405134cf67e2f;l=299 void canonicalize_angle(float* startAngle, float* endAngle) { float newStartAngle = SkScalarMod(*startAngle, 360.f); float delta = newStartAngle - *startAngle; *startAngle = newStartAngle; *endAngle = *endAngle + delta; } // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc;drc=572074cb06425797e7e110511db405134cf67e2f;l=245 float adjust_end_angle(float startAngle, float endAngle, bool ccw) { float newEndAngle = endAngle; if (!ccw && endAngle - startAngle >= 360.f) { newEndAngle = startAngle + 360.f; } else if (ccw && startAngle - endAngle >= 360.f) { newEndAngle = startAngle - 360.f; } else if (!ccw && startAngle > endAngle) { newEndAngle = startAngle + (360.f - SkScalarMod(startAngle - endAngle, 360.f)); } else if (ccw && startAngle < endAngle) { newEndAngle = startAngle - (360.f - SkScalarMod(endAngle - startAngle, 360.f)); } return newEndAngle; } } // namespace /////////////////////////////////////////////////////////////////////////////////////////////////// // Canvas Lines /////////////////////////////////////////////////////////////////////////////////////////////////// struct LineSegmentParams { float fCircleRadius; SkPoint fCircleCenters[4]; float fLineLengthMaximum; float fLineMinimum; }; class CanvasLineSegment : public MMObject { public: CanvasLineSegment(SkRandom* random, const LineSegmentParams& params) { int circle = random->nextRangeU(0, 3); static constexpr SkColor kColors[] = { 0xffe01040, 0xff10c030, 0xff744cba, 0xffe05010 }; fColor = kColors[circle]; fLineWidth = std::pow(random->nextF(), 12) * 20 + 3; fOmega = random->nextF() * 3 + 0.2f; float theta = random->nextRangeF(0, 2*SK_ScalarPI); fCosTheta = std::cos(theta); fSinTheta = std::sin(theta); fStart = params.fCircleCenters[circle] + SkPoint::Make(params.fCircleRadius * fCosTheta, params.fCircleRadius * fSinTheta); fLength = params.fLineMinimum; fLength += std::pow(random->nextF(), 8) * params.fLineLengthMaximum; fSegmentDirection = random->nextF() > 0.5 ? -1 : 1; } ~CanvasLineSegment() override = default; void draw(SkCanvas* canvas) override { SkPaint paint; paint.setAntiAlias(true); paint.setColor(fColor); paint.setStrokeWidth(fLineWidth); paint.setStyle(SkPaint::kStroke_Style); SkPoint end = { fStart.fX + fSegmentDirection * fLength * fCosTheta, fStart.fY + fSegmentDirection * fLength * fSinTheta }; canvas->drawLine(fStart, end, paint); } void animate(double nanos) override { fLength += std::sin(time_counter_value(nanos, 100) * fOmega); } private: SkColor fColor; float fLineWidth; float fOmega; float fCosTheta; float fSinTheta; SkPoint fStart; float fLength; float fSegmentDirection; }; class CanvasLineSegmentStage : public Stage { public: CanvasLineSegmentStage(SkSize size) : Stage(size, /*startingObjectCount=*/5000, /*objectIncrement*/1000) { fParams.fLineMinimum = 20; fParams.fLineLengthMaximum = 40; fParams.fCircleRadius = fSize.fWidth/8 - .4 * (fParams.fLineMinimum + fParams.fLineLengthMaximum); fParams.fCircleCenters[0] = SkPoint::Make(5.5 / 32 * fSize.fWidth, 2.1 / 3*fSize.fHeight); fParams.fCircleCenters[1] = SkPoint::Make(12.5 / 32 * fSize.fWidth, .9 / 3*fSize.fHeight); fParams.fCircleCenters[2] = SkPoint::Make(19.5 / 32 * fSize.fWidth, 2.1 / 3*fSize.fHeight); fParams.fCircleCenters[3] = SkPoint::Make(26.5 / 32 * fSize.fWidth, .9 / 3*fSize.fHeight); fHalfSize = SkSize::Make(fSize.fWidth * 0.5f, fSize.fHeight * 0.5f); fTwoFifthsSizeX = fSize.fWidth * .4; this->initializeObjects(); } ~CanvasLineSegmentStage() override = default; void draw(SkCanvas* canvas) override { canvas->clear(SK_ColorWHITE); float dx = fTwoFifthsSizeX * std::cos(fCurrentAngle); float dy = fTwoFifthsSizeX * std::sin(fCurrentAngle); float colorStopStep = SkScalarInterp(-.1f, .1f, fCurrentGradientStep); int brightnessStep = SkScalarRoundToInt(SkScalarInterp(32, 64, fCurrentGradientStep)); SkColor color1Step = SkColorSetARGB(brightnessStep, brightnessStep, (brightnessStep << 1), 102); SkColor color2Step = SkColorSetARGB((brightnessStep << 1), (brightnessStep << 1), brightnessStep, 102); SkPoint pts[2] = { {fHalfSize.fWidth + dx, fHalfSize.fHeight + dy}, {fHalfSize.fWidth - dx, fHalfSize.fHeight - dy} }; SkColor colors[] = { color1Step, color1Step, color2Step, color2Step }; float pos[] = { 0, 0.2f + colorStopStep, 0.8f - colorStopStep, 1 }; sk_sp gradientShader = SkGradientShader::MakeLinear(pts, colors, pos, 4, SkTileMode::kClamp, 0); SkPaint paint; paint.setAntiAlias(true); paint.setStrokeWidth(15); for (int i = 0; i < 4; i++) { const SkColor strokeColors[] = { 0xffe01040, 0xff10c030, 0xff744cba, 0xffe05010 }; const SkColor fillColors[] = { 0xff70051d, 0xff016112, 0xff2F0C6E, 0xff702701 }; paint.setColor(strokeColors[i]); paint.setStyle(SkPaint::kStroke_Style); SkRect arcRect = SkRect::MakeXYWH(fParams.fCircleCenters[i].fX - fParams.fCircleRadius, fParams.fCircleCenters[i].fY- fParams.fCircleRadius, 2*fParams.fCircleRadius, 2*fParams.fCircleRadius); canvas->drawArc(arcRect, 0, 360, false, paint); paint.setColor(fillColors[i]); paint.setStyle(SkPaint::kFill_Style); canvas->drawArc(arcRect, 0, 360, false, paint); paint.setShader(gradientShader); canvas->drawArc(arcRect, 0, 360, false, paint); paint.setShader(nullptr); } this->Stage::draw(canvas); } bool animate(double nanos) override { fCurrentAngle = time_fractional_value(nanos, 3000) * SK_ScalarPI * 2; fCurrentGradientStep = 0.5f + 0.5f * std::sin( time_fractional_value(nanos, 5000) * SK_ScalarPI * 2); this->Stage::animate(nanos); return true; } std::unique_ptr createObject() override { return std::make_unique(&fRandom,fParams); } private: LineSegmentParams fParams; SkSize fHalfSize; float fTwoFifthsSizeX; float fCurrentAngle = 0; float fCurrentGradientStep = 0.5f; }; /////////////////////////////////////////////////////////////////////////////////////////////////// // Canvas Arcs /////////////////////////////////////////////////////////////////////////////////////////////////// class CanvasArc : public MMObject { public: CanvasArc(SkRandom* random, SkSize size) { constexpr float kMaxX = 6; constexpr float kMaxY = 3; const SkColor baseColors[3] = { 0xff101010, 0xff808080, 0xffc0c0c0 }; const SkColor bonusColors[3] = { 0xffe01040, 0xff10c030, 0xffe05010 }; float distanceX = size.fWidth / kMaxX; float distanceY = size.fHeight / (kMaxY + 1); int randY = random->nextRangeU(0, kMaxY); int randX = random->nextRangeU(0, kMaxX - 1 * (randY % 2)); fPoint = SkPoint::Make(distanceX * (randX + (randY % 2) / 2), distanceY * (randY + 0.5f)); fRadius = 20 + std::pow(random->nextF(), 5) * (std::min(distanceX, distanceY) / 1.8f); fStartAngle = random->nextRangeF(0, 2*SK_ScalarPI); fEndAngle = random->nextRangeF(0, 2*SK_ScalarPI); fOmega = (random->nextF() - 0.5f) * 0.3f; fCounterclockwise = random->nextBool(); // The MotionMark code appends a random element from an array and appends it to the color // array, then randomly picks from that. We'll just pick that random element and use it // if the index is out of bounds for the base color array. SkColor bonusColor = bonusColors[(randX + sk_float_ceil2int(randY * 0.5f)) % 3]; int colorIndex = random->nextRangeU(0, 3); fColor = colorIndex == 3 ? bonusColor : baseColors[colorIndex]; fLineWidth = 1 + std::pow(random->nextF(), 5) * 30; fDoStroke = random->nextRangeU(0, 3) != 0; } ~CanvasArc() override = default; void draw(SkCanvas* canvas) override { SkPaint paint; paint.setAntiAlias(true); paint.setColor(fColor); SkRect arcRect = SkRect::MakeXYWH(fPoint.fX - fRadius, fPoint.fY - fRadius, 2*fRadius, 2*fRadius); float startAngleDeg = fStartAngle * 180.f / SK_ScalarPI; float endAngleDeg = fEndAngle * 180.f / SK_ScalarPI; canonicalize_angle(&startAngleDeg, &endAngleDeg); endAngleDeg = adjust_end_angle(startAngleDeg, endAngleDeg, fCounterclockwise); float sweepAngle = startAngleDeg - endAngleDeg; if (fDoStroke) { paint.setStrokeWidth(fLineWidth); paint.setStyle(SkPaint::kStroke_Style); canvas->drawArc(arcRect, startAngleDeg, sweepAngle, false, paint); } else { paint.setStyle(SkPaint::kFill_Style); // The MotionMark code creates a path for fills via lineTo(point), arc(), lineTo(point). // For now we'll just use drawArc for both but might need to revisit. canvas->drawArc(arcRect, startAngleDeg, sweepAngle, true, paint); } } void animate(double /*nanos*/) override { fStartAngle += fOmega; fEndAngle += fOmega / 2; } private: SkPoint fPoint; float fRadius; float fStartAngle; // in radians float fEndAngle; // in radians SkColor fColor; float fOmega; // in radians bool fDoStroke; bool fCounterclockwise; float fLineWidth; }; class CanvasArcStage : public Stage { public: CanvasArcStage(SkSize size) : Stage(size, /*startingObjectCount=*/1000, /*objectIncrement=*/200) { this->initializeObjects(); } ~CanvasArcStage() override = default; void draw(SkCanvas* canvas) override { canvas->clear(SK_ColorWHITE); this->Stage::draw(canvas); } std::unique_ptr createObject() override { return std::make_unique(&fRandom, fSize); } }; /////////////////////////////////////////////////////////////////////////////////////////////////// // Paths /////////////////////////////////////////////////////////////////////////////////////////////////// class CanvasLinePoint : public MMObject { protected: void setEndPoint(SkRandom* random, SkSize size, SkPoint* prevCoord) { const SkSize kGridSize = { 80, 40 }; const SkPoint kGridCenter = { 40, 20 }; const SkPoint kOffsets[4] = { {-4, 0}, {2, 0}, {1, -2}, {1, 2} }; SkPoint coordinate = prevCoord ? *prevCoord : kGridCenter; if (prevCoord) { SkPoint offset = kOffsets[random->nextRangeU(0, 3)]; coordinate += offset; if (coordinate.fX < 0 || coordinate.fX > kGridSize.width()) coordinate.fX -= offset.fX * 2; if (coordinate.fY < 0 || coordinate.fY > kGridSize.height()) coordinate.fY -= offset.fY * 2; } fPoint = SkPoint::Make((coordinate.fX + 0.5f) * size.width() / (kGridSize.width() + 1), (coordinate.fY + 0.5f) * size.height() / (kGridSize.height() + 1)); fCoordinate = coordinate; } public: CanvasLinePoint(SkRandom* random, SkSize size, SkPoint* prev) { const SkColor kColors[7] = { 0xff101010, 0xff808080, 0xffc0c0c0, 0xff101010, 0xff808080, 0xffc0c0c0, 0xffe01040 }; fColor = kColors[random->nextRangeU(0, 6)]; fWidth = std::pow(random->nextF(), 5) * 20 + 1; fIsSplit = random->nextBool(); this->setEndPoint(random, size, prev); } ~CanvasLinePoint() override = default; virtual void append(SkPath* path) { path->lineTo(fPoint); } // unused, all the work is done by append void draw(SkCanvas*) override {} void animate(double) override {} SkColor getColor() { return fColor; } float getWidth() { return fWidth; } SkPoint getPoint() { return fPoint; } SkPoint getCoord() { return fCoordinate; } bool isSplit() { return fIsSplit; } void toggleIsSplit() { fIsSplit = !fIsSplit; } private: SkPoint fPoint; SkPoint fCoordinate; SkColor fColor; float fWidth; bool fIsSplit; }; class CanvasQuadraticSegment : public CanvasLinePoint { public: CanvasQuadraticSegment(SkRandom* random, SkSize size, SkPoint* prev) : CanvasLinePoint(random, size, prev) { // Note: The construction of these points is odd but mirrors the Javascript code. // The chosen point from the base constructor is instead the control point. fPoint2 = this->getPoint(); // Get another random point for the actual end point of the segment. this->setEndPoint(random, size, prev); } void append(SkPath* path) override { path->quadTo(fPoint2, this->getPoint()); } private: SkPoint fPoint2; }; class CanvasBezierSegment : public CanvasLinePoint { public: CanvasBezierSegment(SkRandom* random, SkSize size, SkPoint* prev) : CanvasLinePoint(random, size, prev) { // Note: The construction of these points is odd but mirrors the Javascript code. // The chosen point from the base constructor is instead the control point. fPoint2 = this->getPoint(); // Get the second control point. this->setEndPoint(random, size, prev); fPoint3 = this->getPoint(); // Get third random point for the actual end point of the segment. this->setEndPoint(random, size, prev); } void append(SkPath* path) override { path->cubicTo(fPoint2, fPoint3, this->getPoint()); } private: SkPoint fPoint2; SkPoint fPoint3; }; std::unique_ptr make_line_path(SkRandom* random, SkSize size, SkPoint* prev) { int choice = random->nextRangeU(0, 3); switch (choice) { case 0: return std::make_unique(random, size, prev); break; case 1: return std::make_unique(random, size, prev); break; case 2: case 3: default: return std::make_unique(random, size, prev); break; } } class CanvasLinePathStage : public Stage { public: CanvasLinePathStage(SkSize size) : Stage(size, /*startingObjectCount=*/5000, /*objectIncrement=*/1000) { this->initializeObjects(); } ~CanvasLinePathStage() override = default; void draw(SkCanvas* canvas) override { canvas->clear(SK_ColorWHITE); SkPath currentPath; SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); for (size_t i = 0; i < fObjects.size(); ++i) { CanvasLinePoint* object = reinterpret_cast(fObjects[i].get()); if (i == 0) { paint.setStrokeWidth(object->getWidth()); paint.setColor(object->getColor()); currentPath.moveTo(object->getPoint()); } else { object->append(¤tPath); if (object->isSplit()) { canvas->drawPath(currentPath, paint); paint.setStrokeWidth(object->getWidth()); paint.setColor(object->getColor()); currentPath.reset(); currentPath.moveTo(object->getPoint()); } if (fRandom.nextF() > 0.995) { object->toggleIsSplit(); } } } canvas->drawPath(currentPath, paint); } bool animate(double /*nanos*/) override { // Nothing to do, but return true so we redraw. return true; } std::unique_ptr createObject() override { if (fObjects.empty()) { return make_line_path(&fRandom, fSize, nullptr); } else { CanvasLinePoint* prevObject = reinterpret_cast(fObjects.back().get()); SkPoint coord = prevObject->getCoord(); return make_line_path(&fRandom, fSize, &coord); } } }; /////////////////////////////////////////////////////////////////////////////////////////////////// // Bouncing Particles /////////////////////////////////////////////////////////////////////////////////////////////////// SkPoint random_position(SkRandom* random, SkSize maxPosition) { return {(float)random->nextRangeU(0, maxPosition.width()), (float)random->nextRangeU(0, maxPosition.height())}; } float random_angle(SkRandom* random) { return random->nextRangeF(0, 2*SK_FloatPI); } float random_velocity(SkRandom* random, float maxVelocity) { return random->nextRangeF(maxVelocity/8, maxVelocity); } class Rotater { public: Rotater(float rotateInterval) : fTimeDelta(0) , fRotateInterval(rotateInterval) {} void next(float timeDelta) { fTimeDelta = SkScalarMod(fTimeDelta + timeDelta, fRotateInterval); } float degrees() { return (360 * fTimeDelta) / fRotateInterval; } private: float fTimeDelta; float fRotateInterval; }; Rotater random_rotater(SkRandom* random) { return Rotater(random->nextRangeF(10, 100)); } SkPoint point_on_circle(float angle, float radius) { return {radius * SkScalarCos(angle), radius * SkScalarSin(angle)}; } class BouncingParticle : public MMObject { public: BouncingParticle(SkRandom* random, SkSize stageSize, SkSize particleSize, float maxVelocity) : fStageSize(stageSize) , fSize(particleSize) , fPosition(random_position(random, {stageSize.fWidth - particleSize.fWidth, stageSize.fHeight - particleSize.fHeight})) , fAngle(random_angle(random)) , fVelocity(random_velocity(random, maxVelocity)) , fRotater(random_rotater(random)) { } void animate(double deltaNanos) override { // TODO: consolidate and pass in millis to the Stages constexpr double kMillisPerNano = 0.0000001; fPosition += point_on_circle(fAngle, fVelocity * (deltaNanos * kMillisPerNano)); fRotater.next(deltaNanos * kMillisPerNano); // If particle is going to move off right side if (fPosition.fX + fSize.width() > fStageSize.width()) { // If direction is East-South, go West-South. if (fAngle >= 0 && fAngle < SK_FloatPI / 2) fAngle = SK_FloatPI - fAngle; // If angle is East-North, go West-North. else if (fAngle > SK_FloatPI / 2 * 3) fAngle = fAngle - (fAngle - SK_FloatPI / 2 * 3) * 2; // Make sure the particle does not go outside the stage boundaries. fPosition.fX = fStageSize.width() - fSize.width(); } // If particle is going to move off left side if (fPosition.fX < 0) { // If angle is West-South, go East-South. if (fAngle > SK_FloatPI / 2 && fAngle < SK_FloatPI) fAngle = SK_FloatPI - fAngle; // If angle is West-North, go East-North. else if (fAngle > SK_FloatPI && fAngle < SK_FloatPI / 2 * 3) fAngle = fAngle + (SK_FloatPI / 2 * 3 - fAngle) * 2; // Make sure the particle does not go outside the stage boundaries. fPosition.fX = 0; } // If particle is going to move off bottom side if (fPosition.fY + fSize.height() > fStageSize.height()) { // If direction is South, go North. if (fAngle > 0 && fAngle < SK_FloatPI) fAngle = SK_FloatPI * 2 - fAngle; // Make sure the particle does not go outside the stage boundaries. fPosition.fY = fStageSize.height() - fSize.height(); } // If particle is going to move off top side if (fPosition.fY < 0) { // If direction is North, go South. if (fAngle > SK_FloatPI && fAngle < SK_FloatPI * 2) fAngle = fAngle - (fAngle - SK_FloatPI) * 2; // Make sure the particle does not go outside the stage boundaries. fPosition.fY = 0; } } protected: SkSize fStageSize; SkSize fSize; SkPoint fPosition; float fAngle; float fVelocity; Rotater fRotater; }; class BouncingParticlesStage : public Stage { public: BouncingParticlesStage(SkSize size) : Stage(size, /*startingObjectCount=*/3000, /*objectIncrement=*/500) , fParticleSize({150, 150}) , fMaxVelocity(10){ } bool animate(double nanos) override { // The particles take delta time if (fLastTime < 0) { fLastTime = nanos; } for (size_t i = 0; i < fObjects.size(); ++i) { fObjects[i]->animate(nanos - fLastTime); } fLastTime = nanos; return true; } protected: SkSize fParticleSize; float fMaxVelocity; double fLastTime = -1; }; class BouncingTaggedImage : public BouncingParticle { public: BouncingTaggedImage(SkRandom* random, SkSize stageSize, SkSize particleSize, float maxVelocity) : BouncingParticle(random, stageSize, particleSize, maxVelocity) { this->move(); fRect = SkRect::MakeSize(fSize); fRect.offset(-fSize.width()/2, -fSize.height()/2); } void move() { fTransform = SkMatrix::RotateDeg(std::floor(fRotater.degrees())); fTransform.setTranslateX(fPosition.fX); fTransform.setTranslateY(fPosition.fY); } void animate(double deltaNanos) override { BouncingParticle::animate(deltaNanos); this->move(); } void draw(SkCanvas* canvas) override { // handled by the Stage } const SkMatrix& transform() { return fTransform; } SkRect rect() { return fRect; } private: SkMatrix fTransform; SkRect fRect; }; class BouncingTaggedImagesStage : public BouncingParticlesStage { public: BouncingTaggedImagesStage(SkSize size) : BouncingParticlesStage(size) { this->initializeObjects(); } ~BouncingTaggedImagesStage() override = default; void initImages(SkCanvas* canvas) { const char* kImageSrcs[kImageCount] = { "images/brickwork-texture.jpg", "images/dog.jpg", "images/color_wheel.jpg", "images/mandrill_512_q075.jpg", "images/flutter_logo.jpg", }; auto rContext = canvas->recordingContext(); #if defined(SK_GRAPHITE) skgpu::graphite::Recorder* recorder = nullptr; recorder = canvas->recorder(); #endif for (int i = 0; i < kImageCount; ++i) { auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(GetResourceAsData(kImageSrcs[i]), skgpu::Mipmapped::kYes); SkASSERT(lazyYUV); #if defined(SK_GRAPHITE) if (recorder) { fImages[i] = lazyYUV->refImage(recorder, sk_gpu_test::LazyYUVImage::Type::kFromPixmaps); } else #endif { fImages[i] = lazyYUV->refImage(rContext, sk_gpu_test::LazyYUVImage::Type::kFromPixmaps); } } } void draw(SkCanvas* canvas) override { if (fNeedToInitImages) { this->initImages(canvas); fNeedToInitImages = false; } canvas->clear(SK_ColorWHITE); SkPaint paint; SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNearest); for (size_t i = 0; i < fObjects.size(); ++i) { BouncingTaggedImage* object = reinterpret_cast(fObjects[i].get()); canvas->save(); canvas->concat(object->transform()); canvas->drawImageRect(fImages[i % kImageCount], object->rect(), sampling, nullptr); canvas->restore(); } } std::unique_ptr createObject() override { return std::make_unique(&fRandom, fSize, fParticleSize, fMaxVelocity); } void reset() { fNeedToInitImages = true; } private: static constexpr int kImageCount = 5; bool fNeedToInitImages = true; sk_sp fImages[kImageCount]; }; /////////////////////////////////////////////////////////////////////////////////////////////////// class CanvasLinesSlide : public MotionMarkSlide { public: CanvasLinesSlide() {fName = "MotionMarkCanvasLines"; } void load(SkScalar w, SkScalar h) override { fStage = std::make_unique(SkSize::Make(w, h)); } }; class CanvasArcsSlide : public MotionMarkSlide { public: CanvasArcsSlide() {fName = "MotionMarkCanvasArcs"; } void load(SkScalar w, SkScalar h) override { fStage = std::make_unique(SkSize::Make(w, h)); } }; class PathsSlide : public MotionMarkSlide { public: PathsSlide() {fName = "MotionMarkPaths"; } void load(SkScalar w, SkScalar h) override { fStage = std::make_unique(SkSize::Make(w, h)); } }; class BouncingTaggedImagesSlide : public MotionMarkSlide { public: BouncingTaggedImagesSlide() {fName = "MotionMarkBouncingTaggedImages"; } void load(SkScalar w, SkScalar h) override { fStage = std::make_unique(SkSize::Make(w, h)); } void gpuTeardown() override { if (fStage) { reinterpret_cast(fStage.get())->reset(); } } }; DEF_SLIDE( return new CanvasLinesSlide(); ) DEF_SLIDE( return new CanvasArcsSlide(); ) DEF_SLIDE( return new PathsSlide(); ) DEF_SLIDE( return new BouncingTaggedImagesSlide(); )