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.
5 #include "content/browser/gpu/shader_disk_cache.h"
7 #include "base/profiler/scoped_tracker.h"
8 #include "base/threading/thread_checker.h"
9 #include "content/browser/gpu/gpu_process_host.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "gpu/command_buffer/common/constants.h"
12 #include "net/base/cache_type.h"
13 #include "net/base/io_buffer.h"
14 #include "net/base/net_errors.h"
20 static const base::FilePath::CharType kGpuCachePath
[] =
21 FILE_PATH_LITERAL("GPUCache");
23 void EntryCloser(disk_cache::Entry
* entry
) {
27 void FreeDiskCacheIterator(scoped_ptr
<disk_cache::Backend::Iterator
> iterator
) {
32 // ShaderDiskCacheEntry handles the work of caching/updating the cached
34 class ShaderDiskCacheEntry
35 : public base::ThreadChecker
,
36 public base::RefCounted
<ShaderDiskCacheEntry
> {
38 ShaderDiskCacheEntry(base::WeakPtr
<ShaderDiskCache
> cache
,
39 const std::string
& key
,
40 const std::string
& shader
);
44 friend class base::RefCounted
<ShaderDiskCacheEntry
>;
53 ~ShaderDiskCacheEntry();
55 void OnOpComplete(int rv
);
57 int OpenCallback(int rv
);
58 int WriteCallback(int rv
);
59 int IOComplete(int rv
);
61 base::WeakPtr
<ShaderDiskCache
> cache_
;
65 disk_cache::Entry
* entry_
;
67 DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry
);
70 // ShaderDiskReadHelper is used to load all of the cached shaders from the
71 // disk cache and send to the memory cache.
72 class ShaderDiskReadHelper
73 : public base::ThreadChecker
,
74 public base::RefCounted
<ShaderDiskReadHelper
> {
76 ShaderDiskReadHelper(base::WeakPtr
<ShaderDiskCache
> cache
, int host_id
);
80 friend class base::RefCounted
<ShaderDiskReadHelper
>;
91 ~ShaderDiskReadHelper();
93 void OnOpComplete(int rv
);
96 int OpenNextEntryComplete(int rv
);
97 int ReadComplete(int rv
);
98 int IterationComplete(int rv
);
100 base::WeakPtr
<ShaderDiskCache
> cache_
;
102 scoped_ptr
<disk_cache::Backend::Iterator
> iter_
;
103 scoped_refptr
<net::IOBufferWithSize
> buf_
;
105 disk_cache::Entry
* entry_
;
107 DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper
);
110 class ShaderClearHelper
111 : public base::RefCounted
<ShaderClearHelper
>,
112 public base::SupportsWeakPtr
<ShaderClearHelper
> {
114 ShaderClearHelper(scoped_refptr
<ShaderDiskCache
> cache
,
115 const base::FilePath
& path
,
116 const base::Time
& delete_begin
,
117 const base::Time
& delete_end
,
118 const base::Closure
& callback
);
122 friend class base::RefCounted
<ShaderClearHelper
>;
130 ~ShaderClearHelper();
132 void DoClearShaderCache(int rv
);
134 scoped_refptr
<ShaderDiskCache
> cache_
;
136 base::FilePath path_
;
137 base::Time delete_begin_
;
138 base::Time delete_end_
;
139 base::Closure callback_
;
141 DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper
);
144 ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr
<ShaderDiskCache
> cache
,
145 const std::string
& key
,
146 const std::string
& shader
)
148 op_type_(OPEN_ENTRY
),
154 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
156 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
157 base::Bind(&EntryCloser
, entry_
));
160 void ShaderDiskCacheEntry::Cache() {
161 DCHECK(CalledOnValidThread());
165 int rv
= cache_
->backend()->OpenEntry(
168 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this));
169 if (rv
!= net::ERR_IO_PENDING
)
173 void ShaderDiskCacheEntry::OnOpComplete(int rv
) {
174 DCHECK(CalledOnValidThread());
181 rv
= OpenCallback(rv
);
184 rv
= WriteCallback(rv
);
190 rv
= net::ERR_IO_PENDING
; // break the loop.
193 NOTREACHED(); // Invalid op_type_ provided.
196 } while (rv
!= net::ERR_IO_PENDING
);
199 int ShaderDiskCacheEntry::OpenCallback(int rv
) {
200 DCHECK(CalledOnValidThread());
201 // Called through OnOpComplete, so we know |cache_| is valid.
203 cache_
->backend()->OnExternalCacheHit(key_
);
204 cache_
->EntryComplete(this);
205 op_type_
= TERMINATE
;
209 op_type_
= CREATE_ENTRY
;
210 return cache_
->backend()->CreateEntry(
213 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this));
216 int ShaderDiskCacheEntry::WriteCallback(int rv
) {
217 DCHECK(CalledOnValidThread());
218 // Called through OnOpComplete, so we know |cache_| is valid.
220 LOG(ERROR
) << "Failed to create shader cache entry: " << rv
;
221 cache_
->EntryComplete(this);
222 op_type_
= TERMINATE
;
226 op_type_
= WRITE_DATA
;
227 scoped_refptr
<net::StringIOBuffer
> io_buf
= new net::StringIOBuffer(shader_
);
228 return entry_
->WriteData(
233 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this),
237 int ShaderDiskCacheEntry::IOComplete(int rv
) {
238 DCHECK(CalledOnValidThread());
239 // Called through OnOpComplete, so we know |cache_| is valid.
240 cache_
->EntryComplete(this);
241 op_type_
= TERMINATE
;
245 ShaderDiskReadHelper::ShaderDiskReadHelper(
246 base::WeakPtr
<ShaderDiskCache
> cache
,
255 void ShaderDiskReadHelper::LoadCache() {
256 DCHECK(CalledOnValidThread());
259 OnOpComplete(net::OK
);
262 void ShaderDiskReadHelper::OnOpComplete(int rv
) {
263 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed.
264 tracked_objects::ScopedTracker
tracking_profile(
265 FROM_HERE_WITH_EXPLICIT_FUNCTION(
266 "422516 ShaderDiskReadHelper::OnOpComplete"));
268 DCHECK(CalledOnValidThread());
275 rv
= OpenNextEntry();
277 case OPEN_NEXT_COMPLETE
:
278 rv
= OpenNextEntryComplete(rv
);
281 rv
= ReadComplete(rv
);
283 case ITERATION_FINISHED
:
284 rv
= IterationComplete(rv
);
287 cache_
->ReadComplete();
288 rv
= net::ERR_IO_PENDING
; // break the loop
291 NOTREACHED(); // Invalid state for read helper
292 rv
= net::ERR_FAILED
;
295 } while (rv
!= net::ERR_IO_PENDING
);
298 int ShaderDiskReadHelper::OpenNextEntry() {
299 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed.
300 tracked_objects::ScopedTracker
tracking_profile(
301 FROM_HERE_WITH_EXPLICIT_FUNCTION(
302 "422516 ShaderDiskReadHelper::OpenNextEntry"));
304 DCHECK(CalledOnValidThread());
305 // Called through OnOpComplete, so we know |cache_| is valid.
306 op_type_
= OPEN_NEXT_COMPLETE
;
308 iter_
= cache_
->backend()->CreateIterator();
309 return iter_
->OpenNextEntry(
310 &entry_
, base::Bind(&ShaderDiskReadHelper::OnOpComplete
, this));
313 int ShaderDiskReadHelper::OpenNextEntryComplete(int rv
) {
314 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed.
315 tracked_objects::ScopedTracker
tracking_profile(
316 FROM_HERE_WITH_EXPLICIT_FUNCTION(
317 "422516 ShaderDiskReadHelper::OpenNextEntryComplete"));
319 DCHECK(CalledOnValidThread());
320 // Called through OnOpComplete, so we know |cache_| is valid.
321 if (rv
== net::ERR_FAILED
) {
323 op_type_
= ITERATION_FINISHED
;
330 op_type_
= READ_COMPLETE
;
331 buf_
= new net::IOBufferWithSize(entry_
->GetDataSize(1));
332 return entry_
->ReadData(
337 base::Bind(&ShaderDiskReadHelper::OnOpComplete
, this));
340 int ShaderDiskReadHelper::ReadComplete(int rv
) {
341 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed.
342 tracked_objects::ScopedTracker
tracking_profile(
343 FROM_HERE_WITH_EXPLICIT_FUNCTION(
344 "422516 ShaderDiskReadHelper::ReadComplete"));
346 DCHECK(CalledOnValidThread());
347 // Called through OnOpComplete, so we know |cache_| is valid.
348 if (rv
&& rv
== buf_
->size()) {
349 GpuProcessHost
* host
= GpuProcessHost::FromID(host_id_
);
351 host
->LoadedShader(entry_
->GetKey(), std::string(buf_
->data(),
359 op_type_
= OPEN_NEXT
;
363 int ShaderDiskReadHelper::IterationComplete(int rv
) {
364 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed.
365 tracked_objects::ScopedTracker
tracking_profile(
366 FROM_HERE_WITH_EXPLICIT_FUNCTION(
367 "422516 ShaderDiskReadHelper::IterationComplete"));
369 DCHECK(CalledOnValidThread());
370 // Called through OnOpComplete, so we know |cache_| is valid.
372 op_type_
= TERMINATE
;
376 ShaderDiskReadHelper::~ShaderDiskReadHelper() {
378 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
379 base::Bind(&EntryCloser
, entry_
));
382 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
383 base::Bind(&FreeDiskCacheIterator
,
384 base::Passed(&iter_
)));
388 ShaderClearHelper::ShaderClearHelper(scoped_refptr
<ShaderDiskCache
> cache
,
389 const base::FilePath
& path
,
390 const base::Time
& delete_begin
,
391 const base::Time
& delete_end
,
392 const base::Closure
& callback
)
394 op_type_(VERIFY_CACHE_SETUP
),
396 delete_begin_(delete_begin
),
397 delete_end_(delete_end
),
398 callback_(callback
) {
401 ShaderClearHelper::~ShaderClearHelper() {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
405 void ShaderClearHelper::Clear() {
406 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
407 DoClearShaderCache(net::OK
);
410 void ShaderClearHelper::DoClearShaderCache(int rv
) {
411 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
413 // Hold a ref to ourselves so when we do the CacheCleared call we don't get
414 // auto-deleted when our ref count drops to zero.
415 scoped_refptr
<ShaderClearHelper
> helper
= this;
417 while (rv
!= net::ERR_IO_PENDING
) {
419 case VERIFY_CACHE_SETUP
:
420 rv
= cache_
->SetAvailableCallback(
421 base::Bind(&ShaderClearHelper::DoClearShaderCache
, AsWeakPtr()));
422 op_type_
= DELETE_CACHE
;
426 delete_begin_
, delete_end_
,
427 base::Bind(&ShaderClearHelper::DoClearShaderCache
, AsWeakPtr()));
428 op_type_
= TERMINATE
;
431 ShaderCacheFactory::GetInstance()->CacheCleared(path_
);
433 rv
= net::ERR_IO_PENDING
; // Break the loop.
436 NOTREACHED(); // Invalid state provided.
437 op_type_
= TERMINATE
;
444 ShaderCacheFactory
* ShaderCacheFactory::GetInstance() {
445 return Singleton
<ShaderCacheFactory
,
446 LeakySingletonTraits
<ShaderCacheFactory
> >::get();
449 ShaderCacheFactory::ShaderCacheFactory() {
452 ShaderCacheFactory::~ShaderCacheFactory() {
455 void ShaderCacheFactory::SetCacheInfo(int32 client_id
,
456 const base::FilePath
& path
) {
457 client_id_to_path_map_
[client_id
] = path
;
460 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id
) {
461 client_id_to_path_map_
.erase(client_id
);
464 scoped_refptr
<ShaderDiskCache
> ShaderCacheFactory::Get(int32 client_id
) {
465 ClientIdToPathMap::iterator iter
=
466 client_id_to_path_map_
.find(client_id
);
467 if (iter
== client_id_to_path_map_
.end())
469 return ShaderCacheFactory::GetByPath(iter
->second
);
472 scoped_refptr
<ShaderDiskCache
> ShaderCacheFactory::GetByPath(
473 const base::FilePath
& path
) {
474 ShaderCacheMap::iterator iter
= shader_cache_map_
.find(path
);
475 if (iter
!= shader_cache_map_
.end())
478 ShaderDiskCache
* cache
= new ShaderDiskCache(path
);
483 void ShaderCacheFactory::AddToCache(const base::FilePath
& key
,
484 ShaderDiskCache
* cache
) {
485 shader_cache_map_
[key
] = cache
;
488 void ShaderCacheFactory::RemoveFromCache(const base::FilePath
& key
) {
489 shader_cache_map_
.erase(key
);
492 void ShaderCacheFactory::ClearByPath(const base::FilePath
& path
,
493 const base::Time
& delete_begin
,
494 const base::Time
& delete_end
,
495 const base::Closure
& callback
) {
496 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
497 DCHECK(!callback
.is_null());
499 scoped_refptr
<ShaderClearHelper
> helper
= new ShaderClearHelper(
500 GetByPath(path
), path
, delete_begin
, delete_end
, callback
);
502 // We could receive requests to clear the same path with different
503 // begin/end times. So, we keep a list of requests. If we haven't seen this
504 // path before we kick off the clear and add it to the list. If we have see it
505 // already, then we already have a clear running. We add this clear to the
506 // list and wait for any previous clears to finish.
507 ShaderClearMap::iterator iter
= shader_clear_map_
.find(path
);
508 if (iter
!= shader_clear_map_
.end()) {
509 iter
->second
.push(helper
);
513 shader_clear_map_
.insert(
514 std::pair
<base::FilePath
, ShaderClearQueue
>(path
, ShaderClearQueue()));
515 shader_clear_map_
[path
].push(helper
);
519 void ShaderCacheFactory::CacheCleared(const base::FilePath
& path
) {
520 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
522 ShaderClearMap::iterator iter
= shader_clear_map_
.find(path
);
523 if (iter
== shader_clear_map_
.end()) {
524 LOG(ERROR
) << "Completed clear but missing clear helper.";
530 // If there are remaining items in the list we trigger the Clear on the
532 if (!iter
->second
.empty()) {
533 iter
->second
.front()->Clear();
537 shader_clear_map_
.erase(path
);
540 ShaderDiskCache::ShaderDiskCache(const base::FilePath
& cache_path
)
541 : cache_available_(false),
543 cache_path_(cache_path
),
544 is_initialized_(false) {
545 ShaderCacheFactory::GetInstance()->AddToCache(cache_path_
, this);
548 ShaderDiskCache::~ShaderDiskCache() {
549 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_
);
552 void ShaderDiskCache::Init() {
553 if (is_initialized_
) {
554 NOTREACHED(); // can't initialize disk cache twice.
557 is_initialized_
= true;
559 int rv
= disk_cache::CreateCacheBackend(
561 net::CACHE_BACKEND_DEFAULT
,
562 cache_path_
.Append(kGpuCachePath
),
563 gpu::kDefaultMaxProgramCacheMemoryBytes
,
565 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE
).get(),
568 base::Bind(&ShaderDiskCache::CacheCreatedCallback
, this));
571 cache_available_
= true;
574 void ShaderDiskCache::Cache(const std::string
& key
, const std::string
& shader
) {
575 if (!cache_available_
)
578 scoped_refptr
<ShaderDiskCacheEntry
> shim
=
579 new ShaderDiskCacheEntry(AsWeakPtr(), key
, shader
);
582 entry_map_
[shim
.get()] = shim
;
585 int ShaderDiskCache::Clear(
586 const base::Time begin_time
, const base::Time end_time
,
587 const net::CompletionCallback
& completion_callback
) {
589 if (begin_time
.is_null()) {
590 rv
= backend_
->DoomAllEntries(completion_callback
);
592 rv
= backend_
->DoomEntriesBetween(begin_time
, end_time
,
593 completion_callback
);
598 int32
ShaderDiskCache::Size() {
599 if (!cache_available_
)
601 return backend_
->GetEntryCount();
604 int ShaderDiskCache::SetAvailableCallback(
605 const net::CompletionCallback
& callback
) {
606 if (cache_available_
)
608 available_callback_
= callback
;
609 return net::ERR_IO_PENDING
;
612 void ShaderDiskCache::CacheCreatedCallback(int rv
) {
614 LOG(ERROR
) << "Shader Cache Creation failed: " << rv
;
617 helper_
= new ShaderDiskReadHelper(AsWeakPtr(), host_id_
);
618 helper_
->LoadCache();
621 void ShaderDiskCache::EntryComplete(void* entry
) {
622 entry_map_
.erase(entry
);
624 if (entry_map_
.empty() && !cache_complete_callback_
.is_null())
625 cache_complete_callback_
.Run(net::OK
);
628 void ShaderDiskCache::ReadComplete() {
631 // The cache is considered available after we have finished reading any
632 // of the old cache values off disk. This prevents a potential race where we
633 // are reading from disk and execute a cache clear at the same time.
634 cache_available_
= true;
635 if (!available_callback_
.is_null()) {
636 available_callback_
.Run(net::OK
);
637 available_callback_
.Reset();
641 int ShaderDiskCache::SetCacheCompleteCallback(
642 const net::CompletionCallback
& callback
) {
643 if (entry_map_
.empty()) {
646 cache_complete_callback_
= callback
;
647 return net::ERR_IO_PENDING
;
650 } // namespace content