OLD | NEW |
| (Empty) |
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 "gpu/ipc/host/shader_disk_cache.h" | |
6 | |
7 #include "base/macros.h" | |
8 #include "base/memory/ptr_util.h" | |
9 #include "base/single_thread_task_runner.h" | |
10 #include "base/threading/thread_checker.h" | |
11 #include "gpu/command_buffer/common/constants.h" | |
12 #include "net/base/cache_type.h" | |
13 #include "net/base/io_buffer.h" | |
14 #include "net/base/net_errors.h" | |
15 | |
16 namespace gpu { | |
17 | |
18 namespace { | |
19 | |
20 static const base::FilePath::CharType kGpuCachePath[] = | |
21 FILE_PATH_LITERAL("GPUCache"); | |
22 | |
23 static ShaderCacheFactory* factory_instance = nullptr; | |
24 | |
25 } // namespace | |
26 | |
27 // ShaderDiskCacheEntry handles the work of caching/updating the cached | |
28 // shaders. | |
29 class ShaderDiskCacheEntry : public base::ThreadChecker { | |
30 public: | |
31 ShaderDiskCacheEntry(ShaderDiskCache* cache, | |
32 const std::string& key, | |
33 const std::string& shader); | |
34 ~ShaderDiskCacheEntry(); | |
35 | |
36 void Cache(); | |
37 | |
38 private: | |
39 enum OpType { | |
40 OPEN_ENTRY, | |
41 WRITE_DATA, | |
42 CREATE_ENTRY, | |
43 }; | |
44 | |
45 void OnOpComplete(int rv); | |
46 | |
47 int OpenCallback(int rv); | |
48 int WriteCallback(int rv); | |
49 int IOComplete(int rv); | |
50 | |
51 ShaderDiskCache* cache_; | |
52 OpType op_type_; | |
53 std::string key_; | |
54 std::string shader_; | |
55 disk_cache::Entry* entry_; | |
56 base::WeakPtr<ShaderDiskCacheEntry> weak_ptr_; | |
57 base::WeakPtrFactory<ShaderDiskCacheEntry> weak_ptr_factory_; | |
58 | |
59 DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry); | |
60 }; | |
61 | |
62 // ShaderDiskReadHelper is used to load all of the cached shaders from the | |
63 // disk cache and send to the memory cache. | |
64 class ShaderDiskReadHelper : public base::ThreadChecker { | |
65 public: | |
66 using ShaderLoadedCallback = ShaderDiskCache::ShaderLoadedCallback; | |
67 ShaderDiskReadHelper(ShaderDiskCache* cache, | |
68 const ShaderLoadedCallback& callback); | |
69 ~ShaderDiskReadHelper(); | |
70 | |
71 void LoadCache(); | |
72 | |
73 private: | |
74 enum OpType { | |
75 TERMINATE, | |
76 OPEN_NEXT, | |
77 OPEN_NEXT_COMPLETE, | |
78 READ_COMPLETE, | |
79 ITERATION_FINISHED | |
80 }; | |
81 | |
82 void OnOpComplete(int rv); | |
83 | |
84 int OpenNextEntry(); | |
85 int OpenNextEntryComplete(int rv); | |
86 int ReadComplete(int rv); | |
87 int IterationComplete(int rv); | |
88 | |
89 ShaderDiskCache* cache_; | |
90 ShaderLoadedCallback shader_loaded_callback_; | |
91 OpType op_type_; | |
92 std::unique_ptr<disk_cache::Backend::Iterator> iter_; | |
93 scoped_refptr<net::IOBufferWithSize> buf_; | |
94 disk_cache::Entry* entry_; | |
95 base::WeakPtrFactory<ShaderDiskReadHelper> weak_ptr_factory_; | |
96 | |
97 DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper); | |
98 }; | |
99 | |
100 class ShaderClearHelper : public base::ThreadChecker { | |
101 public: | |
102 ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache, | |
103 const base::FilePath& path, | |
104 const base::Time& delete_begin, | |
105 const base::Time& delete_end, | |
106 const base::Closure& callback); | |
107 ~ShaderClearHelper(); | |
108 | |
109 void Clear(); | |
110 | |
111 private: | |
112 enum OpType { TERMINATE, VERIFY_CACHE_SETUP, DELETE_CACHE }; | |
113 | |
114 void DoClearShaderCache(int rv); | |
115 | |
116 scoped_refptr<ShaderDiskCache> cache_; | |
117 OpType op_type_; | |
118 base::FilePath path_; | |
119 base::Time delete_begin_; | |
120 base::Time delete_end_; | |
121 base::Closure callback_; | |
122 base::WeakPtrFactory<ShaderClearHelper> weak_ptr_factory_; | |
123 | |
124 DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper); | |
125 }; | |
126 | |
127 //////////////////////////////////////////////////////////////////////////////// | |
128 // ShaderDiskCacheEntry | |
129 | |
130 ShaderDiskCacheEntry::ShaderDiskCacheEntry(ShaderDiskCache* cache, | |
131 const std::string& key, | |
132 const std::string& shader) | |
133 : cache_(cache), | |
134 op_type_(OPEN_ENTRY), | |
135 key_(key), | |
136 shader_(shader), | |
137 entry_(nullptr), | |
138 weak_ptr_factory_(this) { | |
139 weak_ptr_ = weak_ptr_factory_.GetWeakPtr(); | |
140 } | |
141 | |
142 ShaderDiskCacheEntry::~ShaderDiskCacheEntry() { | |
143 DCHECK(CalledOnValidThread()); | |
144 if (entry_) | |
145 entry_->Close(); | |
146 } | |
147 | |
148 void ShaderDiskCacheEntry::Cache() { | |
149 DCHECK(CalledOnValidThread()); | |
150 int rv = cache_->backend()->OpenEntry( | |
151 key_, &entry_, base::Bind(&ShaderDiskCacheEntry::OnOpComplete, | |
152 weak_ptr_factory_.GetWeakPtr())); | |
153 if (rv != net::ERR_IO_PENDING) | |
154 OnOpComplete(rv); | |
155 } | |
156 | |
157 void ShaderDiskCacheEntry::OnOpComplete(int rv) { | |
158 DCHECK(CalledOnValidThread()); | |
159 // The function calls inside the switch block below can end up destroying | |
160 // |this|. So hold on to a WeakPtr<>, and terminate the while loop if |this| | |
161 // has been destroyed. | |
162 auto weak_ptr = std::move(weak_ptr_); | |
163 do { | |
164 switch (op_type_) { | |
165 case OPEN_ENTRY: | |
166 rv = OpenCallback(rv); | |
167 break; | |
168 case CREATE_ENTRY: | |
169 rv = WriteCallback(rv); | |
170 break; | |
171 case WRITE_DATA: | |
172 rv = IOComplete(rv); | |
173 break; | |
174 } | |
175 } while (rv != net::ERR_IO_PENDING && weak_ptr); | |
176 if (weak_ptr) | |
177 weak_ptr_ = std::move(weak_ptr); | |
178 } | |
179 | |
180 int ShaderDiskCacheEntry::OpenCallback(int rv) { | |
181 DCHECK(CalledOnValidThread()); | |
182 if (rv == net::OK) { | |
183 cache_->backend()->OnExternalCacheHit(key_); | |
184 cache_->EntryComplete(this); | |
185 return rv; | |
186 } | |
187 | |
188 op_type_ = CREATE_ENTRY; | |
189 return cache_->backend()->CreateEntry( | |
190 key_, &entry_, base::Bind(&ShaderDiskCacheEntry::OnOpComplete, | |
191 weak_ptr_factory_.GetWeakPtr())); | |
192 } | |
193 | |
194 int ShaderDiskCacheEntry::WriteCallback(int rv) { | |
195 DCHECK(CalledOnValidThread()); | |
196 if (rv != net::OK) { | |
197 LOG(ERROR) << "Failed to create shader cache entry: " << rv; | |
198 cache_->EntryComplete(this); | |
199 return rv; | |
200 } | |
201 | |
202 op_type_ = WRITE_DATA; | |
203 scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(shader_); | |
204 return entry_->WriteData(1, 0, io_buf.get(), shader_.length(), | |
205 base::Bind(&ShaderDiskCacheEntry::OnOpComplete, | |
206 weak_ptr_factory_.GetWeakPtr()), | |
207 false); | |
208 } | |
209 | |
210 int ShaderDiskCacheEntry::IOComplete(int rv) { | |
211 DCHECK(CalledOnValidThread()); | |
212 cache_->EntryComplete(this); | |
213 return rv; | |
214 } | |
215 | |
216 //////////////////////////////////////////////////////////////////////////////// | |
217 // ShaderDiskReadHelper | |
218 | |
219 ShaderDiskReadHelper::ShaderDiskReadHelper(ShaderDiskCache* cache, | |
220 const ShaderLoadedCallback& callback) | |
221 : cache_(cache), | |
222 shader_loaded_callback_(callback), | |
223 op_type_(OPEN_NEXT), | |
224 buf_(NULL), | |
225 entry_(NULL), | |
226 weak_ptr_factory_(this) {} | |
227 | |
228 ShaderDiskReadHelper::~ShaderDiskReadHelper() { | |
229 DCHECK(CalledOnValidThread()); | |
230 if (entry_) | |
231 entry_->Close(); | |
232 iter_ = nullptr; | |
233 } | |
234 | |
235 void ShaderDiskReadHelper::LoadCache() { | |
236 DCHECK(CalledOnValidThread()); | |
237 OnOpComplete(net::OK); | |
238 } | |
239 | |
240 void ShaderDiskReadHelper::OnOpComplete(int rv) { | |
241 DCHECK(CalledOnValidThread()); | |
242 do { | |
243 switch (op_type_) { | |
244 case OPEN_NEXT: | |
245 rv = OpenNextEntry(); | |
246 break; | |
247 case OPEN_NEXT_COMPLETE: | |
248 rv = OpenNextEntryComplete(rv); | |
249 break; | |
250 case READ_COMPLETE: | |
251 rv = ReadComplete(rv); | |
252 break; | |
253 case ITERATION_FINISHED: | |
254 rv = IterationComplete(rv); | |
255 break; | |
256 case TERMINATE: | |
257 cache_->ReadComplete(); | |
258 rv = net::ERR_IO_PENDING; // break the loop | |
259 break; | |
260 } | |
261 } while (rv != net::ERR_IO_PENDING); | |
262 } | |
263 | |
264 int ShaderDiskReadHelper::OpenNextEntry() { | |
265 DCHECK(CalledOnValidThread()); | |
266 op_type_ = OPEN_NEXT_COMPLETE; | |
267 if (!iter_) | |
268 iter_ = cache_->backend()->CreateIterator(); | |
269 return iter_->OpenNextEntry(&entry_, | |
270 base::Bind(&ShaderDiskReadHelper::OnOpComplete, | |
271 weak_ptr_factory_.GetWeakPtr())); | |
272 } | |
273 | |
274 int ShaderDiskReadHelper::OpenNextEntryComplete(int rv) { | |
275 DCHECK(CalledOnValidThread()); | |
276 if (rv == net::ERR_FAILED) { | |
277 iter_.reset(); | |
278 op_type_ = ITERATION_FINISHED; | |
279 return net::OK; | |
280 } | |
281 | |
282 if (rv < 0) | |
283 return rv; | |
284 | |
285 op_type_ = READ_COMPLETE; | |
286 buf_ = new net::IOBufferWithSize(entry_->GetDataSize(1)); | |
287 return entry_->ReadData(1, 0, buf_.get(), buf_->size(), | |
288 base::Bind(&ShaderDiskReadHelper::OnOpComplete, | |
289 weak_ptr_factory_.GetWeakPtr())); | |
290 } | |
291 | |
292 int ShaderDiskReadHelper::ReadComplete(int rv) { | |
293 DCHECK(CalledOnValidThread()); | |
294 if (rv && rv == buf_->size() && !shader_loaded_callback_.is_null()) { | |
295 shader_loaded_callback_.Run(entry_->GetKey(), | |
296 std::string(buf_->data(), buf_->size())); | |
297 } | |
298 | |
299 buf_ = NULL; | |
300 entry_->Close(); | |
301 entry_ = NULL; | |
302 | |
303 op_type_ = OPEN_NEXT; | |
304 return net::OK; | |
305 } | |
306 | |
307 int ShaderDiskReadHelper::IterationComplete(int rv) { | |
308 DCHECK(CalledOnValidThread()); | |
309 iter_.reset(); | |
310 op_type_ = TERMINATE; | |
311 return net::OK; | |
312 } | |
313 | |
314 //////////////////////////////////////////////////////////////////////////////// | |
315 // ShaderClearHelper | |
316 | |
317 ShaderClearHelper::ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache, | |
318 const base::FilePath& path, | |
319 const base::Time& delete_begin, | |
320 const base::Time& delete_end, | |
321 const base::Closure& callback) | |
322 : cache_(std::move(cache)), | |
323 op_type_(VERIFY_CACHE_SETUP), | |
324 path_(path), | |
325 delete_begin_(delete_begin), | |
326 delete_end_(delete_end), | |
327 callback_(callback), | |
328 weak_ptr_factory_(this) {} | |
329 | |
330 ShaderClearHelper::~ShaderClearHelper() { | |
331 DCHECK(CalledOnValidThread()); | |
332 } | |
333 | |
334 void ShaderClearHelper::Clear() { | |
335 DCHECK(CalledOnValidThread()); | |
336 DoClearShaderCache(net::OK); | |
337 } | |
338 | |
339 void ShaderClearHelper::DoClearShaderCache(int rv) { | |
340 DCHECK(CalledOnValidThread()); | |
341 while (rv != net::ERR_IO_PENDING) { | |
342 switch (op_type_) { | |
343 case VERIFY_CACHE_SETUP: | |
344 rv = cache_->SetAvailableCallback( | |
345 base::Bind(&ShaderClearHelper::DoClearShaderCache, | |
346 weak_ptr_factory_.GetWeakPtr())); | |
347 op_type_ = DELETE_CACHE; | |
348 break; | |
349 case DELETE_CACHE: | |
350 rv = cache_->Clear(delete_begin_, delete_end_, | |
351 base::Bind(&ShaderClearHelper::DoClearShaderCache, | |
352 weak_ptr_factory_.GetWeakPtr())); | |
353 op_type_ = TERMINATE; | |
354 break; | |
355 case TERMINATE: | |
356 callback_.Run(); | |
357 // Calling CacheCleared() destroys |this|. | |
358 ShaderCacheFactory::GetInstance()->CacheCleared(path_); | |
359 rv = net::ERR_IO_PENDING; // Break the loop. | |
360 break; | |
361 } | |
362 } | |
363 } | |
364 | |
365 //////////////////////////////////////////////////////////////////////////////// | |
366 // ShaderCacheFactory | |
367 | |
368 // static | |
369 void ShaderCacheFactory::InitInstance( | |
370 scoped_refptr<base::SingleThreadTaskRunner> task_runner, | |
371 scoped_refptr<base::SingleThreadTaskRunner> cache_task_runner) { | |
372 if (task_runner->BelongsToCurrentThread()) { | |
373 CreateFactoryInstance(std::move(cache_task_runner)); | |
374 } else { | |
375 task_runner->PostTask(FROM_HERE, | |
376 base::Bind(&ShaderCacheFactory::CreateFactoryInstance, | |
377 std::move(cache_task_runner))); | |
378 } | |
379 } | |
380 | |
381 // static | |
382 ShaderCacheFactory* ShaderCacheFactory::GetInstance() { | |
383 DCHECK(!factory_instance || factory_instance->CalledOnValidThread()); | |
384 return factory_instance; | |
385 } | |
386 | |
387 ShaderCacheFactory::ShaderCacheFactory( | |
388 scoped_refptr<base::SingleThreadTaskRunner> cache_task_runner) | |
389 : cache_task_runner_(std::move(cache_task_runner)) {} | |
390 | |
391 ShaderCacheFactory::~ShaderCacheFactory() {} | |
392 | |
393 // static | |
394 void ShaderCacheFactory::CreateFactoryInstance( | |
395 scoped_refptr<base::SingleThreadTaskRunner> cache_task_runner) { | |
396 DCHECK(!factory_instance); | |
397 factory_instance = new ShaderCacheFactory(std::move(cache_task_runner)); | |
398 } | |
399 | |
400 void ShaderCacheFactory::SetCacheInfo(int32_t client_id, | |
401 const base::FilePath& path) { | |
402 DCHECK(CalledOnValidThread()); | |
403 client_id_to_path_map_[client_id] = path; | |
404 } | |
405 | |
406 void ShaderCacheFactory::RemoveCacheInfo(int32_t client_id) { | |
407 DCHECK(CalledOnValidThread()); | |
408 client_id_to_path_map_.erase(client_id); | |
409 } | |
410 | |
411 scoped_refptr<ShaderDiskCache> ShaderCacheFactory::Get(int32_t client_id) { | |
412 DCHECK(CalledOnValidThread()); | |
413 ClientIdToPathMap::iterator iter = client_id_to_path_map_.find(client_id); | |
414 if (iter == client_id_to_path_map_.end()) | |
415 return NULL; | |
416 return ShaderCacheFactory::GetByPath(iter->second); | |
417 } | |
418 | |
419 scoped_refptr<ShaderDiskCache> ShaderCacheFactory::GetByPath( | |
420 const base::FilePath& path) { | |
421 DCHECK(CalledOnValidThread()); | |
422 ShaderCacheMap::iterator iter = shader_cache_map_.find(path); | |
423 if (iter != shader_cache_map_.end()) | |
424 return iter->second; | |
425 | |
426 ShaderDiskCache* cache = new ShaderDiskCache(path); | |
427 cache->Init(cache_task_runner_); | |
428 return cache; | |
429 } | |
430 | |
431 void ShaderCacheFactory::AddToCache(const base::FilePath& key, | |
432 ShaderDiskCache* cache) { | |
433 DCHECK(CalledOnValidThread()); | |
434 shader_cache_map_[key] = cache; | |
435 } | |
436 | |
437 void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) { | |
438 DCHECK(CalledOnValidThread()); | |
439 shader_cache_map_.erase(key); | |
440 } | |
441 | |
442 void ShaderCacheFactory::ClearByPath(const base::FilePath& path, | |
443 const base::Time& delete_begin, | |
444 const base::Time& delete_end, | |
445 const base::Closure& callback) { | |
446 DCHECK(CalledOnValidThread()); | |
447 DCHECK(!callback.is_null()); | |
448 | |
449 auto helper = base::MakeUnique<ShaderClearHelper>( | |
450 GetByPath(path), path, delete_begin, delete_end, callback); | |
451 | |
452 // We could receive requests to clear the same path with different | |
453 // begin/end times. So, we keep a list of requests. If we haven't seen this | |
454 // path before we kick off the clear and add it to the list. If we have see it | |
455 // already, then we already have a clear running. We add this clear to the | |
456 // list and wait for any previous clears to finish. | |
457 ShaderClearMap::iterator iter = shader_clear_map_.find(path); | |
458 if (iter != shader_clear_map_.end()) { | |
459 iter->second.push(std::move(helper)); | |
460 return; | |
461 } | |
462 | |
463 // Insert the helper in the map before calling Clear(), since it can lead to a | |
464 // call back into CacheCleared(). | |
465 ShaderClearHelper* helper_ptr = helper.get(); | |
466 shader_clear_map_.insert( | |
467 std::pair<base::FilePath, ShaderClearQueue>(path, ShaderClearQueue())); | |
468 shader_clear_map_[path].push(std::move(helper)); | |
469 helper_ptr->Clear(); | |
470 } | |
471 | |
472 void ShaderCacheFactory::CacheCleared(const base::FilePath& path) { | |
473 DCHECK(CalledOnValidThread()); | |
474 | |
475 ShaderClearMap::iterator iter = shader_clear_map_.find(path); | |
476 if (iter == shader_clear_map_.end()) { | |
477 LOG(ERROR) << "Completed clear but missing clear helper."; | |
478 return; | |
479 } | |
480 | |
481 iter->second.pop(); | |
482 | |
483 // If there are remaining items in the list we trigger the Clear on the | |
484 // next one. | |
485 if (!iter->second.empty()) { | |
486 iter->second.front()->Clear(); | |
487 return; | |
488 } | |
489 | |
490 shader_clear_map_.erase(iter); | |
491 } | |
492 | |
493 //////////////////////////////////////////////////////////////////////////////// | |
494 // ShaderDiskCache | |
495 | |
496 ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path) | |
497 : cache_available_(false), cache_path_(cache_path), is_initialized_(false) { | |
498 ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this); | |
499 } | |
500 | |
501 ShaderDiskCache::~ShaderDiskCache() { | |
502 ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_); | |
503 } | |
504 | |
505 void ShaderDiskCache::Init( | |
506 scoped_refptr<base::SingleThreadTaskRunner> cache_task_runner) { | |
507 if (is_initialized_) { | |
508 NOTREACHED(); // can't initialize disk cache twice. | |
509 return; | |
510 } | |
511 is_initialized_ = true; | |
512 | |
513 int rv = disk_cache::CreateCacheBackend( | |
514 net::SHADER_CACHE, net::CACHE_BACKEND_DEFAULT, | |
515 cache_path_.Append(kGpuCachePath), | |
516 gpu::kDefaultMaxProgramCacheMemoryBytes, true, cache_task_runner, NULL, | |
517 &backend_, base::Bind(&ShaderDiskCache::CacheCreatedCallback, this)); | |
518 | |
519 if (rv == net::OK) | |
520 cache_available_ = true; | |
521 } | |
522 | |
523 void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) { | |
524 if (!cache_available_) | |
525 return; | |
526 | |
527 auto shim = base::MakeUnique<ShaderDiskCacheEntry>(this, key, shader); | |
528 shim->Cache(); | |
529 auto* raw_ptr = shim.get(); | |
530 entries_.insert(std::make_pair(raw_ptr, std::move(shim))); | |
531 } | |
532 | |
533 int ShaderDiskCache::Clear(const base::Time begin_time, | |
534 const base::Time end_time, | |
535 const net::CompletionCallback& completion_callback) { | |
536 int rv; | |
537 if (begin_time.is_null()) { | |
538 rv = backend_->DoomAllEntries(completion_callback); | |
539 } else { | |
540 rv = | |
541 backend_->DoomEntriesBetween(begin_time, end_time, completion_callback); | |
542 } | |
543 return rv; | |
544 } | |
545 | |
546 int32_t ShaderDiskCache::Size() { | |
547 if (!cache_available_) | |
548 return -1; | |
549 return backend_->GetEntryCount(); | |
550 } | |
551 | |
552 int ShaderDiskCache::SetAvailableCallback( | |
553 const net::CompletionCallback& callback) { | |
554 if (cache_available_) | |
555 return net::OK; | |
556 available_callback_ = callback; | |
557 return net::ERR_IO_PENDING; | |
558 } | |
559 | |
560 void ShaderDiskCache::CacheCreatedCallback(int rv) { | |
561 if (rv != net::OK) { | |
562 LOG(ERROR) << "Shader Cache Creation failed: " << rv; | |
563 return; | |
564 } | |
565 helper_ = | |
566 base::MakeUnique<ShaderDiskReadHelper>(this, shader_loaded_callback_); | |
567 helper_->LoadCache(); | |
568 } | |
569 | |
570 void ShaderDiskCache::EntryComplete(ShaderDiskCacheEntry* entry) { | |
571 entries_.erase(entry); | |
572 if (entries_.empty() && !cache_complete_callback_.is_null()) | |
573 cache_complete_callback_.Run(net::OK); | |
574 } | |
575 | |
576 void ShaderDiskCache::ReadComplete() { | |
577 helper_ = nullptr; | |
578 | |
579 // The cache is considered available after we have finished reading any | |
580 // of the old cache values off disk. This prevents a potential race where we | |
581 // are reading from disk and execute a cache clear at the same time. | |
582 cache_available_ = true; | |
583 if (!available_callback_.is_null()) { | |
584 available_callback_.Run(net::OK); | |
585 available_callback_.Reset(); | |
586 } | |
587 } | |
588 | |
589 int ShaderDiskCache::SetCacheCompleteCallback( | |
590 const net::CompletionCallback& callback) { | |
591 if (entries_.empty()) { | |
592 return net::OK; | |
593 } | |
594 cache_complete_callback_ = callback; | |
595 return net::ERR_IO_PENDING; | |
596 } | |
597 | |
598 } // namespace gpu | |
OLD | NEW |