shader_disk_cache.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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
15namespace content {
16
17namespace {
18
19static const base::FilePath::CharType kGpuCachePath[] =
20    FILE_PATH_LITERAL("GPUCache");
21
22void 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.
30class 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.
68class 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
106class 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
140ShaderDiskCacheEntry::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
150ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
151  if (entry_)
152    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
153                            base::Bind(&EntryCloser, entry_));
154}
155
156void 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
169void 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
195int 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
212int 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
233int 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
241ShaderDiskReadHelper::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
252void ShaderDiskReadHelper::LoadCache() {
253  DCHECK(CalledOnValidThread());
254  if (!cache_.get())
255    return;
256  OnOpComplete(net::OK);
257}
258
259void 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
290int 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
300int 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
321int 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
339int 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
348ShaderDiskReadHelper::~ShaderDiskReadHelper() {
349  if (entry_)
350    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
351                            base::Bind(&EntryCloser, entry_));
352}
353
354ShaderClearHelper::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
367ShaderClearHelper::~ShaderClearHelper() {
368  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
369}
370
371void ShaderClearHelper::Clear() {
372  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
373  DoClearShaderCache(net::OK);
374}
375
376void 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
410ShaderCacheFactory* ShaderCacheFactory::GetInstance() {
411  return Singleton<ShaderCacheFactory,
412      LeakySingletonTraits<ShaderCacheFactory> >::get();
413}
414
415ShaderCacheFactory::ShaderCacheFactory() {
416}
417
418ShaderCacheFactory::~ShaderCacheFactory() {
419}
420
421void ShaderCacheFactory::SetCacheInfo(int32 client_id,
422                                      const base::FilePath& path) {
423  client_id_to_path_map_[client_id] = path;
424}
425
426void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) {
427  client_id_to_path_map_.erase(client_id);
428}
429
430scoped_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
438scoped_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
449void ShaderCacheFactory::AddToCache(const base::FilePath& key,
450                                    ShaderDiskCache* cache) {
451  shader_cache_map_[key] = cache;
452}
453
454void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) {
455  shader_cache_map_.erase(key);
456}
457
458void 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
485void 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
506ShaderDiskCache::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
514ShaderDiskCache::~ShaderDiskCache() {
515  ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_);
516}
517
518void 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
540void 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
551int 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
564int32 ShaderDiskCache::Size() {
565  if (!cache_available_)
566    return -1;
567  return backend_->GetEntryCount();
568}
569
570int 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
578void 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
587void 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
594void 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
607int 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