File: ambient_animation_frame_rate_controller.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (223 lines) | stat: -rw-r--r-- 8,508 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// 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 "ash/frame_throttler/frame_throttling_controller.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "cc/paint/skottie_wrapper.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "ui/aura/window.h"

namespace ash {
namespace {

AmbientAnimationFrameRateSchedule BuildSchedule(lottie::Animation* animation) {
  DCHECK(animation);
  AmbientAnimationFrameRateSchedule schedule =
      BuildAmbientAnimationFrameRateSchedule(
          animation->skottie()->GetAllMarkers());
  if (schedule.empty()) {
    // This means the animation file needs to be fixed. This should never happen
    // in the field in theory, but just in case, resort to the default frame
    // rate schedule (no throttling).
    LOG(DFATAL) << "Ambient animation has invalid frame rate markers.";
    schedule = BuildDefaultFrameRateSchedule();
  }
  return schedule;
}

}  // namespace

AmbientAnimationFrameRateController::AmbientAnimationFrameRateController(
    FrameThrottlingController* frame_throttling_controller)
    : frame_throttling_controller_(frame_throttling_controller),
      current_section_(schedule_.end()) {
  DCHECK(frame_throttling_controller_);
}

AmbientAnimationFrameRateController::~AmbientAnimationFrameRateController() {
  ThrottleFrameRate(kDefaultFrameInterval);
}

void AmbientAnimationFrameRateController::AnimationFramePainted(
    const lottie::Animation* animation,
    float) {
  if (animation != tracking_animation_.get())
    return;

  auto new_current_section = FindCurrentSection();
  if (new_current_section == current_section_)
    return;

  DVLOG(1) << "Found new frame rate section: " << *new_current_section;
  current_section_ = new_current_section;
  ThrottleFrameRateForCurrentSection();
}

// Note either AnimationIsDeleting() or OnWindowDestroying() could come first.
// Whichever one does should cause both the window and animation to be removed
// from book-keeping entirely.
void AmbientAnimationFrameRateController::AnimationIsDeleting(
    const lottie::Animation* deleting_animation) {
  aura::Window* window_to_remove = nullptr;
  for (const auto& [window, animation] : windows_to_throttle_) {
    if (animation == deleting_animation) {
      window_to_remove = window;
      break;
    }
  }
  DCHECK(window_to_remove);
  RemoveWindowToThrottle(window_to_remove);
}

void AmbientAnimationFrameRateController::OnWindowDestroying(
    aura::Window* window) {
  RemoveWindowToThrottle(window);
}

void AmbientAnimationFrameRateController::AddWindowToThrottle(
    aura::Window* window,
    lottie::Animation* animation) {
  DCHECK(window);
  DCHECK(window->GetFrameSinkId().is_valid())
      << "Window missing frame sink id: " << window->GetId();
  DCHECK(animation);
  if (windows_to_throttle_.contains(window))
    return;

  if (tracking_animation_) {
    DCHECK_EQ(tracking_animation_->skottie()->id(), animation->skottie()->id())
        << "All lottie animations must have the same json content";
  }
  windows_to_throttle_[window] = animation;
  TrySetNewTrackingAnimation();
  // Always observe even if the incoming |animation| is not the
  // |tracking_animation_| so that we get notified when AnimationIsDeleting().
  animation_observations_.AddObservation(animation);
  window_observations_.AddObservation(window);
  // Update throttling with the expanded list of |windows_to_throttle_|.
  ThrottleFrameRateForCurrentSection();
}

AmbientAnimationFrameRateScheduleIterator
AmbientAnimationFrameRateController::FindCurrentSection() const {
  DCHECK(tracking_animation_);
  std::optional<float> current_progress =
      tracking_animation_->GetCurrentProgress();
  if (!current_progress) {
    DVLOG(1) << "Animation is not playing currently. Cannot map timestamp to "
                "scheduled frame rate.";
    return schedule_.end();
  }

  // Always start searching from the last section the animation was on. Since
  // animations progress linearly in small increments, most of the time, the
  // |current_section_| will not change.
  AmbientAnimationFrameRateScheduleIterator new_current_section =
      current_section_ == schedule_.end() ? schedule_.begin()
                                          : current_section_;
  AmbientAnimationFrameRateScheduleIterator orig_current_section =
      new_current_section;
  while (!new_current_section->Contains(*current_progress)) {
    ++new_current_section;
    // Note the AmbientAnimationFrameRateSchedule by design is contiguous. Every
    // possible timestamp falls within a section of the schedule, so it's
    // impossible to infinite loop here.
    DCHECK(new_current_section != orig_current_section)
        << "Infinite loop detected. AmbientAnimationFrameRateSchedule has gap "
           "and is malformed.";
    // The schedule is cyclic. Loop back to the beginning.
    if (new_current_section == schedule_.end())
      new_current_section = schedule_.begin();
  }
  return new_current_section;
}

void AmbientAnimationFrameRateController::ThrottleFrameRateForCurrentSection() {
  // TODO(esum): There is a corner case not accounted for yet. Say the frame
  // interval is large (1 second). And say we throttle to 1 fps at time 10 sec,
  // and we need to restore the default 60 fps at 19.1 sec. We will get:
  // AnimationFramePainted(10 sec)
  // AnimationFramePainted(11 sec)
  // ...
  // AnimationFramePainted(19 sec) - Still not past the 19.1 second mark
  // AnimationFramePainted(20 sec) - Switch back to 60 fps here.
  //
  // This is bad because we switch back .9 seconds too late, which is a lot.
  // To fix this, we could start a timer that fires when the current section is
  // over and we need to switch to the new frame rate. It currently is not a
  // problem because in practice, the frame rates never get small enough to
  // notice this issue. But it will be a problem with the slideshow lottie
  // animation.
  if (current_section_ != schedule_.end())
    ThrottleFrameRate(current_section_->frame_interval);
}

void AmbientAnimationFrameRateController::ThrottleFrameRate(
    base::TimeDelta frame_interval) {
  std::vector<raw_ptr<aura::Window, VectorExperimental>> windows_as_vector;
  for (const auto& [window, animation] : windows_to_throttle_) {
    windows_as_vector.push_back(window);
  }

  if (frame_interval == kDefaultFrameInterval) {
    VLOG(1) << "Resetting frame rate to default";
    frame_throttling_controller_->EndThrottling();
  } else {
    DVLOG(1) << "Throttling frame rate to " << frame_interval.ToHz() << "hz";
    frame_throttling_controller_->StartThrottling(windows_as_vector,
                                                  frame_interval);
  }
}

void AmbientAnimationFrameRateController::RemoveWindowToThrottle(
    aura::Window* window) {
  window_observations_.RemoveObservation(window);
  auto iter = windows_to_throttle_.find(window);
  DCHECK(iter != windows_to_throttle_.end());
  lottie::Animation* unregistered_animation = iter->second;
  animation_observations_.RemoveObservation(unregistered_animation);
  windows_to_throttle_.erase(iter);
  if (unregistered_animation != tracking_animation_)
    return;

  tracking_animation_ = nullptr;
  TrySetNewTrackingAnimation();
  if (tracking_animation_) {
    DVLOG(1)
        << "Observing new lottie Animation. Resetting frame rate throttling.";
    ThrottleFrameRateForCurrentSection();
  } else {
    DVLOG(1) << "No more lottie animations are active. Restoring default frame "
                "rate and going idle...";
    ThrottleFrameRate(kDefaultFrameInterval);
  }
}

void AmbientAnimationFrameRateController::TrySetNewTrackingAnimation() {
  if (tracking_animation_) {
    DVLOG(4) << "Tracking animation already set.";
    return;
  }

  if (windows_to_throttle_.empty()) {
    DVLOG(4) << "No lottie animations to track. Going idle.";
    return;
  }

  tracking_animation_ = windows_to_throttle_.begin()->second.get();
  schedule_ = BuildSchedule(tracking_animation_.get());
  // Set |current_section_| to be |schedule_.end()| temporarily so that
  // FindCurrentSection() knows to start searching the new schedule from
  // scratch.
  current_section_ = schedule_.end();
  current_section_ = FindCurrentSection();
}

}  // namespace ash