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/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"
19 static const base::FilePath::CharType kGpuCachePath
[] =
20 FILE_PATH_LITERAL("GPUCache");
22 void EntryCloser(disk_cache::Entry
* entry
) {
26 void FreeDiskCacheIterator(scoped_ptr
<disk_cache::Backend::Iterator
> iterator
) {
31 // ShaderDiskCacheEntry handles the work of caching/updating the cached
33 class ShaderDiskCacheEntry
34 : public base::ThreadChecker
,
35 public base::RefCounted
<ShaderDiskCacheEntry
> {
37 ShaderDiskCacheEntry(base::WeakPtr
<ShaderDiskCache
> cache
,
38 const std::string
& key
,
39 const std::string
& shader
);
43 friend class base::RefCounted
<ShaderDiskCacheEntry
>;
52 ~ShaderDiskCacheEntry();
54 void OnOpComplete(int rv
);
56 int OpenCallback(int rv
);
57 int WriteCallback(int rv
);
58 int IOComplete(int rv
);
60 base::WeakPtr
<ShaderDiskCache
> cache_
;
64 disk_cache::Entry
* entry_
;
66 DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry
);
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
> {
75 ShaderDiskReadHelper(base::WeakPtr
<ShaderDiskCache
> cache
, int host_id
);
79 friend class base::RefCounted
<ShaderDiskReadHelper
>;
90 ~ShaderDiskReadHelper();
92 void OnOpComplete(int rv
);
95 int OpenNextEntryComplete(int rv
);
96 int ReadComplete(int rv
);
97 int IterationComplete(int rv
);
99 base::WeakPtr
<ShaderDiskCache
> cache_
;
101 scoped_ptr
<disk_cache::Backend::Iterator
> iter_
;
102 scoped_refptr
<net::IOBufferWithSize
> buf_
;
104 disk_cache::Entry
* entry_
;
106 DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper
);
109 class ShaderClearHelper
110 : public base::RefCounted
<ShaderClearHelper
>,
111 public base::SupportsWeakPtr
<ShaderClearHelper
> {
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
);
121 friend class base::RefCounted
<ShaderClearHelper
>;
129 ~ShaderClearHelper();
131 void DoClearShaderCache(int rv
);
133 scoped_refptr
<ShaderDiskCache
> cache_
;
135 base::FilePath path_
;
136 base::Time delete_begin_
;
137 base::Time delete_end_
;
138 base::Closure callback_
;
140 DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper
);
143 ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr
<ShaderDiskCache
> cache
,
144 const std::string
& key
,
145 const std::string
& shader
)
147 op_type_(OPEN_ENTRY
),
153 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
155 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
156 base::Bind(&EntryCloser
, entry_
));
159 void ShaderDiskCacheEntry::Cache() {
160 DCHECK(CalledOnValidThread());
164 int rv
= cache_
->backend()->OpenEntry(
167 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this));
168 if (rv
!= net::ERR_IO_PENDING
)
172 void ShaderDiskCacheEntry::OnOpComplete(int rv
) {
173 DCHECK(CalledOnValidThread());
180 rv
= OpenCallback(rv
);
183 rv
= WriteCallback(rv
);
189 rv
= net::ERR_IO_PENDING
; // break the loop.
192 NOTREACHED(); // Invalid op_type_ provided.
195 } while (rv
!= net::ERR_IO_PENDING
);
198 int ShaderDiskCacheEntry::OpenCallback(int rv
) {
199 DCHECK(CalledOnValidThread());
200 // Called through OnOpComplete, so we know |cache_| is valid.
202 cache_
->backend()->OnExternalCacheHit(key_
);
203 cache_
->EntryComplete(this);
204 op_type_
= TERMINATE
;
208 op_type_
= CREATE_ENTRY
;
209 return cache_
->backend()->CreateEntry(
212 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this));
215 int ShaderDiskCacheEntry::WriteCallback(int rv
) {
216 DCHECK(CalledOnValidThread());
217 // Called through OnOpComplete, so we know |cache_| is valid.
219 LOG(ERROR
) << "Failed to create shader cache entry: " << rv
;
220 cache_
->EntryComplete(this);
221 op_type_
= TERMINATE
;
225 op_type_
= WRITE_DATA
;
226 scoped_refptr
<net::StringIOBuffer
> io_buf
= new net::StringIOBuffer(shader_
);
227 return entry_
->WriteData(
232 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this),
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
;
244 ShaderDiskReadHelper::ShaderDiskReadHelper(
245 base::WeakPtr
<ShaderDiskCache
> cache
,
254 void ShaderDiskReadHelper::LoadCache() {
255 DCHECK(CalledOnValidThread());
258 OnOpComplete(net::OK
);
261 void ShaderDiskReadHelper::OnOpComplete(int rv
) {
262 DCHECK(CalledOnValidThread());
269 rv
= OpenNextEntry();
271 case OPEN_NEXT_COMPLETE
:
272 rv
= OpenNextEntryComplete(rv
);
275 rv
= ReadComplete(rv
);
277 case ITERATION_FINISHED
:
278 rv
= IterationComplete(rv
);
281 cache_
->ReadComplete();
282 rv
= net::ERR_IO_PENDING
; // break the loop
285 NOTREACHED(); // Invalid state for read helper
286 rv
= net::ERR_FAILED
;
289 } while (rv
!= net::ERR_IO_PENDING
);
292 int ShaderDiskReadHelper::OpenNextEntry() {
293 DCHECK(CalledOnValidThread());
294 // Called through OnOpComplete, so we know |cache_| is valid.
295 op_type_
= OPEN_NEXT_COMPLETE
;
297 iter_
= cache_
->backend()->CreateIterator();
298 return iter_
->OpenNextEntry(
299 &entry_
, base::Bind(&ShaderDiskReadHelper::OnOpComplete
, this));
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
) {
307 op_type_
= ITERATION_FINISHED
;
314 op_type_
= READ_COMPLETE
;
315 buf_
= new net::IOBufferWithSize(entry_
->GetDataSize(1));
316 return entry_
->ReadData(
321 base::Bind(&ShaderDiskReadHelper::OnOpComplete
, this));
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_
);
330 host
->LoadedShader(entry_
->GetKey(), std::string(buf_
->data(),
338 op_type_
= OPEN_NEXT
;
342 int ShaderDiskReadHelper::IterationComplete(int rv
) {
343 DCHECK(CalledOnValidThread());
344 // Called through OnOpComplete, so we know |cache_| is valid.
346 op_type_
= TERMINATE
;
350 ShaderDiskReadHelper::~ShaderDiskReadHelper() {
352 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
353 base::Bind(&EntryCloser
, entry_
));
356 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
357 base::Bind(&FreeDiskCacheIterator
,
358 base::Passed(&iter_
)));
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
)
368 op_type_(VERIFY_CACHE_SETUP
),
370 delete_begin_(delete_begin
),
371 delete_end_(delete_end
),
372 callback_(callback
) {
375 ShaderClearHelper::~ShaderClearHelper() {
376 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
379 void ShaderClearHelper::Clear() {
380 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
381 DoClearShaderCache(net::OK
);
384 void ShaderClearHelper::DoClearShaderCache(int rv
) {
385 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
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;
391 while (rv
!= net::ERR_IO_PENDING
) {
393 case VERIFY_CACHE_SETUP
:
394 rv
= cache_
->SetAvailableCallback(
395 base::Bind(&ShaderClearHelper::DoClearShaderCache
, AsWeakPtr()));
396 op_type_
= DELETE_CACHE
;
400 delete_begin_
, delete_end_
,
401 base::Bind(&ShaderClearHelper::DoClearShaderCache
, AsWeakPtr()));
402 op_type_
= TERMINATE
;
405 ShaderCacheFactory::GetInstance()->CacheCleared(path_
);
407 rv
= net::ERR_IO_PENDING
; // Break the loop.
410 NOTREACHED(); // Invalid state provided.
411 op_type_
= TERMINATE
;
418 ShaderCacheFactory
* ShaderCacheFactory::GetInstance() {
419 return Singleton
<ShaderCacheFactory
,
420 LeakySingletonTraits
<ShaderCacheFactory
> >::get();
423 ShaderCacheFactory::ShaderCacheFactory() {
426 ShaderCacheFactory::~ShaderCacheFactory() {
429 void ShaderCacheFactory::SetCacheInfo(int32 client_id
,
430 const base::FilePath
& path
) {
431 client_id_to_path_map_
[client_id
] = path
;
434 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id
) {
435 client_id_to_path_map_
.erase(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())
443 return ShaderCacheFactory::GetByPath(iter
->second
);
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())
452 ShaderDiskCache
* cache
= new ShaderDiskCache(path
);
457 void ShaderCacheFactory::AddToCache(const base::FilePath
& key
,
458 ShaderDiskCache
* cache
) {
459 shader_cache_map_
[key
] = cache
;
462 void ShaderCacheFactory::RemoveFromCache(const base::FilePath
& key
) {
463 shader_cache_map_
.erase(key
);
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_CURRENTLY_ON(BrowserThread::IO
);
471 DCHECK(!callback
.is_null());
473 scoped_refptr
<ShaderClearHelper
> helper
= new ShaderClearHelper(
474 GetByPath(path
), path
, delete_begin
, delete_end
, callback
);
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
);
487 shader_clear_map_
.insert(
488 std::pair
<base::FilePath
, ShaderClearQueue
>(path
, ShaderClearQueue()));
489 shader_clear_map_
[path
].push(helper
);
493 void ShaderCacheFactory::CacheCleared(const base::FilePath
& path
) {
494 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
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.";
504 // If there are remaining items in the list we trigger the Clear on the
506 if (!iter
->second
.empty()) {
507 iter
->second
.front()->Clear();
511 shader_clear_map_
.erase(path
);
514 ShaderDiskCache::ShaderDiskCache(const base::FilePath
& cache_path
)
515 : cache_available_(false),
517 cache_path_(cache_path
),
518 is_initialized_(false) {
519 ShaderCacheFactory::GetInstance()->AddToCache(cache_path_
, this);
522 ShaderDiskCache::~ShaderDiskCache() {
523 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_
);
526 void ShaderDiskCache::Init() {
527 if (is_initialized_
) {
528 NOTREACHED(); // can't initialize disk cache twice.
531 is_initialized_
= true;
533 int rv
= disk_cache::CreateCacheBackend(
535 net::CACHE_BACKEND_DEFAULT
,
536 cache_path_
.Append(kGpuCachePath
),
537 gpu::kDefaultMaxProgramCacheMemoryBytes
,
539 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE
).get(),
542 base::Bind(&ShaderDiskCache::CacheCreatedCallback
, this));
545 cache_available_
= true;
548 void ShaderDiskCache::Cache(const std::string
& key
, const std::string
& shader
) {
549 if (!cache_available_
)
552 scoped_refptr
<ShaderDiskCacheEntry
> shim
=
553 new ShaderDiskCacheEntry(AsWeakPtr(), key
, shader
);
556 entry_map_
[shim
.get()] = shim
;
559 int ShaderDiskCache::Clear(
560 const base::Time begin_time
, const base::Time end_time
,
561 const net::CompletionCallback
& completion_callback
) {
563 if (begin_time
.is_null()) {
564 rv
= backend_
->DoomAllEntries(completion_callback
);
566 rv
= backend_
->DoomEntriesBetween(begin_time
, end_time
,
567 completion_callback
);
572 int32
ShaderDiskCache::Size() {
573 if (!cache_available_
)
575 return backend_
->GetEntryCount();
578 int ShaderDiskCache::SetAvailableCallback(
579 const net::CompletionCallback
& callback
) {
580 if (cache_available_
)
582 available_callback_
= callback
;
583 return net::ERR_IO_PENDING
;
586 void ShaderDiskCache::CacheCreatedCallback(int rv
) {
588 LOG(ERROR
) << "Shader Cache Creation failed: " << rv
;
591 helper_
= new ShaderDiskReadHelper(AsWeakPtr(), host_id_
);
592 helper_
->LoadCache();
595 void ShaderDiskCache::EntryComplete(void* entry
) {
596 entry_map_
.erase(entry
);
598 if (entry_map_
.empty() && !cache_complete_callback_
.is_null())
599 cache_complete_callback_
.Run(net::OK
);
602 void ShaderDiskCache::ReadComplete() {
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();
615 int ShaderDiskCache::SetCacheCompleteCallback(
616 const net::CompletionCallback
& callback
) {
617 if (entry_map_
.empty()) {
620 cache_complete_callback_
= callback
;
621 return net::ERR_IO_PENDING
;
624 } // namespace content