Home | History | Annotate | Download | only in gpu
      1 // Copyright (c) 2013 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 "content/browser/gpu/shader_disk_cache.h"
      6 
      7 #include "base/threading/thread_checker.h"
      8 #include "content/browser/gpu/gpu_process_host.h"
      9 #include "content/public/browser/browser_thread.h"
     10 #include "gpu/command_buffer/common/constants.h"
     11 #include "net/base/cache_type.h"
     12 #include "net/base/io_buffer.h"
     13 #include "net/base/net_errors.h"
     14 
     15 namespace content {
     16 
     17 namespace {
     18 
     19 static const base::FilePath::CharType kGpuCachePath[] =
     20     FILE_PATH_LITERAL("GPUCache");
     21 
     22 void EntryCloser(disk_cache::Entry* entry) {
     23   entry->Close();
     24 }
     25 
     26 void FreeDiskCacheIterator(scoped_ptr<disk_cache::Backend::Iterator> iterator) {
     27 }
     28 
     29 }  // namespace
     30 
     31 // ShaderDiskCacheEntry handles the work of caching/updating the cached
     32 // shaders.
     33 class ShaderDiskCacheEntry
     34     : public base::ThreadChecker,
     35       public base::RefCounted<ShaderDiskCacheEntry> {
     36  public:
     37   ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,
     38                        const std::string& key,
     39                        const std::string& shader);
     40   void Cache();
     41 
     42  private:
     43   friend class base::RefCounted<ShaderDiskCacheEntry>;
     44 
     45   enum OpType {
     46     TERMINATE,
     47     OPEN_ENTRY,
     48     WRITE_DATA,
     49     CREATE_ENTRY,
     50   };
     51 
     52   ~ShaderDiskCacheEntry();
     53 
     54   void OnOpComplete(int rv);
     55 
     56   int OpenCallback(int rv);
     57   int WriteCallback(int rv);
     58   int IOComplete(int rv);
     59 
     60   base::WeakPtr<ShaderDiskCache> cache_;
     61   OpType op_type_;
     62   std::string key_;
     63   std::string shader_;
     64   disk_cache::Entry* entry_;
     65 
     66   DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry);
     67 };
     68 
     69 // ShaderDiskReadHelper is used to load all of the cached shaders from the
     70 // disk cache and send to the memory cache.
     71 class ShaderDiskReadHelper
     72     : public base::ThreadChecker,
     73       public base::RefCounted<ShaderDiskReadHelper> {
     74  public:
     75   ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache, int host_id);
     76   void LoadCache();
     77 
     78  private:
     79   friend class base::RefCounted<ShaderDiskReadHelper>;
     80 
     81   enum OpType {
     82     TERMINATE,
     83     OPEN_NEXT,
     84     OPEN_NEXT_COMPLETE,
     85     READ_COMPLETE,
     86     ITERATION_FINISHED
     87   };
     88 
     89 
     90   ~ShaderDiskReadHelper();
     91 
     92   void OnOpComplete(int rv);
     93 
     94   int OpenNextEntry();
     95   int OpenNextEntryComplete(int rv);
     96   int ReadComplete(int rv);
     97   int IterationComplete(int rv);
     98 
     99   base::WeakPtr<ShaderDiskCache> cache_;
    100   OpType op_type_;
    101   scoped_ptr<disk_cache::Backend::Iterator> iter_;
    102   scoped_refptr<net::IOBufferWithSize> buf_;
    103   int host_id_;
    104   disk_cache::Entry* entry_;
    105 
    106   DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper);
    107 };
    108 
    109 class ShaderClearHelper
    110     : public base::RefCounted<ShaderClearHelper>,
    111       public base::SupportsWeakPtr<ShaderClearHelper> {
    112  public:
    113   ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,
    114                     const base::FilePath& path,
    115                     const base::Time& delete_begin,
    116                     const base::Time& delete_end,
    117                     const base::Closure& callback);
    118   void Clear();
    119 
    120  private:
    121   friend class base::RefCounted<ShaderClearHelper>;
    122 
    123   enum OpType {
    124     TERMINATE,
    125     VERIFY_CACHE_SETUP,
    126     DELETE_CACHE
    127   };
    128 
    129   ~ShaderClearHelper();
    130 
    131   void DoClearShaderCache(int rv);
    132 
    133   scoped_refptr<ShaderDiskCache> cache_;
    134   OpType op_type_;
    135   base::FilePath path_;
    136   base::Time delete_begin_;
    137   base::Time delete_end_;
    138   base::Closure callback_;
    139 
    140   DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper);
    141 };
    142 
    143 ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,
    144                                            const std::string& key,
    145                                            const std::string& shader)
    146   : cache_(cache),
    147     op_type_(OPEN_ENTRY),
    148     key_(key),
    149     shader_(shader),
    150     entry_(NULL) {
    151 }
    152 
    153 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
    154   if (entry_)
    155     BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
    156                             base::Bind(&EntryCloser, entry_));
    157 }
    158 
    159 void ShaderDiskCacheEntry::Cache() {
    160   DCHECK(CalledOnValidThread());
    161   if (!cache_.get())
    162     return;
    163 
    164   int rv = cache_->backend()->OpenEntry(
    165       key_,
    166       &entry_,
    167       base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this));
    168   if (rv != net::ERR_IO_PENDING)
    169     OnOpComplete(rv);
    170 }
    171 
    172 void ShaderDiskCacheEntry::OnOpComplete(int rv) {
    173   DCHECK(CalledOnValidThread());
    174   if (!cache_.get())
    175     return;
    176 
    177   do {
    178     switch (op_type_) {
    179       case OPEN_ENTRY:
    180         rv = OpenCallback(rv);
    181         break;
    182       case CREATE_ENTRY:
    183         rv = WriteCallback(rv);
    184         break;
    185       case WRITE_DATA:
    186         rv = IOComplete(rv);
    187         break;
    188       case TERMINATE:
    189         rv = net::ERR_IO_PENDING;  // break the loop.
    190         break;
    191       default:
    192         NOTREACHED();  // Invalid op_type_ provided.
    193         break;
    194     }
    195   } while (rv != net::ERR_IO_PENDING);
    196 }
    197 
    198 int ShaderDiskCacheEntry::OpenCallback(int rv) {
    199   DCHECK(CalledOnValidThread());
    200   // Called through OnOpComplete, so we know |cache_| is valid.
    201   if (rv == net::OK) {
    202     cache_->backend()->OnExternalCacheHit(key_);
    203     cache_->EntryComplete(this);
    204     op_type_ = TERMINATE;
    205     return rv;
    206   }
    207 
    208   op_type_ = CREATE_ENTRY;
    209   return cache_->backend()->CreateEntry(
    210       key_,
    211       &entry_,
    212       base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this));
    213 }
    214 
    215 int ShaderDiskCacheEntry::WriteCallback(int rv) {
    216   DCHECK(CalledOnValidThread());
    217   // Called through OnOpComplete, so we know |cache_| is valid.
    218   if (rv != net::OK) {
    219     LOG(ERROR) << "Failed to create shader cache entry: " << rv;
    220     cache_->EntryComplete(this);
    221     op_type_ = TERMINATE;
    222     return rv;
    223   }
    224 
    225   op_type_ = WRITE_DATA;
    226   scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(shader_);
    227   return entry_->WriteData(
    228       1,
    229       0,
    230       io_buf.get(),
    231       shader_.length(),
    232       base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this),
    233       false);
    234 }
    235 
    236 int ShaderDiskCacheEntry::IOComplete(int rv) {
    237   DCHECK(CalledOnValidThread());
    238   // Called through OnOpComplete, so we know |cache_| is valid.
    239   cache_->EntryComplete(this);
    240   op_type_ = TERMINATE;
    241   return rv;
    242 }
    243 
    244 ShaderDiskReadHelper::ShaderDiskReadHelper(
    245     base::WeakPtr<ShaderDiskCache> cache,
    246     int host_id)
    247     : cache_(cache),
    248       op_type_(OPEN_NEXT),
    249       buf_(NULL),
    250       host_id_(host_id),
    251       entry_(NULL) {
    252 }
    253 
    254 void ShaderDiskReadHelper::LoadCache() {
    255   DCHECK(CalledOnValidThread());
    256   if (!cache_.get())
    257     return;
    258   OnOpComplete(net::OK);
    259 }
    260 
    261 void ShaderDiskReadHelper::OnOpComplete(int rv) {
    262   DCHECK(CalledOnValidThread());
    263   if (!cache_.get())
    264     return;
    265 
    266   do {
    267     switch (op_type_) {
    268       case OPEN_NEXT:
    269         rv = OpenNextEntry();
    270         break;
    271       case OPEN_NEXT_COMPLETE:
    272         rv = OpenNextEntryComplete(rv);
    273         break;
    274       case READ_COMPLETE:
    275         rv = ReadComplete(rv);
    276         break;
    277       case ITERATION_FINISHED:
    278         rv = IterationComplete(rv);
    279         break;
    280       case TERMINATE:
    281         cache_->ReadComplete();
    282         rv = net::ERR_IO_PENDING;  // break the loop
    283         break;
    284       default:
    285         NOTREACHED();  // Invalid state for read helper
    286         rv = net::ERR_FAILED;
    287         break;
    288     }
    289   } while (rv != net::ERR_IO_PENDING);
    290 }
    291 
    292 int ShaderDiskReadHelper::OpenNextEntry() {
    293   DCHECK(CalledOnValidThread());
    294   // Called through OnOpComplete, so we know |cache_| is valid.
    295   op_type_ = OPEN_NEXT_COMPLETE;
    296   if (!iter_)
    297     iter_ = cache_->backend()->CreateIterator();
    298   return iter_->OpenNextEntry(
    299       &entry_, base::Bind(&ShaderDiskReadHelper::OnOpComplete, this));
    300 }
    301 
    302 int ShaderDiskReadHelper::OpenNextEntryComplete(int rv) {
    303   DCHECK(CalledOnValidThread());
    304   // Called through OnOpComplete, so we know |cache_| is valid.
    305   if (rv == net::ERR_FAILED) {
    306     iter_.reset();
    307     op_type_ = ITERATION_FINISHED;
    308     return net::OK;
    309   }
    310 
    311   if (rv < 0)
    312     return rv;
    313 
    314   op_type_ = READ_COMPLETE;
    315   buf_ = new net::IOBufferWithSize(entry_->GetDataSize(1));
    316   return entry_->ReadData(
    317       1,
    318       0,
    319       buf_.get(),
    320       buf_->size(),
    321       base::Bind(&ShaderDiskReadHelper::OnOpComplete, this));
    322 }
    323 
    324 int ShaderDiskReadHelper::ReadComplete(int rv) {
    325   DCHECK(CalledOnValidThread());
    326   // Called through OnOpComplete, so we know |cache_| is valid.
    327   if (rv && rv == buf_->size()) {
    328     GpuProcessHost* host = GpuProcessHost::FromID(host_id_);
    329     if (host)
    330       host->LoadedShader(entry_->GetKey(), std::string(buf_->data(),
    331                                                        buf_->size()));
    332   }
    333 
    334   buf_ = NULL;
    335   entry_->Close();
    336   entry_ = NULL;
    337 
    338   op_type_ = OPEN_NEXT;
    339   return net::OK;
    340 }
    341 
    342 int ShaderDiskReadHelper::IterationComplete(int rv) {
    343   DCHECK(CalledOnValidThread());
    344   // Called through OnOpComplete, so we know |cache_| is valid.
    345   iter_.reset();
    346   op_type_ = TERMINATE;
    347   return net::OK;
    348 }
    349 
    350 ShaderDiskReadHelper::~ShaderDiskReadHelper() {
    351   if (entry_) {
    352     BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
    353                             base::Bind(&EntryCloser, entry_));
    354   }
    355   if (iter_) {
    356     BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
    357                             base::Bind(&FreeDiskCacheIterator,
    358                                        base::Passed(&iter_)));
    359   }
    360 }
    361 
    362 ShaderClearHelper::ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,
    363                     const base::FilePath& path,
    364                     const base::Time& delete_begin,
    365                     const base::Time& delete_end,
    366                     const base::Closure& callback)
    367     : cache_(cache),
    368       op_type_(VERIFY_CACHE_SETUP),
    369       path_(path),
    370       delete_begin_(delete_begin),
    371       delete_end_(delete_end),
    372       callback_(callback) {
    373 }
    374 
    375 ShaderClearHelper::~ShaderClearHelper() {
    376   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    377 }
    378 
    379 void ShaderClearHelper::Clear() {
    380   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    381   DoClearShaderCache(net::OK);
    382 }
    383 
    384 void ShaderClearHelper::DoClearShaderCache(int rv) {
    385   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    386 
    387   // Hold a ref to ourselves so when we do the CacheCleared call we don't get
    388   // auto-deleted when our ref count drops to zero.
    389   scoped_refptr<ShaderClearHelper> helper = this;
    390 
    391   while (rv != net::ERR_IO_PENDING) {
    392     switch (op_type_) {
    393       case VERIFY_CACHE_SETUP:
    394         rv = cache_->SetAvailableCallback(
    395             base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr()));
    396         op_type_ = DELETE_CACHE;
    397         break;
    398       case DELETE_CACHE:
    399         rv = cache_->Clear(
    400             delete_begin_, delete_end_,
    401             base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr()));
    402         op_type_ = TERMINATE;
    403         break;
    404       case TERMINATE:
    405         ShaderCacheFactory::GetInstance()->CacheCleared(path_);
    406         callback_.Run();
    407         rv = net::ERR_IO_PENDING;  // Break the loop.
    408         break;
    409       default:
    410         NOTREACHED();  // Invalid state provided.
    411         op_type_ = TERMINATE;
    412         break;
    413     }
    414   }
    415 }
    416 
    417 // static
    418 ShaderCacheFactory* ShaderCacheFactory::GetInstance() {
    419   return Singleton<ShaderCacheFactory,
    420       LeakySingletonTraits<ShaderCacheFactory> >::get();
    421 }
    422 
    423 ShaderCacheFactory::ShaderCacheFactory() {
    424 }
    425 
    426 ShaderCacheFactory::~ShaderCacheFactory() {
    427 }
    428 
    429 void ShaderCacheFactory::SetCacheInfo(int32 client_id,
    430                                       const base::FilePath& path) {
    431   client_id_to_path_map_[client_id] = path;
    432 }
    433 
    434 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) {
    435   client_id_to_path_map_.erase(client_id);
    436 }
    437 
    438 scoped_refptr<ShaderDiskCache> ShaderCacheFactory::Get(int32 client_id) {
    439   ClientIdToPathMap::iterator iter =
    440       client_id_to_path_map_.find(client_id);
    441   if (iter == client_id_to_path_map_.end())
    442     return NULL;
    443   return ShaderCacheFactory::GetByPath(iter->second);
    444 }
    445 
    446 scoped_refptr<ShaderDiskCache> ShaderCacheFactory::GetByPath(
    447     const base::FilePath& path) {
    448   ShaderCacheMap::iterator iter = shader_cache_map_.find(path);
    449   if (iter != shader_cache_map_.end())
    450     return iter->second;
    451 
    452   ShaderDiskCache* cache = new ShaderDiskCache(path);
    453   cache->Init();
    454   return cache;
    455 }
    456 
    457 void ShaderCacheFactory::AddToCache(const base::FilePath& key,
    458                                     ShaderDiskCache* cache) {
    459   shader_cache_map_[key] = cache;
    460 }
    461 
    462 void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) {
    463   shader_cache_map_.erase(key);
    464 }
    465 
    466 void ShaderCacheFactory::ClearByPath(const base::FilePath& path,
    467                                      const base::Time& delete_begin,
    468                                      const base::Time& delete_end,
    469                                      const base::Closure& callback) {
    470   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    471   DCHECK(!callback.is_null());
    472 
    473   scoped_refptr<ShaderClearHelper> helper = new ShaderClearHelper(
    474       GetByPath(path), path, delete_begin, delete_end, callback);
    475 
    476   // We could receive requests to clear the same path with different
    477   // begin/end times. So, we keep a list of requests. If we haven't seen this
    478   // path before we kick off the clear and add it to the list. If we have see it
    479   // already, then we already have a clear running. We add this clear to the
    480   // list and wait for any previous clears to finish.
    481   ShaderClearMap::iterator iter = shader_clear_map_.find(path);
    482   if (iter != shader_clear_map_.end()) {
    483     iter->second.push(helper);
    484     return;
    485   }
    486 
    487   shader_clear_map_.insert(
    488       std::pair<base::FilePath, ShaderClearQueue>(path, ShaderClearQueue()));
    489   shader_clear_map_[path].push(helper);
    490   helper->Clear();
    491 }
    492 
    493 void ShaderCacheFactory::CacheCleared(const base::FilePath& path) {
    494   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    495 
    496   ShaderClearMap::iterator iter = shader_clear_map_.find(path);
    497   if (iter == shader_clear_map_.end()) {
    498     LOG(ERROR) << "Completed clear but missing clear helper.";
    499     return;
    500   }
    501 
    502   iter->second.pop();
    503 
    504   // If there are remaining items in the list we trigger the Clear on the
    505   // next one.
    506   if (!iter->second.empty()) {
    507     iter->second.front()->Clear();
    508     return;
    509   }
    510 
    511   shader_clear_map_.erase(path);
    512 }
    513 
    514 ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path)
    515     : cache_available_(false),
    516       host_id_(0),
    517       cache_path_(cache_path),
    518       is_initialized_(false) {
    519   ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this);
    520 }
    521 
    522 ShaderDiskCache::~ShaderDiskCache() {
    523   ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_);
    524 }
    525 
    526 void ShaderDiskCache::Init() {
    527   if (is_initialized_) {
    528     NOTREACHED();  // can't initialize disk cache twice.
    529     return;
    530   }
    531   is_initialized_ = true;
    532 
    533   int rv = disk_cache::CreateCacheBackend(
    534       net::SHADER_CACHE,
    535       net::CACHE_BACKEND_DEFAULT,
    536       cache_path_.Append(kGpuCachePath),
    537       gpu::kDefaultMaxProgramCacheMemoryBytes,
    538       true,
    539       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
    540       NULL,
    541       &backend_,
    542       base::Bind(&ShaderDiskCache::CacheCreatedCallback, this));
    543 
    544   if (rv == net::OK)
    545     cache_available_ = true;
    546 }
    547 
    548 void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) {
    549   if (!cache_available_)
    550     return;
    551 
    552   scoped_refptr<ShaderDiskCacheEntry> shim =
    553       new ShaderDiskCacheEntry(AsWeakPtr(), key, shader);
    554   shim->Cache();
    555 
    556   entry_map_[shim.get()] = shim;
    557 }
    558 
    559 int ShaderDiskCache::Clear(
    560     const base::Time begin_time, const base::Time end_time,
    561     const net::CompletionCallback& completion_callback) {
    562   int rv;
    563   if (begin_time.is_null()) {
    564     rv = backend_->DoomAllEntries(completion_callback);
    565   } else {
    566     rv = backend_->DoomEntriesBetween(begin_time, end_time,
    567                                       completion_callback);
    568   }
    569   return rv;
    570 }
    571 
    572 int32 ShaderDiskCache::Size() {
    573   if (!cache_available_)
    574     return -1;
    575   return backend_->GetEntryCount();
    576 }
    577 
    578 int ShaderDiskCache::SetAvailableCallback(
    579     const net::CompletionCallback& callback) {
    580   if (cache_available_)
    581     return net::OK;
    582   available_callback_ = callback;
    583   return net::ERR_IO_PENDING;
    584 }
    585 
    586 void ShaderDiskCache::CacheCreatedCallback(int rv) {
    587   if (rv != net::OK) {
    588     LOG(ERROR) << "Shader Cache Creation failed: " << rv;
    589     return;
    590   }
    591   helper_ = new ShaderDiskReadHelper(AsWeakPtr(), host_id_);
    592   helper_->LoadCache();
    593 }
    594 
    595 void ShaderDiskCache::EntryComplete(void* entry) {
    596   entry_map_.erase(entry);
    597 
    598   if (entry_map_.empty() && !cache_complete_callback_.is_null())
    599     cache_complete_callback_.Run(net::OK);
    600 }
    601 
    602 void ShaderDiskCache::ReadComplete() {
    603   helper_ = NULL;
    604 
    605   // The cache is considered available after we have finished reading any
    606   // of the old cache values off disk. This prevents a potential race where we
    607   // are reading from disk and execute a cache clear at the same time.
    608   cache_available_ = true;
    609   if (!available_callback_.is_null()) {
    610     available_callback_.Run(net::OK);
    611     available_callback_.Reset();
    612   }
    613 }
    614 
    615 int ShaderDiskCache::SetCacheCompleteCallback(
    616     const net::CompletionCallback& callback) {
    617   if (entry_map_.empty()) {
    618     return net::OK;
    619   }
    620   cache_complete_callback_ = callback;
    621   return net::ERR_IO_PENDING;
    622 }
    623 
    624 }  // namespace content
    625 
    626