OLD | NEW |
1 // Copyright 2011 The Chromium Authors. All rights reserved. | 1 // Copyright 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "cc/scheduler/scheduler_state_machine.h" | 5 #include "cc/scheduler/scheduler_state_machine.h" |
6 | 6 |
7 #include "base/format_macros.h" | 7 #include "base/format_macros.h" |
8 #include "base/logging.h" | 8 #include "base/logging.h" |
9 #include "base/strings/stringprintf.h" | 9 #include "base/strings/stringprintf.h" |
10 #include "base/trace_event/trace_event.h" | 10 #include "base/trace_event/trace_event.h" |
11 #include "base/trace_event/trace_event_argument.h" | 11 #include "base/trace_event/trace_event_argument.h" |
12 #include "base/values.h" | 12 #include "base/values.h" |
13 #include "ui/gfx/frame_time.h" | 13 #include "ui/gfx/frame_time.h" |
14 | 14 |
15 namespace cc { | 15 namespace cc { |
16 | 16 |
17 SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) | 17 SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings) |
18 : settings_(settings), | 18 : settings_(settings), |
19 output_surface_state_(OUTPUT_SURFACE_LOST), | 19 output_surface_state_(OUTPUT_SURFACE_LOST), |
20 begin_impl_frame_state_(BEGIN_IMPL_FRAME_STATE_IDLE), | 20 begin_impl_frame_state_(BEGIN_IMPL_FRAME_STATE_IDLE), |
21 commit_state_(COMMIT_STATE_IDLE), | 21 commit_state_(COMMIT_STATE_IDLE), |
22 forced_redraw_state_(FORCED_REDRAW_STATE_IDLE), | 22 forced_redraw_state_(FORCED_REDRAW_STATE_IDLE), |
23 commit_count_(0), | 23 commit_count_(0), |
24 current_frame_number_(0), | 24 current_frame_number_(0), |
25 last_frame_number_animate_performed_(-1), | 25 last_frame_number_animate_performed_(-1), |
26 last_frame_number_swap_performed_(-1), | 26 last_frame_number_swap_performed_(-1), |
27 last_frame_number_swap_requested_(-1), | 27 last_frame_number_swap_requested_(-1), |
28 last_frame_number_begin_main_frame_sent_(-1), | 28 last_frame_number_begin_main_frame_sent_(-1), |
| 29 animate_funnel_(false), |
| 30 perform_swap_funnel_(false), |
| 31 request_swap_funnel_(false), |
| 32 send_begin_main_frame_funnel_(false), |
29 prepare_tiles_funnel_(0), | 33 prepare_tiles_funnel_(0), |
30 consecutive_checkerboard_animations_(0), | 34 consecutive_checkerboard_animations_(0), |
31 max_pending_swaps_(1), | 35 max_pending_swaps_(1), |
32 pending_swaps_(0), | 36 pending_swaps_(0), |
33 needs_redraw_(false), | 37 needs_redraw_(false), |
34 needs_animate_(false), | 38 needs_animate_(false), |
35 needs_prepare_tiles_(false), | 39 needs_prepare_tiles_(false), |
36 needs_commit_(false), | 40 needs_commit_(false), |
37 inside_poll_for_anticipated_draw_triggers_(false), | 41 inside_poll_for_anticipated_draw_triggers_(false), |
38 visible_(false), | 42 visible_(false), |
39 can_start_(false), | 43 can_start_(false), |
40 can_draw_(false), | 44 can_draw_(false), |
41 has_pending_tree_(false), | 45 has_pending_tree_(false), |
42 pending_tree_is_ready_for_activation_(false), | 46 pending_tree_is_ready_for_activation_(false), |
43 active_tree_needs_first_draw_(false), | 47 active_tree_needs_first_draw_(false), |
44 did_commit_after_animating_(false), | |
45 did_create_and_initialize_first_output_surface_(false), | 48 did_create_and_initialize_first_output_surface_(false), |
46 impl_latency_takes_priority_(false), | 49 impl_latency_takes_priority_(false), |
47 skip_next_begin_main_frame_to_reduce_latency_(false), | 50 skip_next_begin_main_frame_to_reduce_latency_(false), |
48 skip_begin_main_frame_to_reduce_latency_(false), | 51 skip_begin_main_frame_to_reduce_latency_(false), |
49 continuous_painting_(false), | 52 continuous_painting_(false), |
50 children_need_begin_frames_(false), | 53 children_need_begin_frames_(false), |
51 defer_commits_(false) { | 54 defer_commits_(false), |
| 55 last_commit_had_no_updates_(false) { |
52 } | 56 } |
53 | 57 |
54 const char* SchedulerStateMachine::OutputSurfaceStateToString( | 58 const char* SchedulerStateMachine::OutputSurfaceStateToString( |
55 OutputSurfaceState state) { | 59 OutputSurfaceState state) { |
56 switch (state) { | 60 switch (state) { |
57 case OUTPUT_SURFACE_ACTIVE: | 61 case OUTPUT_SURFACE_ACTIVE: |
58 return "OUTPUT_SURFACE_ACTIVE"; | 62 return "OUTPUT_SURFACE_ACTIVE"; |
59 case OUTPUT_SURFACE_LOST: | 63 case OUTPUT_SURFACE_LOST: |
60 return "OUTPUT_SURFACE_LOST"; | 64 return "OUTPUT_SURFACE_LOST"; |
61 case OUTPUT_SURFACE_CREATING: | 65 case OUTPUT_SURFACE_CREATING: |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 return "ACTION_PREPARE_TILES"; | 148 return "ACTION_PREPARE_TILES"; |
145 } | 149 } |
146 NOTREACHED(); | 150 NOTREACHED(); |
147 return "???"; | 151 return "???"; |
148 } | 152 } |
149 | 153 |
150 scoped_refptr<base::trace_event::ConvertableToTraceFormat> | 154 scoped_refptr<base::trace_event::ConvertableToTraceFormat> |
151 SchedulerStateMachine::AsValue() const { | 155 SchedulerStateMachine::AsValue() const { |
152 scoped_refptr<base::trace_event::TracedValue> state = | 156 scoped_refptr<base::trace_event::TracedValue> state = |
153 new base::trace_event::TracedValue(); | 157 new base::trace_event::TracedValue(); |
154 AsValueInto(state.get(), gfx::FrameTime::Now()); | 158 AsValueInto(state.get()); |
155 return state; | 159 return state; |
156 } | 160 } |
157 | 161 |
158 void SchedulerStateMachine::AsValueInto(base::trace_event::TracedValue* state, | 162 void SchedulerStateMachine::AsValueInto( |
159 base::TimeTicks now) const { | 163 base::trace_event::TracedValue* state) const { |
160 state->BeginDictionary("major_state"); | 164 state->BeginDictionary("major_state"); |
161 state->SetString("next_action", ActionToString(NextAction())); | 165 state->SetString("next_action", ActionToString(NextAction())); |
162 state->SetString("begin_impl_frame_state", | 166 state->SetString("begin_impl_frame_state", |
163 BeginImplFrameStateToString(begin_impl_frame_state_)); | 167 BeginImplFrameStateToString(begin_impl_frame_state_)); |
164 state->SetString("commit_state", CommitStateToString(commit_state_)); | 168 state->SetString("commit_state", CommitStateToString(commit_state_)); |
165 state->SetString("output_surface_state_", | 169 state->SetString("output_surface_state_", |
166 OutputSurfaceStateToString(output_surface_state_)); | 170 OutputSurfaceStateToString(output_surface_state_)); |
167 state->SetString("forced_redraw_state", | 171 state->SetString("forced_redraw_state", |
168 ForcedRedrawOnTimeoutStateToString(forced_redraw_state_)); | 172 ForcedRedrawOnTimeoutStateToString(forced_redraw_state_)); |
169 state->EndDictionary(); | 173 state->EndDictionary(); |
170 | 174 |
171 state->BeginDictionary("major_timestamps_in_ms"); | |
172 state->SetDouble("0_interval", | |
173 begin_impl_frame_args_.interval.InMicroseconds() / 1000.0L); | |
174 state->SetDouble( | |
175 "1_now_to_deadline", | |
176 (begin_impl_frame_args_.deadline - now).InMicroseconds() / 1000.0L); | |
177 state->SetDouble( | |
178 "2_frame_time_to_now", | |
179 (now - begin_impl_frame_args_.frame_time).InMicroseconds() / 1000.0L); | |
180 state->SetDouble("3_frame_time_to_deadline", | |
181 (begin_impl_frame_args_.deadline - | |
182 begin_impl_frame_args_.frame_time).InMicroseconds() / | |
183 1000.0L); | |
184 state->SetDouble("4_now", | |
185 (now - base::TimeTicks()).InMicroseconds() / 1000.0L); | |
186 state->SetDouble( | |
187 "5_frame_time", | |
188 (begin_impl_frame_args_.frame_time - base::TimeTicks()).InMicroseconds() / | |
189 1000.0L); | |
190 state->SetDouble( | |
191 "6_deadline", | |
192 (begin_impl_frame_args_.deadline - base::TimeTicks()).InMicroseconds() / | |
193 1000.0L); | |
194 state->EndDictionary(); | |
195 | |
196 state->BeginDictionary("minor_state"); | 175 state->BeginDictionary("minor_state"); |
197 state->SetInteger("commit_count", commit_count_); | 176 state->SetInteger("commit_count", commit_count_); |
198 state->SetInteger("current_frame_number", current_frame_number_); | 177 state->SetInteger("current_frame_number", current_frame_number_); |
199 | |
200 state->SetInteger("last_frame_number_animate_performed", | 178 state->SetInteger("last_frame_number_animate_performed", |
201 last_frame_number_animate_performed_); | 179 last_frame_number_animate_performed_); |
202 state->SetInteger("last_frame_number_swap_performed", | 180 state->SetInteger("last_frame_number_swap_performed", |
203 last_frame_number_swap_performed_); | 181 last_frame_number_swap_performed_); |
204 state->SetInteger("last_frame_number_swap_requested", | 182 state->SetInteger("last_frame_number_swap_requested", |
205 last_frame_number_swap_requested_); | 183 last_frame_number_swap_requested_); |
206 state->SetInteger("last_frame_number_begin_main_frame_sent", | 184 state->SetInteger("last_frame_number_begin_main_frame_sent", |
207 last_frame_number_begin_main_frame_sent_); | 185 last_frame_number_begin_main_frame_sent_); |
208 | 186 state->SetBoolean("funnel: animate_funnel", animate_funnel_); |
209 state->SetInteger("prepare_tiles_funnel", prepare_tiles_funnel_); | 187 state->SetBoolean("funnel: perform_swap_funnel", perform_swap_funnel_); |
| 188 state->SetBoolean("funnel: request_swap_funnel", request_swap_funnel_); |
| 189 state->SetBoolean("funnel: send_begin_main_frame_funnel", |
| 190 send_begin_main_frame_funnel_); |
| 191 state->SetInteger("funnel: prepare_tiles_funnel", prepare_tiles_funnel_); |
210 state->SetInteger("consecutive_checkerboard_animations", | 192 state->SetInteger("consecutive_checkerboard_animations", |
211 consecutive_checkerboard_animations_); | 193 consecutive_checkerboard_animations_); |
212 state->SetInteger("max_pending_swaps_", max_pending_swaps_); | 194 state->SetInteger("max_pending_swaps_", max_pending_swaps_); |
213 state->SetInteger("pending_swaps_", pending_swaps_); | 195 state->SetInteger("pending_swaps_", pending_swaps_); |
214 state->SetBoolean("needs_redraw", needs_redraw_); | 196 state->SetBoolean("needs_redraw", needs_redraw_); |
215 state->SetBoolean("needs_animate_", needs_animate_); | 197 state->SetBoolean("needs_animate_", needs_animate_); |
216 state->SetBoolean("needs_prepare_tiles", needs_prepare_tiles_); | 198 state->SetBoolean("needs_prepare_tiles", needs_prepare_tiles_); |
217 state->SetBoolean("needs_commit", needs_commit_); | 199 state->SetBoolean("needs_commit", needs_commit_); |
218 state->SetBoolean("visible", visible_); | 200 state->SetBoolean("visible", visible_); |
219 state->SetBoolean("can_start", can_start_); | 201 state->SetBoolean("can_start", can_start_); |
220 state->SetBoolean("can_draw", can_draw_); | 202 state->SetBoolean("can_draw", can_draw_); |
221 state->SetBoolean("has_pending_tree", has_pending_tree_); | 203 state->SetBoolean("has_pending_tree", has_pending_tree_); |
222 state->SetBoolean("pending_tree_is_ready_for_activation", | 204 state->SetBoolean("pending_tree_is_ready_for_activation", |
223 pending_tree_is_ready_for_activation_); | 205 pending_tree_is_ready_for_activation_); |
224 state->SetBoolean("active_tree_needs_first_draw", | 206 state->SetBoolean("active_tree_needs_first_draw", |
225 active_tree_needs_first_draw_); | 207 active_tree_needs_first_draw_); |
226 state->SetBoolean("did_commit_after_animating", did_commit_after_animating_); | |
227 state->SetBoolean("did_create_and_initialize_first_output_surface", | 208 state->SetBoolean("did_create_and_initialize_first_output_surface", |
228 did_create_and_initialize_first_output_surface_); | 209 did_create_and_initialize_first_output_surface_); |
229 state->SetBoolean("impl_latency_takes_priority", | 210 state->SetBoolean("impl_latency_takes_priority", |
230 impl_latency_takes_priority_); | 211 impl_latency_takes_priority_); |
231 state->SetBoolean("main_thread_is_in_high_latency_mode", | 212 state->SetBoolean("main_thread_is_in_high_latency_mode", |
232 MainThreadIsInHighLatencyMode()); | 213 MainThreadIsInHighLatencyMode()); |
233 state->SetBoolean("skip_begin_main_frame_to_reduce_latency", | 214 state->SetBoolean("skip_begin_main_frame_to_reduce_latency", |
234 skip_begin_main_frame_to_reduce_latency_); | 215 skip_begin_main_frame_to_reduce_latency_); |
235 state->SetBoolean("skip_next_begin_main_frame_to_reduce_latency", | 216 state->SetBoolean("skip_next_begin_main_frame_to_reduce_latency", |
236 skip_next_begin_main_frame_to_reduce_latency_); | 217 skip_next_begin_main_frame_to_reduce_latency_); |
237 state->SetBoolean("continuous_painting", continuous_painting_); | 218 state->SetBoolean("continuous_painting", continuous_painting_); |
238 state->SetBoolean("children_need_begin_frames", children_need_begin_frames_); | 219 state->SetBoolean("children_need_begin_frames", children_need_begin_frames_); |
239 state->SetBoolean("defer_commits", defer_commits_); | 220 state->SetBoolean("defer_commits", defer_commits_); |
240 state->EndDictionary(); | 221 state->EndDictionary(); |
241 } | 222 } |
242 | 223 |
243 void SchedulerStateMachine::AdvanceCurrentFrameNumber() { | 224 void SchedulerStateMachine::AdvanceCurrentFrameNumber() { |
244 current_frame_number_++; | 225 current_frame_number_++; |
245 | 226 |
| 227 animate_funnel_ = false; |
| 228 perform_swap_funnel_ = false; |
| 229 request_swap_funnel_ = false; |
| 230 send_begin_main_frame_funnel_ = false; |
| 231 |
246 // "Drain" the PrepareTiles funnel. | 232 // "Drain" the PrepareTiles funnel. |
247 if (prepare_tiles_funnel_ > 0) | 233 if (prepare_tiles_funnel_ > 0) |
248 prepare_tiles_funnel_--; | 234 prepare_tiles_funnel_--; |
249 | 235 |
250 skip_begin_main_frame_to_reduce_latency_ = | 236 skip_begin_main_frame_to_reduce_latency_ = |
251 skip_next_begin_main_frame_to_reduce_latency_; | 237 skip_next_begin_main_frame_to_reduce_latency_; |
252 skip_next_begin_main_frame_to_reduce_latency_ = false; | 238 skip_next_begin_main_frame_to_reduce_latency_ = false; |
253 } | 239 } |
254 | 240 |
255 bool SchedulerStateMachine::HasAnimatedThisFrame() const { | |
256 return last_frame_number_animate_performed_ == current_frame_number_; | |
257 } | |
258 | |
259 bool SchedulerStateMachine::HasSentBeginMainFrameThisFrame() const { | |
260 return current_frame_number_ == | |
261 last_frame_number_begin_main_frame_sent_; | |
262 } | |
263 | |
264 bool SchedulerStateMachine::HasSwappedThisFrame() const { | |
265 return current_frame_number_ == last_frame_number_swap_performed_; | |
266 } | |
267 | |
268 bool SchedulerStateMachine::HasRequestedSwapThisFrame() const { | |
269 return current_frame_number_ == last_frame_number_swap_requested_; | |
270 } | |
271 | |
272 bool SchedulerStateMachine::PendingDrawsShouldBeAborted() const { | 241 bool SchedulerStateMachine::PendingDrawsShouldBeAborted() const { |
273 // These are all the cases where we normally cannot or do not want to draw | 242 // These are all the cases where we normally cannot or do not want to draw |
274 // but, if needs_redraw_ is true and we do not draw to make forward progress, | 243 // but, if needs_redraw_ is true and we do not draw to make forward progress, |
275 // we might deadlock with the main thread. | 244 // we might deadlock with the main thread. |
276 // This should be a superset of PendingActivationsShouldBeForced() since | 245 // This should be a superset of PendingActivationsShouldBeForced() since |
277 // activation of the pending tree is blocked by drawing of the active tree and | 246 // activation of the pending tree is blocked by drawing of the active tree and |
278 // the main thread might be blocked on activation of the most recent commit. | 247 // the main thread might be blocked on activation of the most recent commit. |
279 if (PendingActivationsShouldBeForced()) | 248 if (PendingActivationsShouldBeForced()) |
280 return true; | 249 return true; |
281 | 250 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
334 | 303 |
335 bool SchedulerStateMachine::ShouldDraw() const { | 304 bool SchedulerStateMachine::ShouldDraw() const { |
336 // If we need to abort draws, we should do so ASAP since the draw could | 305 // If we need to abort draws, we should do so ASAP since the draw could |
337 // be blocking other important actions (like output surface initialization), | 306 // be blocking other important actions (like output surface initialization), |
338 // from occuring. If we are waiting for the first draw, then perfom the | 307 // from occuring. If we are waiting for the first draw, then perfom the |
339 // aborted draw to keep things moving. If we are not waiting for the first | 308 // aborted draw to keep things moving. If we are not waiting for the first |
340 // draw however, we don't want to abort for no reason. | 309 // draw however, we don't want to abort for no reason. |
341 if (PendingDrawsShouldBeAborted()) | 310 if (PendingDrawsShouldBeAborted()) |
342 return active_tree_needs_first_draw_; | 311 return active_tree_needs_first_draw_; |
343 | 312 |
| 313 // Do not draw too many times in a single frame. It's okay that we don't check |
| 314 // this before checking for aborted draws because aborted draws do not request |
| 315 // a swap. |
| 316 if (request_swap_funnel_) |
| 317 return false; |
| 318 |
344 // Don't draw if we are waiting on the first commit after a surface. | 319 // Don't draw if we are waiting on the first commit after a surface. |
345 if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE) | 320 if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE) |
346 return false; | 321 return false; |
347 | 322 |
348 // If a commit has occurred after the animate call, we need to call animate | |
349 // again before we should draw. | |
350 if (did_commit_after_animating_) | |
351 return false; | |
352 | |
353 // After this line, we only want to send a swap request once per frame. | |
354 if (HasRequestedSwapThisFrame()) | |
355 return false; | |
356 | |
357 // Do not queue too many swaps. | 323 // Do not queue too many swaps. |
358 if (pending_swaps_ >= max_pending_swaps_) | 324 if (pending_swaps_ >= max_pending_swaps_) |
359 return false; | 325 return false; |
360 | 326 |
361 // Except for the cases above, do not draw outside of the BeginImplFrame | 327 // Except for the cases above, do not draw outside of the BeginImplFrame |
362 // deadline. | 328 // deadline. |
363 if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) | 329 if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) |
364 return false; | 330 return false; |
365 | 331 |
366 // Only handle forced redraws due to timeouts on the regular deadline. | 332 // Only handle forced redraws due to timeouts on the regular deadline. |
(...skipping 16 matching lines...) Expand all Loading... |
383 | 349 |
384 // If we want to force activation, do so ASAP. | 350 // If we want to force activation, do so ASAP. |
385 if (PendingActivationsShouldBeForced()) | 351 if (PendingActivationsShouldBeForced()) |
386 return true; | 352 return true; |
387 | 353 |
388 // At this point, only activate if we are ready to activate. | 354 // At this point, only activate if we are ready to activate. |
389 return pending_tree_is_ready_for_activation_; | 355 return pending_tree_is_ready_for_activation_; |
390 } | 356 } |
391 | 357 |
392 bool SchedulerStateMachine::ShouldAnimate() const { | 358 bool SchedulerStateMachine::ShouldAnimate() const { |
| 359 // Do not animate too many times in a single frame. |
| 360 if (animate_funnel_) |
| 361 return false; |
| 362 |
393 // Don't animate if we are waiting on the first commit after a surface. | 363 // Don't animate if we are waiting on the first commit after a surface. |
394 if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE) | 364 if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE) |
395 return false; | 365 return false; |
396 | 366 |
397 // If a commit occurred after our last call, we need to do animation again. | |
398 if (HasAnimatedThisFrame() && !did_commit_after_animating_) | |
399 return false; | |
400 | |
401 if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING && | 367 if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING && |
402 begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) | 368 begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) |
403 return false; | 369 return false; |
404 | 370 |
405 return needs_redraw_ || needs_animate_; | 371 return needs_redraw_ || needs_animate_; |
406 } | 372 } |
407 | 373 |
408 bool SchedulerStateMachine::CouldSendBeginMainFrame() const { | 374 bool SchedulerStateMachine::CouldSendBeginMainFrame() const { |
| 375 // Do not send begin main frame too many times in a single frame. |
| 376 if (send_begin_main_frame_funnel_) |
| 377 return false; |
| 378 |
409 if (!needs_commit_) | 379 if (!needs_commit_) |
410 return false; | 380 return false; |
411 | 381 |
412 // We can not perform commits if we are not visible. | 382 // We can not perform commits if we are not visible. |
413 if (!visible_) | 383 if (!visible_) |
414 return false; | 384 return false; |
415 | 385 |
416 // Do not make a new commits when it is deferred. | 386 // Do not make a new commits when it is deferred. |
417 if (defer_commits_) | 387 if (defer_commits_) |
418 return false; | 388 return false; |
(...skipping 23 matching lines...) Expand all Loading... |
442 // thread isn't consuming user input. | 412 // thread isn't consuming user input. |
443 if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE && | 413 if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE && |
444 BeginFrameNeeded()) | 414 BeginFrameNeeded()) |
445 return false; | 415 return false; |
446 | 416 |
447 // We need a new commit for the forced redraw. This honors the | 417 // We need a new commit for the forced redraw. This honors the |
448 // single commit per interval because the result will be swapped to screen. | 418 // single commit per interval because the result will be swapped to screen. |
449 if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) | 419 if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) |
450 return true; | 420 return true; |
451 | 421 |
452 // After this point, we only start a commit once per frame. | |
453 if (HasSentBeginMainFrameThisFrame()) | |
454 return false; | |
455 | |
456 // We shouldn't normally accept commits if there isn't an OutputSurface. | 422 // We shouldn't normally accept commits if there isn't an OutputSurface. |
457 if (!HasInitializedOutputSurface()) | 423 if (!HasInitializedOutputSurface()) |
458 return false; | 424 return false; |
459 | 425 |
460 // SwapAck throttle the BeginMainFrames unless we just swapped. | 426 // SwapAck throttle the BeginMainFrames unless we just swapped. |
461 // TODO(brianderson): Remove this restriction to improve throughput. | 427 // TODO(brianderson): Remove this restriction to improve throughput. |
462 bool just_swapped_in_deadline = | 428 bool just_swapped_in_deadline = |
463 begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE && | 429 begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE && |
464 HasSwappedThisFrame(); | 430 perform_swap_funnel_; |
465 if (pending_swaps_ >= max_pending_swaps_ && !just_swapped_in_deadline) | 431 if (pending_swaps_ >= max_pending_swaps_ && !just_swapped_in_deadline) |
466 return false; | 432 return false; |
467 | 433 |
468 if (skip_begin_main_frame_to_reduce_latency_) | 434 if (skip_begin_main_frame_to_reduce_latency_) |
469 return false; | 435 return false; |
470 | 436 |
471 return true; | 437 return true; |
472 } | 438 } |
473 | 439 |
474 bool SchedulerStateMachine::ShouldCommit() const { | 440 bool SchedulerStateMachine::ShouldCommit() const { |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
531 void SchedulerStateMachine::UpdateState(Action action) { | 497 void SchedulerStateMachine::UpdateState(Action action) { |
532 switch (action) { | 498 switch (action) { |
533 case ACTION_NONE: | 499 case ACTION_NONE: |
534 return; | 500 return; |
535 | 501 |
536 case ACTION_ACTIVATE_SYNC_TREE: | 502 case ACTION_ACTIVATE_SYNC_TREE: |
537 UpdateStateOnActivation(); | 503 UpdateStateOnActivation(); |
538 return; | 504 return; |
539 | 505 |
540 case ACTION_ANIMATE: | 506 case ACTION_ANIMATE: |
| 507 DCHECK(!animate_funnel_); |
541 last_frame_number_animate_performed_ = current_frame_number_; | 508 last_frame_number_animate_performed_ = current_frame_number_; |
| 509 animate_funnel_ = true; |
542 needs_animate_ = false; | 510 needs_animate_ = false; |
543 did_commit_after_animating_ = false; | |
544 // TODO(skyostil): Instead of assuming this, require the client to tell | 511 // TODO(skyostil): Instead of assuming this, require the client to tell |
545 // us. | 512 // us. |
546 SetNeedsRedraw(); | 513 SetNeedsRedraw(); |
547 return; | 514 return; |
548 | 515 |
549 case ACTION_SEND_BEGIN_MAIN_FRAME: | 516 case ACTION_SEND_BEGIN_MAIN_FRAME: |
550 DCHECK(!has_pending_tree_ || | 517 DCHECK(!has_pending_tree_ || |
551 settings_.main_frame_before_activation_enabled); | 518 settings_.main_frame_before_activation_enabled); |
552 DCHECK(visible_); | 519 DCHECK(visible_); |
| 520 DCHECK(!send_begin_main_frame_funnel_); |
553 commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT; | 521 commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT; |
554 needs_commit_ = false; | 522 needs_commit_ = false; |
| 523 send_begin_main_frame_funnel_ = true; |
555 last_frame_number_begin_main_frame_sent_ = | 524 last_frame_number_begin_main_frame_sent_ = |
556 current_frame_number_; | 525 current_frame_number_; |
557 return; | 526 return; |
558 | 527 |
559 case ACTION_COMMIT: { | 528 case ACTION_COMMIT: { |
560 bool commit_has_no_updates = false; | 529 bool commit_has_no_updates = false; |
561 UpdateStateOnCommit(commit_has_no_updates); | 530 UpdateStateOnCommit(commit_has_no_updates); |
562 return; | 531 return; |
563 } | 532 } |
564 | 533 |
(...skipping 24 matching lines...) Expand all Loading... |
589 | 558 |
590 case ACTION_PREPARE_TILES: | 559 case ACTION_PREPARE_TILES: |
591 UpdateStateOnPrepareTiles(); | 560 UpdateStateOnPrepareTiles(); |
592 return; | 561 return; |
593 } | 562 } |
594 } | 563 } |
595 | 564 |
596 void SchedulerStateMachine::UpdateStateOnCommit(bool commit_has_no_updates) { | 565 void SchedulerStateMachine::UpdateStateOnCommit(bool commit_has_no_updates) { |
597 commit_count_++; | 566 commit_count_++; |
598 | 567 |
599 if (!commit_has_no_updates && HasAnimatedThisFrame()) | 568 if (!commit_has_no_updates) |
600 did_commit_after_animating_ = true; | 569 animate_funnel_ = false; |
601 | 570 |
602 if (commit_has_no_updates || settings_.main_frame_before_activation_enabled) { | 571 if (commit_has_no_updates || settings_.main_frame_before_activation_enabled) { |
603 commit_state_ = COMMIT_STATE_IDLE; | 572 commit_state_ = COMMIT_STATE_IDLE; |
604 } else if (settings_.impl_side_painting) { | 573 } else if (settings_.impl_side_painting) { |
605 commit_state_ = COMMIT_STATE_WAITING_FOR_ACTIVATION; | 574 commit_state_ = COMMIT_STATE_WAITING_FOR_ACTIVATION; |
606 } else { | 575 } else { |
607 commit_state_ = settings_.main_thread_should_always_be_low_latency | 576 commit_state_ = settings_.main_thread_should_always_be_low_latency |
608 ? COMMIT_STATE_WAITING_FOR_DRAW | 577 ? COMMIT_STATE_WAITING_FOR_DRAW |
609 : COMMIT_STATE_IDLE; | 578 : COMMIT_STATE_IDLE; |
610 } | 579 } |
(...skipping 27 matching lines...) Expand all Loading... |
638 forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)) { | 607 forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)) { |
639 needs_redraw_ = true; | 608 needs_redraw_ = true; |
640 active_tree_needs_first_draw_ = true; | 609 active_tree_needs_first_draw_ = true; |
641 } | 610 } |
642 | 611 |
643 // This post-commit work is common to both completed and aborted commits. | 612 // This post-commit work is common to both completed and aborted commits. |
644 pending_tree_is_ready_for_activation_ = false; | 613 pending_tree_is_ready_for_activation_ = false; |
645 | 614 |
646 if (continuous_painting_) | 615 if (continuous_painting_) |
647 needs_commit_ = true; | 616 needs_commit_ = true; |
| 617 last_commit_had_no_updates_ = commit_has_no_updates; |
648 } | 618 } |
649 | 619 |
650 void SchedulerStateMachine::UpdateStateOnActivation() { | 620 void SchedulerStateMachine::UpdateStateOnActivation() { |
651 if (commit_state_ == COMMIT_STATE_WAITING_FOR_ACTIVATION) { | 621 if (commit_state_ == COMMIT_STATE_WAITING_FOR_ACTIVATION) { |
652 commit_state_ = settings_.main_thread_should_always_be_low_latency | 622 commit_state_ = settings_.main_thread_should_always_be_low_latency |
653 ? COMMIT_STATE_WAITING_FOR_DRAW | 623 ? COMMIT_STATE_WAITING_FOR_DRAW |
654 : COMMIT_STATE_IDLE; | 624 : COMMIT_STATE_IDLE; |
655 } | 625 } |
656 | 626 |
657 if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION) | 627 if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION) |
(...skipping 11 matching lines...) Expand all Loading... |
669 void SchedulerStateMachine::UpdateStateOnDraw(bool did_request_swap) { | 639 void SchedulerStateMachine::UpdateStateOnDraw(bool did_request_swap) { |
670 if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) | 640 if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) |
671 forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; | 641 forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; |
672 | 642 |
673 if (commit_state_ == COMMIT_STATE_WAITING_FOR_DRAW) | 643 if (commit_state_ == COMMIT_STATE_WAITING_FOR_DRAW) |
674 commit_state_ = COMMIT_STATE_IDLE; | 644 commit_state_ = COMMIT_STATE_IDLE; |
675 | 645 |
676 needs_redraw_ = false; | 646 needs_redraw_ = false; |
677 active_tree_needs_first_draw_ = false; | 647 active_tree_needs_first_draw_ = false; |
678 | 648 |
679 if (did_request_swap) | 649 if (did_request_swap) { |
| 650 DCHECK(!request_swap_funnel_); |
| 651 request_swap_funnel_ = true; |
680 last_frame_number_swap_requested_ = current_frame_number_; | 652 last_frame_number_swap_requested_ = current_frame_number_; |
| 653 } |
681 } | 654 } |
682 | 655 |
683 void SchedulerStateMachine::UpdateStateOnPrepareTiles() { | 656 void SchedulerStateMachine::UpdateStateOnPrepareTiles() { |
684 needs_prepare_tiles_ = false; | 657 needs_prepare_tiles_ = false; |
685 } | 658 } |
686 | 659 |
687 void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency() { | 660 void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency() { |
688 TRACE_EVENT_INSTANT0("cc", | 661 TRACE_EVENT_INSTANT0("cc", |
689 "Scheduler: SkipNextBeginMainFrameToReduceLatency", | 662 "Scheduler: SkipNextBeginMainFrameToReduceLatency", |
690 TRACE_EVENT_SCOPE_THREAD); | 663 TRACE_EVENT_SCOPE_THREAD); |
(...skipping 22 matching lines...) Expand all Loading... |
713 // Proactive BeginFrames are bad for the synchronous compositor because we | 686 // Proactive BeginFrames are bad for the synchronous compositor because we |
714 // have to draw when we get the BeginFrame and could end up drawing many | 687 // have to draw when we get the BeginFrame and could end up drawing many |
715 // duplicate frames if our new frame isn't ready in time. | 688 // duplicate frames if our new frame isn't ready in time. |
716 // To poll for state with the synchronous compositor without having to draw, | 689 // To poll for state with the synchronous compositor without having to draw, |
717 // we rely on ShouldPollForAnticipatedDrawTriggers instead. | 690 // we rely on ShouldPollForAnticipatedDrawTriggers instead. |
718 // Synchronous compositor doesn't have a browser. | 691 // Synchronous compositor doesn't have a browser. |
719 DCHECK(!children_need_begin_frames_); | 692 DCHECK(!children_need_begin_frames_); |
720 return BeginFrameNeededToAnimateOrDraw(); | 693 return BeginFrameNeededToAnimateOrDraw(); |
721 } | 694 } |
722 | 695 |
723 bool SchedulerStateMachine::ShouldSetNeedsBeginFrames( | |
724 bool frame_source_needs_begin_frames) const { | |
725 bool needs_begin_frame = BeginFrameNeeded(); | |
726 | |
727 // Never call SetNeedsBeginFrames if the frame source has the right value. | |
728 if (needs_begin_frame == frame_source_needs_begin_frames) | |
729 return false; | |
730 | |
731 // Always request the BeginFrame immediately if it's needed. | |
732 if (needs_begin_frame) | |
733 return true; | |
734 | |
735 // Stop requesting BeginFrames after a deadline. | |
736 if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) | |
737 return true; | |
738 | |
739 // Stop requesting BeginFrames immediately when output surface is lost. | |
740 if (!HasInitializedOutputSurface()) | |
741 return true; | |
742 | |
743 return false; | |
744 } | |
745 | |
746 bool SchedulerStateMachine::ShouldPollForAnticipatedDrawTriggers() const { | 696 bool SchedulerStateMachine::ShouldPollForAnticipatedDrawTriggers() const { |
747 // ShouldPollForAnticipatedDrawTriggers is what we use in place of | 697 // ShouldPollForAnticipatedDrawTriggers is what we use in place of |
748 // ProactiveBeginFrameWanted when we are using the synchronous | 698 // ProactiveBeginFrameWanted when we are using the synchronous |
749 // compositor. | 699 // compositor. |
750 if (!SupportsProactiveBeginFrame()) { | 700 if (!SupportsProactiveBeginFrame()) { |
751 return !BeginFrameNeededToAnimateOrDraw() && ProactiveBeginFrameWanted(); | 701 return !BeginFrameNeededToAnimateOrDraw() && ProactiveBeginFrameWanted(); |
752 } | 702 } |
753 | 703 |
754 // Non synchronous compositors should rely on | 704 // Non synchronous compositors should rely on |
755 // ProactiveBeginFrameWanted to poll for state instead. | 705 // ProactiveBeginFrameWanted to poll for state instead. |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
808 // Changing priorities may allow us to activate (given the new priorities), | 758 // Changing priorities may allow us to activate (given the new priorities), |
809 // which may result in a new frame. | 759 // which may result in a new frame. |
810 if (needs_prepare_tiles_) | 760 if (needs_prepare_tiles_) |
811 return true; | 761 return true; |
812 | 762 |
813 // If we just sent a swap request, it's likely that we are going to produce | 763 // If we just sent a swap request, it's likely that we are going to produce |
814 // another frame soon. This helps avoid negative glitches in our | 764 // another frame soon. This helps avoid negative glitches in our |
815 // SetNeedsBeginFrame requests, which may propagate to the BeginImplFrame | 765 // SetNeedsBeginFrame requests, which may propagate to the BeginImplFrame |
816 // provider and get sampled at an inopportune time, delaying the next | 766 // provider and get sampled at an inopportune time, delaying the next |
817 // BeginImplFrame. | 767 // BeginImplFrame. |
818 if (HasRequestedSwapThisFrame()) | 768 if (request_swap_funnel_) |
| 769 return true; |
| 770 |
| 771 // If the last commit was aborted because of early out (no updates), we should |
| 772 // still want a begin frame in case there is a commit coming again. |
| 773 if (last_commit_had_no_updates_) |
819 return true; | 774 return true; |
820 | 775 |
821 return false; | 776 return false; |
822 } | 777 } |
823 | 778 |
824 void SchedulerStateMachine::OnBeginImplFrame(const BeginFrameArgs& args) { | 779 void SchedulerStateMachine::OnBeginImplFrame() { |
825 AdvanceCurrentFrameNumber(); | 780 AdvanceCurrentFrameNumber(); |
826 begin_impl_frame_args_ = args; | |
827 DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_IDLE) | 781 DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_IDLE) |
828 << AsValue()->ToString(); | 782 << AsValue()->ToString(); |
829 begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING; | 783 begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING; |
| 784 last_commit_had_no_updates_ = false; |
830 } | 785 } |
831 | 786 |
832 void SchedulerStateMachine::OnBeginImplFrameDeadlinePending() { | 787 void SchedulerStateMachine::OnBeginImplFrameDeadlinePending() { |
833 DCHECK_EQ(begin_impl_frame_state_, | 788 DCHECK_EQ(begin_impl_frame_state_, |
834 BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING) | 789 BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING) |
835 << AsValue()->ToString(); | 790 << AsValue()->ToString(); |
836 begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME; | 791 begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME; |
837 } | 792 } |
838 | 793 |
839 void SchedulerStateMachine::OnBeginImplFrameDeadline() { | 794 void SchedulerStateMachine::OnBeginImplFrameDeadline() { |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
903 } | 858 } |
904 | 859 |
905 bool SchedulerStateMachine::MainThreadIsInHighLatencyMode() const { | 860 bool SchedulerStateMachine::MainThreadIsInHighLatencyMode() const { |
906 // If a commit is pending before the previous commit has been drawn, we | 861 // If a commit is pending before the previous commit has been drawn, we |
907 // are definitely in a high latency mode. | 862 // are definitely in a high latency mode. |
908 if (CommitPending() && (active_tree_needs_first_draw_ || has_pending_tree_)) | 863 if (CommitPending() && (active_tree_needs_first_draw_ || has_pending_tree_)) |
909 return true; | 864 return true; |
910 | 865 |
911 // If we just sent a BeginMainFrame and haven't hit the deadline yet, the main | 866 // If we just sent a BeginMainFrame and haven't hit the deadline yet, the main |
912 // thread is in a low latency mode. | 867 // thread is in a low latency mode. |
913 if (HasSentBeginMainFrameThisFrame() && | 868 if (send_begin_main_frame_funnel_ && |
914 (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING || | 869 (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING || |
915 begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME)) | 870 begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME)) |
916 return false; | 871 return false; |
917 | 872 |
918 // If there's a commit in progress it must either be from the previous frame | 873 // If there's a commit in progress it must either be from the previous frame |
919 // or it started after the impl thread's deadline. In either case the main | 874 // or it started after the impl thread's deadline. In either case the main |
920 // thread is in high latency mode. | 875 // thread is in high latency mode. |
921 if (CommitPending()) | 876 if (CommitPending()) |
922 return true; | 877 return true; |
923 | 878 |
924 // Similarly, if there's a pending tree the main thread is in high latency | 879 // Similarly, if there's a pending tree the main thread is in high latency |
925 // mode, because either | 880 // mode, because either |
926 // it's from the previous frame | 881 // it's from the previous frame |
927 // or | 882 // or |
928 // we're currently drawing the active tree and the pending tree will thus | 883 // we're currently drawing the active tree and the pending tree will thus |
929 // only be drawn in the next frame. | 884 // only be drawn in the next frame. |
930 if (has_pending_tree_) | 885 if (has_pending_tree_) |
931 return true; | 886 return true; |
932 | 887 |
933 if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) { | 888 if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) { |
934 // Even if there's a new active tree to draw at the deadline or we've just | 889 // Even if there's a new active tree to draw at the deadline or we've just |
935 // swapped it, it may have been triggered by a previous BeginImplFrame, in | 890 // swapped it, it may have been triggered by a previous BeginImplFrame, in |
936 // which case the main thread is in a high latency mode. | 891 // which case the main thread is in a high latency mode. |
937 return (active_tree_needs_first_draw_ || HasSwappedThisFrame()) && | 892 return (active_tree_needs_first_draw_ || perform_swap_funnel_) && |
938 !HasSentBeginMainFrameThisFrame(); | 893 !send_begin_main_frame_funnel_; |
939 } | 894 } |
940 | 895 |
941 // If the active tree needs its first draw in any other state, we know the | 896 // If the active tree needs its first draw in any other state, we know the |
942 // main thread is in a high latency mode. | 897 // main thread is in a high latency mode. |
943 return active_tree_needs_first_draw_; | 898 return active_tree_needs_first_draw_; |
944 } | 899 } |
945 | 900 |
946 void SchedulerStateMachine::DidEnterPollForAnticipatedDrawTriggers() { | 901 void SchedulerStateMachine::DidEnterPollForAnticipatedDrawTriggers() { |
947 AdvanceCurrentFrameNumber(); | 902 AdvanceCurrentFrameNumber(); |
948 inside_poll_for_anticipated_draw_triggers_ = true; | 903 inside_poll_for_anticipated_draw_triggers_ = true; |
(...skipping 20 matching lines...) Expand all Loading... |
969 } | 924 } |
970 } | 925 } |
971 | 926 |
972 void SchedulerStateMachine::SetMaxSwapsPending(int max) { | 927 void SchedulerStateMachine::SetMaxSwapsPending(int max) { |
973 max_pending_swaps_ = max; | 928 max_pending_swaps_ = max; |
974 } | 929 } |
975 | 930 |
976 void SchedulerStateMachine::DidSwapBuffers() { | 931 void SchedulerStateMachine::DidSwapBuffers() { |
977 pending_swaps_++; | 932 pending_swaps_++; |
978 DCHECK_LE(pending_swaps_, max_pending_swaps_); | 933 DCHECK_LE(pending_swaps_, max_pending_swaps_); |
| 934 DCHECK(!perform_swap_funnel_); |
979 | 935 |
| 936 perform_swap_funnel_ = true; |
980 last_frame_number_swap_performed_ = current_frame_number_; | 937 last_frame_number_swap_performed_ = current_frame_number_; |
981 } | 938 } |
982 | 939 |
983 void SchedulerStateMachine::DidSwapBuffersComplete() { | 940 void SchedulerStateMachine::DidSwapBuffersComplete() { |
984 DCHECK_GT(pending_swaps_, 0); | 941 DCHECK_GT(pending_swaps_, 0); |
985 pending_swaps_--; | 942 pending_swaps_--; |
986 } | 943 } |
987 | 944 |
988 void SchedulerStateMachine::SetImplLatencyTakesPriority( | 945 void SchedulerStateMachine::SetImplLatencyTakesPriority( |
989 bool impl_latency_takes_priority) { | 946 bool impl_latency_takes_priority) { |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1123 static_cast<int>(begin_impl_frame_state_), | 1080 static_cast<int>(begin_impl_frame_state_), |
1124 static_cast<int>(commit_state_), | 1081 static_cast<int>(commit_state_), |
1125 has_pending_tree_ ? 'T' : 'F', | 1082 has_pending_tree_ ? 'T' : 'F', |
1126 pending_tree_is_ready_for_activation_ ? 'T' : 'F', | 1083 pending_tree_is_ready_for_activation_ ? 'T' : 'F', |
1127 active_tree_needs_first_draw_ ? 'T' : 'F', | 1084 active_tree_needs_first_draw_ ? 'T' : 'F', |
1128 max_pending_swaps_, | 1085 max_pending_swaps_, |
1129 pending_swaps_); | 1086 pending_swaps_); |
1130 } | 1087 } |
1131 | 1088 |
1132 } // namespace cc | 1089 } // namespace cc |
OLD | NEW |