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 void FreeDiskCacheIterator(scoped_ptr<disk_cache::Backend::Iterator> iterator) { 27 } 28 29 } // namespace 30 31 // ShaderDiskCacheEntry handles the work of caching/updating the cached 32 // shaders. 33 class ShaderDiskCacheEntry 34 : public base::ThreadChecker, 35 public base::RefCounted<ShaderDiskCacheEntry> { 36 public: 37 ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache, 38 const std::string& key, 39 const std::string& shader); 40 void Cache(); 41 42 private: 43 friend class base::RefCounted<ShaderDiskCacheEntry>; 44 45 enum OpType { 46 TERMINATE, 47 OPEN_ENTRY, 48 WRITE_DATA, 49 CREATE_ENTRY, 50 }; 51 52 ~ShaderDiskCacheEntry(); 53 54 void OnOpComplete(int rv); 55 56 int OpenCallback(int rv); 57 int WriteCallback(int rv); 58 int IOComplete(int rv); 59 60 base::WeakPtr<ShaderDiskCache> cache_; 61 OpType op_type_; 62 std::string key_; 63 std::string shader_; 64 disk_cache::Entry* entry_; 65 66 DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry); 67 }; 68 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> { 74 public: 75 ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache, int host_id); 76 void LoadCache(); 77 78 private: 79 friend class base::RefCounted<ShaderDiskReadHelper>; 80 81 enum OpType { 82 TERMINATE, 83 OPEN_NEXT, 84 OPEN_NEXT_COMPLETE, 85 READ_COMPLETE, 86 ITERATION_FINISHED 87 }; 88 89 90 ~ShaderDiskReadHelper(); 91 92 void OnOpComplete(int rv); 93 94 int OpenNextEntry(); 95 int OpenNextEntryComplete(int rv); 96 int ReadComplete(int rv); 97 int IterationComplete(int rv); 98 99 base::WeakPtr<ShaderDiskCache> cache_; 100 OpType op_type_; 101 scoped_ptr<disk_cache::Backend::Iterator> iter_; 102 scoped_refptr<net::IOBufferWithSize> buf_; 103 int host_id_; 104 disk_cache::Entry* entry_; 105 106 DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper); 107 }; 108 109 class ShaderClearHelper 110 : public base::RefCounted<ShaderClearHelper>, 111 public base::SupportsWeakPtr<ShaderClearHelper> { 112 public: 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); 118 void Clear(); 119 120 private: 121 friend class base::RefCounted<ShaderClearHelper>; 122 123 enum OpType { 124 TERMINATE, 125 VERIFY_CACHE_SETUP, 126 DELETE_CACHE 127 }; 128 129 ~ShaderClearHelper(); 130 131 void DoClearShaderCache(int rv); 132 133 scoped_refptr<ShaderDiskCache> cache_; 134 OpType op_type_; 135 base::FilePath path_; 136 base::Time delete_begin_; 137 base::Time delete_end_; 138 base::Closure callback_; 139 140 DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper); 141 }; 142 143 ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache, 144 const std::string& key, 145 const std::string& shader) 146 : cache_(cache), 147 op_type_(OPEN_ENTRY), 148 key_(key), 149 shader_(shader), 150 entry_(NULL) { 151 } 152 153 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() { 154 if (entry_) 155 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 156 base::Bind(&EntryCloser, entry_)); 157 } 158 159 void ShaderDiskCacheEntry::Cache() { 160 DCHECK(CalledOnValidThread()); 161 if (!cache_.get()) 162 return; 163 164 int rv = cache_->backend()->OpenEntry( 165 key_, 166 &entry_, 167 base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this)); 168 if (rv != net::ERR_IO_PENDING) 169 OnOpComplete(rv); 170 } 171 172 void ShaderDiskCacheEntry::OnOpComplete(int rv) { 173 DCHECK(CalledOnValidThread()); 174 if (!cache_.get()) 175 return; 176 177 do { 178 switch (op_type_) { 179 case OPEN_ENTRY: 180 rv = OpenCallback(rv); 181 break; 182 case CREATE_ENTRY: 183 rv = WriteCallback(rv); 184 break; 185 case WRITE_DATA: 186 rv = IOComplete(rv); 187 break; 188 case TERMINATE: 189 rv = net::ERR_IO_PENDING; // break the loop. 190 break; 191 default: 192 NOTREACHED(); // Invalid op_type_ provided. 193 break; 194 } 195 } while (rv != net::ERR_IO_PENDING); 196 } 197 198 int ShaderDiskCacheEntry::OpenCallback(int rv) { 199 DCHECK(CalledOnValidThread()); 200 // Called through OnOpComplete, so we know |cache_| is valid. 201 if (rv == net::OK) { 202 cache_->backend()->OnExternalCacheHit(key_); 203 cache_->EntryComplete(this); 204 op_type_ = TERMINATE; 205 return rv; 206 } 207 208 op_type_ = CREATE_ENTRY; 209 return cache_->backend()->CreateEntry( 210 key_, 211 &entry_, 212 base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this)); 213 } 214 215 int ShaderDiskCacheEntry::WriteCallback(int rv) { 216 DCHECK(CalledOnValidThread()); 217 // Called through OnOpComplete, so we know |cache_| is valid. 218 if (rv != net::OK) { 219 LOG(ERROR) << "Failed to create shader cache entry: " << rv; 220 cache_->EntryComplete(this); 221 op_type_ = TERMINATE; 222 return rv; 223 } 224 225 op_type_ = WRITE_DATA; 226 scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(shader_); 227 return entry_->WriteData( 228 1, 229 0, 230 io_buf.get(), 231 shader_.length(), 232 base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this), 233 false); 234 } 235 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; 241 return rv; 242 } 243 244 ShaderDiskReadHelper::ShaderDiskReadHelper( 245 base::WeakPtr<ShaderDiskCache> cache, 246 int host_id) 247 : cache_(cache), 248 op_type_(OPEN_NEXT), 249 buf_(NULL), 250 host_id_(host_id), 251 entry_(NULL) { 252 } 253 254 void ShaderDiskReadHelper::LoadCache() { 255 DCHECK(CalledOnValidThread()); 256 if (!cache_.get()) 257 return; 258 OnOpComplete(net::OK); 259 } 260 261 void ShaderDiskReadHelper::OnOpComplete(int rv) { 262 DCHECK(CalledOnValidThread()); 263 if (!cache_.get()) 264 return; 265 266 do { 267 switch (op_type_) { 268 case OPEN_NEXT: 269 rv = OpenNextEntry(); 270 break; 271 case OPEN_NEXT_COMPLETE: 272 rv = OpenNextEntryComplete(rv); 273 break; 274 case READ_COMPLETE: 275 rv = ReadComplete(rv); 276 break; 277 case ITERATION_FINISHED: 278 rv = IterationComplete(rv); 279 break; 280 case TERMINATE: 281 cache_->ReadComplete(); 282 rv = net::ERR_IO_PENDING; // break the loop 283 break; 284 default: 285 NOTREACHED(); // Invalid state for read helper 286 rv = net::ERR_FAILED; 287 break; 288 } 289 } while (rv != net::ERR_IO_PENDING); 290 } 291 292 int ShaderDiskReadHelper::OpenNextEntry() { 293 DCHECK(CalledOnValidThread()); 294 // Called through OnOpComplete, so we know |cache_| is valid. 295 op_type_ = OPEN_NEXT_COMPLETE; 296 if (!iter_) 297 iter_ = cache_->backend()->CreateIterator(); 298 return iter_->OpenNextEntry( 299 &entry_, base::Bind(&ShaderDiskReadHelper::OnOpComplete, this)); 300 } 301 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) { 306 iter_.reset(); 307 op_type_ = ITERATION_FINISHED; 308 return net::OK; 309 } 310 311 if (rv < 0) 312 return rv; 313 314 op_type_ = READ_COMPLETE; 315 buf_ = new net::IOBufferWithSize(entry_->GetDataSize(1)); 316 return entry_->ReadData( 317 1, 318 0, 319 buf_.get(), 320 buf_->size(), 321 base::Bind(&ShaderDiskReadHelper::OnOpComplete, this)); 322 } 323 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_); 329 if (host) 330 host->LoadedShader(entry_->GetKey(), std::string(buf_->data(), 331 buf_->size())); 332 } 333 334 buf_ = NULL; 335 entry_->Close(); 336 entry_ = NULL; 337 338 op_type_ = OPEN_NEXT; 339 return net::OK; 340 } 341 342 int ShaderDiskReadHelper::IterationComplete(int rv) { 343 DCHECK(CalledOnValidThread()); 344 // Called through OnOpComplete, so we know |cache_| is valid. 345 iter_.reset(); 346 op_type_ = TERMINATE; 347 return net::OK; 348 } 349 350 ShaderDiskReadHelper::~ShaderDiskReadHelper() { 351 if (entry_) { 352 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 353 base::Bind(&EntryCloser, entry_)); 354 } 355 if (iter_) { 356 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 357 base::Bind(&FreeDiskCacheIterator, 358 base::Passed(&iter_))); 359 } 360 } 361 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) 367 : cache_(cache), 368 op_type_(VERIFY_CACHE_SETUP), 369 path_(path), 370 delete_begin_(delete_begin), 371 delete_end_(delete_end), 372 callback_(callback) { 373 } 374 375 ShaderClearHelper::~ShaderClearHelper() { 376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 377 } 378 379 void ShaderClearHelper::Clear() { 380 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 381 DoClearShaderCache(net::OK); 382 } 383 384 void ShaderClearHelper::DoClearShaderCache(int rv) { 385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 386 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; 390 391 while (rv != net::ERR_IO_PENDING) { 392 switch (op_type_) { 393 case VERIFY_CACHE_SETUP: 394 rv = cache_->SetAvailableCallback( 395 base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr())); 396 op_type_ = DELETE_CACHE; 397 break; 398 case DELETE_CACHE: 399 rv = cache_->Clear( 400 delete_begin_, delete_end_, 401 base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr())); 402 op_type_ = TERMINATE; 403 break; 404 case TERMINATE: 405 ShaderCacheFactory::GetInstance()->CacheCleared(path_); 406 callback_.Run(); 407 rv = net::ERR_IO_PENDING; // Break the loop. 408 break; 409 default: 410 NOTREACHED(); // Invalid state provided. 411 op_type_ = TERMINATE; 412 break; 413 } 414 } 415 } 416 417 // static 418 ShaderCacheFactory* ShaderCacheFactory::GetInstance() { 419 return Singleton<ShaderCacheFactory, 420 LeakySingletonTraits<ShaderCacheFactory> >::get(); 421 } 422 423 ShaderCacheFactory::ShaderCacheFactory() { 424 } 425 426 ShaderCacheFactory::~ShaderCacheFactory() { 427 } 428 429 void ShaderCacheFactory::SetCacheInfo(int32 client_id, 430 const base::FilePath& path) { 431 client_id_to_path_map_[client_id] = path; 432 } 433 434 void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) { 435 client_id_to_path_map_.erase(client_id); 436 } 437 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()) 442 return NULL; 443 return ShaderCacheFactory::GetByPath(iter->second); 444 } 445 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()) 450 return iter->second; 451 452 ShaderDiskCache* cache = new ShaderDiskCache(path); 453 cache->Init(); 454 return cache; 455 } 456 457 void ShaderCacheFactory::AddToCache(const base::FilePath& key, 458 ShaderDiskCache* cache) { 459 shader_cache_map_[key] = cache; 460 } 461 462 void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) { 463 shader_cache_map_.erase(key); 464 } 465 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(BrowserThread::CurrentlyOn(BrowserThread::IO)); 471 DCHECK(!callback.is_null()); 472 473 scoped_refptr<ShaderClearHelper> helper = new ShaderClearHelper( 474 GetByPath(path), path, delete_begin, delete_end, callback); 475 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); 484 return; 485 } 486 487 shader_clear_map_.insert( 488 std::pair<base::FilePath, ShaderClearQueue>(path, ShaderClearQueue())); 489 shader_clear_map_[path].push(helper); 490 helper->Clear(); 491 } 492 493 void ShaderCacheFactory::CacheCleared(const base::FilePath& path) { 494 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 495 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."; 499 return; 500 } 501 502 iter->second.pop(); 503 504 // If there are remaining items in the list we trigger the Clear on the 505 // next one. 506 if (!iter->second.empty()) { 507 iter->second.front()->Clear(); 508 return; 509 } 510 511 shader_clear_map_.erase(path); 512 } 513 514 ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path) 515 : cache_available_(false), 516 host_id_(0), 517 cache_path_(cache_path), 518 is_initialized_(false) { 519 ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this); 520 } 521 522 ShaderDiskCache::~ShaderDiskCache() { 523 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_); 524 } 525 526 void ShaderDiskCache::Init() { 527 if (is_initialized_) { 528 NOTREACHED(); // can't initialize disk cache twice. 529 return; 530 } 531 is_initialized_ = true; 532 533 int rv = disk_cache::CreateCacheBackend( 534 net::SHADER_CACHE, 535 net::CACHE_BACKEND_DEFAULT, 536 cache_path_.Append(kGpuCachePath), 537 gpu::kDefaultMaxProgramCacheMemoryBytes, 538 true, 539 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), 540 NULL, 541 &backend_, 542 base::Bind(&ShaderDiskCache::CacheCreatedCallback, this)); 543 544 if (rv == net::OK) 545 cache_available_ = true; 546 } 547 548 void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) { 549 if (!cache_available_) 550 return; 551 552 scoped_refptr<ShaderDiskCacheEntry> shim = 553 new ShaderDiskCacheEntry(AsWeakPtr(), key, shader); 554 shim->Cache(); 555 556 entry_map_[shim.get()] = shim; 557 } 558 559 int ShaderDiskCache::Clear( 560 const base::Time begin_time, const base::Time end_time, 561 const net::CompletionCallback& completion_callback) { 562 int rv; 563 if (begin_time.is_null()) { 564 rv = backend_->DoomAllEntries(completion_callback); 565 } else { 566 rv = backend_->DoomEntriesBetween(begin_time, end_time, 567 completion_callback); 568 } 569 return rv; 570 } 571 572 int32 ShaderDiskCache::Size() { 573 if (!cache_available_) 574 return -1; 575 return backend_->GetEntryCount(); 576 } 577 578 int ShaderDiskCache::SetAvailableCallback( 579 const net::CompletionCallback& callback) { 580 if (cache_available_) 581 return net::OK; 582 available_callback_ = callback; 583 return net::ERR_IO_PENDING; 584 } 585 586 void ShaderDiskCache::CacheCreatedCallback(int rv) { 587 if (rv != net::OK) { 588 LOG(ERROR) << "Shader Cache Creation failed: " << rv; 589 return; 590 } 591 helper_ = new ShaderDiskReadHelper(AsWeakPtr(), host_id_); 592 helper_->LoadCache(); 593 } 594 595 void ShaderDiskCache::EntryComplete(void* entry) { 596 entry_map_.erase(entry); 597 598 if (entry_map_.empty() && !cache_complete_callback_.is_null()) 599 cache_complete_callback_.Run(net::OK); 600 } 601 602 void ShaderDiskCache::ReadComplete() { 603 helper_ = NULL; 604 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(); 612 } 613 } 614 615 int ShaderDiskCache::SetCacheCompleteCallback( 616 const net::CompletionCallback& callback) { 617 if (entry_map_.empty()) { 618 return net::OK; 619 } 620 cache_complete_callback_ = callback; 621 return net::ERR_IO_PENDING; 622 } 623 624 } // namespace content 625 626