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
) {
28 // ShaderDiskCacheEntry handles the work of caching/updating the cached
30 class ShaderDiskCacheEntry
31 : public base::ThreadChecker
,
32 public base::RefCounted
<ShaderDiskCacheEntry
> {
34 ShaderDiskCacheEntry(base::WeakPtr
<ShaderDiskCache
> cache
,
35 const std::string
& key
,
36 const std::string
& shader
);
40 friend class base::RefCounted
<ShaderDiskCacheEntry
>;
49 ~ShaderDiskCacheEntry();
51 void OnOpComplete(int rv
);
53 int OpenCallback(int rv
);
54 int WriteCallback(int rv
);
55 int IOComplete(int rv
);
57 base::WeakPtr
<ShaderDiskCache
> cache_
;
61 disk_cache::Entry
* entry_
;
63 DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry
);
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
> {
72 ShaderDiskReadHelper(base::WeakPtr
<ShaderDiskCache
> cache
, int host_id
);
76 friend class base::RefCounted
<ShaderDiskReadHelper
>;
87 ~ShaderDiskReadHelper();
89 void OnOpComplete(int rv
);
92 int OpenNextEntryComplete(int rv
);
93 int ReadComplete(int rv
);
94 int IterationComplete(int rv
);
96 base::WeakPtr
<ShaderDiskCache
> cache_
;
99 scoped_refptr
<net::IOBufferWithSize
> buf_
;
101 disk_cache::Entry
* entry_
;
103 DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper
);
106 class ShaderClearHelper
107 : public base::RefCounted
<ShaderClearHelper
>,
108 public base::SupportsWeakPtr
<ShaderClearHelper
> {
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
);
118 friend class base::RefCounted
<ShaderClearHelper
>;
126 ~ShaderClearHelper();
128 void DoClearShaderCache(int rv
);
130 scoped_refptr
<ShaderDiskCache
> cache_
;
132 base::FilePath path_
;
133 base::Time delete_begin_
;
134 base::Time delete_end_
;
135 base::Closure callback_
;
137 DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper
);
140 ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr
<ShaderDiskCache
> cache
,
141 const std::string
& key
,
142 const std::string
& shader
)
144 op_type_(OPEN_ENTRY
),
150 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
152 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
153 base::Bind(&EntryCloser
, entry_
));
156 void ShaderDiskCacheEntry::Cache() {
157 DCHECK(CalledOnValidThread());
161 int rv
= cache_
->backend()->OpenEntry(
164 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this));
165 if (rv
!= net::ERR_IO_PENDING
)
169 void ShaderDiskCacheEntry::OnOpComplete(int rv
) {
170 DCHECK(CalledOnValidThread());
177 rv
= OpenCallback(rv
);
180 rv
= WriteCallback(rv
);
186 rv
= net::ERR_IO_PENDING
; // break the loop.
189 NOTREACHED(); // Invalid op_type_ provided.
192 } while (rv
!= net::ERR_IO_PENDING
);
195 int ShaderDiskCacheEntry::OpenCallback(int rv
) {
196 DCHECK(CalledOnValidThread());
197 // Called through OnOpComplete, so we know |cache_| is valid.
199 cache_
->backend()->OnExternalCacheHit(key_
);
200 cache_
->EntryComplete(this);
201 op_type_
= TERMINATE
;
205 op_type_
= CREATE_ENTRY
;
206 return cache_
->backend()->CreateEntry(
209 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this));
212 int ShaderDiskCacheEntry::WriteCallback(int rv
) {
213 DCHECK(CalledOnValidThread());
214 // Called through OnOpComplete, so we know |cache_| is valid.
216 LOG(ERROR
) << "Failed to create shader cache entry: " << rv
;
217 cache_
->EntryComplete(this);
218 op_type_
= TERMINATE
;
222 op_type_
= WRITE_DATA
;
223 scoped_refptr
<net::StringIOBuffer
> io_buf
= new net::StringIOBuffer(shader_
);
224 return entry_
->WriteData(
229 base::Bind(&ShaderDiskCacheEntry::OnOpComplete
, this),
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
;
241 ShaderDiskReadHelper::ShaderDiskReadHelper(
242 base::WeakPtr
<ShaderDiskCache
> cache
,
252 void ShaderDiskReadHelper::LoadCache() {
253 DCHECK(CalledOnValidThread());
256 OnOpComplete(net::OK
);
259 void ShaderDiskReadHelper::OnOpComplete(int rv
) {
260 DCHECK(CalledOnValidThread());
267 rv
= OpenNextEntry();
269 case OPEN_NEXT_COMPLETE
:
270 rv
= OpenNextEntryComplete(rv
);
273 rv
= ReadComplete(rv
);
275 case ITERATION_FINISHED
:
276 rv
= IterationComplete(rv
);
279 cache_
->ReadComplete();
280 rv
= net::ERR_IO_PENDING
; // break the loop
283 NOTREACHED(); // Invalid state for read helper
284 rv
= net::ERR_FAILED
;
287 } while (rv
!= net::ERR_IO_PENDING
);
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(
297 base::Bind(&ShaderDiskReadHelper::OnOpComplete
, this));
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
;
311 op_type_
= READ_COMPLETE
;
312 buf_
= new net::IOBufferWithSize(entry_
->GetDataSize(1));
313 return entry_
->ReadData(
318 base::Bind(&ShaderDiskReadHelper::OnOpComplete
, this));
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_
);
327 host
->LoadedShader(entry_
->GetKey(), std::string(buf_
->data(),
335 op_type_
= OPEN_NEXT
;
339 int ShaderDiskReadHelper::IterationComplete(int rv
) {
340 DCHECK(CalledOnValidThread());
341 // Called through OnOpComplete, so we know |cache_| is valid.
342 cache_
->backend()->EndEnumeration(&iter_
);
344 op_type_
= TERMINATE
;
348 ShaderDiskReadHelper::~ShaderDiskReadHelper() {
350 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
351 base::Bind(&EntryCloser
, entry_
));
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
)
360 op_type_(VERIFY_CACHE_SETUP
),
362 delete_begin_(delete_begin
),
363 delete_end_(delete_end
),
364 callback_(callback
) {
367 ShaderClearHelper::~ShaderClearHelper() {
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
371 void ShaderClearHelper::Clear() {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
373 DoClearShaderCache(net::OK
);
376 void ShaderClearHelper::DoClearShaderCache(int rv
) {
377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
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;
383 while (rv
!= net::ERR_IO_PENDING
) {
385 case VERIFY_CACHE_SETUP
:
386 rv
= cache_
->SetAvailableCallback(
387 base::Bind(&ShaderClearHelper::DoClearShaderCache
, AsWeakPtr()));
388 op_type_
= DELETE_CACHE
;
392 delete_begin_
, delete_end_
,
393 base::Bind(&ShaderClearHelper::DoClearShaderCache
, AsWeakPtr()));
394 op_type_
= TERMINATE
;
397 ShaderCacheFactory::GetInstance()->CacheCleared(path_
);
399 rv
= net::ERR_IO_PENDING
; // Break the loop.
402 NOTREACHED(); // Invalid state provided.
403 op_type_
= TERMINATE
;
410 ShaderCacheFactory
* ShaderCacheFactory::GetInstance() {
411 return Singleton
<ShaderCacheFactory
,
412 LeakySingletonTraits
<ShaderCacheFactory
> >::get();
415 ShaderCacheFactory::ShaderCacheFactory() {
418 ShaderCacheFactory::~ShaderCacheFactory() {
421 void ShaderCacheFactory::SetCacheInfo(int32 client_id
,
422 const base::FilePath
& path
) {
423 client_id_to_path_map_
[client_id
] = path
;
426 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id
) {
427 client_id_to_path_map_
.erase(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())
435 return ShaderCacheFactory::GetByPath(iter
->second
);
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())
444 ShaderDiskCache
* cache
= new ShaderDiskCache(path
);
449 void ShaderCacheFactory::AddToCache(const base::FilePath
& key
,
450 ShaderDiskCache
* cache
) {
451 shader_cache_map_
[key
] = cache
;
454 void ShaderCacheFactory::RemoveFromCache(const base::FilePath
& key
) {
455 shader_cache_map_
.erase(key
);
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());
465 scoped_refptr
<ShaderClearHelper
> helper
= new ShaderClearHelper(
466 GetByPath(path
), path
, delete_begin
, delete_end
, callback
);
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
);
479 shader_clear_map_
.insert(
480 std::pair
<base::FilePath
, ShaderClearQueue
>(path
, ShaderClearQueue()));
481 shader_clear_map_
[path
].push(helper
);
485 void ShaderCacheFactory::CacheCleared(const base::FilePath
& path
) {
486 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
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.";
496 // If there are remaining items in the list we trigger the Clear on the
498 if (!iter
->second
.empty()) {
499 iter
->second
.front()->Clear();
503 shader_clear_map_
.erase(path
);
506 ShaderDiskCache::ShaderDiskCache(const base::FilePath
& cache_path
)
507 : cache_available_(false),
509 cache_path_(cache_path
),
510 is_initialized_(false) {
511 ShaderCacheFactory::GetInstance()->AddToCache(cache_path_
, this);
514 ShaderDiskCache::~ShaderDiskCache() {
515 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_
);
518 void ShaderDiskCache::Init() {
519 if (is_initialized_
) {
520 NOTREACHED(); // can't initialize disk cache twice.
523 is_initialized_
= true;
525 int rv
= disk_cache::CreateCacheBackend(
527 net::CACHE_BACKEND_DEFAULT
,
528 cache_path_
.Append(kGpuCachePath
),
529 gpu::kDefaultMaxProgramCacheMemoryBytes
,
531 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE
).get(),
534 base::Bind(&ShaderDiskCache::CacheCreatedCallback
, this));
537 cache_available_
= true;
540 void ShaderDiskCache::Cache(const std::string
& key
, const std::string
& shader
) {
541 if (!cache_available_
)
544 scoped_refptr
<ShaderDiskCacheEntry
> shim
=
545 new ShaderDiskCacheEntry(AsWeakPtr(), key
, shader
);
548 entry_map_
[shim
] = shim
;
551 int ShaderDiskCache::Clear(
552 const base::Time begin_time
, const base::Time end_time
,
553 const net::CompletionCallback
& completion_callback
) {
555 if (begin_time
.is_null()) {
556 rv
= backend_
->DoomAllEntries(completion_callback
);
558 rv
= backend_
->DoomEntriesBetween(begin_time
, end_time
,
559 completion_callback
);
564 int32
ShaderDiskCache::Size() {
565 if (!cache_available_
)
567 return backend_
->GetEntryCount();
570 int ShaderDiskCache::SetAvailableCallback(
571 const net::CompletionCallback
& callback
) {
572 if (cache_available_
)
574 available_callback_
= callback
;
575 return net::ERR_IO_PENDING
;
578 void ShaderDiskCache::CacheCreatedCallback(int rv
) {
580 LOG(ERROR
) << "Shader Cache Creation failed: " << rv
;
583 helper_
= new ShaderDiskReadHelper(AsWeakPtr(), host_id_
);
584 helper_
->LoadCache();
587 void ShaderDiskCache::EntryComplete(void* entry
) {
588 entry_map_
.erase(entry
);
590 if (entry_map_
.empty() && !cache_complete_callback_
.is_null())
591 cache_complete_callback_
.Run(net::OK
);
594 void ShaderDiskCache::ReadComplete() {
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();
607 int ShaderDiskCache::SetCacheCompleteCallback(
608 const net::CompletionCallback
& callback
) {
609 if (entry_map_
.empty()) {
612 cache_complete_callback_
= callback
;
613 return net::ERR_IO_PENDING
;
616 } // namespace content