// Copyright (c) 2013 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 "content/browser/gpu/shader_disk_cache.h" #include "base/threading/thread_checker.h" #include "content/browser/gpu/gpu_process_host.h" #include "content/public/browser/browser_thread.h" #include "gpu/command_buffer/common/constants.h" #include "net/base/cache_type.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" namespace content { namespace { static const base::FilePath::CharType kGpuCachePath[] = FILE_PATH_LITERAL("GPUCache"); void EntryCloser(disk_cache::Entry* entry) { entry->Close(); } } // namespace // ShaderDiskCacheEntry handles the work of caching/updating the cached // shaders. class ShaderDiskCacheEntry : public base::ThreadChecker, public base::RefCounted<ShaderDiskCacheEntry> { public: ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache, const std::string& key, const std::string& shader); void Cache(); private: friend class base::RefCounted<ShaderDiskCacheEntry>; enum OpType { TERMINATE, OPEN_ENTRY, WRITE_DATA, CREATE_ENTRY, }; ~ShaderDiskCacheEntry(); void OnOpComplete(int rv); int OpenCallback(int rv); int WriteCallback(int rv); int IOComplete(int rv); base::WeakPtr<ShaderDiskCache> cache_; OpType op_type_; std::string key_; std::string shader_; disk_cache::Entry* entry_; DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry); }; // ShaderDiskReadHelper is used to load all of the cached shaders from the // disk cache and send to the memory cache. class ShaderDiskReadHelper : public base::ThreadChecker, public base::RefCounted<ShaderDiskReadHelper> { public: ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache, int host_id); void LoadCache(); private: friend class base::RefCounted<ShaderDiskReadHelper>; enum OpType { TERMINATE, OPEN_NEXT, OPEN_NEXT_COMPLETE, READ_COMPLETE, ITERATION_FINISHED }; ~ShaderDiskReadHelper(); void OnOpComplete(int rv); int OpenNextEntry(); int OpenNextEntryComplete(int rv); int ReadComplete(int rv); int IterationComplete(int rv); base::WeakPtr<ShaderDiskCache> cache_; OpType op_type_; void* iter_; scoped_refptr<net::IOBufferWithSize> buf_; int host_id_; disk_cache::Entry* entry_; DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper); }; class ShaderClearHelper : public base::RefCounted<ShaderClearHelper>, public base::SupportsWeakPtr<ShaderClearHelper> { public: ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache, const base::FilePath& path, const base::Time& delete_begin, const base::Time& delete_end, const base::Closure& callback); void Clear(); private: friend class base::RefCounted<ShaderClearHelper>; enum OpType { TERMINATE, VERIFY_CACHE_SETUP, DELETE_CACHE }; ~ShaderClearHelper(); void DoClearShaderCache(int rv); scoped_refptr<ShaderDiskCache> cache_; OpType op_type_; base::FilePath path_; base::Time delete_begin_; base::Time delete_end_; base::Closure callback_; DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper); }; ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache, const std::string& key, const std::string& shader) : cache_(cache), op_type_(OPEN_ENTRY), key_(key), shader_(shader), entry_(NULL) { } ShaderDiskCacheEntry::~ShaderDiskCacheEntry() { if (entry_) BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&EntryCloser, entry_)); } void ShaderDiskCacheEntry::Cache() { DCHECK(CalledOnValidThread()); if (!cache_.get()) return; int rv = cache_->backend()->OpenEntry( key_, &entry_, base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this)); if (rv != net::ERR_IO_PENDING) OnOpComplete(rv); } void ShaderDiskCacheEntry::OnOpComplete(int rv) { DCHECK(CalledOnValidThread()); if (!cache_.get()) return; do { switch (op_type_) { case OPEN_ENTRY: rv = OpenCallback(rv); break; case CREATE_ENTRY: rv = WriteCallback(rv); break; case WRITE_DATA: rv = IOComplete(rv); break; case TERMINATE: rv = net::ERR_IO_PENDING; // break the loop. break; default: NOTREACHED(); // Invalid op_type_ provided. break; } } while (rv != net::ERR_IO_PENDING); } int ShaderDiskCacheEntry::OpenCallback(int rv) { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. if (rv == net::OK) { cache_->backend()->OnExternalCacheHit(key_); cache_->EntryComplete(this); op_type_ = TERMINATE; return rv; } op_type_ = CREATE_ENTRY; return cache_->backend()->CreateEntry( key_, &entry_, base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this)); } int ShaderDiskCacheEntry::WriteCallback(int rv) { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. if (rv != net::OK) { LOG(ERROR) << "Failed to create shader cache entry: " << rv; cache_->EntryComplete(this); op_type_ = TERMINATE; return rv; } op_type_ = WRITE_DATA; scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(shader_); return entry_->WriteData( 1, 0, io_buf.get(), shader_.length(), base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this), false); } int ShaderDiskCacheEntry::IOComplete(int rv) { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. cache_->EntryComplete(this); op_type_ = TERMINATE; return rv; } ShaderDiskReadHelper::ShaderDiskReadHelper( base::WeakPtr<ShaderDiskCache> cache, int host_id) : cache_(cache), op_type_(OPEN_NEXT), iter_(NULL), buf_(NULL), host_id_(host_id), entry_(NULL) { } void ShaderDiskReadHelper::LoadCache() { DCHECK(CalledOnValidThread()); if (!cache_.get()) return; OnOpComplete(net::OK); } void ShaderDiskReadHelper::OnOpComplete(int rv) { DCHECK(CalledOnValidThread()); if (!cache_.get()) return; do { switch (op_type_) { case OPEN_NEXT: rv = OpenNextEntry(); break; case OPEN_NEXT_COMPLETE: rv = OpenNextEntryComplete(rv); break; case READ_COMPLETE: rv = ReadComplete(rv); break; case ITERATION_FINISHED: rv = IterationComplete(rv); break; case TERMINATE: cache_->ReadComplete(); rv = net::ERR_IO_PENDING; // break the loop break; default: NOTREACHED(); // Invalid state for read helper rv = net::ERR_FAILED; break; } } while (rv != net::ERR_IO_PENDING); } int ShaderDiskReadHelper::OpenNextEntry() { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. op_type_ = OPEN_NEXT_COMPLETE; return cache_->backend()->OpenNextEntry( &iter_, &entry_, base::Bind(&ShaderDiskReadHelper::OnOpComplete, this)); } int ShaderDiskReadHelper::OpenNextEntryComplete(int rv) { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. if (rv == net::ERR_FAILED) { op_type_ = ITERATION_FINISHED; return net::OK; } if (rv < 0) return rv; op_type_ = READ_COMPLETE; buf_ = new net::IOBufferWithSize(entry_->GetDataSize(1)); return entry_->ReadData( 1, 0, buf_.get(), buf_->size(), base::Bind(&ShaderDiskReadHelper::OnOpComplete, this)); } int ShaderDiskReadHelper::ReadComplete(int rv) { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. if (rv && rv == buf_->size()) { GpuProcessHost* host = GpuProcessHost::FromID(host_id_); if (host) host->LoadedShader(entry_->GetKey(), std::string(buf_->data(), buf_->size())); } buf_ = NULL; entry_->Close(); entry_ = NULL; op_type_ = OPEN_NEXT; return net::OK; } int ShaderDiskReadHelper::IterationComplete(int rv) { DCHECK(CalledOnValidThread()); // Called through OnOpComplete, so we know |cache_| is valid. cache_->backend()->EndEnumeration(&iter_); iter_ = NULL; op_type_ = TERMINATE; return net::OK; } ShaderDiskReadHelper::~ShaderDiskReadHelper() { if (entry_) BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&EntryCloser, entry_)); } ShaderClearHelper::ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache, const base::FilePath& path, const base::Time& delete_begin, const base::Time& delete_end, const base::Closure& callback) : cache_(cache), op_type_(VERIFY_CACHE_SETUP), path_(path), delete_begin_(delete_begin), delete_end_(delete_end), callback_(callback) { } ShaderClearHelper::~ShaderClearHelper() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); } void ShaderClearHelper::Clear() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DoClearShaderCache(net::OK); } void ShaderClearHelper::DoClearShaderCache(int rv) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Hold a ref to ourselves so when we do the CacheCleared call we don't get // auto-deleted when our ref count drops to zero. scoped_refptr<ShaderClearHelper> helper = this; while (rv != net::ERR_IO_PENDING) { switch (op_type_) { case VERIFY_CACHE_SETUP: rv = cache_->SetAvailableCallback( base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr())); op_type_ = DELETE_CACHE; break; case DELETE_CACHE: rv = cache_->Clear( delete_begin_, delete_end_, base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr())); op_type_ = TERMINATE; break; case TERMINATE: ShaderCacheFactory::GetInstance()->CacheCleared(path_); callback_.Run(); rv = net::ERR_IO_PENDING; // Break the loop. break; default: NOTREACHED(); // Invalid state provided. op_type_ = TERMINATE; break; } } } // static ShaderCacheFactory* ShaderCacheFactory::GetInstance() { return Singleton<ShaderCacheFactory, LeakySingletonTraits<ShaderCacheFactory> >::get(); } ShaderCacheFactory::ShaderCacheFactory() { } ShaderCacheFactory::~ShaderCacheFactory() { } void ShaderCacheFactory::SetCacheInfo(int32 client_id, const base::FilePath& path) { client_id_to_path_map_[client_id] = path; } void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) { client_id_to_path_map_.erase(client_id); } scoped_refptr<ShaderDiskCache> ShaderCacheFactory::Get(int32 client_id) { ClientIdToPathMap::iterator iter = client_id_to_path_map_.find(client_id); if (iter == client_id_to_path_map_.end()) return NULL; return ShaderCacheFactory::GetByPath(iter->second); } scoped_refptr<ShaderDiskCache> ShaderCacheFactory::GetByPath( const base::FilePath& path) { ShaderCacheMap::iterator iter = shader_cache_map_.find(path); if (iter != shader_cache_map_.end()) return iter->second; ShaderDiskCache* cache = new ShaderDiskCache(path); cache->Init(); return cache; } void ShaderCacheFactory::AddToCache(const base::FilePath& key, ShaderDiskCache* cache) { shader_cache_map_[key] = cache; } void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) { shader_cache_map_.erase(key); } void ShaderCacheFactory::ClearByPath(const base::FilePath& path, const base::Time& delete_begin, const base::Time& delete_end, const base::Closure& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(!callback.is_null()); scoped_refptr<ShaderClearHelper> helper = new ShaderClearHelper( GetByPath(path), path, delete_begin, delete_end, callback); // We could receive requests to clear the same path with different // begin/end times. So, we keep a list of requests. If we haven't seen this // path before we kick off the clear and add it to the list. If we have see it // already, then we already have a clear running. We add this clear to the // list and wait for any previous clears to finish. ShaderClearMap::iterator iter = shader_clear_map_.find(path); if (iter != shader_clear_map_.end()) { iter->second.push(helper); return; } shader_clear_map_.insert( std::pair<base::FilePath, ShaderClearQueue>(path, ShaderClearQueue())); shader_clear_map_[path].push(helper); helper->Clear(); } void ShaderCacheFactory::CacheCleared(const base::FilePath& path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); ShaderClearMap::iterator iter = shader_clear_map_.find(path); if (iter == shader_clear_map_.end()) { LOG(ERROR) << "Completed clear but missing clear helper."; return; } iter->second.pop(); // If there are remaining items in the list we trigger the Clear on the // next one. if (!iter->second.empty()) { iter->second.front()->Clear(); return; } shader_clear_map_.erase(path); } ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path) : cache_available_(false), host_id_(0), cache_path_(cache_path), is_initialized_(false) { ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this); } ShaderDiskCache::~ShaderDiskCache() { ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_); } void ShaderDiskCache::Init() { if (is_initialized_) { NOTREACHED(); // can't initialize disk cache twice. return; } is_initialized_ = true; int rv = disk_cache::CreateCacheBackend( net::SHADER_CACHE, net::CACHE_BACKEND_DEFAULT, cache_path_.Append(kGpuCachePath), gpu::kDefaultMaxProgramCacheMemoryBytes, true, BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), NULL, &backend_, base::Bind(&ShaderDiskCache::CacheCreatedCallback, this)); if (rv == net::OK) cache_available_ = true; } void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) { if (!cache_available_) return; scoped_refptr<ShaderDiskCacheEntry> shim = new ShaderDiskCacheEntry(AsWeakPtr(), key, shader); shim->Cache(); entry_map_[shim] = shim; } int ShaderDiskCache::Clear( const base::Time begin_time, const base::Time end_time, const net::CompletionCallback& completion_callback) { int rv; if (begin_time.is_null()) { rv = backend_->DoomAllEntries(completion_callback); } else { rv = backend_->DoomEntriesBetween(begin_time, end_time, completion_callback); } return rv; } int32 ShaderDiskCache::Size() { if (!cache_available_) return -1; return backend_->GetEntryCount(); } int ShaderDiskCache::SetAvailableCallback( const net::CompletionCallback& callback) { if (cache_available_) return net::OK; available_callback_ = callback; return net::ERR_IO_PENDING; } void ShaderDiskCache::CacheCreatedCallback(int rv) { if (rv != net::OK) { LOG(ERROR) << "Shader Cache Creation failed: " << rv; return; } helper_ = new ShaderDiskReadHelper(AsWeakPtr(), host_id_); helper_->LoadCache(); } void ShaderDiskCache::EntryComplete(void* entry) { entry_map_.erase(entry); if (entry_map_.empty() && !cache_complete_callback_.is_null()) cache_complete_callback_.Run(net::OK); } void ShaderDiskCache::ReadComplete() { helper_ = NULL; // The cache is considered available after we have finished reading any // of the old cache values off disk. This prevents a potential race where we // are reading from disk and execute a cache clear at the same time. cache_available_ = true; if (!available_callback_.is_null()) { available_callback_.Run(net::OK); available_callback_.Reset(); } } int ShaderDiskCache::SetCacheCompleteCallback( const net::CompletionCallback& callback) { if (entry_map_.empty()) { return net::OK; } cache_complete_callback_ = callback; return net::ERR_IO_PENDING; } } // namespace content