blob: 79e85b3e0336146e41e82aaaf4d0463e21d2e141 [file] [log] [blame]
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/output/context_cache_controller.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/synchronization/lock.h"
#include "gpu/command_buffer/client/context_support.h"
#include "third_party/skia/include/gpu/GrContext.h"
namespace cc {
namespace {
static const int kIdleCleanupDelaySeconds = 1;
} // namespace
ContextCacheController::ScopedToken::ScopedToken() = default;
ContextCacheController::ScopedToken::~ScopedToken() {
DCHECK(released_);
}
void ContextCacheController::ScopedToken::Release() {
DCHECK(!released_);
released_ = true;
}
ContextCacheController::ContextCacheController(
gpu::ContextSupport* context_support,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: context_support_(context_support),
task_runner_(std::move(task_runner)),
weak_factory_(this) {
// The |weak_factory_| can only be used from a single thread. We
// create/destroy this class and run callbacks on a single thread, but we
// want to be able to post callbacks from multiple threads. We need a weak
// ptr to post callbacks, so acquire one here, while we're on the right
// thread.
weak_ptr_ = weak_factory_.GetWeakPtr();
}
ContextCacheController::~ContextCacheController() = default;
void ContextCacheController::SetGrContext(GrContext* gr_context) {
gr_context_ = gr_context;
}
void ContextCacheController::SetLock(base::Lock* lock) {
context_lock_ = lock;
}
std::unique_ptr<ContextCacheController::ScopedVisibility>
ContextCacheController::ClientBecameVisible() {
if (context_lock_)
context_lock_->AssertAcquired();
bool became_visible = num_clients_visible_ == 0;
++num_clients_visible_;
if (became_visible)
context_support_->SetAggressivelyFreeResources(false);
return base::WrapUnique(new ScopedVisibility());
}
void ContextCacheController::ClientBecameNotVisible(
std::unique_ptr<ScopedVisibility> scoped_visibility) {
DCHECK(scoped_visibility);
scoped_visibility->Release();
if (context_lock_)
context_lock_->AssertAcquired();
DCHECK_GT(num_clients_visible_, 0u);
--num_clients_visible_;
if (num_clients_visible_ == 0) {
// We are freeing resources now - cancel any pending idle callbacks.
InvalidatePendingIdleCallbacks();
if (gr_context_)
gr_context_->freeGpuResources();
context_support_->SetAggressivelyFreeResources(true);
}
}
std::unique_ptr<ContextCacheController::ScopedBusy>
ContextCacheController::ClientBecameBusy() {
if (context_lock_)
context_lock_->AssertAcquired();
++num_clients_busy_;
// We are busy, cancel any pending idle callbacks.
InvalidatePendingIdleCallbacks();
return base::WrapUnique(new ScopedBusy());
}
void ContextCacheController::ClientBecameNotBusy(
std::unique_ptr<ScopedBusy> scoped_busy) {
DCHECK(scoped_busy);
scoped_busy->Release();
if (context_lock_)
context_lock_->AssertAcquired();
DCHECK_GT(num_clients_busy_, 0u);
--num_clients_busy_;
// If we have become idle and we are visible, queue a task to drop resources
// after a delay. If are not visible, we have already dropped resources.
if (num_clients_busy_ == 0 && num_clients_visible_ > 0 && task_runner_) {
// If we already have a callback pending, don't post a new one. The pending
// callback will handle posting a new callback itself. This prevents us from
// flooding the system with tasks.
if (!callback_pending_) {
{
base::AutoLock hold(current_idle_generation_lock_);
PostIdleCallback(current_idle_generation_);
}
callback_pending_ = true;
}
}
}
void ContextCacheController::PostIdleCallback(
uint32_t current_idle_generation) const {
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(&ContextCacheController::OnIdle, weak_ptr_,
current_idle_generation),
base::TimeDelta::FromSeconds(kIdleCleanupDelaySeconds));
}
void ContextCacheController::InvalidatePendingIdleCallbacks() {
base::AutoLock hold(current_idle_generation_lock_);
++current_idle_generation_;
}
void ContextCacheController::OnIdle(uint32_t idle_generation) {
// First check if we should run our idle callback at all. If we have become
// busy since scheduling, just schedule another idle callback and return.
{
base::AutoLock hold(current_idle_generation_lock_);
if (current_idle_generation_ != idle_generation) {
PostIdleCallback(current_idle_generation_);
return;
}
}
// Try to acquire the context lock - if we can't acquire it then we've become
// busy since checking |current_idle_generation_| above. In this case, just
// re-post our idle callback and return.
if (context_lock_ && !context_lock_->Try()) {
base::AutoLock hold(current_idle_generation_lock_);
PostIdleCallback(current_idle_generation_);
return;
}
if (gr_context_)
gr_context_->freeGpuResources();
// Toggle SetAggressivelyFreeResources to drop command buffer data.
context_support_->SetAggressivelyFreeResources(true);
context_support_->SetAggressivelyFreeResources(false);
callback_pending_ = false;
if (context_lock_)
context_lock_->Release();
}
} // namespace cc