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