1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/viz/common/gpu/context_cache_controller.h"
6
7 #include <chrono>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/memory/ptr_util.h"
13 #include "base/synchronization/lock.h"
14 #include "gpu/command_buffer/client/context_support.h"
15 #include "third_party/skia/include/gpu/GrContext.h"
16
17 namespace viz {
18 namespace {
19 static const int kIdleCleanupDelaySeconds = 1;
20 static const int kOldResourceCleanupDelaySeconds = 15;
21 } // namespace
22
23 ContextCacheController::ScopedToken::ScopedToken() = default;
24
~ScopedToken()25 ContextCacheController::ScopedToken::~ScopedToken() {
26 DCHECK(released_);
27 }
28
Release()29 void ContextCacheController::ScopedToken::Release() {
30 DCHECK(!released_);
31 released_ = true;
32 }
33
ContextCacheController(gpu::ContextSupport * context_support,scoped_refptr<base::SingleThreadTaskRunner> task_runner)34 ContextCacheController::ContextCacheController(
35 gpu::ContextSupport* context_support,
36 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
37 : context_support_(context_support), task_runner_(std::move(task_runner)) {
38 // The |weak_factory_| can only be used from a single thread. We
39 // create/destroy this class and run callbacks on a single thread, but we
40 // want to be able to post callbacks from multiple threads. We need a weak
41 // ptr to post callbacks, so acquire one here, while we're on the right
42 // thread.
43 weak_ptr_ = weak_factory_.GetWeakPtr();
44 }
45
~ContextCacheController()46 ContextCacheController::~ContextCacheController() {
47 if (held_visibility_)
48 ClientBecameNotVisible(std::move(held_visibility_));
49 }
50
SetGrContext(GrContext * gr_context)51 void ContextCacheController::SetGrContext(GrContext* gr_context) {
52 gr_context_ = gr_context;
53 }
54
SetLock(base::Lock * lock)55 void ContextCacheController::SetLock(base::Lock* lock) {
56 context_lock_ = lock;
57 }
58
59 std::unique_ptr<ContextCacheController::ScopedVisibility>
ClientBecameVisible()60 ContextCacheController::ClientBecameVisible() {
61 if (context_lock_)
62 context_lock_->AssertAcquired();
63
64 bool became_visible = num_clients_visible_ == 0;
65 ++num_clients_visible_;
66
67 if (became_visible)
68 context_support_->SetAggressivelyFreeResources(false);
69
70 return base::WrapUnique(new ScopedVisibility());
71 }
72
ClientBecameNotVisible(std::unique_ptr<ScopedVisibility> scoped_visibility)73 void ContextCacheController::ClientBecameNotVisible(
74 std::unique_ptr<ScopedVisibility> scoped_visibility) {
75 DCHECK(scoped_visibility);
76 scoped_visibility->Release();
77
78 if (context_lock_)
79 context_lock_->AssertAcquired();
80
81 DCHECK_GT(num_clients_visible_, 0u);
82 --num_clients_visible_;
83
84 if (num_clients_visible_ == 0) {
85 // We are freeing resources now - cancel any pending idle callbacks.
86 InvalidatePendingIdleCallbacks();
87
88 if (gr_context_)
89 gr_context_->freeGpuResources();
90 context_support_->SetAggressivelyFreeResources(true);
91 context_support_->FlushPendingWork();
92 }
93 }
94
ClientBecameNotVisibleDuringShutdown(std::unique_ptr<ScopedVisibility> scoped_visibility)95 void ContextCacheController::ClientBecameNotVisibleDuringShutdown(
96 std::unique_ptr<ScopedVisibility> scoped_visibility) {
97 // We only need to hold on to one visibility token, so free any others that
98 // come in.
99 if (!held_visibility_)
100 held_visibility_ = std::move(scoped_visibility);
101 else
102 ClientBecameNotVisible(std::move(scoped_visibility));
103 }
104
105 std::unique_ptr<ContextCacheController::ScopedBusy>
ClientBecameBusy()106 ContextCacheController::ClientBecameBusy() {
107 if (context_lock_)
108 context_lock_->AssertAcquired();
109
110 ++num_clients_busy_;
111 // We are busy, cancel any pending idle callbacks.
112 InvalidatePendingIdleCallbacks();
113
114 return base::WrapUnique(new ScopedBusy());
115 }
116
ClientBecameNotBusy(std::unique_ptr<ScopedBusy> scoped_busy)117 void ContextCacheController::ClientBecameNotBusy(
118 std::unique_ptr<ScopedBusy> scoped_busy) {
119 DCHECK(scoped_busy);
120 scoped_busy->Release();
121
122 if (context_lock_)
123 context_lock_->AssertAcquired();
124
125 DCHECK_GT(num_clients_busy_, 0u);
126 --num_clients_busy_;
127
128 // Here we ask GrContext to free any resources that haven't been used in
129 // a long while even if it is under budget.
130 if (gr_context_) {
131 gr_context_->performDeferredCleanup(
132 std::chrono::seconds(kOldResourceCleanupDelaySeconds));
133 }
134
135 // If we have become idle and we are visible, queue a task to drop resources
136 // after a delay. If are not visible, we have already dropped resources.
137 if (num_clients_busy_ == 0 && num_clients_visible_ > 0 && task_runner_) {
138 // If we already have a callback pending, don't post a new one. The pending
139 // callback will handle posting a new callback itself. This prevents us from
140 // flooding the system with tasks.
141 if (!callback_pending_) {
142 {
143 base::AutoLock hold(current_idle_generation_lock_);
144 PostIdleCallback(current_idle_generation_);
145 }
146 callback_pending_ = true;
147 }
148 }
149 }
150
PostIdleCallback(uint32_t current_idle_generation) const151 void ContextCacheController::PostIdleCallback(
152 uint32_t current_idle_generation) const {
153 task_runner_->PostDelayedTask(
154 FROM_HERE,
155 base::BindOnce(&ContextCacheController::OnIdle, weak_ptr_,
156 current_idle_generation),
157 base::TimeDelta::FromSeconds(kIdleCleanupDelaySeconds));
158 }
159
InvalidatePendingIdleCallbacks()160 void ContextCacheController::InvalidatePendingIdleCallbacks() {
161 base::AutoLock hold(current_idle_generation_lock_);
162 ++current_idle_generation_;
163 }
164
OnIdle(uint32_t idle_generation)165 void ContextCacheController::OnIdle(uint32_t idle_generation)
166 NO_THREAD_SAFETY_ANALYSIS {
167 // First check if we should run our idle callback at all. If we have become
168 // busy since scheduling, just schedule another idle callback and return.
169 {
170 base::AutoLock hold(current_idle_generation_lock_);
171 if (current_idle_generation_ != idle_generation) {
172 PostIdleCallback(current_idle_generation_);
173 return;
174 }
175 }
176
177 // Try to acquire the context lock - if we can't acquire it then we've become
178 // busy since checking |current_idle_generation_| above. In this case, just
179 // re-post our idle callback and return.
180 //
181 // NO_THREAD_SAFETY_ANALYSIS: Locking depends on runtime properties.
182 if (context_lock_ && !context_lock_->Try()) {
183 base::AutoLock hold(current_idle_generation_lock_);
184 PostIdleCallback(current_idle_generation_);
185 return;
186 }
187
188 if (gr_context_)
189 gr_context_->freeGpuResources();
190
191 // Toggle SetAggressivelyFreeResources to drop command buffer data.
192 context_support_->SetAggressivelyFreeResources(true);
193 context_support_->FlushPendingWork();
194 context_support_->SetAggressivelyFreeResources(false);
195
196 callback_pending_ = false;
197
198 if (context_lock_)
199 context_lock_->Release();
200 }
201
202 } // namespace viz
203