| 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/trees/thread_proxy.h" | 5 #include "cc/trees/thread_proxy.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/auto_reset.h" | 9 #include "base/auto_reset.h" |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/debug/trace_event.h" | 11 #include "base/debug/trace_event.h" |
| 12 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
| 13 #include "cc/input/input_handler.h" | 13 #include "cc/input/input_handler.h" |
| 14 #include "cc/output/context_provider.h" | 14 #include "cc/output/context_provider.h" |
| 15 #include "cc/output/output_surface.h" | 15 #include "cc/output/output_surface.h" |
| 16 #include "cc/quads/draw_quad.h" | 16 #include "cc/quads/draw_quad.h" |
| 17 #include "cc/resources/prioritized_resource_manager.h" | 17 #include "cc/resources/prioritized_resource_manager.h" |
| 18 #include "cc/scheduler/delay_based_time_source.h" | 18 #include "cc/scheduler/delay_based_time_source.h" |
| 19 #include "cc/scheduler/frame_rate_controller.h" | 19 #include "cc/scheduler/frame_rate_controller.h" |
| 20 #include "cc/scheduler/scheduler.h" | 20 #include "cc/scheduler/scheduler.h" |
| 21 #include "cc/trees/layer_tree_host.h" | 21 #include "cc/trees/layer_tree_host.h" |
| 22 #include "cc/trees/layer_tree_impl.h" | 22 #include "cc/trees/layer_tree_impl.h" |
| 23 | 23 |
| 24 class DEVNULL {}; |
| 25 template <typename A> |
| 26 DEVNULL& operator <<(DEVNULL &d, A a) { return d; } |
| 27 //static DEVNULL gDevNull; |
| 28 //#undef VLOG |
| 29 //#define VLOG(x) gDevNull |
| 30 |
| 24 namespace { | 31 namespace { |
| 25 | 32 |
| 26 // Measured in seconds. | 33 // Measured in seconds. |
| 27 const double kContextRecreationTickRate = 0.03; | 34 const double kContextRecreationTickRate = 0.03; |
| 28 | 35 |
| 29 // Measured in seconds. | 36 // Measured in seconds. |
| 30 const double kSmoothnessTakesPriorityExpirationDelay = 0.25; | 37 const double kSmoothnessTakesPriorityExpirationDelay = 0.25; |
| 31 | 38 |
| 32 const size_t kDrawDurationHistorySize = 60; | 39 const size_t kDrawDurationHistorySize = 60; |
| 33 const double kDrawDurationEstimationPercentile = 100.0; | 40 const double kDrawDurationEstimationPercentile = 100.0; |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 69 commit_requested_(false), | 76 commit_requested_(false), |
| 70 commit_request_sent_to_impl_thread_(false), | 77 commit_request_sent_to_impl_thread_(false), |
| 71 created_offscreen_context_provider_(false), | 78 created_offscreen_context_provider_(false), |
| 72 layer_tree_host_(layer_tree_host), | 79 layer_tree_host_(layer_tree_host), |
| 73 started_(false), | 80 started_(false), |
| 74 textures_acquired_(true), | 81 textures_acquired_(true), |
| 75 in_composite_and_readback_(false), | 82 in_composite_and_readback_(false), |
| 76 manage_tiles_pending_(false), | 83 manage_tiles_pending_(false), |
| 77 weak_factory_on_impl_thread_(this), | 84 weak_factory_on_impl_thread_(this), |
| 78 weak_factory_(this), | 85 weak_factory_(this), |
| 86 frame_did_draw_(false), |
| 79 begin_frame_sent_to_main_thread_completion_event_on_impl_thread_(NULL), | 87 begin_frame_sent_to_main_thread_completion_event_on_impl_thread_(NULL), |
| 80 readback_request_on_impl_thread_(NULL), | 88 readback_request_on_impl_thread_(NULL), |
| 81 commit_completion_event_on_impl_thread_(NULL), | 89 commit_completion_event_on_impl_thread_(NULL), |
| 82 completion_event_for_commit_held_on_tree_activation_(NULL), | 90 completion_event_for_commit_held_on_tree_activation_(NULL), |
| 83 texture_acquisition_completion_event_on_impl_thread_(NULL), | 91 texture_acquisition_completion_event_on_impl_thread_(NULL), |
| 84 next_frame_is_newly_committed_frame_on_impl_thread_(false), | 92 next_frame_is_newly_committed_frame_on_impl_thread_(false), |
| 85 throttle_frame_production_( | 93 throttle_frame_production_( |
| 86 layer_tree_host->settings().throttle_frame_production), | 94 layer_tree_host->settings().throttle_frame_production), |
| 87 begin_frame_scheduling_enabled_( | 95 begin_frame_scheduling_enabled_( |
| 88 layer_tree_host->settings().begin_frame_scheduling_enabled), | 96 layer_tree_host->settings().begin_frame_scheduling_enabled), |
| (...skipping 18 matching lines...) Expand all Loading... |
| 107 TRACE_EVENT0("cc", "ThreadProxy::CompositeAndReadback"); | 115 TRACE_EVENT0("cc", "ThreadProxy::CompositeAndReadback"); |
| 108 DCHECK(IsMainThread()); | 116 DCHECK(IsMainThread()); |
| 109 DCHECK(layer_tree_host_); | 117 DCHECK(layer_tree_host_); |
| 110 DCHECK(!defer_commits_); | 118 DCHECK(!defer_commits_); |
| 111 | 119 |
| 112 if (!layer_tree_host_->InitializeOutputSurfaceIfNeeded()) { | 120 if (!layer_tree_host_->InitializeOutputSurfaceIfNeeded()) { |
| 113 TRACE_EVENT0("cc", "CompositeAndReadback_EarlyOut_LR_Uninitialized"); | 121 TRACE_EVENT0("cc", "CompositeAndReadback_EarlyOut_LR_Uninitialized"); |
| 114 return false; | 122 return false; |
| 115 } | 123 } |
| 116 | 124 |
| 117 // Perform a synchronous commit. | 125 // Perform a synchronous commit with an associated readback. |
| 126 ReadbackRequest request; |
| 127 request.rect = rect; |
| 128 request.pixels = pixels; |
| 118 { | 129 { |
| 119 DebugScopedSetMainThreadBlocked main_thread_blocked(this); | 130 DebugScopedSetMainThreadBlocked main_thread_blocked(this); |
| 120 CompletionEvent begin_frame_sent_to_main_thread_completion; | 131 CompletionEvent begin_frame_sent_to_main_thread_completion; |
| 121 Proxy::ImplThreadTaskRunner()->PostTask( | 132 Proxy::ImplThreadTaskRunner()->PostTask( |
| 122 FROM_HERE, | 133 FROM_HERE, |
| 123 base::Bind(&ThreadProxy::ForceCommitOnImplThread, | 134 base::Bind(&ThreadProxy::ForceCommitForReadbackOnImplThread, |
| 124 impl_thread_weak_ptr_, | 135 impl_thread_weak_ptr_, |
| 125 &begin_frame_sent_to_main_thread_completion)); | 136 &begin_frame_sent_to_main_thread_completion, |
| 137 &request)); |
| 126 begin_frame_sent_to_main_thread_completion.Wait(); | 138 begin_frame_sent_to_main_thread_completion.Wait(); |
| 127 } | 139 } |
| 128 in_composite_and_readback_ = true; | 140 in_composite_and_readback_ = true; |
| 141 // This is the forced commit. |
| 142 // Note: The Impl thread also queues a separate BeginFrameOnMainThread on the |
| 143 // main thread, which will be called after this CompositeAndReadback |
| 144 // completes, to replace the forced commit. |
| 129 BeginFrameOnMainThread(scoped_ptr<BeginFrameAndCommitState>()); | 145 BeginFrameOnMainThread(scoped_ptr<BeginFrameAndCommitState>()); |
| 130 in_composite_and_readback_ = false; | 146 in_composite_and_readback_ = false; |
| 131 | 147 |
| 132 // Perform a synchronous readback. | 148 request.completion.Wait(); |
| 133 ReadbackRequest request; | |
| 134 request.rect = rect; | |
| 135 request.pixels = pixels; | |
| 136 { | |
| 137 DebugScopedSetMainThreadBlocked main_thread_blocked(this); | |
| 138 Proxy::ImplThreadTaskRunner()->PostTask( | |
| 139 FROM_HERE, | |
| 140 base::Bind(&ThreadProxy::RequestReadbackOnImplThread, | |
| 141 impl_thread_weak_ptr_, | |
| 142 &request)); | |
| 143 request.completion.Wait(); | |
| 144 } | |
| 145 return request.success; | 149 return request.success; |
| 146 } | 150 } |
| 147 | 151 |
| 148 void ThreadProxy::ForceCommitOnImplThread(CompletionEvent* completion) { | 152 void ThreadProxy::ForceCommitForReadbackOnImplThread( |
| 149 TRACE_EVENT0("cc", "ThreadProxy::ForceCommitOnImplThread"); | 153 CompletionEvent* begin_frame_sent_completion, |
| 154 ReadbackRequest* request) { |
| 155 TRACE_EVENT0("cc", "ThreadProxy::ForceCommitForReadbackOnImplThread"); |
| 150 DCHECK(IsImplThread()); | 156 DCHECK(IsImplThread()); |
| 151 DCHECK(!begin_frame_sent_to_main_thread_completion_event_on_impl_thread_); | 157 DCHECK(!begin_frame_sent_to_main_thread_completion_event_on_impl_thread_); |
| 158 DCHECK(!readback_request_on_impl_thread_); |
| 152 | 159 |
| 153 scheduler_on_impl_thread_->SetNeedsForcedCommit(); | |
| 154 if (scheduler_on_impl_thread_->CommitPending()) { | |
| 155 completion->Signal(); | |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 begin_frame_sent_to_main_thread_completion_event_on_impl_thread_ = completion; | |
| 160 } | |
| 161 | |
| 162 void ThreadProxy::RequestReadbackOnImplThread(ReadbackRequest* request) { | |
| 163 DCHECK(Proxy::IsImplThread()); | |
| 164 DCHECK(!readback_request_on_impl_thread_); | |
| 165 if (!layer_tree_host_impl_) { | 160 if (!layer_tree_host_impl_) { |
| 161 begin_frame_sent_completion->Signal(); |
| 166 request->success = false; | 162 request->success = false; |
| 167 request->completion.Signal(); | 163 request->completion.Signal(); |
| 168 return; | 164 return; |
| 169 } | 165 } |
| 170 | 166 |
| 171 readback_request_on_impl_thread_ = request; | 167 readback_request_on_impl_thread_ = request; |
| 172 scheduler_on_impl_thread_->SetNeedsRedraw(); | 168 |
| 173 scheduler_on_impl_thread_->SetNeedsForcedRedraw(); | 169 scheduler_on_impl_thread_->SetNeedsForcedCommitForReadback(); |
| 170 if (scheduler_on_impl_thread_->CommitPending()) { |
| 171 begin_frame_sent_completion->Signal(); |
| 172 return; |
| 173 } |
| 174 |
| 175 begin_frame_sent_to_main_thread_completion_event_on_impl_thread_ = |
| 176 begin_frame_sent_completion; |
| 174 } | 177 } |
| 175 | 178 |
| 176 void ThreadProxy::FinishAllRendering() { | 179 void ThreadProxy::FinishAllRendering() { |
| 177 DCHECK(Proxy::IsMainThread()); | 180 DCHECK(Proxy::IsMainThread()); |
| 178 DCHECK(!defer_commits_); | 181 DCHECK(!defer_commits_); |
| 179 | 182 |
| 180 // Make sure all GL drawing is finished on the impl thread. | 183 // Make sure all GL drawing is finished on the impl thread. |
| 181 DebugScopedSetMainThreadBlocked main_thread_blocked(this); | 184 DebugScopedSetMainThreadBlocked main_thread_blocked(this); |
| 182 CompletionEvent completion; | 185 CompletionEvent completion; |
| 183 Proxy::ImplThreadTaskRunner()->PostTask( | 186 Proxy::ImplThreadTaskRunner()->PostTask( |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 Proxy::MainThreadTaskRunner()->PostTask( | 366 Proxy::MainThreadTaskRunner()->PostTask( |
| 364 FROM_HERE, | 367 FROM_HERE, |
| 365 base::Bind(&ThreadProxy::DidCompleteSwapBuffers, main_thread_weak_ptr_)); | 368 base::Bind(&ThreadProxy::DidCompleteSwapBuffers, main_thread_weak_ptr_)); |
| 366 } | 369 } |
| 367 | 370 |
| 368 void ThreadProxy::SetNeedsBeginFrameOnImplThread(bool enable) { | 371 void ThreadProxy::SetNeedsBeginFrameOnImplThread(bool enable) { |
| 369 DCHECK(IsImplThread()); | 372 DCHECK(IsImplThread()); |
| 370 TRACE_EVENT1("cc", "ThreadProxy::SetNeedsBeginFrameOnImplThread", | 373 TRACE_EVENT1("cc", "ThreadProxy::SetNeedsBeginFrameOnImplThread", |
| 371 "enable", enable); | 374 "enable", enable); |
| 372 layer_tree_host_impl_->SetNeedsBeginFrame(enable); | 375 layer_tree_host_impl_->SetNeedsBeginFrame(enable); |
| 376 layer_tree_host_impl_->UpdateBackgroundAnimateTicking(!enable); |
| 373 } | 377 } |
| 374 | 378 |
| 375 void ThreadProxy::BeginFrameOnImplThread(const BeginFrameArgs& args) { | 379 void ThreadProxy::BeginFrameOnImplThread(const BeginFrameArgs& args) { |
| 376 DCHECK(IsImplThread()); | 380 DCHECK(IsImplThread()); |
| 377 TRACE_EVENT0("cc", "ThreadProxy::BeginFrameOnImplThread"); | 381 TRACE_EVENT0("cc", "ThreadProxy::BeginFrameOnImplThread"); |
| 382 |
| 383 base::TimeTicks monotonic_time = |
| 384 layer_tree_host_impl_->CurrentFrameTimeTicks(); |
| 385 base::Time wall_clock_time = layer_tree_host_impl_->CurrentFrameTime(); |
| 386 layer_tree_host_impl_->Animate(monotonic_time, wall_clock_time); |
| 387 |
| 388 // Reinitialize for the current frame. |
| 389 frame_did_draw_ = false; |
| 390 |
| 378 scheduler_on_impl_thread_->BeginFrame(args); | 391 scheduler_on_impl_thread_->BeginFrame(args); |
| 379 } | 392 } |
| 380 | 393 |
| 394 void ThreadProxy::DidBeginFrameDeadlineOnImplThread() { |
| 395 // Do not start animations if we skip drawing the frame to avoid |
| 396 // checkerboarding. |
| 397 layer_tree_host_impl_->UpdateAnimationState( |
| 398 frame_did_draw_ || !layer_tree_host_impl_->CanDraw()); |
| 399 layer_tree_host_impl_->ResetCurrentFrameTimeForNextFrame(); |
| 400 } |
| 401 |
| 381 void ThreadProxy::OnCanDrawStateChanged(bool can_draw) { | 402 void ThreadProxy::OnCanDrawStateChanged(bool can_draw) { |
| 382 DCHECK(IsImplThread()); | 403 DCHECK(IsImplThread()); |
| 383 TRACE_EVENT1( | 404 TRACE_EVENT1( |
| 384 "cc", "ThreadProxy::OnCanDrawStateChanged", "can_draw", can_draw); | 405 "cc", "ThreadProxy::OnCanDrawStateChanged", "can_draw", can_draw); |
| 385 scheduler_on_impl_thread_->SetCanDraw(can_draw); | 406 scheduler_on_impl_thread_->SetCanDraw(can_draw); |
| 386 layer_tree_host_impl_->UpdateBackgroundAnimateTicking( | 407 layer_tree_host_impl_->UpdateBackgroundAnimateTicking( |
| 387 !scheduler_on_impl_thread_->WillDrawIfNeeded()); | 408 !scheduler_on_impl_thread_->WillDrawIfNeeded()); |
| 388 } | 409 } |
| 389 | 410 |
| 390 void ThreadProxy::OnHasPendingTreeStateChanged(bool has_pending_tree) { | 411 void ThreadProxy::OnHasPendingTreeStateChanged(bool has_pending_tree) { |
| 391 DCHECK(IsImplThread()); | 412 DCHECK(IsImplThread()); |
| 392 TRACE_EVENT1("cc", "ThreadProxy::OnHasPendingTreeStateChanged", | 413 TRACE_EVENT1("cc", "ThreadProxy::OnHasPendingTreeStateChanged", |
| 393 "has_pending_tree", has_pending_tree); | 414 "has_pending_tree", has_pending_tree); |
| 394 scheduler_on_impl_thread_->SetHasPendingTree(has_pending_tree); | 415 scheduler_on_impl_thread_->SetHasTrees( |
| 416 has_pending_tree, |
| 417 layer_tree_host_impl_->active_tree() ? |
| 418 !layer_tree_host_impl_->active_tree()->root_layer() : true); |
| 419 |
| 420 if (!has_pending_tree && !deferred_start_commit_on_impl_thread_.is_null()) { |
| 421 Proxy::ImplThreadTaskRunner()->PostTask( |
| 422 FROM_HERE, |
| 423 deferred_start_commit_on_impl_thread_); |
| 424 deferred_start_commit_on_impl_thread_.Reset(); |
| 425 } |
| 395 } | 426 } |
| 396 | 427 |
| 397 void ThreadProxy::SetNeedsCommitOnImplThread() { | 428 void ThreadProxy::SetNeedsCommitOnImplThread() { |
| 398 DCHECK(IsImplThread()); | 429 DCHECK(IsImplThread()); |
| 399 TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommitOnImplThread"); | 430 TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommitOnImplThread"); |
| 400 scheduler_on_impl_thread_->SetNeedsCommit(); | 431 scheduler_on_impl_thread_->SetNeedsCommit(); |
| 401 } | 432 } |
| 402 | 433 |
| 403 void ThreadProxy::PostAnimationEventsToMainThreadOnImplThread( | 434 void ThreadProxy::PostAnimationEventsToMainThreadOnImplThread( |
| 404 scoped_ptr<AnimationEventsVector> events, | 435 scoped_ptr<AnimationEventsVector> events, |
| (...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 620 TRACE_EVENT0("cc", "ThreadProxy::FinishAllRenderingOnImplThread"); | 651 TRACE_EVENT0("cc", "ThreadProxy::FinishAllRenderingOnImplThread"); |
| 621 DCHECK(IsImplThread()); | 652 DCHECK(IsImplThread()); |
| 622 layer_tree_host_impl_->FinishAllRendering(); | 653 layer_tree_host_impl_->FinishAllRendering(); |
| 623 completion->Signal(); | 654 completion->Signal(); |
| 624 } | 655 } |
| 625 | 656 |
| 626 void ThreadProxy::ScheduledActionSendBeginFrameToMainThread() { | 657 void ThreadProxy::ScheduledActionSendBeginFrameToMainThread() { |
| 627 TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionSendBeginFrameToMainThread"); | 658 TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionSendBeginFrameToMainThread"); |
| 628 scoped_ptr<BeginFrameAndCommitState> begin_frame_state( | 659 scoped_ptr<BeginFrameAndCommitState> begin_frame_state( |
| 629 new BeginFrameAndCommitState); | 660 new BeginFrameAndCommitState); |
| 661 |
| 630 begin_frame_state->monotonic_frame_begin_time = | 662 begin_frame_state->monotonic_frame_begin_time = |
| 631 layer_tree_host_impl_->CurrentPhysicalTimeTicks(); | 663 layer_tree_host_impl_->CurrentPhysicalTimeTicks(); |
| 632 begin_frame_state->scroll_info = | 664 begin_frame_state->scroll_info = |
| 633 layer_tree_host_impl_->ProcessScrollDeltas(); | 665 layer_tree_host_impl_->ProcessScrollDeltas(); |
| 634 | 666 |
| 635 if (!layer_tree_host_impl_->settings().impl_side_painting) { | 667 if (!layer_tree_host_impl_->settings().impl_side_painting) { |
| 636 DCHECK_GT(layer_tree_host_impl_->memory_allocation_limit_bytes(), 0u); | 668 DCHECK_GT(layer_tree_host_impl_->memory_allocation_limit_bytes(), 0u); |
| 637 } | 669 } |
| 670 |
| 671 |
| 638 begin_frame_state->memory_allocation_limit_bytes = | 672 begin_frame_state->memory_allocation_limit_bytes = |
| 639 layer_tree_host_impl_->memory_allocation_limit_bytes(); | 673 layer_tree_host_impl_->memory_allocation_limit_bytes(); |
| 640 Proxy::MainThreadTaskRunner()->PostTask( | 674 Proxy::MainThreadTaskRunner()->PostTask( |
| 641 FROM_HERE, | 675 FROM_HERE, |
| 642 base::Bind(&ThreadProxy::BeginFrameOnMainThread, | 676 base::Bind(&ThreadProxy::BeginFrameOnMainThread, |
| 643 main_thread_weak_ptr_, | 677 main_thread_weak_ptr_, |
| 644 base::Passed(&begin_frame_state))); | 678 base::Passed(&begin_frame_state))); |
| 645 | 679 |
| 646 if (begin_frame_sent_to_main_thread_completion_event_on_impl_thread_) { | 680 if (begin_frame_sent_to_main_thread_completion_event_on_impl_thread_) { |
| 647 begin_frame_sent_to_main_thread_completion_event_on_impl_thread_->Signal(); | 681 begin_frame_sent_to_main_thread_completion_event_on_impl_thread_->Signal(); |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 775 } | 809 } |
| 776 | 810 |
| 777 layer_tree_host_->CommitComplete(); | 811 layer_tree_host_->CommitComplete(); |
| 778 layer_tree_host_->DidBeginFrame(); | 812 layer_tree_host_->DidBeginFrame(); |
| 779 } | 813 } |
| 780 | 814 |
| 781 void ThreadProxy::StartCommitOnImplThread( | 815 void ThreadProxy::StartCommitOnImplThread( |
| 782 CompletionEvent* completion, | 816 CompletionEvent* completion, |
| 783 ResourceUpdateQueue* raw_queue, | 817 ResourceUpdateQueue* raw_queue, |
| 784 scoped_refptr<cc::ContextProvider> offscreen_context_provider) { | 818 scoped_refptr<cc::ContextProvider> offscreen_context_provider) { |
| 785 scoped_ptr<ResourceUpdateQueue> queue(raw_queue); | |
| 786 | |
| 787 TRACE_EVENT0("cc", "ThreadProxy::StartCommitOnImplThread"); | 819 TRACE_EVENT0("cc", "ThreadProxy::StartCommitOnImplThread"); |
| 788 DCHECK(!commit_completion_event_on_impl_thread_); | 820 DCHECK(!commit_completion_event_on_impl_thread_); |
| 789 DCHECK(IsImplThread() && IsMainThreadBlocked()); | 821 DCHECK(IsImplThread() && IsMainThreadBlocked()); |
| 790 DCHECK(scheduler_on_impl_thread_); | 822 DCHECK(scheduler_on_impl_thread_); |
| 791 DCHECK(scheduler_on_impl_thread_->CommitPending()); | 823 DCHECK(scheduler_on_impl_thread_->CommitPending()); |
| 792 | 824 |
| 793 if (!layer_tree_host_impl_) { | 825 if (!layer_tree_host_impl_) { |
| 794 TRACE_EVENT0("cc", "EarlyOut_NoLayerTree"); | 826 TRACE_EVENT0("cc", "EarlyOut_NoLayerTree"); |
| 795 completion->Signal(); | 827 completion->Signal(); |
| 828 |
| 796 return; | 829 return; |
| 797 } | 830 } |
| 798 | 831 |
| 832 // We defer forced commits while there is a pending tree here. |
| 833 if (layer_tree_host_impl_->pending_tree()) { |
| 834 NOTREACHED(); |
| 835 TRACE_EVENT0("cc", "EarlyOut_DeferringDueToPendingTree"); |
| 836 DCHECK(deferred_start_commit_on_impl_thread_.is_null()); |
| 837 deferred_start_commit_on_impl_thread_ = |
| 838 base::Bind(&ThreadProxy::StartCommitOnImplThread, |
| 839 impl_thread_weak_ptr_, |
| 840 completion, |
| 841 raw_queue, |
| 842 offscreen_context_provider); |
| 843 return; |
| 844 } |
| 845 |
| 846 scoped_ptr<ResourceUpdateQueue> queue(raw_queue); |
| 847 |
| 799 if (offscreen_context_provider.get()) | 848 if (offscreen_context_provider.get()) |
| 800 offscreen_context_provider->BindToCurrentThread(); | 849 offscreen_context_provider->BindToCurrentThread(); |
| 801 layer_tree_host_impl_->resource_provider()-> | 850 layer_tree_host_impl_->resource_provider()-> |
| 802 set_offscreen_context_provider(offscreen_context_provider); | 851 set_offscreen_context_provider(offscreen_context_provider); |
| 803 | 852 |
| 804 if (layer_tree_host_->contents_texture_manager()) { | 853 if (layer_tree_host_->contents_texture_manager()) { |
| 805 if (layer_tree_host_->contents_texture_manager()-> | 854 if (layer_tree_host_->contents_texture_manager()-> |
| 806 LinkedEvictedBackingsExist()) { | 855 LinkedEvictedBackingsExist()) { |
| 807 // Clear any uploads we were making to textures linked to evicted | 856 // Clear any uploads we were making to textures linked to evicted |
| 808 // resources | 857 // resources |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 844 | 893 |
| 845 // Complete all remaining texture updates. | 894 // Complete all remaining texture updates. |
| 846 current_resource_update_controller_on_impl_thread_->Finalize(); | 895 current_resource_update_controller_on_impl_thread_->Finalize(); |
| 847 current_resource_update_controller_on_impl_thread_.reset(); | 896 current_resource_update_controller_on_impl_thread_.reset(); |
| 848 | 897 |
| 849 layer_tree_host_impl_->BeginCommit(); | 898 layer_tree_host_impl_->BeginCommit(); |
| 850 layer_tree_host_->BeginCommitOnImplThread(layer_tree_host_impl_.get()); | 899 layer_tree_host_->BeginCommitOnImplThread(layer_tree_host_impl_.get()); |
| 851 layer_tree_host_->FinishCommitOnImplThread(layer_tree_host_impl_.get()); | 900 layer_tree_host_->FinishCommitOnImplThread(layer_tree_host_impl_.get()); |
| 852 layer_tree_host_impl_->CommitComplete(); | 901 layer_tree_host_impl_->CommitComplete(); |
| 853 | 902 |
| 854 layer_tree_host_impl_->UpdateBackgroundAnimateTicking( | |
| 855 !scheduler_on_impl_thread_->WillDrawIfNeeded()); | |
| 856 | |
| 857 next_frame_is_newly_committed_frame_on_impl_thread_ = true; | 903 next_frame_is_newly_committed_frame_on_impl_thread_ = true; |
| 858 | 904 |
| 859 if (layer_tree_host_->settings().impl_side_painting && | 905 if (layer_tree_host_->settings().impl_side_painting && |
| 860 layer_tree_host_->BlocksPendingCommit() && | 906 layer_tree_host_->BlocksPendingCommit() && |
| 861 layer_tree_host_impl_->pending_tree()) { | 907 layer_tree_host_impl_->pending_tree()) { |
| 862 // For some layer types in impl-side painting, the commit is held until | 908 // For some layer types in impl-side painting, the commit is held until |
| 863 // the pending tree is activated. It's also possible that the | 909 // the pending tree is activated. It's also possible that the |
| 864 // pending tree has already activated if there was no work to be done. | 910 // pending tree has already activated if there was no work to be done. |
| 865 TRACE_EVENT_INSTANT0("cc", "HoldCommit", TRACE_EVENT_SCOPE_THREAD); | 911 TRACE_EVENT_INSTANT0("cc", "HoldCommit", TRACE_EVENT_SCOPE_THREAD); |
| 866 completion_event_for_commit_held_on_tree_activation_ = | 912 completion_event_for_commit_held_on_tree_activation_ = |
| 867 commit_completion_event_on_impl_thread_; | 913 commit_completion_event_on_impl_thread_; |
| 868 commit_completion_event_on_impl_thread_ = NULL; | 914 commit_completion_event_on_impl_thread_ = NULL; |
| 869 } else { | 915 } else { |
| 870 commit_completion_event_on_impl_thread_->Signal(); | 916 commit_completion_event_on_impl_thread_->Signal(); |
| 871 commit_completion_event_on_impl_thread_ = NULL; | 917 commit_completion_event_on_impl_thread_ = NULL; |
| 872 } | 918 } |
| 873 | 919 |
| 920 // The commit may have added animations, requiring us to start |
| 921 // background ticking. |
| 922 layer_tree_host_impl_->UpdateBackgroundAnimateTicking( |
| 923 !scheduler_on_impl_thread_->WillDrawIfNeeded()); |
| 874 // SetVisible kicks off the next scheduler action, so this must be last. | 924 // SetVisible kicks off the next scheduler action, so this must be last. |
| 875 scheduler_on_impl_thread_->SetVisible(layer_tree_host_impl_->visible()); | 925 scheduler_on_impl_thread_->SetVisible(layer_tree_host_impl_->visible()); |
| 876 } | 926 } |
| 877 | 927 |
| 878 void ThreadProxy::ScheduledActionCheckForCompletedTileUploads() { | 928 void ThreadProxy::ScheduledActionCheckForCompletedTileUploads() { |
| 879 DCHECK(IsImplThread()); | 929 DCHECK(IsImplThread()); |
| 880 TRACE_EVENT0("cc", | 930 TRACE_EVENT0("cc", |
| 881 "ThreadProxy::ScheduledActionCheckForCompletedTileUploads"); | 931 "ThreadProxy::ScheduledActionCheckForCompletedTileUploads"); |
| 882 layer_tree_host_impl_->CheckForCompletedTileUploads(); | 932 layer_tree_host_impl_->CheckForCompletedTileUploads(); |
| 883 } | 933 } |
| 884 | 934 |
| 885 void ThreadProxy::ScheduledActionActivatePendingTreeIfNeeded() { | 935 void ThreadProxy::ScheduledActionActivatePendingTreeIfNeeded() { |
| 886 DCHECK(IsImplThread()); | 936 DCHECK(IsImplThread()); |
| 887 TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionActivatePendingTreeIfNeeded"); | 937 TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionActivatePendingTreeIfNeeded"); |
| 888 layer_tree_host_impl_->ActivatePendingTreeIfNeeded(); | 938 layer_tree_host_impl_->ActivatePendingTreeIfNeeded(); |
| 889 } | 939 } |
| 890 | 940 |
| 891 void ThreadProxy::ScheduledActionBeginOutputSurfaceCreation() { | 941 void ThreadProxy::ScheduledActionBeginOutputSurfaceCreation() { |
| 892 DCHECK(IsImplThread()); | 942 DCHECK(IsImplThread()); |
| 893 Proxy::MainThreadTaskRunner()->PostTask( | 943 Proxy::MainThreadTaskRunner()->PostTask( |
| 894 FROM_HERE, | 944 FROM_HERE, |
| 895 base::Bind(&ThreadProxy::CreateAndInitializeOutputSurface, | 945 base::Bind(&ThreadProxy::CreateAndInitializeOutputSurface, |
| 896 main_thread_weak_ptr_)); | 946 main_thread_weak_ptr_)); |
| 897 } | 947 } |
| 898 | 948 |
| 899 ScheduledActionDrawAndSwapResult | 949 ScheduledActionDrawSwapReadbackResult |
| 900 ThreadProxy::ScheduledActionDrawAndSwapInternal(bool forced_draw) { | 950 ThreadProxy::ScheduledActionDrawSwapReadbackInternal( |
| 951 bool forced_draw, bool swap_requested, bool readback_requested) { |
| 901 TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionDrawAndSwap"); | 952 TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionDrawAndSwap"); |
| 902 | 953 |
| 903 ScheduledActionDrawAndSwapResult result; | 954 ScheduledActionDrawSwapReadbackResult result; |
| 904 result.did_draw = false; | 955 result.did_draw = false; |
| 905 result.did_swap = false; | 956 result.did_swap = false; |
| 906 DCHECK(IsImplThread()); | 957 DCHECK(IsImplThread()); |
| 907 DCHECK(layer_tree_host_impl_.get()); | 958 DCHECK(layer_tree_host_impl_.get()); |
| 908 if (!layer_tree_host_impl_) | 959 if (!layer_tree_host_impl_) |
| 909 return result; | 960 return result; |
| 910 | 961 |
| 911 DCHECK(layer_tree_host_impl_->renderer()); | 962 DCHECK(layer_tree_host_impl_->renderer()); |
| 912 if (!layer_tree_host_impl_->renderer()) | 963 if (!layer_tree_host_impl_->renderer()) |
| 913 return result; | 964 return result; |
| 914 | 965 |
| 915 base::TimeTicks monotonic_time = | |
| 916 layer_tree_host_impl_->CurrentFrameTimeTicks(); | |
| 917 base::Time wall_clock_time = layer_tree_host_impl_->CurrentFrameTime(); | |
| 918 | |
| 919 // TODO(enne): This should probably happen post-animate. | |
| 920 if (layer_tree_host_impl_->pending_tree()) { | |
| 921 layer_tree_host_impl_->ActivatePendingTreeIfNeeded(); | |
| 922 if (layer_tree_host_impl_->pending_tree()) | |
| 923 layer_tree_host_impl_->pending_tree()->UpdateDrawProperties(); | |
| 924 } | |
| 925 layer_tree_host_impl_->Animate(monotonic_time, wall_clock_time); | |
| 926 layer_tree_host_impl_->UpdateBackgroundAnimateTicking(false); | |
| 927 | |
| 928 base::TimeTicks start_time = base::TimeTicks::HighResNow(); | 966 base::TimeTicks start_time = base::TimeTicks::HighResNow(); |
| 929 base::TimeDelta draw_duration_estimate = DrawDurationEstimate(); | 967 base::TimeDelta draw_duration_estimate = DrawDurationEstimate(); |
| 930 base::AutoReset<bool> mark_inside(&inside_draw_, true); | 968 base::AutoReset<bool> mark_inside(&inside_draw_, true); |
| 931 | 969 |
| 932 // This method is called on a forced draw, regardless of whether we are able | 970 // This method is called on a forced draw, regardless of whether we are able |
| 933 // to produce a frame, as the calling site on main thread is blocked until its | 971 // to produce a frame, as the calling site on main thread is blocked until its |
| 934 // request completes, and we signal completion here. If CanDraw() is false, we | 972 // request completes, and we signal completion here. If CanDraw() is false, we |
| 935 // will indicate success=false to the caller, but we must still signal | 973 // will indicate success=false to the caller, but we must still signal |
| 936 // completion to avoid deadlock. | 974 // completion to avoid deadlock. |
| 937 | 975 |
| 938 // We guard PrepareToDraw() with CanDraw() because it always returns a valid | 976 // We guard PrepareToDraw() with CanDraw() because it always returns a valid |
| 939 // frame, so can only be used when such a frame is possible. Since | 977 // frame, so can only be used when such a frame is possible. Since |
| 940 // DrawLayers() depends on the result of PrepareToDraw(), it is guarded on | 978 // DrawLayers() depends on the result of PrepareToDraw(), it is guarded on |
| 941 // CanDraw() as well. | 979 // CanDraw() as well. |
| 942 | 980 |
| 943 bool drawing_for_readback = !!readback_request_on_impl_thread_; | 981 bool drawing_for_readback = |
| 982 readback_requested && !!readback_request_on_impl_thread_; |
| 944 bool can_do_readback = layer_tree_host_impl_->renderer()->CanReadPixels(); | 983 bool can_do_readback = layer_tree_host_impl_->renderer()->CanReadPixels(); |
| 945 | 984 |
| 946 LayerTreeHostImpl::FrameData frame; | 985 LayerTreeHostImpl::FrameData frame; |
| 947 bool draw_frame = false; | 986 bool draw_frame = false; |
| 948 bool start_ready_animations = true; | |
| 949 | 987 |
| 950 if (layer_tree_host_impl_->CanDraw() && | 988 if (layer_tree_host_impl_->CanDraw() && |
| 951 (!drawing_for_readback || can_do_readback)) { | 989 (!drawing_for_readback || can_do_readback)) { |
| 952 // If it is for a readback, make sure we draw the portion being read back. | 990 // If it is for a readback, make sure we draw the portion being read back. |
| 953 gfx::Rect readback_rect; | 991 gfx::Rect readback_rect; |
| 954 if (drawing_for_readback) | 992 if (drawing_for_readback) |
| 955 readback_rect = readback_request_on_impl_thread_->rect; | 993 readback_rect = readback_request_on_impl_thread_->rect; |
| 956 | 994 |
| 957 // Do not start animations if we skip drawing the frame to avoid | |
| 958 // checkerboarding. | |
| 959 if (layer_tree_host_impl_->PrepareToDraw(&frame, readback_rect) || | 995 if (layer_tree_host_impl_->PrepareToDraw(&frame, readback_rect) || |
| 960 forced_draw) | 996 forced_draw) |
| 961 draw_frame = true; | 997 draw_frame = true; |
| 962 else | |
| 963 start_ready_animations = false; | |
| 964 } | 998 } |
| 965 | 999 |
| 1000 frame_did_draw_ = draw_frame; |
| 1001 |
| 966 if (draw_frame) { | 1002 if (draw_frame) { |
| 967 layer_tree_host_impl_->DrawLayers( | 1003 layer_tree_host_impl_->DrawLayers( |
| 968 &frame, | 1004 &frame, |
| 969 scheduler_on_impl_thread_->LastBeginFrameOnImplThreadTime()); | 1005 scheduler_on_impl_thread_->LastBeginFrameOnImplThreadTime()); |
| 970 result.did_draw = true; | 1006 result.did_draw = true; |
| 971 } | 1007 } |
| 972 layer_tree_host_impl_->DidDrawAllLayers(frame); | 1008 layer_tree_host_impl_->DidDrawAllLayers(frame); |
| 973 | 1009 |
| 974 layer_tree_host_impl_->UpdateAnimationState(start_ready_animations); | |
| 975 | |
| 976 // Check for a pending CompositeAndReadback. | 1010 // Check for a pending CompositeAndReadback. |
| 977 if (readback_request_on_impl_thread_) { | 1011 if (drawing_for_readback) { |
| 978 readback_request_on_impl_thread_->success = false; | 1012 result.did_readback = false; |
| 979 if (draw_frame) { | 1013 if (draw_frame) { |
| 980 layer_tree_host_impl_->Readback(readback_request_on_impl_thread_->pixels, | 1014 layer_tree_host_impl_->Readback(readback_request_on_impl_thread_->pixels, |
| 981 readback_request_on_impl_thread_->rect); | 1015 readback_request_on_impl_thread_->rect); |
| 982 readback_request_on_impl_thread_->success = | 1016 result.did_readback = |
| 983 !layer_tree_host_impl_->IsContextLost(); | 1017 !layer_tree_host_impl_->IsContextLost(); |
| 984 } | 1018 } |
| 1019 readback_request_on_impl_thread_->success = result.did_readback; |
| 985 readback_request_on_impl_thread_->completion.Signal(); | 1020 readback_request_on_impl_thread_->completion.Signal(); |
| 986 readback_request_on_impl_thread_ = NULL; | 1021 readback_request_on_impl_thread_ = NULL; |
| 987 } else if (draw_frame) { | 1022 } else if (draw_frame && swap_requested) { |
| 988 result.did_swap = layer_tree_host_impl_->SwapBuffers(frame); | 1023 result.did_swap = layer_tree_host_impl_->SwapBuffers(frame); |
| 989 | 1024 |
| 990 if (frame.contains_incomplete_tile) | 1025 if (frame.contains_incomplete_tile) |
| 991 DidSwapUseIncompleteTileOnImplThread(); | 1026 DidSwapUseIncompleteTileOnImplThread(); |
| 992 } | 1027 } |
| 993 | 1028 |
| 994 // Tell the main thread that the the newly-commited frame was drawn. | 1029 // Tell the main thread that the the newly-commited frame was drawn. |
| 995 if (next_frame_is_newly_committed_frame_on_impl_thread_) { | 1030 if (next_frame_is_newly_committed_frame_on_impl_thread_) { |
| 996 next_frame_is_newly_committed_frame_on_impl_thread_ = false; | 1031 next_frame_is_newly_committed_frame_on_impl_thread_ = false; |
| 997 Proxy::MainThreadTaskRunner()->PostTask( | 1032 Proxy::MainThreadTaskRunner()->PostTask( |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1069 texture_acquisition_completion_event_on_impl_thread_ = completion; | 1104 texture_acquisition_completion_event_on_impl_thread_ = completion; |
| 1070 scheduler_on_impl_thread_->SetMainThreadNeedsLayerTextures(); | 1105 scheduler_on_impl_thread_->SetMainThreadNeedsLayerTextures(); |
| 1071 } | 1106 } |
| 1072 | 1107 |
| 1073 void ThreadProxy::ScheduledActionAcquireLayerTexturesForMainThread() { | 1108 void ThreadProxy::ScheduledActionAcquireLayerTexturesForMainThread() { |
| 1074 DCHECK(texture_acquisition_completion_event_on_impl_thread_); | 1109 DCHECK(texture_acquisition_completion_event_on_impl_thread_); |
| 1075 texture_acquisition_completion_event_on_impl_thread_->Signal(); | 1110 texture_acquisition_completion_event_on_impl_thread_->Signal(); |
| 1076 texture_acquisition_completion_event_on_impl_thread_ = NULL; | 1111 texture_acquisition_completion_event_on_impl_thread_ = NULL; |
| 1077 } | 1112 } |
| 1078 | 1113 |
| 1079 ScheduledActionDrawAndSwapResult | 1114 ScheduledActionDrawSwapReadbackResult |
| 1080 ThreadProxy::ScheduledActionDrawAndSwapIfPossible() { | 1115 ThreadProxy::ScheduledActionDrawAndSwapIfPossible() { |
| 1081 return ScheduledActionDrawAndSwapInternal(false); | 1116 bool forced_draw = false; |
| 1117 bool swap_requested = true; |
| 1118 bool readback_requested = false; |
| 1119 return ScheduledActionDrawSwapReadbackInternal( |
| 1120 forced_draw, swap_requested, readback_requested); |
| 1082 } | 1121 } |
| 1083 | 1122 |
| 1084 ScheduledActionDrawAndSwapResult | 1123 ScheduledActionDrawSwapReadbackResult |
| 1085 ThreadProxy::ScheduledActionDrawAndSwapForced() { | 1124 ThreadProxy::ScheduledActionDrawAndSwapForced() { |
| 1086 return ScheduledActionDrawAndSwapInternal(true); | 1125 bool forced_draw = true; |
| 1126 bool swap_requested = true; |
| 1127 bool readback_requested = false; |
| 1128 return ScheduledActionDrawSwapReadbackInternal( |
| 1129 forced_draw, swap_requested, readback_requested); |
| 1087 } | 1130 } |
| 1088 | 1131 |
| 1132 ScheduledActionDrawSwapReadbackResult |
| 1133 ThreadProxy::ScheduledActionDrawAndReadback() { |
| 1134 bool forced_draw = true; |
| 1135 bool swap_requested = false; |
| 1136 bool readback_requested = true; |
| 1137 return ScheduledActionDrawSwapReadbackInternal( |
| 1138 forced_draw, swap_requested, readback_requested); |
| 1139 } |
| 1140 |
| 1141 |
| 1089 void ThreadProxy::DidAnticipatedDrawTimeChange(base::TimeTicks time) { | 1142 void ThreadProxy::DidAnticipatedDrawTimeChange(base::TimeTicks time) { |
| 1090 if (current_resource_update_controller_on_impl_thread_) | 1143 if (current_resource_update_controller_on_impl_thread_) |
| 1091 current_resource_update_controller_on_impl_thread_ | 1144 current_resource_update_controller_on_impl_thread_ |
| 1092 ->PerformMoreUpdates(time); | 1145 ->PerformMoreUpdates(time); |
| 1093 layer_tree_host_impl_->ResetCurrentFrameTimeForNextFrame(); | |
| 1094 } | 1146 } |
| 1095 | 1147 |
| 1096 base::TimeDelta ThreadProxy::DrawDurationEstimate() { | 1148 base::TimeDelta ThreadProxy::DrawDurationEstimate() { |
| 1097 base::TimeDelta historical_estimate = | 1149 base::TimeDelta historical_estimate = |
| 1098 draw_duration_history_.Percentile(kDrawDurationEstimationPercentile); | 1150 draw_duration_history_.Percentile(kDrawDurationEstimationPercentile); |
| 1099 base::TimeDelta padding = base::TimeDelta::FromMicroseconds( | 1151 base::TimeDelta padding = base::TimeDelta::FromMicroseconds( |
| 1100 kDrawDurationEstimatePaddingInMicroseconds); | 1152 kDrawDurationEstimatePaddingInMicroseconds); |
| 1101 return historical_estimate + padding; | 1153 return historical_estimate + padding; |
| 1102 } | 1154 } |
| 1103 | 1155 |
| 1156 void ThreadProxy::PostBeginFrameDeadline(const base::Closure &closure, |
| 1157 base::TimeTicks deadline) { |
| 1158 base::TimeDelta delta = deadline - base::TimeTicks::Now(); |
| 1159 if (delta <= base::TimeDelta()) |
| 1160 delta = base::TimeDelta(); |
| 1161 Proxy::ImplThreadTaskRunner()->PostDelayedTask(FROM_HERE, closure, delta); |
| 1162 } |
| 1163 |
| 1104 void ThreadProxy::ReadyToFinalizeTextureUpdates() { | 1164 void ThreadProxy::ReadyToFinalizeTextureUpdates() { |
| 1105 DCHECK(IsImplThread()); | 1165 DCHECK(IsImplThread()); |
| 1106 scheduler_on_impl_thread_->FinishCommit(); | 1166 scheduler_on_impl_thread_->FinishCommit(); |
| 1107 } | 1167 } |
| 1108 | 1168 |
| 1109 void ThreadProxy::DidCommitAndDrawFrame() { | 1169 void ThreadProxy::DidCommitAndDrawFrame() { |
| 1110 DCHECK(IsMainThread()); | 1170 DCHECK(IsMainThread()); |
| 1111 if (!layer_tree_host_) | 1171 if (!layer_tree_host_) |
| 1112 return; | 1172 return; |
| 1113 layer_tree_host_->DidCommitAndDrawFrame(); | 1173 layer_tree_host_->DidCommitAndDrawFrame(); |
| (...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1439 !layer_tree_host_impl_->pending_tree()) { | 1499 !layer_tree_host_impl_->pending_tree()) { |
| 1440 TRACE_EVENT_INSTANT0("cc", "ReleaseCommitbyActivation", | 1500 TRACE_EVENT_INSTANT0("cc", "ReleaseCommitbyActivation", |
| 1441 TRACE_EVENT_SCOPE_THREAD); | 1501 TRACE_EVENT_SCOPE_THREAD); |
| 1442 DCHECK(layer_tree_host_impl_->settings().impl_side_painting); | 1502 DCHECK(layer_tree_host_impl_->settings().impl_side_painting); |
| 1443 completion_event_for_commit_held_on_tree_activation_->Signal(); | 1503 completion_event_for_commit_held_on_tree_activation_->Signal(); |
| 1444 completion_event_for_commit_held_on_tree_activation_ = NULL; | 1504 completion_event_for_commit_held_on_tree_activation_ = NULL; |
| 1445 } | 1505 } |
| 1446 } | 1506 } |
| 1447 | 1507 |
| 1448 } // namespace cc | 1508 } // namespace cc |
| OLD | NEW |