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