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
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
ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,const std::string & key,const std::string & shader)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
~ShaderDiskCacheEntry()150 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
151 if (entry_)
152 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
153 base::Bind(&EntryCloser, entry_));
154 }
155
Cache()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
OnOpComplete(int rv)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
OpenCallback(int rv)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
WriteCallback(int rv)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
IOComplete(int rv)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
ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache,int host_id)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
LoadCache()252 void ShaderDiskReadHelper::LoadCache() {
253 DCHECK(CalledOnValidThread());
254 if (!cache_.get())
255 return;
256 OnOpComplete(net::OK);
257 }
258
OnOpComplete(int rv)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
OpenNextEntry()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
OpenNextEntryComplete(int rv)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
ReadComplete(int rv)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
IterationComplete(int rv)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
~ShaderDiskReadHelper()348 ShaderDiskReadHelper::~ShaderDiskReadHelper() {
349 if (entry_)
350 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
351 base::Bind(&EntryCloser, entry_));
352 }
353
ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,const base::FilePath & path,const base::Time & delete_begin,const base::Time & delete_end,const base::Closure & callback)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
~ShaderClearHelper()367 ShaderClearHelper::~ShaderClearHelper() {
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
369 }
370
Clear()371 void ShaderClearHelper::Clear() {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
373 DoClearShaderCache(net::OK);
374 }
375
DoClearShaderCache(int rv)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
GetInstance()410 ShaderCacheFactory* ShaderCacheFactory::GetInstance() {
411 return Singleton<ShaderCacheFactory,
412 LeakySingletonTraits<ShaderCacheFactory> >::get();
413 }
414
ShaderCacheFactory()415 ShaderCacheFactory::ShaderCacheFactory() {
416 }
417
~ShaderCacheFactory()418 ShaderCacheFactory::~ShaderCacheFactory() {
419 }
420
SetCacheInfo(int32 client_id,const base::FilePath & path)421 void ShaderCacheFactory::SetCacheInfo(int32 client_id,
422 const base::FilePath& path) {
423 client_id_to_path_map_[client_id] = path;
424 }
425
RemoveCacheInfo(int32 client_id)426 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) {
427 client_id_to_path_map_.erase(client_id);
428 }
429
Get(int32 client_id)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
GetByPath(const base::FilePath & path)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
AddToCache(const base::FilePath & key,ShaderDiskCache * cache)449 void ShaderCacheFactory::AddToCache(const base::FilePath& key,
450 ShaderDiskCache* cache) {
451 shader_cache_map_[key] = cache;
452 }
453
RemoveFromCache(const base::FilePath & key)454 void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) {
455 shader_cache_map_.erase(key);
456 }
457
ClearByPath(const base::FilePath & path,const base::Time & delete_begin,const base::Time & delete_end,const base::Closure & callback)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
CacheCleared(const base::FilePath & path)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
ShaderDiskCache(const base::FilePath & cache_path)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
~ShaderDiskCache()514 ShaderDiskCache::~ShaderDiskCache() {
515 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_);
516 }
517
Init()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_DEFAULT,
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
Cache(const std::string & key,const std::string & shader)540 void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) {
541 if (!cache_available_)
542 return;
543
544 scoped_refptr<ShaderDiskCacheEntry> shim =
545 new ShaderDiskCacheEntry(AsWeakPtr(), key, shader);
546 shim->Cache();
547
548 entry_map_[shim] = shim;
549 }
550
Clear(const base::Time begin_time,const base::Time end_time,const net::CompletionCallback & completion_callback)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
Size()564 int32 ShaderDiskCache::Size() {
565 if (!cache_available_)
566 return -1;
567 return backend_->GetEntryCount();
568 }
569
SetAvailableCallback(const net::CompletionCallback & callback)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
CacheCreatedCallback(int rv)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
EntryComplete(void * entry)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
ReadComplete()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
SetCacheCompleteCallback(const net::CompletionCallback & callback)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