Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 #ifndef CC_SCHEDULER_BEGIN_FRAME_SOURCE_H_ | 5 #ifndef CC_SCHEDULER_BEGIN_FRAME_SOURCE_H_ |
| 6 #define CC_SCHEDULER_BEGIN_FRAME_SOURCE_H_ | 6 #define CC_SCHEDULER_BEGIN_FRAME_SOURCE_H_ |
| 7 | 7 |
| 8 #include <stddef.h> | 8 #include <stddef.h> |
| 9 #include <stdint.h> | 9 #include <stdint.h> |
| 10 | 10 |
| 11 #include <set> | 11 #include <set> |
| 12 #include <string> | 12 #include <string> |
| 13 #include <unordered_set> | |
| 13 | 14 |
| 14 #include "base/logging.h" | 15 #include "base/logging.h" |
| 15 #include "base/macros.h" | 16 #include "base/macros.h" |
| 16 #include "base/trace_event/trace_event.h" | 17 #include "base/trace_event/trace_event.h" |
| 17 #include "cc/output/begin_frame_args.h" | 18 #include "cc/output/begin_frame_args.h" |
| 18 #include "cc/scheduler/delay_based_time_source.h" | 19 #include "cc/scheduler/delay_based_time_source.h" |
| 19 | 20 |
| 20 namespace cc { | 21 namespace cc { |
| 21 | 22 |
| 22 // (Pure) Interface for observing BeginFrame messages from BeginFrameSource | 23 // (Pure) Interface for observing BeginFrame messages from BeginFrameSource |
| 23 // objects. | 24 // objects. |
| 24 class CC_EXPORT BeginFrameObserver { | 25 class CC_EXPORT BeginFrameObserver { |
| 25 public: | 26 public: |
| 26 virtual ~BeginFrameObserver() {} | 27 virtual ~BeginFrameObserver() {} |
| 27 | 28 |
| 28 // The |args| given to OnBeginFrame is guaranteed to have | 29 // The |args| given to OnBeginFrame is guaranteed to have |
| 29 // |args|.IsValid()==true and have |args|.frame_time | 30 // |args|.IsValid()==true. If |args|.source_id did not change between |
| 30 // field be strictly greater than the previous call. | 31 // invocations, |args|.sequence_number is guaranteed to be be strictly greater |
| 32 // than the previous call. Further, |args|.frame_time is guaranteed to be | |
| 33 // greater than or equal to the previous call. | |
|
brianderson
2016/12/09 01:08:11
To "Further, |args|.frame_time is guaranteed to be
Eric Seckler
2016/12/09 16:47:16
Yes, done!
| |
| 31 // | 34 // |
| 32 // Side effects: This function can (and most of the time *will*) change the | 35 // Side effects: This function can (and most of the time *will*) change the |
| 33 // return value of the LastUsedBeginFrameArgs method. See the documentation | 36 // return value of the LastUsedBeginFrameArgs method. See the documentation |
| 34 // on that method for more information. | 37 // on that method for more information. |
| 35 virtual void OnBeginFrame(const BeginFrameArgs& args) = 0; | 38 virtual void OnBeginFrame(const BeginFrameArgs& args) = 0; |
| 36 | 39 |
| 37 // Returns the last BeginFrameArgs used by the observer. This method's return | 40 // Returns the last BeginFrameArgs used by the observer. This method's return |
| 38 // value is affected by the OnBeginFrame method! | 41 // value is affected by the OnBeginFrame method! |
| 39 // | 42 // |
| 40 // - Before the first call of OnBeginFrame, this method should return a | 43 // - Before the first call of OnBeginFrame, this method should return a |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 92 // BeginFrameObserver. | 95 // BeginFrameObserver. |
| 93 // | 96 // |
| 94 // BeginFrame calls *normally* occur just after a vsync interrupt when input | 97 // BeginFrame calls *normally* occur just after a vsync interrupt when input |
| 95 // processing has been finished and provide information about the time values | 98 // processing has been finished and provide information about the time values |
| 96 // of the vsync times. *However*, these values can be heavily modified or even | 99 // of the vsync times. *However*, these values can be heavily modified or even |
| 97 // plain made up (when no vsync signal is available or vsync throttling is | 100 // plain made up (when no vsync signal is available or vsync throttling is |
| 98 // turned off). See the BeginFrameObserver for information about the guarantees | 101 // turned off). See the BeginFrameObserver for information about the guarantees |
| 99 // all BeginFrameSources *must* provide. | 102 // all BeginFrameSources *must* provide. |
| 100 class CC_EXPORT BeginFrameSource { | 103 class CC_EXPORT BeginFrameSource { |
| 101 public: | 104 public: |
| 105 BeginFrameSource(); | |
| 102 virtual ~BeginFrameSource() {} | 106 virtual ~BeginFrameSource() {} |
| 103 | 107 |
| 104 // DidFinishFrame provides back pressure to a frame source about frame | 108 // BeginFrameObservers use DidFinishFrame to acknowledge that they have |
| 105 // processing (rather than toggling SetNeedsBeginFrames every frame). It is | 109 // completed handling a BeginFrame. |
| 106 // used by systems like the BackToBackFrameSource to make sure only one frame | 110 // |
| 107 // is pending at a time. | 111 // The DisplayScheduler uses these acknowledgments to trigger an early |
| 112 // deadline once all BeginFrameObservers have completed a frame. | |
| 113 // | |
| 114 // They also provide back pressure to a frame source about frame processing | |
| 115 // (rather than toggling SetNeedsBeginFrames every frame). For example, the | |
| 116 // BackToBackFrameSource uses them to make sure only one frame is pending at a | |
| 117 // time. | |
| 108 virtual void DidFinishFrame(BeginFrameObserver* obs, | 118 virtual void DidFinishFrame(BeginFrameObserver* obs, |
| 109 size_t remaining_frames) = 0; | 119 const BeginFrameAck& ack) = 0; |
| 110 | 120 |
| 111 // Add/Remove an observer from the source. When no observers are added the BFS | 121 // Add/Remove an observer from the source. When no observers are added the BFS |
| 112 // should shut down its timers, disable vsync, etc. | 122 // should shut down its timers, disable vsync, etc. |
| 113 virtual void AddObserver(BeginFrameObserver* obs) = 0; | 123 virtual void AddObserver(BeginFrameObserver* obs) = 0; |
| 114 virtual void RemoveObserver(BeginFrameObserver* obs) = 0; | 124 virtual void RemoveObserver(BeginFrameObserver* obs) = 0; |
| 115 | 125 |
| 116 // Returns false if the begin frame source will just continue to produce | 126 // Returns false if the begin frame source will just continue to produce |
| 117 // begin frames without waiting. | 127 // begin frames without waiting. |
| 118 virtual bool IsThrottled() const = 0; | 128 virtual bool IsThrottled() const = 0; |
| 129 | |
| 130 // Returns an identifier for this BeginFrameSource. Guaranteed unique within a | |
| 131 // process, and very likely unique across processes. This is used to create | |
|
brianderson
2016/12/09 01:08:11
"Guaranteed unique within a process, but not acros
Eric Seckler
2016/12/09 16:47:17
Okay. I guess the question is whether there may be
| |
| 132 // BeginFrames that originate at this source. Note that BeginFrameSources may | |
| 133 // pass on BeginFrames created by other sources, with different IDs. | |
| 134 uint64_t source_id() const; | |
| 135 | |
| 136 private: | |
| 137 uint64_t source_id_; | |
| 119 }; | 138 }; |
| 120 | 139 |
| 121 // A BeginFrameSource that does nothing. | 140 // A BeginFrameSource that does nothing. |
| 122 class CC_EXPORT StubBeginFrameSource : public BeginFrameSource { | 141 class CC_EXPORT StubBeginFrameSource : public BeginFrameSource { |
| 123 public: | 142 public: |
| 124 void DidFinishFrame(BeginFrameObserver* obs, | 143 void DidFinishFrame(BeginFrameObserver* obs, |
| 125 size_t remaining_frames) override {} | 144 const BeginFrameAck& ack) override {} |
| 126 void AddObserver(BeginFrameObserver* obs) override {} | 145 void AddObserver(BeginFrameObserver* obs) override {} |
| 127 void RemoveObserver(BeginFrameObserver* obs) override {} | 146 void RemoveObserver(BeginFrameObserver* obs) override {} |
| 128 bool IsThrottled() const override; | 147 bool IsThrottled() const override; |
| 129 }; | 148 }; |
| 130 | 149 |
| 131 // A frame source which ticks itself independently. | 150 // A frame source which ticks itself independently. |
| 132 class CC_EXPORT SyntheticBeginFrameSource : public BeginFrameSource { | 151 class CC_EXPORT SyntheticBeginFrameSource : public BeginFrameSource { |
| 133 public: | 152 public: |
| 134 ~SyntheticBeginFrameSource() override; | 153 ~SyntheticBeginFrameSource() override; |
| 135 | 154 |
| 136 virtual void OnUpdateVSyncParameters(base::TimeTicks timebase, | 155 virtual void OnUpdateVSyncParameters(base::TimeTicks timebase, |
| 137 base::TimeDelta interval) = 0; | 156 base::TimeDelta interval) = 0; |
| 138 // This overrides any past or future interval from updating vsync parameters. | 157 // This overrides any past or future interval from updating vsync parameters. |
| 139 virtual void SetAuthoritativeVSyncInterval(base::TimeDelta interval) = 0; | 158 virtual void SetAuthoritativeVSyncInterval(base::TimeDelta interval) = 0; |
| 140 }; | 159 }; |
| 141 | 160 |
| 142 // A frame source which calls BeginFrame (at the next possible time) as soon as | 161 // A frame source which calls BeginFrame (at the next possible time) as soon as |
| 143 // remaining frames reaches zero. | 162 // remaining frames reaches zero. |
| 144 class CC_EXPORT BackToBackBeginFrameSource : public SyntheticBeginFrameSource, | 163 class CC_EXPORT BackToBackBeginFrameSource : public SyntheticBeginFrameSource, |
| 145 public DelayBasedTimeSourceClient { | 164 public DelayBasedTimeSourceClient { |
| 146 public: | 165 public: |
| 147 explicit BackToBackBeginFrameSource( | 166 explicit BackToBackBeginFrameSource( |
| 148 std::unique_ptr<DelayBasedTimeSource> time_source); | 167 std::unique_ptr<DelayBasedTimeSource> time_source); |
| 149 ~BackToBackBeginFrameSource() override; | 168 ~BackToBackBeginFrameSource() override; |
| 150 | 169 |
| 151 // BeginFrameSource implementation. | 170 // BeginFrameSource implementation. |
| 152 void AddObserver(BeginFrameObserver* obs) override; | 171 void AddObserver(BeginFrameObserver* obs) override; |
| 153 void RemoveObserver(BeginFrameObserver* obs) override; | 172 void RemoveObserver(BeginFrameObserver* obs) override; |
| 154 void DidFinishFrame(BeginFrameObserver* obs, | 173 void DidFinishFrame(BeginFrameObserver* obs, |
| 155 size_t remaining_frames) override; | 174 const BeginFrameAck& ack) override; |
| 156 bool IsThrottled() const override; | 175 bool IsThrottled() const override; |
| 157 | 176 |
| 158 // SyntheticBeginFrameSource implementation. | 177 // SyntheticBeginFrameSource implementation. |
| 159 void OnUpdateVSyncParameters(base::TimeTicks timebase, | 178 void OnUpdateVSyncParameters(base::TimeTicks timebase, |
| 160 base::TimeDelta interval) override {} | 179 base::TimeDelta interval) override {} |
| 161 void SetAuthoritativeVSyncInterval(base::TimeDelta interval) override {} | 180 void SetAuthoritativeVSyncInterval(base::TimeDelta interval) override {} |
| 162 | 181 |
| 163 // DelayBasedTimeSourceClient implementation. | 182 // DelayBasedTimeSourceClient implementation. |
| 164 void OnTimerTick() override; | 183 void OnTimerTick() override; |
| 165 | 184 |
| 166 private: | 185 private: |
| 167 std::unique_ptr<DelayBasedTimeSource> time_source_; | 186 std::unique_ptr<DelayBasedTimeSource> time_source_; |
| 168 std::unordered_set<BeginFrameObserver*> observers_; | 187 std::unordered_set<BeginFrameObserver*> observers_; |
| 169 std::unordered_set<BeginFrameObserver*> pending_begin_frame_observers_; | 188 std::unordered_set<BeginFrameObserver*> pending_begin_frame_observers_; |
| 189 uint64_t next_sequence_number_; | |
| 170 base::WeakPtrFactory<BackToBackBeginFrameSource> weak_factory_; | 190 base::WeakPtrFactory<BackToBackBeginFrameSource> weak_factory_; |
| 171 | 191 |
| 172 DISALLOW_COPY_AND_ASSIGN(BackToBackBeginFrameSource); | 192 DISALLOW_COPY_AND_ASSIGN(BackToBackBeginFrameSource); |
| 173 }; | 193 }; |
| 174 | 194 |
| 175 // A frame source which is locked to an external parameters provides from a | 195 // A frame source which is locked to an external parameters provides from a |
| 176 // vsync source and generates BeginFrameArgs for it. | 196 // vsync source and generates BeginFrameArgs for it. |
| 177 class CC_EXPORT DelayBasedBeginFrameSource : public SyntheticBeginFrameSource, | 197 class CC_EXPORT DelayBasedBeginFrameSource : public SyntheticBeginFrameSource, |
| 178 public DelayBasedTimeSourceClient { | 198 public DelayBasedTimeSourceClient { |
| 179 public: | 199 public: |
| 180 explicit DelayBasedBeginFrameSource( | 200 explicit DelayBasedBeginFrameSource( |
| 181 std::unique_ptr<DelayBasedTimeSource> time_source); | 201 std::unique_ptr<DelayBasedTimeSource> time_source); |
| 182 ~DelayBasedBeginFrameSource() override; | 202 ~DelayBasedBeginFrameSource() override; |
| 183 | 203 |
| 184 // BeginFrameSource implementation. | 204 // BeginFrameSource implementation. |
| 185 void AddObserver(BeginFrameObserver* obs) override; | 205 void AddObserver(BeginFrameObserver* obs) override; |
| 186 void RemoveObserver(BeginFrameObserver* obs) override; | 206 void RemoveObserver(BeginFrameObserver* obs) override; |
| 187 void DidFinishFrame(BeginFrameObserver* obs, | 207 void DidFinishFrame(BeginFrameObserver* obs, |
| 188 size_t remaining_frames) override {} | 208 const BeginFrameAck& ack) override {} |
| 189 bool IsThrottled() const override; | 209 bool IsThrottled() const override; |
| 190 | 210 |
| 191 // SyntheticBeginFrameSource implementation. | 211 // SyntheticBeginFrameSource implementation. |
| 192 void OnUpdateVSyncParameters(base::TimeTicks timebase, | 212 void OnUpdateVSyncParameters(base::TimeTicks timebase, |
| 193 base::TimeDelta interval) override; | 213 base::TimeDelta interval) override; |
| 194 void SetAuthoritativeVSyncInterval(base::TimeDelta interval) override; | 214 void SetAuthoritativeVSyncInterval(base::TimeDelta interval) override; |
| 195 | 215 |
| 196 // DelayBasedTimeSourceClient implementation. | 216 // DelayBasedTimeSourceClient implementation. |
| 197 void OnTimerTick() override; | 217 void OnTimerTick() override; |
| 198 | 218 |
| 199 private: | 219 private: |
| 200 BeginFrameArgs CreateBeginFrameArgs(base::TimeTicks frame_time, | 220 BeginFrameArgs CreateBeginFrameArgs(uint64_t sequence_number, |
| 221 base::TimeTicks frame_time, | |
| 201 BeginFrameArgs::BeginFrameArgsType type); | 222 BeginFrameArgs::BeginFrameArgsType type); |
| 202 | 223 |
| 203 std::unique_ptr<DelayBasedTimeSource> time_source_; | 224 std::unique_ptr<DelayBasedTimeSource> time_source_; |
| 204 std::unordered_set<BeginFrameObserver*> observers_; | 225 std::unordered_set<BeginFrameObserver*> observers_; |
| 205 base::TimeTicks last_timebase_; | 226 base::TimeTicks last_timebase_; |
| 206 base::TimeDelta authoritative_interval_; | 227 base::TimeDelta authoritative_interval_; |
| 228 BeginFrameArgs current_begin_frame_args_; | |
| 207 | 229 |
| 208 DISALLOW_COPY_AND_ASSIGN(DelayBasedBeginFrameSource); | 230 DISALLOW_COPY_AND_ASSIGN(DelayBasedBeginFrameSource); |
| 209 }; | 231 }; |
| 210 | 232 |
| 233 // Helper class that tracks outstanding acknowledgments from | |
| 234 // BeginFrameObservers. | |
| 235 class CC_EXPORT BeginFrameObserverAckTracker { | |
| 236 public: | |
| 237 BeginFrameObserverAckTracker(); | |
| 238 virtual ~BeginFrameObserverAckTracker(); | |
| 239 | |
| 240 // The BeginFrameSource uses these methods to notify us when a BeginFrame was | |
| 241 // started and sent to a specific observer, an observer finished a frame, or | |
| 242 // an observer was added/removed. | |
| 243 void OnBeginFrame(const BeginFrameArgs& args); | |
| 244 void OnObserverBeginFrame(BeginFrameObserver* obs, | |
| 245 const BeginFrameArgs& args); | |
| 246 void OnObserverFinishedFrame(BeginFrameObserver* obs, | |
| 247 const BeginFrameAck& ack); | |
| 248 void OnObserverAdded(BeginFrameObserver* obs); | |
| 249 void OnObserverRemoved(BeginFrameObserver* obs); | |
| 250 | |
| 251 // Returns |true| if all the source's observers completed the current frame. | |
| 252 bool AllObserversFinishedFrame() const; | |
| 253 | |
| 254 // Returns |true| if any observer had damages during the current frame. | |
| 255 bool AnyObserversHadDamages() const; | |
| 256 | |
| 257 // Returns the sequence number of the latest_confirmed_frame that any of | |
| 258 // the observers have contributed for the latest frame. | |
| 259 uint64_t LatestConfirmedFrame() const; | |
| 260 | |
| 261 private: | |
| 262 void SourceChanged(const BeginFrameArgs& args); | |
| 263 | |
| 264 uint64_t current_source_id_; | |
| 265 uint64_t current_sequence_number_; | |
| 266 std::set<BeginFrameObserver*> observers_; | |
| 267 std::set<BeginFrameObserver*> finished_observers_; | |
| 268 bool observers_had_damages_; | |
|
brianderson
2016/12/09 01:08:11
observers_had_damage_
Eric Seckler
2016/12/09 16:47:17
Done.
Sami
2016/12/09 18:36:37
Should we do a s/damages/damage/g overall?
| |
| 269 std::unordered_map<BeginFrameObserver*, uint64_t> latest_confirmed_frames_; | |
| 270 }; | |
| 271 | |
| 211 class CC_EXPORT ExternalBeginFrameSourceClient { | 272 class CC_EXPORT ExternalBeginFrameSourceClient { |
| 212 public: | 273 public: |
| 213 // Only called when changed. Assumed false by default. | 274 // Only called when changed. Assumed false by default. |
| 214 virtual void OnNeedsBeginFrames(bool needs_begin_frames) = 0; | 275 virtual void OnNeedsBeginFrames(bool needs_begin_frames) = 0; |
| 276 // Called when all observers have completed a frame. | |
| 277 virtual void OnDidFinishFrame(const BeginFrameAck& ack) = 0; | |
| 215 }; | 278 }; |
| 216 | 279 |
| 217 // A BeginFrameSource that is only ticked manually. Usually the endpoint | 280 // A BeginFrameSource that is only ticked manually. Usually the endpoint |
| 218 // of messages from some other thread/process that send OnBeginFrame and | 281 // of messages from some other thread/process that send OnBeginFrame and |
| 219 // receive SetNeedsBeginFrame messages. This turns such messages back into | 282 // receive SetNeedsBeginFrame messages. This turns such messages back into |
| 220 // an observable BeginFrameSource. | 283 // an observable BeginFrameSource. |
| 221 class CC_EXPORT ExternalBeginFrameSource : public BeginFrameSource { | 284 class CC_EXPORT ExternalBeginFrameSource : public BeginFrameSource { |
| 222 public: | 285 public: |
| 223 // Client lifetime must be preserved by owner past the lifetime of this class. | 286 // Client lifetime must be preserved by owner past the lifetime of this class. |
| 224 explicit ExternalBeginFrameSource(ExternalBeginFrameSourceClient* client); | 287 explicit ExternalBeginFrameSource(ExternalBeginFrameSourceClient* client); |
| 225 ~ExternalBeginFrameSource() override; | 288 ~ExternalBeginFrameSource() override; |
| 226 | 289 |
| 227 // BeginFrameSource implementation. | 290 // BeginFrameSource implementation. |
| 228 void AddObserver(BeginFrameObserver* obs) override; | 291 void AddObserver(BeginFrameObserver* obs) override; |
| 229 void RemoveObserver(BeginFrameObserver* obs) override; | 292 void RemoveObserver(BeginFrameObserver* obs) override; |
| 230 void DidFinishFrame(BeginFrameObserver* obs, | 293 void DidFinishFrame(BeginFrameObserver* obs, |
| 231 size_t remaining_frames) override {} | 294 const BeginFrameAck& ack) override; |
| 232 bool IsThrottled() const override; | 295 bool IsThrottled() const override; |
| 233 | 296 |
| 234 void OnSetBeginFrameSourcePaused(bool paused); | 297 void OnSetBeginFrameSourcePaused(bool paused); |
| 235 void OnBeginFrame(const BeginFrameArgs& args); | 298 void OnBeginFrame(const BeginFrameArgs& args); |
| 236 | 299 |
| 237 protected: | 300 protected: |
| 301 void MaybeFinishFrame(); | |
| 302 void FinishFrame(); | |
| 303 | |
| 238 BeginFrameArgs missed_begin_frame_args_; | 304 BeginFrameArgs missed_begin_frame_args_; |
| 239 std::unordered_set<BeginFrameObserver*> observers_; | 305 std::unordered_set<BeginFrameObserver*> observers_; |
| 240 ExternalBeginFrameSourceClient* client_; | 306 ExternalBeginFrameSourceClient* client_; |
| 241 bool paused_ = false; | 307 bool paused_ = false; |
| 308 bool frame_active_ = false; | |
| 309 BeginFrameObserverAckTracker ack_tracker_; | |
|
brianderson
2016/12/09 01:08:11
For my education, is this something that is actual
Eric Seckler
2016/12/09 16:47:17
Mus uses ExternalBeginFrameSources in WindowCompos
| |
| 242 | 310 |
| 243 private: | 311 private: |
| 244 DISALLOW_COPY_AND_ASSIGN(ExternalBeginFrameSource); | 312 DISALLOW_COPY_AND_ASSIGN(ExternalBeginFrameSource); |
| 245 }; | 313 }; |
| 246 | 314 |
| 315 // A BeginFrameSource that delegates to another given source. | |
|
brianderson
2016/12/09 01:08:11
Can you add a comment explaining why this class is
Eric Seckler
2016/12/09 16:47:16
I think moving the ownership of the BeginFrameSour
| |
| 316 class CC_EXPORT DelegatingBeginFrameSource : public BeginFrameSource { | |
| 317 public: | |
| 318 // |delegate| lifetime must be preserved past the lifetime of this class. | |
| 319 explicit DelegatingBeginFrameSource(BeginFrameSource* delegate); | |
| 320 ~DelegatingBeginFrameSource() override {} | |
| 321 | |
| 322 // BeginFrameSource implementation. | |
| 323 void DidFinishFrame(BeginFrameObserver* obs, | |
| 324 const BeginFrameAck& ack) override; | |
| 325 void AddObserver(BeginFrameObserver* obs) override; | |
| 326 void RemoveObserver(BeginFrameObserver* obs) override; | |
| 327 bool IsThrottled() const override; | |
| 328 | |
| 329 private: | |
| 330 BeginFrameSource* delegate_; // Not owned. | |
| 331 }; | |
| 332 | |
| 247 } // namespace cc | 333 } // namespace cc |
| 248 | 334 |
| 249 #endif // CC_SCHEDULER_BEGIN_FRAME_SOURCE_H_ | 335 #endif // CC_SCHEDULER_BEGIN_FRAME_SOURCE_H_ |
| OLD | NEW |