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