shader_disk_cache.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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 15namespace content { 16 17namespace { 18 19static const base::FilePath::CharType kGpuCachePath[] = 20 FILE_PATH_LITERAL("GPUCache"); 21 22void 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. 30class 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. 68class 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 106class 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 140ShaderDiskCacheEntry::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 150ShaderDiskCacheEntry::~ShaderDiskCacheEntry() { 151 if (entry_) 152 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 153 base::Bind(&EntryCloser, entry_)); 154} 155 156void 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 169void 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 195int 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 212int 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 233int 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 241ShaderDiskReadHelper::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 252void ShaderDiskReadHelper::LoadCache() { 253 DCHECK(CalledOnValidThread()); 254 if (!cache_.get()) 255 return; 256 OnOpComplete(net::OK); 257} 258 259void 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 290int 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 300int 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 321int 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 339int 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 348ShaderDiskReadHelper::~ShaderDiskReadHelper() { 349 if (entry_) 350 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 351 base::Bind(&EntryCloser, entry_)); 352} 353 354ShaderClearHelper::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 367ShaderClearHelper::~ShaderClearHelper() { 368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 369} 370 371void ShaderClearHelper::Clear() { 372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 373 DoClearShaderCache(net::OK); 374} 375 376void 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 410ShaderCacheFactory* ShaderCacheFactory::GetInstance() { 411 return Singleton<ShaderCacheFactory, 412 LeakySingletonTraits<ShaderCacheFactory> >::get(); 413} 414 415ShaderCacheFactory::ShaderCacheFactory() { 416} 417 418ShaderCacheFactory::~ShaderCacheFactory() { 419} 420 421void ShaderCacheFactory::SetCacheInfo(int32 client_id, 422 const base::FilePath& path) { 423 client_id_to_path_map_[client_id] = path; 424} 425 426void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) { 427 client_id_to_path_map_.erase(client_id); 428} 429 430scoped_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 438scoped_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 449void ShaderCacheFactory::AddToCache(const base::FilePath& key, 450 ShaderDiskCache* cache) { 451 shader_cache_map_[key] = cache; 452} 453 454void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) { 455 shader_cache_map_.erase(key); 456} 457 458void 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 485void 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 506ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path) 507 : cache_available_(false), 508 host_id_(0), 509 cache_path_(cache_path), 510 is_initialized_(false), 511 backend_(NULL) { 512 ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this); 513} 514 515ShaderDiskCache::~ShaderDiskCache() { 516 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_); 517 if (backend_) 518 delete backend_; 519} 520 521void ShaderDiskCache::Init() { 522 if (is_initialized_) { 523 NOTREACHED(); // can't initialize disk cache twice. 524 return; 525 } 526 is_initialized_ = true; 527 528 int rv = disk_cache::CreateCacheBackend( 529 net::SHADER_CACHE, 530 net::CACHE_BACKEND_BLOCKFILE, 531 cache_path_.Append(kGpuCachePath), 532 gpu::kDefaultMaxProgramCacheMemoryBytes, 533 true, 534 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), 535 NULL, 536 &backend_, 537 base::Bind(&ShaderDiskCache::CacheCreatedCallback, this)); 538 539 if (rv == net::OK) 540 cache_available_ = true; 541} 542 543void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) { 544 if (!cache_available_) 545 return; 546 547 ShaderDiskCacheEntry* shim = 548 new ShaderDiskCacheEntry(AsWeakPtr(), key, shader); 549 shim->Cache(); 550 551 entry_map_[shim] = shim; 552} 553 554int ShaderDiskCache::Clear( 555 const base::Time begin_time, const base::Time end_time, 556 const net::CompletionCallback& completion_callback) { 557 int rv; 558 if (begin_time.is_null()) { 559 rv = backend_->DoomAllEntries(completion_callback); 560 } else { 561 rv = backend_->DoomEntriesBetween(begin_time, end_time, 562 completion_callback); 563 } 564 return rv; 565} 566 567int32 ShaderDiskCache::Size() { 568 if (!cache_available_) 569 return -1; 570 return backend_->GetEntryCount(); 571} 572 573int ShaderDiskCache::SetAvailableCallback( 574 const net::CompletionCallback& callback) { 575 if (cache_available_) 576 return net::OK; 577 available_callback_ = callback; 578 return net::ERR_IO_PENDING; 579} 580 581void ShaderDiskCache::CacheCreatedCallback(int rv) { 582 if (rv != net::OK) { 583 LOG(ERROR) << "Shader Cache Creation failed: " << rv; 584 return; 585 } 586 helper_ = new ShaderDiskReadHelper(AsWeakPtr(), host_id_); 587 helper_->LoadCache(); 588} 589 590void ShaderDiskCache::EntryComplete(void* entry) { 591 entry_map_.erase(entry); 592 593 if (entry_map_.empty() && !cache_complete_callback_.is_null()) 594 cache_complete_callback_.Run(net::OK); 595} 596 597void ShaderDiskCache::ReadComplete() { 598 helper_ = NULL; 599 600 // The cache is considered available after we have finished reading any 601 // of the old cache values off disk. This prevents a potential race where we 602 // are reading from disk and execute a cache clear at the same time. 603 cache_available_ = true; 604 if (!available_callback_.is_null()) { 605 available_callback_.Run(net::OK); 606 available_callback_.Reset(); 607 } 608} 609 610int ShaderDiskCache::SetCacheCompleteCallback( 611 const net::CompletionCallback& callback) { 612 if (entry_map_.empty()) { 613 return net::OK; 614 } 615 cache_complete_callback_ = callback; 616 return net::ERR_IO_PENDING; 617} 618 619} // namespace content 620 621