| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/ambient/ui/ambient_animation_frame_rate_controller.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/frame_throttler/frame_throttling_controller.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/check.h" |
| #include "base/containers/flat_set.h" |
| #include "base/containers/span.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/time/time.h" |
| #include "cc/paint/skottie_marker.h" |
| #include "cc/paint/skottie_resource_metadata.h" |
| #include "cc/paint/skottie_wrapper.h" |
| #include "components/viz/common/surfaces/frame_sink_id.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkSize.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/size_f.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/lottie/animation.h" |
| |
| namespace ash { |
| namespace { |
| |
| using ::testing::Eq; |
| using ::testing::IsEmpty; |
| using ::testing::UnorderedElementsAre; |
| |
| constexpr gfx::Size kTestAnimationSize = gfx::Size(100, 100); |
| |
| // Tailor-made for creating an animation with custom markers without having to |
| // create full-blown valid lottie JSON data with markers embedded in it. |
| class TestSkottieWrapper : public cc::SkottieWrapper { |
| public: |
| TestSkottieWrapper(base::TimeDelta duration, |
| std::vector<cc::SkottieMarker> markers) |
| : duration_(duration), markers_(std::move(markers)) {} |
| TestSkottieWrapper(const TestSkottieWrapper&) = delete; |
| TestSkottieWrapper& operator=(const TestSkottieWrapper&) = delete; |
| |
| // cc::SkottieWrapper implementation: |
| bool is_valid() const override { return true; } |
| const cc::SkottieResourceMetadataMap& GetImageAssetMetadata() const override { |
| return image_asset_map_; |
| } |
| const base::flat_set<std::string>& GetTextNodeNames() const override { |
| return text_node_names_; |
| } |
| cc::SkottieTextPropertyValueMap GetCurrentTextPropertyValues() |
| const override { |
| return cc::SkottieTextPropertyValueMap(); |
| } |
| cc::SkottieTransformPropertyValueMap GetCurrentTransformPropertyValues() |
| const override { |
| return cc::SkottieTransformPropertyValueMap(); |
| } |
| cc::SkottieColorMap GetCurrentColorPropertyValues() const override { |
| return cc::SkottieColorMap(); |
| } |
| const std::vector<cc::SkottieMarker>& GetAllMarkers() const override { |
| return markers_; |
| } |
| void Seek(float t, FrameDataCallback frame_data_cb) override {} |
| void Seek(float t); |
| void Draw(SkCanvas* canvas, |
| float t, |
| const SkRect& rect, |
| FrameDataCallback frame_data_cb, |
| const cc::SkottieColorMap& color_map, |
| const cc::SkottieTextPropertyValueMap& text_map) override {} |
| float duration() const override { return duration_.InSecondsF(); } |
| SkSize size() const override { |
| return gfx::SizeFToSkSize(gfx::SizeF(kTestAnimationSize)); |
| } |
| base::span<const uint8_t> raw_data() const override { |
| return base::span<const uint8_t>(); |
| } |
| uint32_t id() const override { return 0; } |
| |
| private: |
| ~TestSkottieWrapper() override = default; |
| |
| const base::TimeDelta duration_; |
| const base::flat_set<std::string> text_node_names_; |
| const cc::SkottieResourceMetadataMap image_asset_map_; |
| const std::vector<cc::SkottieMarker> markers_; |
| }; |
| |
| class AmbientAnimationFrameRateControllerTest : public AshTestBase { |
| protected: |
| AmbientAnimationFrameRateControllerTest() |
| : clock_(base::TimeTicks::Now()), |
| canvas_(kTestAnimationSize, /*image_scale=*/1.f, /*is_opaque=*/false) {} |
| |
| void AdvanceTimeAndPaint(lottie::Animation& animation, |
| float normalized_amount) { |
| AdvanceTimeAndPaint({&animation}, normalized_amount); |
| } |
| |
| void AdvanceTimeAndPaint(std::vector<lottie::Animation*> animations, |
| float normalized_amount) { |
| CHECK(!animations.empty()); |
| CHECK_GE(normalized_amount, 0.f); |
| CHECK_LE(normalized_amount, 1.f); |
| clock_ += (normalized_amount * animations.front()->GetAnimationDuration()); |
| for (lottie::Animation* animation : animations) { |
| animation->Paint(&canvas_, clock_, kTestAnimationSize); |
| } |
| } |
| |
| base::TimeTicks clock_; |
| gfx::Canvas canvas_; |
| }; |
| |
| TEST_F(AmbientAnimationFrameRateControllerTest, BasicThrottling) { |
| constexpr viz::FrameSinkId kTestFrameSinkId = {99, 99}; |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| window->SetEmbedFrameSinkId(kTestFrameSinkId); |
| |
| std::vector<cc::SkottieMarker> markers = { |
| {"_CrOS_Marker_Throttled_20fps", 0.2f, 0.4f}, |
| {"_CrOS_Marker_Throttled_30fps", 0.8f, 0.9f}, |
| }; |
| lottie::Animation animation( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| AmbientAnimationFrameRateController ambient_frame_rate_controller( |
| Shell::Get()->frame_throttling_controller()); |
| ambient_frame_rate_controller.AddWindowToThrottle(window.get(), &animation); |
| animation.Start(lottie::Animation::PlaybackConfig::CreateDefault(animation)); |
| |
| // T: 0 |
| AdvanceTimeAndPaint(animation, .0f); |
| |
| // T: .1 |
| AdvanceTimeAndPaint(animation, .1f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| |
| // T: .21 |
| AdvanceTimeAndPaint(animation, .21f - .1f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(20)); |
| |
| // T: .39 |
| AdvanceTimeAndPaint(animation, .39f - .21f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(20)); |
| |
| // T: .41 |
| AdvanceTimeAndPaint(animation, .41f - .39f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| |
| // T: .79 |
| AdvanceTimeAndPaint(animation, .79f - .41f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| |
| // T: .81 |
| AdvanceTimeAndPaint(animation, .81f - .79f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(30)); |
| |
| // T: .89 |
| AdvanceTimeAndPaint(animation, .89f - .81f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(30)); |
| |
| // T: .91 |
| AdvanceTimeAndPaint(animation, .91f - .89f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| |
| // T: .1 |
| AdvanceTimeAndPaint(animation, 1.1f - .9f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| |
| // T: .21 |
| AdvanceTimeAndPaint(animation, .21f - .1f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(20)); |
| } |
| |
| TEST_F(AmbientAnimationFrameRateControllerTest, |
| MarkersAtBordersOfAnimationCycle) { |
| constexpr viz::FrameSinkId kTestFrameSinkId = {99, 99}; |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| window->SetEmbedFrameSinkId(kTestFrameSinkId); |
| |
| std::vector<cc::SkottieMarker> markers = { |
| {"_CrOS_Marker_Throttled_20fps", 0.f, 0.1f}, |
| {"_CrOS_Marker_Throttled_30fps", 0.9f, 1.f}, |
| }; |
| lottie::Animation animation( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| AmbientAnimationFrameRateController ambient_frame_rate_controller( |
| Shell::Get()->frame_throttling_controller()); |
| ambient_frame_rate_controller.AddWindowToThrottle(window.get(), &animation); |
| animation.Start(lottie::Animation::PlaybackConfig::CreateDefault(animation)); |
| |
| // T: 0 |
| AdvanceTimeAndPaint(animation, .0f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(20)); |
| |
| // T: .91 |
| AdvanceTimeAndPaint(animation, .91f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(30)); |
| |
| // T: .01 |
| AdvanceTimeAndPaint(animation, 1.01 - .91f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(20)); |
| } |
| |
| TEST_F(AmbientAnimationFrameRateControllerTest, NoMarkers) { |
| constexpr viz::FrameSinkId kTestFrameSinkId = {99, 99}; |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| window->SetEmbedFrameSinkId(kTestFrameSinkId); |
| |
| lottie::Animation animation(base::MakeRefCounted<TestSkottieWrapper>( |
| base::Seconds(1), std::vector<cc::SkottieMarker>())); |
| AmbientAnimationFrameRateController ambient_frame_rate_controller( |
| Shell::Get()->frame_throttling_controller()); |
| ambient_frame_rate_controller.AddWindowToThrottle(window.get(), &animation); |
| animation.Start(lottie::Animation::PlaybackConfig::CreateDefault(animation)); |
| |
| constexpr int kNumTimeStepsToTest = 30; |
| constexpr float kTimeStepSize = .1f; |
| for (int i = 0; i < kNumTimeStepsToTest; ++i) { |
| AdvanceTimeAndPaint(animation, kTimeStepSize); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| } |
| } |
| |
| // The time steps in this test skip multiple markers at a time. |
| TEST_F(AmbientAnimationFrameRateControllerTest, LargeTimesteps) { |
| constexpr viz::FrameSinkId kTestFrameSinkId = {99, 99}; |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| window->SetEmbedFrameSinkId(kTestFrameSinkId); |
| |
| std::vector<cc::SkottieMarker> markers = { |
| {"_CrOS_Marker_Throttled_10fps", 0.f, 0.1f}, |
| {"_CrOS_Marker_Throttled_20fps", 0.1f, 0.2f}, |
| {"_CrOS_Marker_Throttled_30fps", 0.2f, 0.3f}, |
| {"_CrOS_Marker_Throttled_40fps", 0.3f, 0.4f}, |
| {"_CrOS_Marker_Throttled_50fps", 0.4f, 0.5f}, |
| }; |
| lottie::Animation animation( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| AmbientAnimationFrameRateController ambient_frame_rate_controller( |
| Shell::Get()->frame_throttling_controller()); |
| ambient_frame_rate_controller.AddWindowToThrottle(window.get(), &animation); |
| animation.Start(lottie::Animation::PlaybackConfig::CreateDefault(animation)); |
| |
| // T: 0 |
| AdvanceTimeAndPaint(animation, .0f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(10)); |
| |
| // T: .25 |
| AdvanceTimeAndPaint(animation, .25f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(30)); |
| |
| // T: .45 |
| AdvanceTimeAndPaint(animation, .45f - .25f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(50)); |
| } |
| |
| TEST_F(AmbientAnimationFrameRateControllerTest, MultipleWindows) { |
| constexpr viz::FrameSinkId kTestFrameSinkId1 = {99, 99}; |
| std::unique_ptr<aura::Window> window_1 = CreateTestWindow(); |
| window_1->SetEmbedFrameSinkId(kTestFrameSinkId1); |
| |
| constexpr viz::FrameSinkId kTestFrameSinkId2 = {999, 999}; |
| std::unique_ptr<aura::Window> window_2 = CreateTestWindow(); |
| window_2->SetEmbedFrameSinkId(kTestFrameSinkId2); |
| |
| std::vector<cc::SkottieMarker> markers = { |
| {"_CrOS_Marker_Throttled_10fps", 0.4f, 0.6f}, |
| }; |
| lottie::Animation animation_1( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| lottie::Animation animation_2( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| AmbientAnimationFrameRateController ambient_frame_rate_controller( |
| Shell::Get()->frame_throttling_controller()); |
| animation_1.Start( |
| lottie::Animation::PlaybackConfig::CreateDefault(animation_1)); |
| animation_2.Start( |
| lottie::Animation::PlaybackConfig::CreateDefault(animation_2)); |
| |
| // T: 0 |
| AdvanceTimeAndPaint({&animation_1, &animation_2}, .0f); |
| // T: .41 |
| AdvanceTimeAndPaint({&animation_1, &animation_2}, .41f); |
| |
| ambient_frame_rate_controller.AddWindowToThrottle(window_1.get(), |
| &animation_1); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId1)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(10)); |
| |
| // T: .59 |
| AdvanceTimeAndPaint({&animation_1, &animation_2}, .59f - .41f); |
| ambient_frame_rate_controller.AddWindowToThrottle(window_2.get(), |
| &animation_2); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId1, kTestFrameSinkId2)); |
| EXPECT_THAT(Shell::Get() |
| ->frame_throttling_controller() |
| ->GetCurrentThrottledFrameRate(), |
| Eq(10)); |
| |
| // T: .7 |
| AdvanceTimeAndPaint({&animation_1, &animation_2}, .7f - .59f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| } |
| |
| TEST_F(AmbientAnimationFrameRateControllerTest, |
| HandlesWindowAndAnimationDestroyedFirst) { |
| constexpr viz::FrameSinkId kTestFrameSinkId1 = {99, 99}; |
| std::unique_ptr<aura::Window> window_1 = CreateTestWindow(); |
| window_1->SetEmbedFrameSinkId(kTestFrameSinkId1); |
| |
| constexpr viz::FrameSinkId kTestFrameSinkId2 = {999, 999}; |
| std::unique_ptr<aura::Window> window_2 = CreateTestWindow(); |
| window_2->SetEmbedFrameSinkId(kTestFrameSinkId2); |
| |
| std::vector<cc::SkottieMarker> markers = { |
| {"_CrOS_Marker_Throttled_10fps", 0.f, 0.2f}, |
| }; |
| auto animation_1 = std::make_unique<lottie::Animation>( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| auto animation_2 = std::make_unique<lottie::Animation>( |
| base::MakeRefCounted<TestSkottieWrapper>(base::Seconds(1), markers)); |
| AmbientAnimationFrameRateController ambient_frame_rate_controller( |
| Shell::Get()->frame_throttling_controller()); |
| animation_1->Start( |
| lottie::Animation::PlaybackConfig::CreateDefault(*animation_1)); |
| animation_2->Start( |
| lottie::Animation::PlaybackConfig::CreateDefault(*animation_2)); |
| ambient_frame_rate_controller.AddWindowToThrottle(window_1.get(), |
| animation_1.get()); |
| ambient_frame_rate_controller.AddWindowToThrottle(window_2.get(), |
| animation_2.get()); |
| |
| // T: 0 |
| AdvanceTimeAndPaint({animation_1.get(), animation_2.get()}, .0f); |
| ASSERT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId1, kTestFrameSinkId2)); |
| |
| window_1.reset(); |
| animation_1.reset(); |
| // T: 0.05f |
| AdvanceTimeAndPaint({animation_2.get()}, .05f); |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| UnorderedElementsAre(kTestFrameSinkId2)); |
| |
| // Try the reverse destruction order from before. |
| animation_2.reset(); |
| window_2.reset(); |
| // The frame rate should be restored to default when the animation is |
| // over. |
| EXPECT_THAT( |
| Shell::Get()->frame_throttling_controller()->GetFrameSinkIdsToThrottle(), |
| IsEmpty()); |
| } |
| |
| } // namespace |
| } // namespace ash |