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
EntryCloser(disk_cache::Entry * entry)22 void EntryCloser(disk_cache::Entry* entry) {
23 entry->Close();
24 }
25
FreeDiskCacheIterator(scoped_ptr<disk_cache::Backend::Iterator> iterator)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
ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,const std::string & key,const std::string & shader)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
~ShaderDiskCacheEntry()153 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
154 if (entry_)
155 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
156 base::Bind(&EntryCloser, entry_));
157 }
158
Cache()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
OnOpComplete(int rv)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
OpenCallback(int rv)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
WriteCallback(int rv)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
IOComplete(int rv)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
ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache,int host_id)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
LoadCache()254 void ShaderDiskReadHelper::LoadCache() {
255 DCHECK(CalledOnValidThread());
256 if (!cache_.get())
257 return;
258 OnOpComplete(net::OK);
259 }
260
OnOpComplete(int rv)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
OpenNextEntry()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
OpenNextEntryComplete(int rv)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
ReadComplete(int rv)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
IterationComplete(int rv)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
~ShaderDiskReadHelper()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
ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,const base::FilePath & path,const base::Time & delete_begin,const base::Time & delete_end,const base::Closure & callback)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
~ShaderClearHelper()375 ShaderClearHelper::~ShaderClearHelper() {
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
377 }
378
Clear()379 void ShaderClearHelper::Clear() {
380 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
381 DoClearShaderCache(net::OK);
382 }
383
DoClearShaderCache(int rv)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
GetInstance()418 ShaderCacheFactory* ShaderCacheFactory::GetInstance() {
419 return Singleton<ShaderCacheFactory,
420 LeakySingletonTraits<ShaderCacheFactory> >::get();
421 }
422
ShaderCacheFactory()423 ShaderCacheFactory::ShaderCacheFactory() {
424 }
425
~ShaderCacheFactory()426 ShaderCacheFactory::~ShaderCacheFactory() {
427 }
428
SetCacheInfo(int32 client_id,const base::FilePath & path)429 void ShaderCacheFactory::SetCacheInfo(int32 client_id,
430 const base::FilePath& path) {
431 client_id_to_path_map_[client_id] = path;
432 }
433
RemoveCacheInfo(int32 client_id)434 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) {
435 client_id_to_path_map_.erase(client_id);
436 }
437
Get(int32 client_id)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
GetByPath(const base::FilePath & path)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
AddToCache(const base::FilePath & key,ShaderDiskCache * cache)457 void ShaderCacheFactory::AddToCache(const base::FilePath& key,
458 ShaderDiskCache* cache) {
459 shader_cache_map_[key] = cache;
460 }
461
RemoveFromCache(const base::FilePath & key)462 void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) {
463 shader_cache_map_.erase(key);
464 }
465
ClearByPath(const base::FilePath & path,const base::Time & delete_begin,const base::Time & delete_end,const base::Closure & callback)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
CacheCleared(const base::FilePath & path)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
ShaderDiskCache(const base::FilePath & cache_path)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
~ShaderDiskCache()522 ShaderDiskCache::~ShaderDiskCache() {
523 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_);
524 }
525
Init()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
Cache(const std::string & key,const std::string & shader)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
Clear(const base::Time begin_time,const base::Time end_time,const net::CompletionCallback & completion_callback)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
Size()572 int32 ShaderDiskCache::Size() {
573 if (!cache_available_)
574 return -1;
575 return backend_->GetEntryCount();
576 }
577
SetAvailableCallback(const net::CompletionCallback & callback)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
CacheCreatedCallback(int rv)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
EntryComplete(void * entry)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
ReadComplete()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
SetCacheCompleteCallback(const net::CompletionCallback & callback)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